Friday, June 27, 2008

AsyncSocket for Windows

I wrote a really long post recently entitled The problem with sockets. At the very end I slipped in a tiny little remark: "we're releasing under public domain our C# implementation of AsyncSocket".

It's pretty much a replica of the Mac version. So it supports fully asynchronous reads and writes, and transparently supports IPv4, IPv6, and TLS/SSL. Here's an outline of the public methods and such for you C# folks:


delegate void SocketWillDisconnect(AsyncSocket sender, Exception e);
delegate void SocketDidDisconnect(AsyncSocket sender);
delegate void SocketDidAccept(AsyncSocket sender, AsyncSocket newSocket);
delegate void SocketWillConnect(AsyncSocket sender, Socket socket);
delegate void SocketDidConnect(AsyncSocket sender, IPAddress address, UInt16 port);
delegate void SocketDidRead(AsyncSocket sender, Data data, long tag);
delegate void SocketDidReadPartial(AsyncSocket sender, int partialLength, long tag);
delegate void SocketDidWrite(AsyncSocket sender, long tag);
delegate void SocketDidWritePartial(AsyncSocket sender, int partialLength, long tag);
delegate void SocketDidSecure(AsyncSocket sender);

event SocketWillDisconnect WillDisconnect;
event SocketDidDisconnect DidDisconnect;
event SocketDidAccept DidAccept;
event SocketWillConnect WillConnect;
event SocketDidConnect DidConnect;
event SocketDidRead DidRead;
event SocketDidReadPartial DidReadPartial;
event SocketDidWrite DidWrite;
event SocketDidWritePartial DidWritePartial;
event SocketDidSecure DidSecure;

Object Tag{ get; set; }
ISynchronizeInvoke InvokeableObject{ get; set; }
bool AllowApplicationForms{ get; set; }
bool AllowMultithreadedCallbacks{ get; set; }

float ProgressOfCurrentRead();
float ProgressOfCurrentRead(out long tag, out int bytesDone, out int total);
float ProgressOfCurrentWrite();
float ProgressOfCurrentWrite(out long tag, out int bytesDone, out int total);

bool Accept(UInt16 port);
bool Accept(UInt16 port, out Exception error);
bool Accept(String hostaddr, UInt16 port);
bool Accept(String hostaddr, UInt16 port, out Exception error);

bool Connect(String host, UInt16 port);
bool Connect(String host, UInt16 port, out Exception error);

void StartTLSAsClient(String serverName,
RemoteCertificateValidationCallback rcvc,
LocalCertificateSelectionCallback lcsc);
void StartTLSAsServer(RemoteCertificateValidationCallback rcvc,
LocalCertificateSelectionCallback lcsc);

void Disconnect();
void Disconnect(out Socket socket4, out Socket socket6);
void DisconnectAfterReading();
void DisconnectAfterWriting();

bool Connected { get; };
IPAddress RemoteAddress{ get; }
UInt16 RemotePort{ get; }
IPAddress LocalAddress{ get; }
UInt16 LocalPort{ get; }

void Read(int timeout, long tag);
void Read(int length, int timeout, long tag);
void Read(byte[] term, int timeout, long tag);

void Write(IData data, int timeout, long tag);

static byte[] CRLFData{ get; }
static byte[] CRData{ get; }
static byte[] LFData{ get; }


Also provided is an IData interface, and serveral concrete classes. So one can create an IData object from raw data, a string, a file, etc. It's a simple yet clever wrapper around a byte[] object.

Download WinAsyncSocket.zip

Edit 1: The download now contains two sample projects that use the AsyncSocket class. An echo server that you can connect to using telnet. And a very simple HTTP client.

Edit 2: Due to popular request, AsyncSocket for Windows is now a google code project: DotnetAsyncSocket. This will make it easier for users to submit bugs or patches, report issues, or make feature requests.

The Mac version is always available from the CocoaAsyncSocket google code page.

15 comments:

Sean said...

Would it be possible to provide a working sample of the echo server/client for the C# AsyncSocket please?

Robbie Hanson said...

Sure. I'll post it up here later today or tomorrow.

Sean said...

Awesome. Thanks Robbie.

Robbie Hanson said...

The download now contains two sample projects. An echo server, and a simple http client. The echo server is a good intro to using the AsyncSocket class. The http client is a little more advanced. It's mostly there to showcase TLS.

Sean said...

Thanks

Kwaku said...

Very nice job on this library. I tried to connect to the EchoServer from a Winform application as follows:
using (FileStream fs = new FileStream(@"C:\Temp\YServer.txt", FileMode.Open))
{
byte[] im = new byte[fs.Length];
fs.Read(im, 0, im.Length) listenSocket.Write(new Data(im), -1, 0);
}
listenSocket.Write(new Data(im),-1, 0);
The EchoServer does not appear to rceive the data, though the server, port appear to be correct.

Robbie Hanson said...

Hi Kwaku,

