Welcome to MSDN Blogs Sign in | Join | Help

General things to take into consideration while doing socket programming.

 

I had to recently write a client-server socket application and so I was mulling over all that needs to be considered when programming with sockets especially using TCP. I have summarized them below.

 

1. Partial Packets – Never assume that how much ever you send from one side will be received at the other side in a single receive. You might have to queue in multiple receives. So incase you need the data in full for processing, send the size of the data before the data itself, so that you can wait for so many bytes to arrive before processing the data,

 

2. Message boundaries :

a. A single receive posted at B might get all the data sent by A.

b. Or the sends might be broken and merged with subsequent sends - If A sends abcde followed by fghijkl where a..l represent different bytes, B can receive  abc, then in another receive defg and then hijkl or as abcdefghij and then kl

[http://blogs.msdn.com/joncole gives sample code and explains this in great detail]

 

3. The above argument is true for subsequent synchronous sends. When asynchronous sends are queued, there is no guarantee in the order the sends are processed, i.e., The second BeginSend call could have been processed before the first.

 

3. When A does a graceful close, read on B returns 0 bytes. This is the most commonly used mechanism to know that the other end has closed the connection.

 

4. When Nagling is enabled, the small sends are delayed. So if you have small packets that need to be sent soon, disable nagling, by setting Socket.NoDelay to true.

 

5. Eventhough you supply a very high value in socket.Listen parameter, the OS has a limit on these values (http://support.microsoft.com/kb/127144/en-us)

 

From the Support site:

The WinSock listen() call backlog parameter under Windows NT version 3.5 and 3.51 accepts a maximum value of 100.

The maximum backlog parameter is 5 on Windows NT 4.0 Workstation, and 200 on Windows NT 4.0 Server.

WinSock server applications use the listen() call to establish a socket for listening for incoming connections. This calls second parameter, backlog, is defined as the maximum length to which the queue of pending connections may grow.

The WinSock version 1.1 specification states the maximum value for the backlog parameter is 5. However, Windows NT version 3.5 accepts up to 100 for this value.

 

6. To prevent the TCP Syn attack, whenever a SYN is received, a SYN+ACK is immediately sent irrespective of whether the server can handle the request or not. This SynProtection mechanism is available in Win2k3 and higher. How does this get into our path when we use System.Net to do socket programming as we don’t deal with SYN packets?

 

Here is the scenario.

If suppose the server listens with a backlog of 2 (just for the sake of explanation I have used the value 2) and it has not yet issued the Accept call. If 5 clients try to connect, the connect from all the 5 clients will succeed as the clients get SYN +ACK immediately from the server. But when they try to send data before the server issues Accept, the send fails with the following exception. Only on a send or the first IO request, the winsock allocates data structures for the socket.

 

 Here is the stack trace from my small repro code.

 

System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host

   at System.Net.Sockets.Socket.Send(Byte[] buffer, Int32 offset, Int32 size, So

cketFlags socketFlags)

   at System.Net.Sockets.Socket.Send(Byte[] buffer)

   at client.Run()

So a successful connect need not necessarily mean that client is connected. A subsequent send should pass too.

 

7. When the socket is disconnected (note: not closed), you can use the same socket only to connect to some other server and through one of the asynchronous APIS only

 

8. There is no send/receive timeout for asynchronous calls. They are valid only for synchronous as it is very difficult to cancel overlapped calls, when timeout occurs.

 

clientSocket.ReceiveTimeout =1000;

byte[] b=new byte[1000];

clientSocket.BeginReceive(b, 0, 100, SocketFlags.None, new AsyncCallback(ReceiveCallback), clientSocket);

 

when there is no send on the other end, the ReceiveCallback is never called at all, even after the receivetimeout that has been set.

Posted by malarch | 5 Comments

Socket Duplication - Part 2

Is it possible to transfer sockets from unmanaged processes?

So, a question now arises.. Is it possible to transfer sockets from unmanaged processes to managed processes? The answer is yes.  Isn't that cool? There are a lot of applications around that have a set of managed processes and unmanaged processes as a part of their application. They are mainly managed processes, using unmanaged code for the parts that are time-consuming - just to increase the speedup.

 

So what has to be done to achieve this. Get the WSAProtocolInfo from the WSADuplicateSocket call

 

WSADuplicateSocketW(m_socket, pid, lpWSAProtocolInfo)

 

and pass it on to the managed process.

 

In the managed process create a SocketInformation object.

        SocketInformation sockInfo = new SocketInformation();

 

The WSAProtocolInfo bytes become the ProtocolInformation field of the SocketInformation object.

 

        byte[] protocolInfo;

//  receive protocolInfo from some Interprocess communication mechanism

        sockInfo.ProtocolInformation = protocolInfo;

 

 

The managed code keeps track of the state of the socket separately. Though some of the state information can be queried from the socket itself, it is not possible to get all the required information. So you have to explicitly set the state in the Socket in the SocketInformation class-> whether the socket is a NonBlocking socket, if it was connected, if it was listening or if UseOnlyOverlappedIO option was set in the socket at the time of duplication. This is through the Options field in SocketInformation object.

 

        sockInfo.Options = SocketInformationOptions.Listening | SocketInformationOptions.Connected;

 

 

Here is an example on how to transfer sockets back and forth managed and unmanaged processes. The example shows a socket, which binds and listens in the unmanaged process, and then accepts connections in the managed process.

 

Example in detail … The unmanaged process creates the socket that needs to be duplicated, binds and listens on that socket. It then duplicates and transfers the socket to the managed process using another socket. The managed process receives the duplicated structure and creates a socket out of the socket information. Once it creates a socket, it tests if the new socket can accept and send and receive data by connecting a test socket to the duplicated socket..

 

 

 

 

Unmanaged code:

#include <stdio.h>

#include <winsock2.h>

#include <process.h>

 

void main(int argc, char* argv[]) {

 

    // Initialize Winsock.

    WSADATA wsaData;

    LPWSAPROTOCOL_INFOW  lpWSAProtocolInfo;

 

    int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );

    if ( iResult != NO_ERROR )

        printf("Error at WSAStartup()\n");

 

    // Create a socket that is to be duplicated.

    SOCKET m_socket;

    m_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

 

    if ( m_socket == INVALID_SOCKET ) {

        printf( "Error at socket(): %ld\n", WSAGetLastError() );

        WSACleanup();

        return;

    }

    printf("Created the socket that has to be duplicated\n");

 

//  bind this to a service port and make the socket listen for client connections

    sockaddr_in clientService;   

    clientService.sin_family = AF_INET;

    clientService.sin_addr.s_addr = inet_addr("127.0.0.1");

    clientService.sin_port = htons( 2000 );

 

    if(bind(m_socket, (struct sockaddr*)&clientService, sizeof(clientService))<0)

    {

        printf( "error at bind socket(): %ld\n", WSAGetLastError() );

        WSACleanup();

        return;

    }

 

      if(listen(m_socket, 5) == SOCKET_ERROR)

      {

        printf( "error at listen socket(): %ld\n", WSAGetLastError() );

        WSACleanup();

        return;

      }

      printf("Bind and listen on that socket %s\n", argv[1]);

     

    int pid = atoi(argv[1]);

    printf("duplicating for process pid %d\n", pid);

    lpWSAProtocolInfo = (WSAPROTOCOL_INFOW*)     malloc(sizeof(WSAPROTOCOL_INFOW));

 

    if(WSADuplicateSocketW(m_socket, pid, lpWSAProtocolInfo) == SOCKET_ERROR)

      {

        printf( "error while Duplicating %ld\n", WSAGetLastError());

        WSACleanup();       

        return;

      }

      printf("Duplicated the socekt\n");

 

    printf("Now create another socket to transfer this protocolinfo to the managed end\n");

      SOCKET transportSocket;

    transportSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

    clientService.sin_family = AF_INET;

    clientService.sin_addr.s_addr = inet_addr("127.0.0.1");

    clientService.sin_port = htons( 2001 );

    if(connect(transportSocket, (SOCKADDR*) &clientService,     sizeof(clientService) ) == SOCKET_ERROR)

    {

        printf( "error at connect socket(): %ld\n", WSAGetLastError() );           

        WSACleanup();

        return;

    }

    printf("connected the transport socket--> now transferring protocolinfo of size %d\n" , sizeof(WSAPROTOCOL_INFOW));

  

    send(transportSocket, (char*) lpWSAProtocolInfo, sizeof(WSAPROTOCOL_INFOW), 0);

      printf("sent protocolinfo data thru socket\n");

      closesocket(transportSocket);

 

      closesocket(m_socket);

    WSACleanup();

 

    return;

}

 

 

 

Managed:

 

using System;

using System.Net;

using System.Net.Sockets;

using System.Threading;

using System.Diagnostics;

 

 

public class ManagedDuplication

{

    public static void Main()

    {       

// Run the unmanaged process -which creates a socket, duplicates it and transfers the struct thru another socket – you can start the unmanaged.exe separately in another command prompt too..

        Process p = new Process();

        string command = "Unmanaged.exe";

  p = Process.Start(command, Process.GetCurrentProcess().Id.ToString());

 

        //create a socket to receive the duplicated socket

        Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        serverSocket.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2001));

        serverSocket.Listen(5);

        Socket clientSocket = serverSocket.Accept();

 

        //receive protocolinfo of the duplicated socket

        byte[] recvBytes = new byte[10000];

        Console.WriteLine("Waiting to receive protocolinfo......");

        int count = clientSocket.Receive(recvBytes);

        Console.WriteLine("Received " + count);

 

        //Create the socketInformation out of the received Bytes.

        //specify the state of the socket

        //The socket had already started to listen in unmanaged code

        

        SocketInformation sockInfo = new SocketInformation();

        byte[] protocolInfo = new byte[count];

        Array.Copy(recvBytes, protocolInfo, count);

        sockInfo.ProtocolInformation = protocolInfo;

        sockInfo.Options = SocketInformationOptions.Listening;

        clientSocket.Close();

 

        //Create the socket from protocolInfo

        Console.WriteLine("Trying to create socket out of protocolinfo");

        Socket duplicatedSocket = new Socket(sockInfo);

        Console.WriteLine("Successfully created socket -Now trying to accept");

 

        //create a socket, connect to the duplicated socket to see if the duplicated

        //socket is able to receive connections and send and receive data

        Socket testSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        testSocket.BeginConnect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2000), new AsyncCallback(ConnectCallback), testSocket);

        clientSocket = duplicatedSocket.Accept();

 

        //send data to the testsocket

        clientSocket.Send(new byte[100]);

        byte[] b = new byte[100];

        count = clientSocket.Receive(b);

        Console.WriteLine("received {0} bytes", count);

        //if we are here then the duplicated socket has received data - > test has succeeded       

        clientSocket.Close();

        duplicatedSocket.Close();

        testSocket.Close();

 

        return;

    }

 

    public static void ConnectCallback(IAsyncResult result)

    {

        Socket socket = (Socket)result.AsyncState;

        Console.WriteLine("Test socket trying to connect to the duplicated socket");

        socket.EndConnect(result);

        byte[] b = new byte[1000];

        int count = socket.Receive(b);

        socket.Send(new byte[count]);

        socket.Close();

    }

}

