Asynchronous Socket Operations


The key to writing server applications that scale to many thousands of concurrent connections is using asynchronous socket calls. Chapter 3 introduced the concept of threading as well as asynchronous operations. Using a separate thread to handle each client connection along with blocking I/O calls is acceptable if the server is not expected to handle many connections. However, threads are an expensive resource, and depending on the physical resources available, the maximum number of threads that can be created could be small. If the server is expected to handle thousands of connections, asynchronous socket calls must be used to ensure efficient use of the local resources.

The Socket class implements asynchronous socket operations using completion ports on Windows NT, Windows 2000, Windows XP, and Windows Server 2003. On Windows 9 x and Windows Me, overlapped I/O is used. Table 9-2 lists the asynchronous Socket class methods . Note that the maximum number of outstanding asynchronous calls is limited only by local resources.

Table 9-2: Asynchronous Socket Methods

Start Method

End Method

Description

BeginAccept

EndAccept

Accepts a client connection on a connection-oriented server socket and returns a Socket object for the client connection

BeginConnect

EndConnect

Initiates a client connection to the indicated server

BeginReceive

EndReceive

Receives data into the specified buffer on the connected socket

BeginReceiveFrom

EndReceiveFrom

Receives data into the specified buffer and returns the EndPoint from which the data originated

BeginSend

EndSend

Sends the given data buffer on the connected socket

BeginSendTo

EndSendTo

Sends the given data buffer to the specified destination

Posting Asynchronous Operations

As we saw in Chapter 3, all asynchronous socket operations take a delegate and a context object. The delegate is a method that s invoked when the asynchronous operation completes either successfully or with an error. The following code shows the prototype for the delegate:

C#

 publicdelegatevoidAsyncCallback(IAsyncResultar); 

Visual Basic .NET

 PublicDelegateSubAsyncCallback(ByValarAsIAsyncResult) 

Perhaps the most important part of posting and managing multiple asynchronous operations is the context information associated with each Begin operation. This context blob can be any object to identify the operation ”typically, it s a class that contains at a minimum the Socket object on which the asynchronous operation was posted as well as an indicator of the type of operation completed. This context information is especially relevant if multiple asynchronous operations are posted simultaneously , such as sends and receives.

The following IoPacket class is an example of the state information associated with an operation:

C#

 publicenumIoPacketType { Accept, Connect, Send, SendTo, Receive, ReceiveFrom } publicclassIoPacket { publicIoPacketTypeioType; publicSocketioSocket; publicIoPacket(ioPacketTypetype,SocketasyncSocket) { ioType=type; ioSocket=asyncSocket; } } 

Visual Basic .NET

 PublicEnumIoPacketType Accept Connect Send SendTo Receive ReceiveFrom EndEnum PublicClassIoPacket PublicioTypeAsIoPacketType PublicioSocketAsSocket PublicSubNew(ByValtypeAsIoPacketType,ByValasyncSocketAsSocket) ioType=type ioSocket=asyncSocket EndSub EndClass 

For each operation posted on the socket object, an instance of the IoPacket class is created that indicates the Socket object and operation type for the asynchronous operation posted. This way, when the delegate is executed, the IoPacket context information can be retrieved to determine the Socket and operation just completed. The following few sections discuss posting and processing specific asynchronous Socket operations.

This chapter s AsyncSocket sample provides examples of asynchronous TCP client and server applications.

Asynchronous Accept

The BeginAccept method posts an asynchronous connection accept request on a listening socket. When the delegate registered with BeginAccept is fired , the EndAccept method on the listening Socket should be called, which returns a Socket object for the client connection request just accepted. The following code snippet shows how to post a BeginAccept on an already created TCP listening socket:

