Skip to content

Commit f1a73d3

Browse files
committed
Implement Named Pipe connections. Fixes #454
1 parent d3d4994 commit f1a73d3

File tree

11 files changed

+178
-47
lines changed

11 files changed

+178
-47
lines changed

docs/content/connection-options.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ These are the basic options that need to be defined to connect to a MySQL databa
4848
<td></td>
4949
<td>(Optional) The case-sensitive name of the initial database to use. This may be required if the MySQL user account only has access rights to particular databases on the server.</td>
5050
</tr>
51+
<tr>
52+
<td>Protocol, ConnectionProtocol, Connection Protocol</td>
53+
<td>Socket</td>
54+
<td>How to connect to the MySQL Server. This option has the following values:
55+
<ul>
56+
<li><b>Socket</b> (default): Use TCP/IP sockets.</li>
57+
<li><b>Unix</b>: Use a Unix socket.</li>
58+
<li><b>Pipe</b>: Use a Windows named pipe.</li>
59+
</ul>
60+
</td>
61+
</tr>
62+
<tr>
63+
<td>Pipe, PipeName, Pipe Name</td>
64+
<td>MYSQL</td>
65+
<td>The name of the Windows named pipe to use to connect to the server. You must also set <code>ConnectionProtocol=pipe</code> to used named pipes.</td>
66+
</tr>
5167
</table>
5268

5369
SSL/TLS Options

src/MySqlConnector/Core/ConnectionPool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ private ConnectionPool(ConnectionSettings cs)
448448
m_hostSessions[hostName] = 0;
449449
}
450450

451-
m_loadBalancer = cs.ConnectionType != ConnectionType.Tcp ? null :
451+
m_loadBalancer = cs.ConnectionProtocol != MySqlConnectionProtocol.Sockets ? null :
452452
cs.HostNames.Count == 1 || cs.LoadBalance == MySqlLoadBalance.FailOver ? FailOverLoadBalancer.Instance :
453453
cs.LoadBalance == MySqlLoadBalance.Random ? RandomLoadBalancer.Instance :
454454
cs.LoadBalance == MySqlLoadBalance.LeastConnections ? new LeastConnectionsLoadBalancer(this) :

src/MySqlConnector/Core/ConnectionSettings.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,31 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
1313
ConnectionStringBuilder = csb;
1414
ConnectionString = csb.ConnectionString;
1515

16-
// Base Options
17-
if (!Utility.IsWindows() && (csb.Server.StartsWith("/", StringComparison.Ordinal) || csb.Server.StartsWith("./", StringComparison.Ordinal)))
16+
if (csb.ConnectionProtocol == MySqlConnectionProtocol.UnixSocket || (!Utility.IsWindows() && (csb.Server.StartsWith("/", StringComparison.Ordinal) || csb.Server.StartsWith("./", StringComparison.Ordinal))))
1817
{
1918
if (!File.Exists(csb.Server))
2019
throw new MySqlException("Cannot find Unix Socket at " + csb.Server);
21-
ConnectionType = ConnectionType.Unix;
20+
ConnectionProtocol = MySqlConnectionProtocol.UnixSocket;
2221
UnixSocket = Path.GetFullPath(csb.Server);
2322
}
23+
else if (csb.ConnectionProtocol == MySqlConnectionProtocol.NamedPipe)
24+
{
25+
ConnectionProtocol = MySqlConnectionProtocol.NamedPipe;
26+
HostNames = (csb.Server == "." || string.Equals(csb.Server, "localhost", StringComparison.OrdinalIgnoreCase)) ? s_localhostPipeServer : new[] { csb.Server };
27+
PipeName = csb.PipeName;
28+
}
29+
else if (csb.ConnectionProtocol == MySqlConnectionProtocol.SharedMemory)
30+
{
31+
throw new NotSupportedException("Shared Memory connections are not supported");
32+
}
2433
else
2534
{
26-
ConnectionType = ConnectionType.Tcp;
35+
ConnectionProtocol = MySqlConnectionProtocol.Sockets;
2736
HostNames = csb.Server.Split(',');
2837
LoadBalance = csb.LoadBalance;
2938
Port = (int) csb.Port;
3039
}
40+
3141
UserID = csb.UserID;
3242
Password = csb.Password;
3343
Database = csb.Database;
@@ -97,10 +107,11 @@ private static MySqlGuidFormat GetEffectiveGuidFormat(MySqlGuidFormat guidFormat
97107

98108
// Base Options
99109
public string ConnectionString { get; }
100-
public ConnectionType ConnectionType { get; }
110+
public MySqlConnectionProtocol ConnectionProtocol { get; }
101111
public IReadOnlyList<string> HostNames { get; }
102112
public MySqlLoadBalance LoadBalance { get; }
103113
public int Port { get; }
114+
public string PipeName { get; }
104115
public string UnixSocket { get; }
105116
public string UserID { get; }
106117
public string Password { get; }
@@ -163,5 +174,7 @@ public int ConnectionTimeoutMilliseconds
163174
return m_connectionTimeoutMilliseconds.Value;
164175
}
165176
}
177+
178+
static readonly string[] s_localhostPipeServer = { "." };
166179
}
167180
}