Posted by malarch | 3 Comments

Socket Duplication - Part 1

Socket Duplication is a new feature supported in Whidbey.  So I thought a series on this feature will be very helpful to those who are going to use this API.

 

In this series, I will discuss how duplication is done in the unmanaged world, how it translates to managed code, some samples on how to do duplication and what you have to watch out for while duplicating....OK now let us get started with Part 1 of the series .

 

Socket Duplication API is just a thin wrapper around the underlying winsock call WSADuplicateSocket

 

Unmanaged World:

 Typically in the unmanaged world, to duplicate a socket in Process A and to make it available in Process B,

  1. Process A calls WSADuplicateSocket with B's process ID  and gets a structured blob data corresponding to the duplicated socket.
  2. Process A then transmits the blob to B by some IPC mechanism.
  3. Process B then uses the blob to construct a new socket descriptor for the same socket through the WSASocket call.
  4. Both the processes A & B can now use the same socket -> that is the socket is now shared.

The descriptors that reference a shared socket can be used independently for I/O, which means both the processes sharing the socket can read from and write to the socket simultaneously.  OS does not implement any access control mechanism and the coordination has to be done by the processes. But the general usage is to have one process incharge of creating connections and duplicating and transferring to another process which uses this socket.

 

Managed World:

So to make it easy, in managed code, once a socket has been duplicated by a process, it is automatically closed at the source process end. Since duplication increases the reference count on the handle of the socket, the socket is valid even when one of the descriptors is closed.  Hence the API  name DuplicateAndClose. 

 

