|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI've been working with sockets since 2000, using Delphi 5.0 and some third-party libraries (Synapse). My very first socket application just copied files between many clients and one server. The client app checks a folder to see if files exist, asks the server where to copy the files in the network and, after copying the files, flags the database record indicating that a file has been moved. The server listens to the client connections, and both exchange XML messages indicating the state of each file copy. Synapse is a blocking socket implementation, and I needed a thread pooling mechanism that works like an HTTP server, because I couldn't keep the connection open (one thread per connection). My solution was to use some IOCP functions to pool the client requests (code) and close the connection after the message exchange was terminated. Now, using C#, I decided to write a socket server and client library that helps me to only have to think about the message exchange (the process) and let .NET do the hard job. So, I needed the following features:
Socket ConnectionThe The Internally, in the library implementation, all the connection interfaces are created using the base connection implementations: Socket ServiceThe Here is an example of a public class SimpleEchoService : ISocketService
{
public void OnConnected(ConnectionEventArgs e)
{
//----- Check the host!
if (e.Connection.HostType == HostType.htServer)
{
//----- Enqueue receive!
e.Connection.BeginReceive();
}
else
{
//----- Enqueue send a custom message!
byte[] b =
GetMessage(e.Connection.SocketHandle.ToInt32());
e.Connection.BeginSend(b);
}
}
public void OnSent(MessageEventArgs e)
{
//----- Check the host. In this case both start a receive!
if (e.Connection.HostType == HostType.htServer)
{
//----- Enqueue receive!
e.Connection.BeginReceive();
}
else
{
//----- Enqueue receive!
e.Connection.BeginReceive();
}
}
public override void OnReceived(MessageEventArgs e)
{
//----- Check the host!
if (e.Connection.HostType == HostType.htServer)
{
//----- If server, send the data buffer received!
byte[] b = e.Buffer;
e.Connection.BeginSend(b);
}
else
{
//----- If client, generate another
//----- custom message and send it!
byte[] b = GetMessage(e.Connection.SocketHandle.ToInt32());
e.Connection.BeginSend(b);
}
}
public override void OnDisconnected(DisconnectedEventArgs e)
{
//----- Check the host!
if (e.Connection.HostType == HostType.htServer)
{
//----- Nothing!
}
else
{
//----- Reconnect with server!
e.Connection.AsClientConnection().BeginReconnect();
}
}
}
The Connection HostWith the Encrypt and CompressEvery time you send and receive messages, the host checks if the data must be encrypted and/or compressed, and this work is made by the The encrypted data buffer is labelled with the service header and the data buffer length, becoming a packet buffer. This packet buffer is controlled by the Enqueuing requestsEvery time you call Send requestIn the internal void BeginSend(BaseSocketConnection connection, byte[] buffer)
{
...
//----- Check Queue!
lock (connection.WriteQueue)
{
if (connection.WriteQueueHasItems)
{
//----- If the connection is sending, enqueue the message!
connection.WriteQueue.Enqueue(writeMessage);
}
else
{
//----- If the connection is not sending, send the message!
connection.WriteQueueHasItems = true;
...
When the message is sent, in the send callback, the host checks the queue again and initiates another send process, if needed: private void BeginSendCallback(IAsyncResult ar)
{
...
//----- Check Queue!
lock (connection.WriteQueue)
{
if (connection.WriteQueue.Count > 0)
{
//----- If has items, send it!
MessageBuffer dequeueWriteMessage =
connection.WriteQueue.Dequeue();
...
}
else
{
connection.WriteQueueHasItems = false;
}
}
...
Receive requestThe same technique applies to the receive method: all the calls to internal void BeginReceive(BaseSocketConnection connection)
{
...
//----- Check Queue!
lock (connection.SyncReadCount)
{
if (connection.ReadCanEnqueue)
{
if (connection.ReadCount == 0)
{
//----- if the connection is not receiving, start the receive!
MessageBuffer readMessage = new MessageBuffer
(FSocketBufferSize);
...
}
//----- Increase the read count!
connection.ReadCount++;
}
}
...
After that, when the message is received and parsed in the receive callback, the host checks the read queue again, and initiates another receive process, if needed: private void BeginReadCallback(IAsyncResult ar)
{
...
//----- Check Queue!
lock (connection.SyncReadCount)
{
connection.ReadCount--;
if (connection.ReadCount > 0)
{
//----- if the read queue has items, start to receive!
...
}
}
...
Ensure send and receiveTo ensure that all data buffer is sent, the private void BeginSendCallback(IAsyncResult ar)
{
...
byte[] sent = null;
int writeBytes = .EndSend(ar);
if (writeBytes < writeMessage.PacketBuffer.Length)
{
//----- Continue to send until all bytes are sent!
writeMessage.PacketOffSet += writeBytes;
.BeginSend(writeMessage.PacketBuffer, writeMessage.PacketOffSet,
writeMessage.PacketRemaining, SocketFlags.None ...);
}
else
{
sent = new byte[writeMessage.RawBuffer.Length];
Array.Copy(writeMessage.RawBuffer, 0, sent, 0,
writeMessage.RawBuffer.Length);
FireOnSent(connection, sent);
}
}
The same approach is used in the receive data buffers because, to read data, a private void BeginReadCallback(IAsyncResult ar)
{
...
CallbackData callbackData = (CallbackData)ar.AsyncState;
connection = callbackData.Connection;
readMessage = callbackData.Buffer;
int readBytes = 0;
...
readBytes = .EndReceive(ar);
...
if (readBytes > 0)
{
...
//----- Has bytes!
...
//----- Process received data!
readMessage.PacketOffSet += readBytes;
...
if (readSocket)
{
//----- Read More!
.BeginReceive(readMessage.PacketBuffer,
readMessage.PacketOffSet,
readMessage.PacketRemaining,
SocketFlags.None, ...);
}
}
...
Check message headerIf the socket service uses some header, all the send and receive processes need to create a packet message indicating the header and the message length. This packet label is created using the following structure: The first label's part is the socket service header. The header is an array of bytes of any length, and you need some advice here: if you choose a very small header, maybe you can have a message with the same array of bytes somewhere, and the host will lose the sequence. If you choose a very long array of bytes, the host can spend the processor's time to verify if the message header is equal to the socket service. The second part is the packet message length. This length is calculated adding the raw message data buffer length, encrypted and/or compressed, plus the header length. Sending packetsAs said before, every time you send messages, the host checks if the data must be encrypted and/or compressed, and, if you choose to use some header, the raw buffer is controlled by the public static MessageBuffer GetPacketMessage(
BaseSocketConnection connection, ref byte[] buffer)
{
byte[] workBuffer = null;
workBuffer = CryptUtils.EncryptData(connection, buffer);
if (connection.Header != null && connection.Header.Length >= 0)
{
//----- Need header!
int headerSize = connection.Header.Length + 2;
byte[] result = new byte[workBuffer.Length + headerSize];
int messageLength = result.Length;
//----- Header!
for (int i = 0; i < connection.Header.Length; i++)
{
result[i] = connection.Header[i];
}
//----- Length!
result[connection.Header.Length] =
Convert.ToByte((messageLength & 0xFF00) >> 8);
result[connection.Header.Length + 1] =
Convert.ToByte(messageLength & 0xFF);
Array.Copy(workBuffer, 0, result,
headerSize, workBuffer.Length);
return new MessageBuffer(ref buffer, ref result);
}
else
{
//----- No header!
return new MessageBuffer(ref buffer, ref workBuffer);
}
}
Receiving packetsThe receive process, if you're using some socket service header, needs to check the header, and continues to read bytes till all the packet message is received. This process is executed in the read callback: private void BeginReadCallback(IAsyncResult ar)
{
...
byte[] received = null
byte[] rawBuffer = null;
byte[] connectionHeader = connection.Header;
readMessage.PacketOffSet += readBytes;
if ((connectionHeader != null) && (connectionHeader.Length > 0))
{
//----- Message with header!
int headerSize = connectionHeader.Length + 2;
bool readPacket = false;
bool readSocket = false;
do
{
connection.LastAction = DateTime.Now;
if (readMessage.PacketOffSet > headerSize)
{
//----- Has Header!
for (int i = 0; i < connectionHeader.Length; i++)
{
if (connectionHeader[i] != readMessage.PacketBuffer[i])
{
//----- Bad Header!
throw new BadHeaderException(
"Message header is different from Host header.");
}
}
//----- Get Length!
int messageLength =
(readMessage.PacketBuffer[connectionHeader.Length] << 8) +
readMessage.PacketBuffer[connectionHeader.Length + 1];
if (messageLength > FMessageBufferSize)
{
throw new MessageLengthException("Message " +
"length is greater than Host maximum message length.");
}
//----- Check Length!
if (messageLength == readMessage.PacketOffSet)
{
//----- Equal -> Get rawBuffer!
rawBuffer =
readMessage.GetRawBuffer(messageLength, headerSize);
readPacket = false;
readSocket = false;
}
else
{
if (messageLength < readMessage.PacketOffSet)
{
//----- Less -> Get rawBuffer and fire event!
rawBuffer =
readMessage.GetRawBuffer(messageLength, headerSize);
//----- Decrypt!
rawBuffer = CryptUtils.DecryptData(connection,
ref rawBuffer, FMessageBufferSize);
readPacket = true;
readSocket = false;
received = new byte[rawBuffer.Length];
Array.Copy(rawBuffer, 0, received, 0,
rawBuffer.Length);
FireOnReceived(connection, received, false);
}
else
{
if (messageLength > readMessage.PacketOffSet)
{
//----- Greater -> Read Socket!
if (messageLength > readMessage.PacketLength)
{
readMessage.Resize(messageLength);
}
readPacket = false;
readSocket = true;
}
}
}
}
else
{
if (readMessage.PacketRemaining < headerSize)
{
//----- Adjust room for more!
readMessage.Resize(readMessage.PacketLength + headerSize);
}
readPacket = false;
readSocket = true;
}
} while (readPacket);
if (readSocket)
{
//----- Read More!
...
.BeginReceive(readMessage.PacketBuffer, readMessage.PacketOffSet,
readMessage.PacketRemaining, SocketFlags.None, ...);
...
}
}
else
{
//----- Message with no header!
rawBuffer = readMessage.GetRawBuffer(readBytes, 0);
}
if (rawBuffer != null)
{
//----- Decrypt!
rawBuffer = CryptUtils.DecryptData(connection,
ref rawBuffer, FMessageBufferSize);
received = new byte[rawBuffer.Length];
Array.Copy(rawBuffer, 0, received, 0, rawBuffer.Length);
FireOnReceived(connection, received, true);
readMessage.Resize(FSocketBufferSize);
...
The read callback method first checks if the connection has some header and, if not, just gets the raw buffer and continues. If the connection has some header, the method needs to check the message header against the socket service header. Before doing that, it checks if the packet message length is greater than the connection header length, to ensure that it can parse the total message length. If not, it reads some bytes. After checking the header, the method parses the message length, and checks with the packet length. If the length is equal, it gets the raw buffer and terminates the loop. If the message length is less than that of the packet message, we have the message plus some data. So, the method gets the raw buffer and continues to read using the same Checking idle connectionsUsing the Crypto ServiceThe SSL authenticationThere's a new stream class called Server authenticationThe public void OnSSLServerAuthenticate(out X509Certificate2 certificate,
out bool clientAuthenticate, ref bool checkRevocation)
{
//----- Set server certificate, client
//----- authentication and certificate revocation!
X509Store store = new X509Store(StoreName.My,
StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certs =
store.Certificates.Find(X509FindType.FindBySubjectName,
"ALAZ Library", false);
certificate = certs[0];
clientAuthenticate = false;
checkRevocation = false;
store.Close();
}
Client authenticationOn the client side of the SSL authentication, you need to pass the host name of the server certificate, and if this name doesn't match, the authentication fails. You can pass a client certificate collection using public void OnSSLClientAuthenticate(out string serverName,
ref X509Certificate2Collection certs, ref bool checkRevocation)
{
serverName = "ALAZ Library";
/*
//----- Using client certificate!
X509Store store = new X509Store(StoreName.My,
StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
certs = store.Certificates.Find(
X509FindType.FindBySubjectName,
serverName, true);
checkRevocation = false;
store.Close();
*/
}
CertificatesTo create certificates, you can use the MakeCert.exe tool found in .NET, and there's a lot of information available about it. You can take a look at John Howard's page, this MS post, and this website. Symmetric authenticationTo implement some symmetric encryption and authentication in this library, I decided to put a post in Microsoft newsgroups. Unfortunately, for the post, but luckily for the knowledge sharing (many thanks to Joe Kaplan, Dominick Baier, and Valery Pryamikov), I decided to use William Stacey's implementation example "A generic method to send secure messages using an exchanged session key". In this code, the symmetric key used in the session is encrypted and signed using RSA key pairs, and the client part needs to know the encrypted server's public key, meaning that this key isn't received from the server in the authentication process. Both the client and the server need to know this key through a manual process. To ensure this, the public void OnSymmetricAuthenticate(HostType hostType,
out RSACryptoServiceProvider serverKey)
{
/*
* A RSACryptoServiceProvider is needed to encrypt and send session key.
* In server side you need public and private key to decrypt session key.
* In client side you need only public key to encrypt session key.
*
* You can create a RSACryptoServiceProvider from a string
* (file, registry), a CspParameters or a certificate.
*/
//----- Using string!
/*
serverKey = new RSACryptoServiceProvider();
serverKey.FromXMLString("XML key string");
*/
//----- Using CspParameters!
CspParameters param = new CspParameters();
param.KeyContainerName = "ALAZ_ECHO_SERVICE";
serverKey = new RSACryptoServiceProvider(param);
/*
//----- Using Certificate Store!
X509Store store = new X509Store(StoreName.My,
StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 certificate = store.Certificates.Find(
X509FindType.FindBySubjectName,
"ALAZ Library", true)[0];
serverKey = new RSACryptoServiceProvider();
if (hostType == HostType.htClient)
{
//----- In client only public key is needed!
serverKey = (RSACryptoServiceProvider)certificate.PublicKey.Key;
}
else
{
//----- In server, both public and private key is needed!
serverKey.FromXmlString(certificate.PrivateKey.ToXmlString(true));
}
store.Close();
*/
}
The authentication messageThe symmetric authentication uses the Client side...
//----- Sign Message!
private byte[] signMessage = new byte[]
{ <sign message array of bytes for authentication> };
...
protected virtual void InitializeConnection(BaseSocketConnection connection)
{
...
//----- Symmetric!
if (connection.EncryptType == EncryptType.etRijndael ||
connection.EncryptType == EncryptType.etTripleDES)
{
if (FHost.HostType == HostType.htClient)
{
//----- Get RSA provider!
RSACryptoServiceProvider serverPublicKey;
RSACryptoServiceProvider clientPrivateKey =
new RSACryptoServiceProvider();
FCryptoService.OnSymmetricAuthenticate(FHost.HostType,
out serverPublicKey);
//----- Generates symmetric algorithm!
SymmetricAlgorithm sa =
CryptUtils.CreateSymmetricAlgoritm(connection.EncryptType);
sa.GenerateIV();
sa.GenerateKey();
//----- Adjust connection cryptors!
connection.Encryptor = sa.CreateEncryptor();
connection.Decryptor = sa.CreateDecryptor();
//----- Create authenticate structure!
AuthMessage am = new AuthMessage();
am.SessionIV = serverPublicKey.Encrypt(sa.IV, false);
am.SessionKey = serverPublicKey.Encrypt(sa.Key, false);
am.SourceKey =
CryptUtils.EncryptDataForAuthenticate(sa,
Encoding.UTF8.GetBytes(clientPrivateKey.ToXmlString(false)),
PaddingMode.ISO10126);
//----- Sign message with am.SourceKey,
//----- am.SessionKey and signMessage!
//----- Need to use PaddingMode.PKCS7 in sign!
MemoryStream m = new MemoryStream();
m.Write(am.SourceKey, 0, am.SourceKey.Length);
m.Write(am.SessionKey, 0, am.SessionKey.Length);
m.Write(signMessage, 0, signMessage.Length);
am.Sign = clientPrivateKey.SignData(
CryptUtils.EncryptDataForAuthenticate(sa,
m.ToArray(), PaddingMode.PKCS7),
new SHA1CryptoServiceProvider());
//----- Serialize authentication message!
XmlSerializer xml = new XmlSerializer(typeof(AuthMessage));
m.SetLength(0);
xml.Serialize(m, am);
//----- Send structure!
MessageBuffer mb = new MessageBuffer(0);
mb.PacketBuffer =
Encoding.Default.GetBytes(Convert.ToBase64String(m.ToArray()));
connection.Socket.BeginSend(
mb.PacketBuffer, mb.PacketOffSet,
mb.PacketRemaining, SocketFlags.None,
new AsyncCallback(InitializeConnectionSendCallback),
new CallbackData(connection, mb));
m.Dispose();
am.SessionIV.Initialize();
am.SessionKey.Initialize();
serverPublicKey.Clear();
clientPrivateKey.Clear();
}
...
}
On the client side of the symmetric authentication, the Server sideprotected virtual void InitializeConnection(BaseSocketConnection connection)
{
...
if (FHost.HostType == HostType.htClient)
{
...
}
else
{
//----- Create empty authenticate structure!
MessageBuffer mb = new MessageBuffer(8192);
//----- Start receive structure!
connection.Socket.BeginReceive(mb.PacketBuffer, mb.PacketOffSet,
mb.PacketRemaining, SocketFlags.None,
new AsyncCallback(InitializeConnectionReceiveCallback), ...);
}
}
private void InitializeConnectionReceiveCallback(IAsyncResult ar)
{
...
bool readSocket = true;
int readBytes = ....EndReceive(ar);
if (readBytes > 0)
{
readMessage.PacketOffSet += readBytes;
byte[] message = null;
try
{
message = Convert.FromBase64String(
Encoding.Default.GetString(readMessage.PacketBuffer,
0, readMessage.PacketOffSet));
}
catch (FormatException)
{
//----- Base64 transformation error!
}
if ((message != null) &&
(Encoding.Default.GetString(message).Contains("</AuthMessage>")))
{
//----- Get RSA provider!
RSACryptoServiceProvider serverPrivateKey;
RSACryptoServiceProvider clientPublicKey =
new RSACryptoServiceProvider();
FCryptoService.OnSymmetricAuthenticate(FHost.HostType,
out serverPrivateKey);
//----- Deserialize authentication message!
MemoryStream m = new MemoryStream();
m.Write(message, 0, message.Length);
m.Position = 0;
XmlSerializer xml = new XmlSerializer(typeof(AuthMessage));
AuthMessage am = (AuthMessage)xml.Deserialize(m);
//----- Generates symmetric algorithm!
SymmetricAlgorithm sa =
CryptUtils.CreateSymmetricAlgoritm(connection.EncryptType);
sa.Key = serverPrivateKey.Decrypt(am.SessionKey, false);
sa.IV = serverPrivateKey.Decrypt(am.SessionIV, false);
//----- Adjust connection cryptors!
connection.Encryptor = sa.CreateEncryptor();
connection.Decryptor = sa.CreateDecryptor();
//----- Verify sign!
clientPublicKey.FromXmlString(Encoding.UTF8.GetString(
CryptUtils.DecryptDataForAuthenticate(sa,
am.SourceKey, PaddingMode.ISO10126)));
m.SetLength(0);
m.Write(am.SourceKey, 0, am.SourceKey.Length);
m.Write(am.SessionKey, 0, am.SessionKey.Length);
m.Write(signMessage, 0, signMessage.Length);
if (!clientPublicKey.VerifyData(
CryptUtils.EncryptDataForAuthenticate(sa, m.ToArray(),
PaddingMode.PKCS7),
new SHA1CryptoServiceProvider(), am.Sign))
{
throw new
SymmetricAuthenticationException("Symmetric sign error.");
}
readSocket = false;
m.Dispose();
am.SessionIV.Initialize();
am.SessionKey.Initialize();
serverPrivateKey.Clear();
clientPublicKey.Clear();
FHost.FireOnConnected(connection);
}
if (readSocket)
{
....BeginReceive(readMessage.PacketBuffer,
readMessage.PacketOffSet,
readMessage.PacketRemaining,
SocketFlags.None,
new AsyncCallback(
InitializeConnectionReceiveCallback), ...);
}
}
On the server side of the symmetric authentication, a Connection CreatorAlthough SocketServer and SocketListenerThe SocketServer constructor and methodsIn the To add HostThreadPoolThis library uses asynchronous socket communication which, in turn, uses the .NET Here are some examples of using //----- Simple server!
SocketServer server = new SocketServer(new SimpleEchoService());
//----- Simple listener!
server.AddListener(new IPEndPoint(IPAddress.Any, 8087));
server.Start();
//----- Server with header!
SocketServer server = new SocketServer(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD });
//----- Listener with simple encryption!
server.AddListener(new IPEndPoint(IPAddress.Any, 8087),
EncryptType.etBase64, CompressionType.ctNone, null);
server.Start();
//----- Server with header and buffer
//----- sizes, no hostthreadpool and idle check setting!
SocketServer server = new SocketServer(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD },
2048, 8192, 0, 0, 60000, 30000);
//----- More than one listener each one with different listen port number!
server.AddListener(new IPEndPoint(IPAddress.Any, 8087));
server.AddListener(new IPEndPoint(IPAddress.Any, 8088),
EncryptType.etBase64, CompressionType.ctNone, null);
server.AddListener(new IPEndPoint(IPAddress.Any, 8089),
EncryptType.etRijndael, CompressionType.ctGZIP,
new SimpleEchoCryptService(), 50, 10);
server.AddListener(new IPEndPoint(IPAddress.Any, 8090),
EncryptType.etSSL, CompressionType.ctNone,
new SimpleEchoCryptService());
server.Start();
SocketClient and SocketConnectorThe SocketClient constructor and methodsThe Here are some examples of using //----- Simple client!
SocketClient client = new SocketClient(new SimpleEchoService());
//----- Simple connector!
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087));
client.Start();
//----- Client with header!
SocketClient client = new SocketClient(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD });
//----- Connector with simple encryption!
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087),
EncryptType.etBase64, CompressionType.ctNone, null);
client.Start();
//----- Client with header and buffer sizes,
//----- no hostthreadpool and idle check setting!
SocketClient client = new SocketClient(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD },
2048, 8192, 0, 0, 60000, 30000);
//----- Connector with encryption and reconnect!
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087),
EncryptType.etSSL, CompressionType.ctGZIP,
new SimpleEchoCryptService(),
5, 30000);
client.Start();
//----- Client with header and buffer sizes,
//----- using hostthreadpool and idle check setting!
SocketClient client = new SocketClient(new SimpleEchoService(),
new byte[] { 0xFF, 0xFE, 0xFD },
4096, 8192, 5, 50, 60000, 30000);
//----- Connector with encryption, reconnect and local endpoint!
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087),
EncryptType.etSSL, CompressionType.ctGZIP,
new SimpleEchoCryptService(),
5, 30000,
new IPEndPoint(IPAddress.Parse("10.10.3.1"), 2000));
client.Start();
//----- Simple client!
SocketClient client = new SocketClient(new SimpleEchoService());
//----- More than one connector each one with different remote socket servers!
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.1"), 8087));
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.2"), 8088),
EncryptType.etBase64, CompressionType.ctNone, null);
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.3"), 8089),
EncryptType.etRijndael, CompressionType.ctGZIP,
new SimpleEchoCryptService());
client.AddConnector(new IPEndPoint(IPAddress.Parse("10.10.1.4"), 8090),
EncryptType.etSSL, CompressionType.ctNone,
new SimpleEchoCryptService(),
5, 30000,
new IPEndPoint(IPAddress.Parse("10.10.3.1"), 2000));
client.Start();
Echo demo projectThere's an Hosts
Services
ConclusionThere's a lot going on here, and I think this library can help anyone who wants to write asynchronous sockets with encryption and compression. Any comments will be appreciated. History
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||