Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting started with socket programming in C# - Best practices

Tags:

c#

.net

sockets

I have seen many resources here on SO about Sockets. I believe none of them covered the details which I wanted to know. In my application, server does all the processing and send periodic updates to the clients.

Intention of this post is to cover all the basic ideas required when developing a socket application and discuss the best practices. Here are the basic things that you will see with almost all socket based applications.

1 - Binding and listening on a socket

I am using the following code. It works well on my machine. Do I need to take care about something else when I deploy this on a real server?

IPHostEntry localHost = Dns.GetHostEntry(Dns.GetHostName()); IPEndPoint endPoint = new IPEndPoint(localHost.AddressList[0], 4444);  serverSocket = new Socket(endPoint.AddressFamily, SocketType.Stream,                       ProtocolType.Tcp); serverSocket.Bind(endPoint); serverSocket.Listen(10); 

2 - Receiving data

I have used a 255 sized byte array. So when I am receiving data which is more than 255 bytes, I need to call the receive method until I get the full data, right? Once I got the full data, I need to append all the bytes received so far to get the full message. Is that correct? Or is there a better approach?

3 - Sending data and specifying the data length

Since there is no way in TCP to find the length of the message to receive, I am planning to add the length to the message. This will be the first byte of the packet. So client systems knows how much data is available to read.

Any other better approach?

4 - Closing the client

When client is closed, it will send a message to server indicating the close. Server will remove the client details from it's client list. Following is the code used at client side to disconnect the socket (messaging part not shown).

client.Shutdown(SocketShutdown.Both); client.Close(); 

Any suggestions or problems?

5 - Closing the server

Server sends message to all clients indicating the shutdown. Each client will disconnect the socket when it receives this message. Clients will send the close message to server and close. Once server receives close message from all the clients, it disconnects the socket and stop listening. Call Dispose on each client sockets to release the resources. Is that the correct approach?

6 - Unknown client disconnections

Sometimes, a client may disconnect without informing the server. My plan to handle this is: When server sends messages to all clients, check the socket status. If it is not connected, remove that client from the client list and close the socket for that client.

Any help would be great!

like image 519
Navaneeth K N Avatar asked Jul 22 '09 03:07

Navaneeth K N


2 Answers

Since this is 'getting started' my answer will stick with a simple implementation rather than a highly scalable one. It's best to first feel comfortable with the simple approach before making things more complicated.

1 - Binding and listening
Your code seems fine to me, personally I use:

serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444)); 

Rather than going the DNS route, but I don't think there is a real problem either way.

1.5 - Accepting client connections
Just mentioning this for completeness' sake... I am assuming you are doing this otherwise you wouldn't get to step 2.

2 - Receiving data
I would make the buffer a little longer than 255 bytes, unless you can expect all your server messages to be at most 255 bytes. I think you'd want a buffer that is likely to be larger than the TCP packet size so you can avoid doing multiple reads to receive a single block of data.

I'd say picking 1500 bytes should be fine, or maybe even 2048 for a nice round number.

Alternately, maybe you can avoid using a byte[] to store data fragments, and instead wrap your server-side client socket in a NetworkStream, wrapped in a BinaryReader, so that you can read the components of your message direclty from the socket without worrying about buffer sizes.

3 - Sending data and specifying data length
Your approach will work just fine, but it does obviously require that it is easy to calculate the length of the packet before you start sending it.

Alternately, if your message format (order of its components) is designed in a fashion so that at any time the client will be able to determine if there should be more data following (for example, code 0x01 means next will be an int and a string, code 0x02 means next will be 16 bytes, etc, etc). Combined with the NetworkStream approach on the client side, this may be a very effective approach.

To be on the safe side you may want to add validation of the components being received to make sure you only process sane values. For example, if you receive an indication for a string of length 1TB you may have had a packet corruption somewhere, and it may be safer to close the connection and force the client to re-connect and 'start over'. This approach gives you a very good catch-all behaviour in case of unexpected failures.