Here is the API                       public SocketInformation DuplicateAndClose ( int targetProcessId )

 

Given a targetProcessID, the API returns SocketInformation object, the information about the socket that can be passed on to the target process. The target process can then recreate the socket using an overload in the socket constructor that accepts socketInformation 

                                                public Socket ( SocketInformation socketInformation)


This api can also be used to share sockets across different appdomains – in which case the targetProcessID is the same as the ID of the duplicating process. The SocketInformation class is serializable – which makes it is easy to transfer the object across appdomains.

 

The following example shows how to duplicate a socket and transfer it to a new appdomain .

 

namespace socketDuplication

{

    public class DuplicationSample

    {

        public static void Main()

        {

            //create a socket

Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

 

            //listen on an  ipaddress and port

            sock.Bind(new IPEndPoint(ipAddress, port));

            sock.Listen(5);

            //Duplicate the socket

SocketInformation socketInfo = sock.DuplicateAndClose(Process.GetCurrentProcess().Id);

 

            //create a new appdomain

AppDomain TestDomain = AppDomain.CreateDomain("DuplicateSocketsTest");

DuplicationNewAppDomain newAppDomain = (DuplicationNewAppDomain)TestDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, "SocketDuplication. DuplicationNewAppDomain");

 

            //transfer socketinfo to the new appdomain