src/MySqlConnector/Core/ConnectionType.cs

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
using System.Diagnostics;
55
using System.Globalization;
66
using System.IO;
7+
#if !NETSTANDARD1_3
8+
using System.IO.Pipes;
9+
#endif
710
using System.Net;
811
using System.Net.Security;
912
using System.Net.Sockets;
@@ -242,10 +245,12 @@ public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer
242245
shouldRetrySsl = (sslProtocols == SslProtocols.None || (sslProtocols & SslProtocols.Tls12) == SslProtocols.Tls12) && Utility.IsWindows();
243246

244247
var connected = false;
245-
if (cs.ConnectionType == ConnectionType.Tcp)
248+
if (cs.ConnectionProtocol == MySqlConnectionProtocol.Sockets)
246249
connected = await OpenTcpSocketAsync(cs, loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false);
247-
else if (cs.ConnectionType == ConnectionType.Unix)
250+
else if (cs.ConnectionProtocol == MySqlConnectionProtocol.UnixSocket)
248251
connected = await OpenUnixSocketAsync(cs, ioBehavior, cancellationToken).ConfigureAwait(false);
252+
else if (cs.ConnectionProtocol == MySqlConnectionProtocol.NamedPipe)
253+
connected = await OpenNamedPipeAsync(cs, ioBehavior, cancellationToken).ConfigureAwait(false);
249254
if (!connected)
250255
{
251256
lock (m_lock)
@@ -254,7 +259,7 @@ public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer
254259
throw new MySqlException("Unable to connect to any of the specified MySQL hosts.");
255260
}
256261

257-
var byteHandler = new SocketByteHandler(m_socket);
262+
var byteHandler = m_socket != null ? (IByteHandler) new SocketByteHandler(m_socket) : new StreamByteHandler(m_stream);
258263
m_payloadHandler = new StandardPayloadHandler(byteHandler);
259264