4/5 - Closing the client and the server
Personally I would opt for just Close without further messages; when a connection is closed you will get an exception on any blocking read/write at the other end of the connection which you will have to cater for.

Since you have to cater for 'unknown disconnections' anyway to get a robust solution, making disconnecting any more complicated is generally pointless.

6 - Unknown disconnections
I would not trust even the socket status... it is possible for a connection to die somewhere along the path between client / server without either the client or the server noticing.

The only guaranteed way to tell a connection that has died unexpectedly is when you next try to send something along the connection. At that point you will always get an exception indicating failure if anything has gone wrong with the connection.

As a result, the only fool-proof way to detect all unexpected connections is to implement a 'ping' mechanism, where ideally the client and the server will periodically send a message to the other end that only results in a response message indicating that the 'ping' was received.

To optimise out needless pings, you may want to have a 'time-out' mechanism that only sends a ping when no other traffic has been received from the other end for a set amount of time (for example, if the last message from the server is more than x seconds old, the client sends a ping to make sure the connection has not died without notification).

More advanced
If you want high scalability you will have to look into asynchronous methods for all the socket operations (Accept / Send / Receive). These are the 'Begin/End' variants, but they are a lot more complicated to use.

I recommend against trying this until you have the simple version up and working.

Also note that if you are not planning to scale further than a few dozen clients this is not actually going to be a problem regardless. Async techniques are really only necessary if you intend to scale into the thousands or hundreds of thousands of connected clients while not having your server die outright.

I probably have forgotten a whole bunch of other important suggestions, but this should be enough to get you a fairly robust and reliable implementation to start with

like image 53
jerryjvl Avatar answered Sep 17 '22 19:09

jerryjvl


1 - Binding and listening on a socket

Looks fine to me. Your code will bind the socket only to one IP address though. If you simply want to listen on any IP address/network interface, use IPAddress.Any:

serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444)); 

To be future proof, you may want to support IPv6. To listen on any IPv6 address, use IPAddress.IPv6Any in place of IPAddress.Any.

Note that you cannot listen on any IPv4 and any IPv6 address at the same time, except if you use a Dual-Stack Socket. This will require you to unset the IPV6_V6ONLY socket option:

serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0); 

To enable Teredo with your socket, you need to set the PROTECTION_LEVEL_UNRESTRICTED socket option:

serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)23, 10); 

2 - Receiving data

I'd recommend using a NetworkStream which wraps the socket in a Stream instead of reading the chunks manually.

Reading a fixed number of bytes is a bit awkward though:

using (var stream = new NetworkStream(serverSocket)) {    var buffer = new byte[MaxMessageLength];    while (true) {       int type = stream.ReadByte();       if (type == BYE) break;       int length = stream.ReadByte();       int offset = 0;       do          offset += stream.Read(buffer, offset, length - offset);       while (offset < length);       ProcessMessage(type, buffer, 0, length);    } } 

Where NetworkStream really shines is that you can use it like any other Stream. If security is important, simply wrap the NetworkStream in a SslStream to authenticate the server and (optionally) the clients with X.509 certificates. Compression works the same way.

var sslStream = new SslStream(stream, false); sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls, true); // receive/send data SSL secured 

3 - Sending data and specifying the data length

Your approach should work, although you probably may not want to go down the road to reinventing the wheel and design a new protocol for this. Have a look at BEEP or maybe even something simple like protobuf.

Depending on your goals, it might be worth thinking about choosing an abstraction above sockets like WCF or some other RPC mechanism.

4/5/6 - Closing & Unknown disconnections

What jerryjvl said :-) The only reliable detection mechanism are pings or sending keep-alives when the connection is idle.

While you have to deal with unknown disconnections in any case, I'd personally keep some protocol element in to close a connection in mutual agreement instead of just closing it without warning.

like image 24
dtb Avatar answered Sep 18 '22 19:09

dtb