            newAppDomain.TransferSocket(socketInfo);

        }

    }

 

 

    public class DuplicationNewAppDomain : MarshalByRefObject

    {

        void TransferSocket(SocketInformation sockInfo)

        {

        }

    }

 }

     

 

Posted by malarch | 0 Comments

Need to communicate across different Address Types?

Do you have  ipv4 only servers which you need to connect from ipv6 clients or viceversa?

Portproxy is the solution. It acts like a proxy between ipv4 and ipv6 applications, forwarding requests based on the ipaddress and port.

You can configure the proxy to forward  requests received on one / any of ipv6/ipv4 address,A port X to ipv6/ ipv4 address  B at port Y. The cool part is that addresses A and B can belong to same machine or they can be in different machines. Eventhough there is no server listening on ipv6 addresses at port 80, we can still fake the server by configuring the portproxy and can connect to the server from an ipv6 client.

for example i can configure to automatically forward

1.  ipv6 port 80 to ipv4 port 80.
2. ipv6 port 80 on machine A to ipv4 address port 80 on machine B.

netsh interface portproxy>add v6tov4 listenport=80 connectaddress=x.x.x.x connectport=80
netsh interface portproxy>show v6tov4
Listen on IPv6:             Connect to IPv4:
Address         Port        Address         Port
--------------- ----------  --------------- ----------
*               80          x.x.x.x  80

Depending on the connectaddress, the requests are forwarded to the same machine or to a different machine. If no connectaddress is specified, localhost is used.

The other cool thing is this feature is already present in windows 20003 machine.. All you have to do is just configure it to automatically forward.  

Posted by malarch | 0 Comments

Tired of creating sockets for each protocol

Tired of creating sockets for each protocol, to make your application listen on all addresses for a given port ? .

There is no more pain. Here is a cool new feature supported in Vista and Windows Server Longhorn. By just setting a socketoption, you will be able to use the same socket to accept connections from both ipv4 and ipv6 clients.

The following sample code shows how to configure the server socket.

  static void Main(string[] args)
        {
            Socket sock = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
            sock.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0);
            sock.Bind(new IPEndPoint(IPAddress.IPv6Any, 8000));
            sock.Listen(4);
            Socket client = sock.Accept();
            Console.WriteLine("Client has connected successfully with the server");
        }

Create an IPv6 socket. Set the socket option IPV6_V6Only to false (by default it is true), to enable the socket to accept connections from ipv4 addresses too. Bind and Listen as usual. Now both ipv4 and ipv6 clients can connect to it
More info can be found in http://www.ietf.org/rfc/rfc3493.txt.

This is because Vista has a completely redesigned TCP/IP stack.

Whereas Windows XP and Win2k3 have a dual stack architecture, i.e.,  the Tcp, Udp layer is separate for both ipv4 and ipv6 protocols, Vista  supports the dual IP layer architecture. That is, Only a single Tcp, Udp layer is present for both the ipv4 and ipv6 protocols.

Other features of the next Generation TCP/IP stack supported in Vista can be found at http://www.microsoft.com/technet/community/columns/cableguy/cg0905.mspx