C#
 try { IoPacketioContext=newIoPacket(IoPacketType.Accept,tcpListenSocket); tcpListenSocket.BeginAccept(newAsyncCallback(AcceptCallback), ioContext); } catch(SocketExceptionerr) { Console.WriteLine(Socketerror:{0}",err.Message); } 
Visual Basic .NET
 Try DimioContextAsIoPacket ioContext=NewIoPacket(IoPacketType.Accept,tcpListenSocket) tcpListenSocket.BeginAccept(NewAsyncCallback(AddressOf_ AcceptCallback),ioContext) CatcherrAsSocketException Console.WriteLine(Socketerror:{0}",err.Message) EndTry 

Once the operation is posted, the delegate will be invoked on completion. The delegate is required to call the corresponding method to terminate the asynchronous operation, which in this case is the EndAccept method. Each terminating operation will return the result of the asynchronous operation that includes the result of the operation as well as the context information.

C#
 publicstaticvoidAcceptCallback(IAsyncResultar) { IoPacketioContext=(IoPacket)ar.AsyncState; SockettcpClient=null; switch(ioContext->ioType) { caseIoPacketType.Accept: try { tcpClient=ioContext.ioSocket.EndAccept(ar); } catch(SocketExceptionerr) { Console.WriteLine(Socketerror:{0}",err.Message); } catch(System.NullReferenceExceptionerr) { Console.WriteLine(Socketclosed:{0}",err.Message); } break; default: Console.WriteLine(Error:InvalidIOtype!); break; } } 
Visual Basic .NET
 PublicSubAcceptCallback(ByValarAsIAsyncResult) DimioContextAsIoPacket=ar.AsyncState DimtcpClientAsSocket=Nothing IfioContext.ioType=IoPacketType.AcceptThen Try tcpClient=ioContext.ioSocket.EndAccept(ar) CatcherrAsSocketException Console.WriteLine("Socketerror:{0}",err.Message) CatcherrAsSystem.NullReferenceException Console.WriteLine("Socketclosed") EndTry Else Console.WriteLine("Error:InvalidIOtype! ") EndIf EndSub 

The delegate retrieves the IoPacket context object from the IAsyncResult passed. It then calls the EndAccept method for the IoPacketType.Accept operation that completed. Note that the terminating asynchronous method should always be called in a try except block. If an error occurs on the asynchronous operation, an exception will be thrown when the termination method is called. In the case of an asynchronous accept, if the connecting client resets the connection, an exception will occur.

Additionally, the only way to cancel an asynchronous socket operation is to close the Socket object itself via the Close method. If there are any pending asynchronous operations, the delegate associated with the operation will be invoked and the System.NullReferenceException error is thrown when the corresponding termination method is called.

It s possible and often desirable to post multiple BeginAccept operations on a TCP listening socket to ensure that a high volume of client connections can be handled simultaneously. Remember that a listening socket can queue a limited number of client connections; by posting multiple asynchronous accept operations, the backlog value is effectively increased. This ability to increase the backlog is true for Socket -based applications running on Windows NT “ based operating systems such as Windows 2000, Windows XP, and Windows Server 2003 because the underlying Winsock API call is AcceptEx . If a listening socket sets the backlog value to the maximum allowed and also posts 100 asynchronous accept operations, the effective backlog is now 300. Chapter 14 will discuss high-performance servers in more detail.

Asynchronous Connect

The asynchronous connect method is useful for applications that need to have multiple sockets simultaneously connecting to servers, possibly with already connected sockets performing asynchronous data transfers. Because a single socket can be connected to only one destination, there can be only one outstanding asynchronous connect call on a given socket.

Posting an asynchronous operation is similar to using BeginAccept except that the BeginConnect method also takes the EndPoint describing the server to connect to. Aside from this difference, posting and completing asynchronous connect operations follow the same guidelines as the BeginAccept example shown earlier in this chapter.

Asynchronous Data Transfer

Because sockets generally spend most of their time sending and receiving data, the asynchronous data transfer methods are the most useful of all the asynchronous Socket methods. The asynchronous data transfer methods are somewhat simpler than their corresponding synchronous methods because they re not overloaded ”that is, each asynchronous function has only one instance of the method. The asynchronous send method, BeginSend , is prototyped as shown in the following code:

C#
 publicIAsyncResultBeginSend(byte[]buffer, intoffset, intsize, SocketFlagssocketFlags, AsyncCallbackcallback, objectstate); 
Visual Basic .NET
 PublicFunctionBeginSend(_ ByValbuffer()AsByte,_ ByValoffsetAsInteger,_ ByValsizeAsInteger,_ ByValsocketFlagsAsSocketFlags,_ ByValcallbackAsAsyncCallback,_ ByValstateAsObject_)AsIAsyncResult 

The asynchronous BeginSendTo method is the same as BeginSend except that the EndPoint parameter describing the datagram destination is specified after the SocketFlags parameter. The asynchronous receive methods are BeginReceive and BeginReceiveFrom , for connection-oriented and connectionless receive operations, respectively. The BeginReceive method is prototyped as shown here:

C#
 publicIAsyncResultBeginReceive(byte[]buffer, intoffset, intsize, SocketFlagssocketFlags, AsyncCallbackcallback, objectstate); 
Visual Basic .NET
 PublicFunctionBeginReceive(_ ByValbuffer()AsByte,_ ByValoffsetAsInteger,_ ByValsizeAsInteger,_ ByValsocketFlagsAsSocketFlags,_ ByValcallbackAsAsyncCallback,_ ByValstateAsObject_)AsIAsyncResult 

The BeginReceiveFrom method takes the same parameters as BeginReceive except for an EndPoint parameter passed by reference after SocketFlags .

We ve seen all the parameters to these functions before because they re the same as the synchronous versions except for the callback and state information. However, note that each data transfer operation takes a byte array for sending or receiving data. This array should not be touched or modified between the time the operation is posted and the time the operation completes.

Preserving the array is especially important when sending data on a connection-oriented streaming protocol such as TCP. Because the TCP protocol performs flow control, the receiver can indicate to the sender to stop sending data when the receiver is not handling the incoming data fast enough. At this point, if a BeginSend is posted, it will not complete until the TCP receiver indicates to resume sending, at which point the local network stack will transmit the byte array and call the associated callback routine. If the buffer is modified, it s undetermined what the actual data sent will be.

Another consideration when calling the asynchronous data methods is posting multiple operations on the same socket. The order in which asynchronous callbacks are invoked is not guaranteed . If two BeginReceive operations, R1 and R2 , are queued on a connected socket, it s possible that the callbacks for the completion of these events could be R2 followed by R1 , which can cause headaches when processing the results of these operations. This problem can be solved by encapsulating the code that posts the BeginReceive operation with a Monitor.Enter call on the Socket object, as follows :

C#
 Monitor.Enter(tcpSocket); ioPacket.ReceiveCount=gGlobalReceiveCount++; tcpSocket.BeginReceive(byteArray, 0, byteArray.Length, SocketFlags.None, newAsyncCallback(recvCallback), ioPacket); Monitor.Exit(tcpSocket); 
Visual Basic .NET
 Monitor.Enter(tcpSocket) ioPacket.ReceiveCount=gGlobalReceiveCount+1 tcpSocket.BeginReceive(_ byteArray,_ 0,_ byteArray.Length,_ SocketFlags.None,_ NewAsyncCallback(AddressOfrecvCallback),_ ioPacket_) Monitor.Exit(tcpSocket) 

In this code, execution is synchronized on the tcpSocket object, which is an instance of a connected TCP Socket class. A global counter is maintained , gGlobalReceiveCount , for marking the order in which the BeginReceive operations are posted. BeginReceive is invoked in the synchronized code to ensure that another thread using the same socket doesn t get swapped in between the assignment of the count and the execution of the operation. Of course, the synchronization is required only if asynchronous socket operations are being posted from multiple threads. When the asynchronous operation completes, the delegate can look at the ReceiveCount property to determine the order in which the operations were posted.

Because the send and receive data channels are independent, there s generally no problem with having a single asynchronous send along with a single asynchronous receive operation posted simultaneously.

Canceling Pending Asynchronous Operations

Once one or more asynchronous operations are posted on a Socket object, it might be necessary to cancel all outstanding asynchronous operations on that socket. The only way to cancel these operations is to close the Socket object via the Close method. Once the Socket object is closed, all pending asynchronous operations will complete. When the corresponding termination method is called (for example, EndReceive or EndSend ), a System.NullReferenceExceptionError exception will occur.

In general, it s always recommended to keep track of the number of pending asynchronous operations on each socket, for several reasons. First, limit the total number of outstanding asynchronous I/O operations. Consider a TCP server with 25,000 client connections on a 100 megabit (Mb) Ethernet connection. It s not feasible to post a 4 kilobyte (KB) send on each connection because that would amount to over 102 megabytes (MB) of data. It s likely an out-of- memory exception would occur because the sheer number of outstanding asynchronous operations as well as the number of send buffers submitted.

Second, the number of pending operations needs to be tracked in the event that the socket closes while socket operations are outstanding. This commonly occurs when the process terminates or the connection abortively closes because of a network attack or an excessively idle connection. In this case, where the process is being terminated , the process should close all active Socket objects and then wait for all outstanding asynchronous calls to complete with an exception before exiting the process. This way, you ensure that all events are handled gracefully and prevent timing issues that can cause exceptions (such as when a delegate fires while the process is cleaning up).




Network Programming for the Microsoft. NET Framework
Network Programming for the MicrosoftВ® .NET Framework (Pro-Developer)
ISBN: 073561959X
EAN: 2147483647
Year: 2003
Pages: 121

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net