260265
payload = await ReceiveAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
@@ -755,7 +760,7 @@ private async Task<bool> OpenTcpSocketAsync(ConnectionSettings cs, ILoadBalancer
755760
HostName = hostName;
756761
m_tcpClient = tcpClient;
757762
m_socket = m_tcpClient.Client;
758-
m_networkStream = m_tcpClient.GetStream();
763+
m_stream = m_tcpClient.GetStream();
759764
m_socket.SetKeepAlive(cs.Keepalive);
760765
lock (m_lock)
761766
m_state = State.Connected;
@@ -806,7 +811,7 @@ private async Task<bool> OpenUnixSocketAsync(ConnectionSettings cs, IOBehavior i
806811
if (socket.Connected)
807812
{
808813
m_socket = socket;
809-
m_networkStream = new NetworkStream(socket);
814+
m_stream = new NetworkStream(socket);
810815

811816
lock (m_lock)
812817
m_state = State.Connected;
@@ -816,6 +821,48 @@ private async Task<bool> OpenUnixSocketAsync(ConnectionSettings cs, IOBehavior i
816821
return false;
817822
}
818823

824+
private Task<bool> OpenNamedPipeAsync(ConnectionSettings cs, IOBehavior ioBehavior, CancellationToken cancellationToken)
825+
{
826+
#if NETSTANDARD1_3
827+
throw new NotSupportedException("Named pipe connections are not supported in netstandard1.3");
828+
#else
829+
if (Log.IsInfoEnabled())
830+
Log.Info("Session{0} connecting to NamedPipe '{1}' on Server '{2}'", m_logArguments[0], cs.PipeName, cs.HostNames[0]);
831+
var namedPipeStream = new NamedPipeClientStream(cs.HostNames[0], cs.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
832+
try
833+
{
834+
using (cancellationToken.Register(() => namedPipeStream.Dispose()))
835+
{
836+
try
837+
{
838+
namedPipeStream.Connect();
839+
}
840+
catch (ObjectDisposedException ex) when (cancellationToken.IsCancellationRequested)
841+
{
842+
m_logArguments[1] = cs.PipeName;
843+
Log.Info("Session{0} connect timeout expired connecting to named pipe '{1}'", m_logArguments);
844+
throw new MySqlException("Connect Timeout expired.", ex);
845+
}
846+
}
847+
}
848+
catch (IOException)
849+
{
850+
namedPipeStream.Dispose();
851+
}
852+
853+
if (namedPipeStream.IsConnected)
854+
{
855+
m_stream = namedPipeStream;
856+
857+
lock (m_lock)
858+
m_state = State.Connected;
859+
return Task.FromResult(true);
860+
}
861+
862+
return Task.FromResult(false);
863+
#endif
864+
}
865+
819866
private async Task InitSslAsync(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, SslProtocols sslProtocols, IOBehavior ioBehavior, CancellationToken cancellationToken)
820867
{
821868
Log.Info("Session{0} initializing TLS connection", m_logArguments);
@@ -936,9 +983,9 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate,
936983

937984
SslStream sslStream;
938985
if (clientCertificates == null)
939-
sslStream = new SslStream(m_networkStream, false, ValidateRemoteCertificate);
986+
sslStream = new SslStream(m_stream, false, ValidateRemoteCertificate);
940987
else
941-
sslStream = new SslStream(m_networkStream, false, ValidateRemoteCertificate, ValidateLocalCertificate);
988+
sslStream = new SslStream(m_stream, false, ValidateRemoteCertificate, ValidateLocalCertificate);
942989

943990
var checkCertificateRevocation = cs.SslMode == MySqlSslMode.VerifyFull;
944991

@@ -1056,7 +1103,7 @@ private void ShutdownSocket()
10561103
{
10571104
Log.Info("Session{0} closing stream/socket", m_logArguments);
10581105
Utility.Dispose(ref m_payloadHandler);
1059-
Utility.Dispose(ref m_networkStream);
1106+
Utility.Dispose(ref m_stream);
10601107
SafeDispose(ref m_tcpClient);
10611108
SafeDispose(ref m_socket);
10621109
#if NET45
@@ -1259,7 +1306,7 @@ private enum State
12591306
State m_state;
12601307
TcpClient m_tcpClient;
12611308
Socket m_socket;
1262-
NetworkStream m_networkStream;
1309+
Stream m_stream;
12631310
SslStream m_sslStream;
12641311
X509Certificate2 m_clientCertificate;
12651312
IPayloadHandler m_payloadHandler;

src/MySqlConnector/MySql.Data.MySqlClient/MySqlConnection.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,7 @@ public override string ConnectionString
211211

212212
public override ConnectionState State => m_connectionState;
213213

214-
public override string DataSource => (GetConnectionSettings().ConnectionType == ConnectionType.Tcp
215-
? string.Join(",", GetConnectionSettings().HostNames)
216-
: GetConnectionSettings().UnixSocket) ?? "";
214+
public override string DataSource => GetConnectionSettings().ConnectionStringBuilder.Server;
217215

218216
public override string ServerVersion => m_session.ServerVersion.OriginalString;
219217

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace MySql.Data.MySqlClient
2+
{
3+
/// <summary>
4+
/// Specifies the type of connection to make to the server.
5+
/// </summary>
6+
public enum MySqlConnectionProtocol
7+
{
8+
/// <summary>
9+
/// TCP/IP connection.
10+
/// </summary>
11+
Sockets = 1,
12+
Socket = 1,
13+
Tcp = 1,
14+
15+
/// <summary>
16+
/// Named pipe connection. Only works on Windows.
17+
/// </summary>
18+
Pipe = 2,
19+
NamedPipe = 2,
20+
21+
/// <summary>
22+
/// Unix domain socket connection. Only works on Unix/Linux.
23+
/// </summary>
24+
UnixSocket = 3,
25+
Unix = 3,
26+
27+
/// <summary>
28+
/// Shared memory connection. Not currently supported.
29+
/// </summary>
30+
SharedMemory = 4,
31+
Memory = 4
32+
}
33+
}

src/MySqlConnector/MySql.Data.MySqlClient/MySqlConnectionStringBuilder.cs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ public MySqlLoadBalance LoadBalance
5454
set => MySqlConnectionStringOption.LoadBalance.SetValue(this, value);
5555
}
5656

57+
public MySqlConnectionProtocol ConnectionProtocol
58+
{
59+
get => MySqlConnectionStringOption.ConnectionProtocol.GetValue(this);
60+
set => MySqlConnectionStringOption.ConnectionProtocol.SetValue(this, value);
61+
}
62+
63+
public string PipeName
64+
{
65+
get => MySqlConnectionStringOption.PipeName.GetValue(this);
66+
set => MySqlConnectionStringOption.PipeName.SetValue(this, value);
67+
}
68+
5769
// SSL/TLS Options
5870
public MySqlSslMode SslMode
5971
{
@@ -289,6 +301,8 @@ internal abstract class MySqlConnectionStringOption
289301
public static readonly MySqlConnectionStringOption<string> Password;
290302
public static readonly MySqlConnectionStringOption<string> Database;
291303
public static readonly MySqlConnectionStringOption<MySqlLoadBalance> LoadBalance;
304+
public static readonly MySqlConnectionStringOption<MySqlConnectionProtocol> ConnectionProtocol;
305+
public static readonly MySqlConnectionStringOption<string> PipeName;
292306

293307
// SSL/TLS Options
294308
public static readonly MySqlConnectionStringOption<MySqlSslMode> SslMode;
@@ -377,6 +391,14 @@ static MySqlConnectionStringOption()
377391
keys: new[] { "LoadBalance", "Load Balance" },
378392
defaultValue: MySqlLoadBalance.RoundRobin));
379393

394+
AddOption(ConnectionProtocol = new MySqlConnectionStringOption<MySqlConnectionProtocol>(
395+
keys: new[] { "ConnectionProtocol", "Connection Protocol", "Protocol" },
396+
defaultValue: MySqlConnectionProtocol.Socket));
397+
398+
AddOption(PipeName = new MySqlConnectionStringOption<string>(
399+
keys: new[] { "PipeName", "Pipe", "Pipe Name" },
400+
defaultValue: "MYSQL"));
401+
380402
// SSL/TLS Options
381403
AddOption(SslMode = new MySqlConnectionStringOption<MySqlSslMode>(
382404
keys: new[] { "SSL Mode", "SslMode" },
@@ -538,14 +560,16 @@ private static T ChangeType(object objectValue)
538560
return (T) (object) false;
539561
}
540562

541-
if ((typeof(T) == typeof(MySqlLoadBalance) || typeof(T) == typeof(MySqlSslMode) || typeof(T) == typeof(MySqlDateTimeKind) || typeof(T) == typeof(MySqlGuidFormat)) && objectValue is string enumString)
563+
if ((typeof(T) == typeof(MySqlLoadBalance) || typeof(T) == typeof(MySqlSslMode) || typeof(T) == typeof(MySqlDateTimeKind) || typeof(T) == typeof(MySqlGuidFormat) || typeof(T) == typeof(MySqlConnectionProtocol)) && objectValue is string enumString)
542564
{
543-
foreach (var val in Enum.GetValues(typeof(T)))
565+
try
566+
{
567+
return (T) Enum.Parse(typeof(T), enumString, ignoreCase: true);
568+
}
569+
catch (Exception ex)
544570
{
545-
if (string.Equals(enumString, val.ToString(), StringComparison.OrdinalIgnoreCase))
546-
return (T) val;
571+
throw new InvalidOperationException("Value '{0}' not supported for option '{1}'.".FormatInvariant(objectValue, typeof(T).Name), ex);
547572
}
548-
throw new InvalidOperationException("Value '{0}' not supported for option '{1}'.".FormatInvariant(objectValue, typeof(T).Name));
549573
}
550574

551575
return (T) Convert.ChangeType(objectValue, typeof(T), CultureInfo.InvariantCulture);

src/MySqlConnector/Protocol/Serialization/StreamByteHandler.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@ public StreamByteHandler(Stream stream)
2222

2323
public ValueTask<int> ReadBytesAsync(ArraySegment<byte> buffer, IOBehavior ioBehavior)
2424
{
25-
return (ioBehavior == IOBehavior.Asynchronous) ?
26-
new ValueTask<int>(DoReadBytesAsync(buffer)) : DoReadBytesSync(buffer);
25+
return ioBehavior == IOBehavior.Asynchronous ? new ValueTask<int>(DoReadBytesAsync(buffer)) :
26+
RemainingTimeout <= 0 ? ValueTaskExtensions.FromException<int>(MySqlException.CreateForTimeout()) :
27+
m_stream.CanTimeout ? DoReadBytesSync(buffer) :
28+
DoReadBytesSyncOverAsync(buffer);
2729

2830
ValueTask<int> DoReadBytesSync(ArraySegment<byte> buffer_)
2931
{
30-
if (RemainingTimeout <= 0)
31-
return ValueTaskExtensions.FromException<int>(MySqlException.CreateForTimeout());
32-
3332
m_stream.ReadTimeout = RemainingTimeout == Constants.InfiniteTimeout ? Timeout.Infinite : RemainingTimeout;
3433
var startTime = RemainingTimeout == Constants.InfiniteTimeout ? 0 : Environment.TickCount;
3534
int bytesRead;
@@ -48,6 +47,19 @@ ValueTask<int> DoReadBytesSync(ArraySegment<byte> buffer_)
4847
return new ValueTask<int>(bytesRead);
4948
}
5049

50+
ValueTask<int> DoReadBytesSyncOverAsync(ArraySegment<byte> buffer_)
51+
{
52+
try
53+
{
54+
// handle timeout by setting a timer to close the stream in the background
55+
return new ValueTask<int>(DoReadBytesAsync(buffer_).GetAwaiter().GetResult());
56+
}
57+
catch (Exception ex)
58+
{
59+
return ValueTaskExtensions.FromException<int>(ex);
60+
}
61+
}
62+
5163
async Task<int> DoReadBytesAsync(ArraySegment<byte> buffer_)
5264
{
5365
var startTime = RemainingTimeout == Constants.InfiniteTimeout ? 0 : Environment.TickCount;

0 commit comments

Comments
 (0)