Posted by malarch | 1 Comments

Tips And Tricks

Planning on updating this section regularly with some Tips, Tricks and some TroubleShooting techniques. So if you have any, please post in the comments, I'll add it to the article

Tips:

1. Enable IPV6 on your machine: In WinXP SP2 / Win2k3, Execute netsh int ipv6 install. In Vista, it is enabled by default.

 

 

TroubleShooting:

1. BackGroundInfo: Dns.GetHostEntry(hostname) returns IPHostEntry, from which the IPAddresses for the host can be obtained. If you pass in the actual hostname, then the IPAddresses corresponding to the host are returned. When localhost is passed as the hostname, the address 127.0.0.1 is returned.

Situation: Eventhough you pass in the actual hostname, do you get 127.0.0.1?

TroubleShooting Tip: Check your network connectivity. When the Dns server cannot be contacted to get the ipaddresses, 127.0.0.1 is returned.

2. Situation: No ipv6 addresses are returned from Dns.Resolve API.

TroubleShooting Tip:

1. Do a ipconfig to see if any ipv6 addresses are displayed. If not, then ipv6 is not installed on machine. So enable ipv6 using command netsh int ipv6 install.

2. Check the config file to see if ipv6 is enabled in machine.config (under path %windir%\microsoft.Net\Framework\<version of framework>\config\machine.config)

<system.net>
<settings>
<ipv6 enabled="true" />
</settings>
</system.net>

In Whidbey, if the machine.config doesn't  have these settings, add them under the configuration tag. In Everett these tags were mandatory. So you might want to just change the boolean value for the enabled attribute

MoreInfo: Dns.GetHostEntry, Dns.GetHostAddresses are new APIs for whidbey and they are not affected by the ipv6 enabled switch in the config file. To maintain compatibility with everett, old APIs like Dns.Resolve still use the ipv6 enabled switch to decide on the addresses returned.

 

Posted by malarch | 0 Comments

ServicePoint.BindIPEndPointDelegate

 

 

In Whidbey, it is now possible to specify the local endpoint that the HttpWebRequest  should use when connecting to the other end. This can be done by setting the BindIPEndPointDelegate in the Servicepoint.

 

The HttpWebrequest has a Servicepoint property that returns the servicepoint associated with the request.

 

The delegate is called before the socket associated with the httpwebrequest attempts to connect to the remote end.

public delegate IPEndPoint BindIPEndPoint (
        ServicePoint servicePoint, 
        IPEndPoint remoteEndPoint, 
        int retryCount)

 

If the bind to the endpoint specified fails, the delegate is called again. This lets you choose a different endpoint.. After Integer.MaxValue retries, OverflowException is thrown..

 

 

Now with this knowledge, identify the problem with the following piece of code.

 

public static IPEndPoint BindIPEndPointCallback(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)

{

Console.WriteLine("BindIPEndpoint called");

          return new IPEndPoint(IPAddress.Any,5000);

}

 

 

public static void Main()

{

HttpWebRequest request = (HttpWebRequest) WebRequest.Create("http://MyServer");

request.ServicePoint.BindIPEndPointDelegate = new BindIPEndPoint(BindIPEndPointCallback);

HttpWebResponse response = (HttpWebResponse)request.GetResponse();

}

 

Clue:

Consider the scenario when Myserver is ipv6 enabled and is listening on both ipv4 and ipv6 addresses on port 80.

 

 

Problem

This works perfectly fine when myserver does not support ipv6. But when myserver supports ipv6, we try to establish connection with myserver using its ipv6 address. But an ipv4 address is specified in the callback. Since the socket has already been created with AddressFamily.InterNetworkv6 when the delegate is called, the bind to an IPv4 address fails. So the delegate is called again. But the same endpoint is returned so the bind fails again. This repeats Integer.MaxValue times and then OverflowException is thrown. Note Integer.MaxValue is a really big value and it looks as though the code has gone to an infinite loop.

 

Solution:

Since we have the remoteEndPoint parameter in the delegate, choose the local endpoint such that the addressfamily corresponds to the remoteEndpoint. . If you are not able to choose the local Endpoint, just return null and an endpoint is automatically chosen for you.

 

The retryCount gives the number of times the delegate was called. The code inside the delegate can try for a specific number of times and if the bind has not succeeded till then it can return null

Posted by malarch | 0 Comments
 
Page view tracker