To test the echo server you can use telnet. Telnet is a simple application you can use from your command line. Just open your command line and type in:

telnet [host] [port]

Where [host] and [port] are replaced by the proper values.

First make sure the EchoServer is working. Launch the Echo Server app, and start it. It will output a message telling you what port it started on. Then connect using telnet and fill in the echo server port:

telnet localhost [port]

This should immediately connect to the echo server. Now type anything and hit enter. The text you typed will be echoed back to you, and displayed in the echo server log.

To exit your telnet session hit "CTRL+]" and then type quit.

Not sure what you're trying to do in your code. If you want, you can email me the source code and I'll take a look.

Kwaku said...

Hansen,
Thought I may have lost my previous comment on this wonderful library. I have a 150K data that I need to send over to the server. On reception, only 36K is received on the server side which is the message buffer size. Hope there is a way or a process that I am not familiar with in order to receive the entire data on the server side. Hope you can provide some assistance on this. Any data less than the buffer size is received correctly. Thanks again for your quick response.

Robbie Hanson said...

I think I know what's causing the problem.

I tried connecting to the EchoServer myself within a new C# project (EchoClient). I copy-pasted your code, and substituted my iTunes XML file (approximately 920 KB on this machine). Each time it only managed to send around 48 K. The odd thing was, it didn't have any errors - on either the sending or within the echo server. It took me a moment to figure out what was happening...

The echo server reads a line at a time. Each time it encounters a CRLF, it echos the line back. There are thousands of CRLF's in the XML file, so the echo server was sitting there faithfully echoing each line back as it received it. Eventually the TCP receive buffer (within the OS) for the EchoClient fills up (since I wasn't reading it), and then the OS refuses to send anymore. But don't think of this as a bug within the OS - it's actually a part of TCP. Namely flow control. TCP makes sure not to flood the network, or either computer. Since the TCP receive buffer filled up, this appeared to the OS that it was receiving data faster than the application could handle it. So it "put the brakes on" until the receive buffer size started decreasing.

All I had to do to fix the problem was add in code to read data from the socket. Even if I don't need it, I'm required to at least read it.

Try adding this after your write:
sender.Read(AsyncSocket.CRLFData, -1, 0);

And this somewhere:
private void asyncSocket_DidRead(AsyncSocket sender, Data data, long tag)
{
sender.Read(AsyncSocket.CRLFData, -1, 0);
}

And don't forget to register for the read event:
asyncSocket.DidRead += new AsyncSocket.SocketDidRead(asyncSocket_DidRead);

Anonymous said...

awesome class!!!!!
THANKYOU!!!!!!!!

Hyde said...

Small Fix:

private void socket_DidConnect(IAsyncResult iar)
{
// We lock in this method to ensure that the SocketDidConnect delegate fires before
// processing any reads or writes. ScheduledDequeue methods may be lurking.
// Also this ensures the flags are properly updated prior to any other locked method executing.
lock (lockObj)
{
try
{
Socket socket = (Socket)iar.AsyncState;

socket.EndConnect(iar);

socketStream = new NetworkStream(socket);
stream = socketStream;

// Notify the delegate
flags |= kDidCallConnectDelegate;
OnSocketDidConnect(RemoteAddress, RemotePort);

// Immediately deal with any already-queued requests.
MaybeDequeueRead();
MaybeDequeueWrite();
}
catch (Exception e)
{

!!!!!!!!!HERE!!!!!! // need to be notified that connection process is failed !OnSocketDidConnect(RemoteAddress, RemotePort);
CloseWithException(e);
}
}
}

Susanta said...

Robbie,

Awesome post. I am tryig use this code in my project for server part. I need to send the images from iPhone to server. I am using the AsyncSoket objetive c in my iPhone and trying to use your code for server part. But it does not work..I have commented the line "newSocket.Write(new Data("Welcome\r\n"), -1, 0);" and listenSocket_DidAccept event is firing. But asyncSocket_DidRead event is not getting executed at all. But I checked my network connection, it receives the data.

Any help is really appreciated. you can contact me at xchangingiphonedeveloper@gmail.com

Thanks,
Susanta

Anonymous said...

Thanks for your effort to share this and I'm using your component for one of my internal projects in my work however I face issues when trying to parse messages since the client application doesn't send any delimiter/termination character in sending messages.

From your example it reads buffer until it find CRLF from the message.

sender.Read(AsyncSocket.CRLFData, -1, 0);

How can I deal with non CRLFData delimeter?

Diay si...Palante!!! said...

Excellent post, and very good so you can use this code in Google Codes for improvement.

Using EchoServer I was very good however when using the client from IOS, the server accepts the client, but I instantly is disconnect so I can not make any step of strings.

A little help please:
enanosk89@gmail.com

Aris Semertzidis said...

Awesome post!!! It just solved all of my problem at once! I was searching a lot to find a clean and stable solution like yours!

Thanks