Use .NET SocketAsyncEventArgs to write socket server code in C#

Use .NET SocketAsyncEventArgs to write socket server code in C#
Is your email address OK?  You are signed up for our newsletters but your email address is either unconfirmed, or has not been reconfirmed in a long time. Please click   here  to have a confirmation email sent so we can confirm your email address and start sending you newsletters again. Alternatively, you can   update your subscriptions.

 

Download SocketAsyncServerAndClient.zip - 200.2 KB



Introduction

Writing fast, scalable socket server code for TCP/IP is not easy. In order to help you write scalable, high performance socket server code, Microsoft created the SocketAsyncEventArgs class. A proven way to write scalable, high performance socket code for TCP/IP in Windows can be seen in this article on I/O completion ports. Also, here's a link to a Microsoft page on I/O Completion Ports SocketAsyncEventArgs uses I/O Completion ports via the asynchronous methods in the .NET Socket class.  SocketAsyncEventArgs object allows us to access the accepting socket with all the functionality of the SocketAsyncEventArgs class, like working asynchronously, raising the Completed event, setting buffer space, etc.

But there are other issues that need to be addressed in order to create dependable socket server code. One issue is buffers. Buffers in TCP are unmanaged, that is, not controlled by the .NET Framework, but by Windows system. So the buffer gets "pinned" to one place in memory, thereby causing memory fragmentation, since the .NET GarbageCollector will not be able to collect that space. This situation is improved by putting all the buffers together in one block of memory, and just reusing that same space over and over. The example in the BufferManager code on Microsoft's page for the SetBuffer method shows how to build it. 
 

Background  

You may have started your research into this topic at Microsoft's main page for the SocketAsyncEventArgs class. The example code on that page got me started. But it was also confusing. It seems that they have removed some example code about the UserToken property. Also, some of the method names were a bit confusing, as were some of the variable names.  Their reason for using a Semaphore was not explained really.  While their code for building the BufferManager was good, the way that they dealt with the SetBuffer method in their ProcessReceive method in example code for theSocketAsyncEventArgs class will pretty much work in only the narrowest of examples. If you send a 10 byte string, and then a 20 byte string, it won't work. Their code sets the buffer to be whatever size you send on the first message from the client. So after the first message it would just send back the first 10 bytes. So, we need a better example of how to get the data and use it.  Plus, in the explanation of the example code they said "For example, if a server application needs to have 15 socket accept operations outstanding at all times to support incoming client connection rates, it can allocate 15 reusable SocketAsyncEventArgs objects for that purpose." But then their example only included reusable SocketAsyncEventArgs objects for receive/send, not for accept. The SocketAsyncEventArgs object for the accept operation would wait until the receive/send finished to do another accept op. Instead we can use a pool, as they mentioned in their explanation, and post accept operations faster.

So, this article was done in an attempt to make it clearer how to use the SocketAsyncEventArgs class. The code in this article was developed on Visual Studio 2008 using .NET 3.5. Pay special attention to the code related to buffers, as buffer-related stuff seems to be an area where people have more difficulty. This article assumes some knowledge of delegates and event handling in Windows. 

Regarding the SocketAsyncEventArgs class, Microsoft's website says it requires "Platforms: Windows 7, Windows Vista, Windows XP SP3, Windows Server 2008, Windows Server 2003. (The) .NET Framework Supported in: 4, 3.5 SP1, 3.0 SP1, 2.0 SP1.  (The) .NET Framework Client Profile Supported in: 4, 3.5 SP1."
 

TCP Protocol and Socket Basics

If you have experience with socket server code, you can skip this section. For those new to socket programming, there are four main steps in using a socket server with TCP. (It's often described as six parts, but I like to put the first three together into one.)

1) Listen for connection requests
In order to listen you need to
(a) create a socket.  
(b) bind that socket to a port, 
(c) listen with that socket.
A client (not a server) can initiate a connection request, by sending a SYN packet. The client does not listen for incoming connections. It always initiates connections by using the Connect or ConnectAsync method. When a client initiates a connection, then the Windows TCP/IP subsystem of the server will respond with SYN, ACK. After the client machine's Windows TCP/IP subsystem responds back with an ACK packet, the connection is established. Windows will handle this TCP/IP stuff for you. In other words, SYN, ACK, PSH, packets, and similar parts of TCP/IP do not have to be coded by you. Very nice. The server's listening socket can maintain a queue of connection requests waiting to be accepted. This queue is called the "backlog". The listening socket passes the connection info to another socket via an "accept" operation, and then gets the next incoming connection in the backlog queue, or if there is none, waits till there is a new connection from a client.  
2) Accept connection requests
In order to have multiple connections on the same port, the listening socket must pass off the connection info to another socket, which accepts it. The accepting socket is not bound to the port. You post an accept operation to pass the connection from the listening socket to the accepting socket. The accept operation can be posted before the incoming connection is established, so that the listening socket immediately passes off the new connection info to the accepting socket. The client does not need to perform an accept operation.
3) Receive/Send via the connection
After the accept operation has completed you can now receive or send data with that connection. (The same SocketAsyncEventArgs object that did the accept operation could also do the receiving or sending, if we post a receive or send on it and have buffer space for it.) In the design of the code below, the SocketAsyncEventArgs which did the accept operation passes the connection info over to another SocketAsyncEventArgs object to do receiving/sending. (We could also split the receiving and sending into 2 separate SocketAsyncEventArgs objects, if we wish. One reason to do that might be a need for different buffer sizes for send vs. receive. We would just need to have the connection info in both the SocketAsyncEventArgsobject that sends and the SocketAsyncEventArgs that receives. And they might need a reference to each other also.)
4) Close the connection
Either client or server can initiate an operation to close the connection. Usually the client would initiate that. Again, the lower level TCP/IP of the disconnect is handled by Windows operating system. The connection can be closed using the Close method, which destroys the Socket and cleans up its managed and unmanaged resources. You can also close by using the Disconnect or DisconnectAsync method, which allows reuse of the Socket.

With TCP there is no guarantee that one send operation on the client will be equal to one receive operation on the server. One send operation on the client might be equal to one, two or more receive operations on the server. And the same is true going back to client from server. So you must have some way of determining where a TCP message begins and/or ends. Three possible ways to handle TCP messages are:
1) Prefix every message with an integer that tells the length of the message.
2) All messages be fixed length. And both client and server must know the length before the message is sent.
3) Append every message with a delimiter to show where it ends. And both client and server must know what the delimiter is before the message is sent. 

Also, your communications protocol must include whether there will be a response (send operation) from the server back to the client after each received message or not. Will that response be after one complete received TCP message, or can it be after more than one message? If it is after one message, the code is simpler probably.

Okay, so let's think about the possible situations that might occur with the data that the server receives in one receive operation with our communication protocol.

1) On the first receive op, receive less bytes than the length of the prefix. 
2) After having received part of the prefix on a previous receive op or ops, then receive another part of the prefix, but not all of it.
3) After having received part of the prefix on a previous receive op or ops, then receive the rest of the prefix, but nothing more.
4) After having received part of the prefix on a previous receive op or ops, we then receive the rest of it, plus part of the message.
5) After having received part of the prefix on a previous receive op or ops, we then receive the rest of it, plus all of the message.
6) Receive exactly the number of bytes that are in the prefix, but nothing more.
7) After having received exactly the number of bytes that are in the prefix on a previous receive op or ops, we then receive part of the message.
8) After having received exactly the number of bytes that are in the prefix on a previous receive op or ops, we then receive all of the message.
9) Receive the number of bytes for the prefix plus part of the message, but NOT all of the message.
10) After having received the prefix and part of the message on a previous receive op or ops, we then receive another part of the message, but not all of it.
11) After having received the prefix and part of the message on a previous receive op or ops, we then receive all the rest of the message.
12) Receive the number of bytes for the prefix plus all of the message on the first receive op. This is actually the most common thing that will happen. But all of the above things can happen and do happen. If both client and server have buffer sizes larger than the messages, then the situations above may not happen when running both client and server on the same machine, or even on a LAN. But TCP is unpredictable over the Internet. So your code needs to allow for all of those possibilities.

Let's look at some code 

Accept operations. In this app the socket which does the accept can be accessed thru a   SocketAsyncEventArgs  object, in its   AcceptSocketproperty. On Microsoft's   AcceptSocket  page it says, "If not supplied (set to null) before calling the   Socket.AcceptAsync  method, a new socket will be created automatically." That's what is done in the code below. We just allow a new Socket object to be created for every new connection. (There is the option of reusing sockets in .NET and having a pool of Socket objects, but so far I have not found it to yield a significant advantage.) We can have a pool of these   SocketAsyncEventArgs  objects, each one containing a separate Socket object, to deal with accept operations. In this pool you do not need one object for each connection the server is maintaining, because after the accept operation completes, a reference to the socket is handed off to another   SocketAsyncEventArgs  object pretty fast. It does   not  seem to help to put a lot of   SocketAsyncEventArgs  objects in the pool. Again, repeating for clarity, the socket which does the accept operation is in   SocketAsyncEventArgs. AcceptSocket  property of the SocketAsyncEventArgs  objects that comes out of the pool of   SocketAsyncEventArgs  objects that we create for accept operations.

Receive/Send operations. In this app the receive and send operations are handled via SocketAsyncEventArgs objects that come out of a pool ofSocketAsyncEventArgs objects that we create for receive/send operations.  This is NOT the same pool as we just examined regarding accept operations. To improve performance we have a pool of these objects which do receive and send operations. The number of SocketAsyncEventArgsobjects in the pool for receive/send operations should probably be equal to the maximum number of concurrent connections allowed.   

What is our communication protocol in this code?
1) One message from client will correspond with one message from the server. 
2) After a connection is made the client will send a message first, and then post a receive op to wait for the response from the server. And for each message that the client sends the server will respond with a message to the client. Then it will post another receive op and wait for the next message from the client. In our code the server will make a few changes to the data before responding to the client, so that we do more than just echo data sent by the client, which is what the Microsoft example does. That approach should help you see what happens with the buffers better than just echoing would. 
3) We will prefix every message with an integer that tells the length of the message. 

 

Okay, let's look at the server code.  Sometimes in code comments I abbreviate "SocketAsyncEventArgs" as "SAEA".

 Collapse
class Program
    {
        //You would want a buffer size larger than 25 probably, unless you know the
        //data will almost always be less than 25. It is just 25 in our test app.
        public static readonly Int32 testBufferSize = 25;
                        
        //This is the maximum number of asynchronous accept operations that can be 
        //posted simultaneously. This determines the size of the pool of 
        //SocketAsyncEventArgs objects that do accept operations. Note that this
        //is NOT the same as the maximum # of connections.
        public static readonly Int32 maxSimultaneousAcceptOps = 4;

        //The size of the queue of incoming connections for the listen socket.
        public static readonly Int32 backlog = 100;

        //For the BufferManager
        public static readonly Int32 opsToPreAlloc = 2;    // 1 for receive, 1 for send
        
        //This variable determines the number of 
        //SocketAsyncEventArg objects put in the pool of objects for receive/send.
        //The value of this variable also affects the Semaphore.
        //This app uses a Semaphore to ensure that the max # of connections
        //value does not get exceeded.
        //Max # of connections to a socket can be limited by the Windows Operating System
        //also.
        public static readonly Int32 maxNumberOfConnections = 100;

        //If this is true, then after a message is received, 
        //the server will send a message back to the client.
        //In our example app, this must be true. The code will not handle false.
        public static bool respondToClient = true;
        
        //If this port # will not work for you, it's okay to change it.
        public static readonly Int32 port = 4444;

        //This number must be the same as the value on the client.
        //Tells what size the message prefix will be. Don't change this unless
        //you change the code, because 4 is the length of 32 bit integer, which
        //is what we are using as prefix.
        public static readonly Int32 receivePrefixLength = 4;
        public static readonly Int32 sendPrefixLength = 4;                
        
        public static Int32 mainTransMissionId = 10001;
        //object that will be used to lock the mainTransMissionId
        public static object lockerForTid = new object();

        public static List<DataHolder> listOfDataHolders;
        //object that will be used to lock the listOfDataHolders
        public static object lockerForList = new object();

        public static Int32 mainSessionId = 1000000001;
        //object that will be used to lock the mainSessionId
        public static object lockerForSesId = new object();        
                
        //If you make this a positive value, it will simulate some delay on the
        //receive/send SAEA object after doing a receive operation.
        //That would be where you would do some work on the received data, 
        //before responding to the client.
        //This is in milliseconds. So a value of 1000 = 1 second delay.
        public static readonly Int32 msDelayAfterGettingMessage = -1;

        static void Main(String[] args)
        {
            try
            {
                //Just displaying the IP addresses of this machine.
                IPAddress[] addressList = 
                     Dns.GetHostEntry(Environment.MachineName).AddressList;
                
                // Get endpoint for the listener.
                IPEndPoint localEndPoint = 
                     new IPEndPoint(addressList[addressList.Length - 1], port);
                                
                //This object holds a lot of settings that we pass from Main method
                //to the SocketListener. In a real app, you might want to read
                //these settings from a database or windows registry settings that
                //you would create.
                SocketListenerSettings theSocketListenerSettings = 
                     new SocketListenerSettings(maxNumberOfConnections, backlog, 
                     maxSimultaneousAcceptOps, receivePrefixLength, 
                     testBufferSize, sendPrefixLength, opsToPreAlloc, localEndPoint);
                
                //instantiate the SocketListener.
                SocketListener socketListener = 
                     new SocketListener(theSocketListenerSettings);
                                
                string closeString = "z";
                string closeCheck = "";
                while (closeCheck != closeString)
                {
                    Console.WriteLine("type '" + closeString 
                     + "' and press Enter to close the program.");
                    closeCheck = Console.ReadLine();                    
                }
            }
            catch (IndexOutOfRangeException)
            {
                Console.WriteLine("port invalid");
            }
            catch (FormatException)
            {
                Console.WriteLine("port invalid");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
            }            
        }
    }


The primary class is SocketListener.

 Collapse
class SocketListener
    {        
        //Buffers for sockets are unmanaged by .NET. 
        //So memory used for buffers gets "pinned", which makes the
        //.NET garbage collector work around it, fragmenting the memory. 
        //Circumvent this problem by putting all buffers together 
        //in one block in memory. Then we will assign a part of that space 
        //to each SocketAsyncEventArgs object, and
        //reuse that buffer space each time we reuse the SocketAsyncEventArgs object.
        //Create a large reusable set of buffers for all socket operations.
        BufferManager theBufferManager;

        // the socket used to listen for incoming connection requests
        Socket listenSocket; 

        //A Semaphore has two parameters, the initial number of available slots
        // and the maximum number of slots. We'll make them the same. 
        //This Semaphore is used to keep from going over max connection #.         
        //(It is not about controlling threading really here.)   
        Semaphore theMaxConnectionsEnforcer;       
        
        //an object that we pass in and which has all the settings the listener needs 
        SocketListenerSettings socketListenerSettings;
        
        // pool of reusable SocketAsyncEventArgs objects for accept operations
        SocketAsyncEventArgsPool poolOfAcceptEventArgs;
        // pool of reusable SocketAsyncEventArgs objects for 
        //receive and send socket operations
        SocketAsyncEventArgsPool poolOfRecSendEventArgs;
        
        //_______________________________________________________________________________
        // Constructor.
        public SocketListener(SocketListenerSettings theSocketListenerSettings)        
        {
            this.socketListenerSettings = theSocketListenerSettings;
                        
            //Allocate memory for buffers. We are using a separate buffer space for
            //receive and send, instead of sharing the buffer space, like the Microsoft
            //example does.
            this.theBufferManager = 
                    new BufferManager(this.socketListenerSettings.BufferSize * 
                    this.socketListenerSettings.MaxConnections * 
                    this.socketListenerSettings.OpsToPreAllocate,
                    this.socketListenerSettings.BufferSize * 
                    this.socketListenerSettings.OpsToPreAllocate);
            
            this.poolOfRecSendEventArgs = 
                    new SocketAsyncEventArgsPool(this.socketListenerSettings.MaxConnections);
            this.poolOfAcceptEventArgs = 
                    new SocketAsyncEventArgsPool(this.socketListenerSettings.MaxAcceptOps);                    
            this.theMaxConnectionsEnforcer = 
                    new Semaphore(this.socketListenerSettings.MaxConnections + 1, 
                    this.socketListenerSettings.MaxConnections + 1);

            //Microsoft's example called these from Main method, which you 
            //can easily do if you wish.
            Init();
            StartListen();
        }

        //____________________________________________________________________________
        // initializes the server by preallocating reusable buffers and 
        // context objects (SocketAsyncEventArgs objects).  
        //It is NOT mandatory that you preallocate them or reuse them. But, but it is 
        //done this way to illustrate how the API can 
        // easily be used to create reusable objects to increase server performance.
        
        internal void Init()
        {   
            // Allocate one large byte buffer block, which all I/O operations will 
            //use a piece of. This gaurds against memory fragmentation.
            this.theBufferManager.InitBuffer();

            // preallocate pool of SocketAsyncEventArgs objects for accept operations           
            for (Int32 i = 0; i < this.socketListenerSettings.MaxAcceptOps; i++)
            {
                // add SocketAsyncEventArg to the pool
                this.poolOfAcceptEventArgs.Push(CreateNewSaeaForAccept(poolOfAcceptEventArgs));
            }           

            //The pool that we built ABOVE is for SocketAsyncEventArgs objects that do
            // accept operations. 
            //Now we will build a separate pool for SAEAs objects 
            //that do receive/send operations. One reason to separate them is that accept
            //operations do NOT need a buffer, but receive/send operations do. 
            //ReceiveAsync and SendAsync require
            //a parameter for buffer size in SocketAsyncEventArgs.Buffer.
            // So, create pool of SAEA objects for receive/send operations.
            SocketAsyncEventArgs eventArgObjectForPool;

            for (Int32 i = 0; i < this.socketListenerSettings.MaxConnections; i++)
            {
                //Allocate the SocketAsyncEventArgs object for this loop, 
                //to go in its place in the stack which will be the pool
                //for receive/send operation context objects.
                eventArgObjectForPool = new SocketAsyncEventArgs();

                // assign a byte buffer from the buffer block to 
                //this particular SocketAsyncEventArg object
                this.theBufferManager.SetBuffer(eventArgObjectForPool);

                //Attach the SocketAsyncEventArgs object
                //to its event handler. Since this SocketAsyncEventArgs object is 
                //used for both receive and send operations, whenever either of those 
                //completes, the IO_Completed method will be called.
                eventArgObjectForPool.Completed += 
                    new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                
                //We can store data in the UserToken property of SAEA object.
                DataHoldingUserToken theTempReceiveSendUserToken = 
                    new DataHoldingUserToken(eventArgObjectForPool,
                   eventArgObjectForPool.Offset, eventArgObjectForPool.Offset + 
                   this.socketListenerSettings.BufferSize, 
                   this.socketListenerSettings.ReceivePrefixLength, 
                   this.socketListenerSettings.SendPrefixLength, i + 1);

                //We'll have an object that we call DataHolder, that we can remove from
                //the UserToken when we are finished with it. So, we can hang on to the
                //DataHolder, pass it to an app, serialize it, or whatever.
                theTempReceiveSendUserToken.CreateNewDataHolder();
                                
                eventArgObjectForPool.UserToken = theTempReceiveSendUserToken;

                // add this SocketAsyncEventArg object to the pool.
                this.poolOfRecSendEventArgs.Push(eventArgObjectForPool);
            }
        }

        //____________________________________________________________________________
        // This method is called when we need to create a new SAEA object to do
        //accept operations. The reason to put it in a separate method is so that
        //we can easily add more objects to the pool if we need to.
        //You can do that if you do NOT use a buffer in the SAEA object that does
        //the accept operations.
        internal SocketAsyncEventArgs CreateNewSaeaForAccept(SocketAsyncEventArgsPool pool)
        {
            //Allocate the SocketAsyncEventArgs object. 
            SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs();

            //SocketAsyncEventArgs.Completed is an event, (the only event,) 
            //declared in the SocketAsyncEventArgs class.
            //See http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.completed.aspx.
            //An event handler should be attached to the event within 
            //a SocketAsyncEventArgs instance when an asynchronous socket 
            //operation is initiated, otherwise the application will not be able 
            //to determine when the operation completes.
            //Attach the event handler, which causes the calling of the 
            //AcceptEventArg_Completed object when the accept op completes.
            acceptEventArg.Completed += 
                    new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);

            AcceptOpUserToken theAcceptOpToken = new AcceptOpUserToken(pool.GetTokenId());
            acceptEventArg.UserToken = theAcceptOpToken;

            return acceptEventArg;

            // accept operations do NOT need a buffer.                
            //You can see that is true by looking at the
            //methods in the .NET Socket class on the Microsoft website. AcceptAsync does
            //not take require a parameter for buffer size.
        }

        //____________________________________________________________________________
        // This method starts the socket server such that it is listening for 
        // incoming connection requests.            
        internal void StartListen()
        {
            // create the socket which listens for incoming connections
            listenSocket = new 
                    Socket(this.socketListenerSettings.LocalEndPoint.AddressFamily, 
                    SocketType.Stream, ProtocolType.Tcp);

            //bind it to the port
            listenSocket.Bind(this.socketListenerSettings.LocalEndPoint);

            // Start the listener with a backlog of however many connections.
            //"backlog" means pending connections. 
            //The backlog number is the number of clients that can wait for a
            //SocketAsyncEventArg object that will do an accept operation.
            //The listening socket keeps the backlog as a queue. The backlog allows 
            //for a certain # of excess clients waiting to be connected.
            //If the backlog is maxed out, then the client will receive an error when
            //trying to connect.
            //max # for backlog can be limited by the operating system.
            listenSocket.Listen(this.socketListenerSettings.Backlog);            

            //Server is listening now****

            // Calls the method which will post accepts on the listening socket.            
            //This call just occurs one time from this StartListen method. 
            //After that the StartAccept method will be called in a loop.
            StartAccept();
        }

        //____________________________________________________________________________
        // Begins an operation to accept a connection request from the client         
        internal void StartAccept()        
        {
            //Get a SocketAsyncEventArgs object to accept the connection.
            SocketAsyncEventArgs acceptEventArg;            
            //Get it from the pool if there is more than one in the pool.
            if (this.poolOfAcceptEventArgs.Count > 1)
            {
                try
                {
                    acceptEventArg = this.poolOfAcceptEventArgs.Pop();
                }
                //or make a new one.
                catch
                {
                    acceptEventArg = CreateNewSaeaForAccept(poolOfAcceptEventArgs);
                }
            }
            //or make a new one.
            else
            {
                acceptEventArg = CreateNewSaeaForAccept(poolOfAcceptEventArgs);
            }
            
            //Semaphore class is used to control access to a resource or pool of 
            //resources. Enter the semaphore by calling the WaitOne method, which is 
            //inherited from the WaitHandle class, and release the semaphore 
            //by calling the Release method. This is a mechanism to prevent exceeding
            // the max # of connections we specified. We'll do this before
            // doing AcceptAsync. If maxConnections value has been reached,
            //then the application will pause here until the Semaphore gets released,
            //which happens in the CloseClientSocket method.            
            this.theMaxConnectionsEnforcer.WaitOne();
            
            //Socket.AcceptAsync begins asynchronous operation to accept the connection.
            //Note the listening socket will pass info to the SocketAsyncEventArgs
            //object that has the Socket that does the accept operation.
            //If you do not create a Socket object and put it in the SAEA object
            //before calling AcceptAsync and use the AcceptSocket property to get it,
            //then a new Socket object will be created for you by .NET.            
            bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);

            //Socket.AcceptAsync returns true if the I/O operation is pending, i.e. is 
            //working asynchronously. The 
            //SocketAsyncEventArgs.Completed event on the acceptEventArg parameter 
            //will be raised upon completion of accept op.
            //AcceptAsync will call the AcceptEventArg_Completed
            //method when it completes, because when we created this SocketAsyncEventArgs
            //object before putting it in the pool, we set the event handler to do it.
            //AcceptAsync returns false if the I/O operation completed synchronously.            
            //The SocketAsyncEventArgs.Completed event on the acceptEventArg parameter 
            //will NOT be raised.
            if (!willRaiseEvent)
            {                
                //The code in this if (!willRaiseEvent) statement only runs 
                //when the operation was completed synchronously. It is needed because 
                //when Socket.AcceptAsync returns false, 
                //it does NOT raise the SocketAsyncEventArgs.Completed event.
                //And we need to call ProcessAccept and pass it the SAEA object.
                //This is only when a new connection is being accepted.
                ProcessAccept(acceptEventArg);
            }
        }
        
        //____________________________________________________________________________
        // This method is the callback method associated with Socket.AcceptAsync 
        // operations and is invoked when an async accept operation completes.
        //This is only when a new connection is being accepted.
        //Notice that Socket.AcceptAsync is returning a value of true, and
        //raising the Completed event when the AcceptAsync method completes.
        private void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            //Any code that you put in this method will NOT be called if
            //the operation completes synchronously, which will probably happen when
            //there is some kind of socket error. It might be better to put the code
            //in the ProcessAccept method.           
            ProcessAccept(e);
        }

        //____________________________________________________________________________       
        //The e parameter passed from the AcceptEventArg_Completed method
        //represents the SocketAsyncEventArgs object that did
        //the accept operation. in this method we'll do the handoff from it to the 
        //SocketAsyncEventArgs object that will do receive/send.
        private void ProcessAccept(SocketAsyncEventArgs acceptEventArgs)
        {   
            //Now that the accept operation completed, we can start another
            //accept operation, which will do the same. Notice that we are NOT
            //passing the SAEA object here.
            LoopToStartAccept();

            // Get a SocketAsyncEventArgs object from the pool of receive/send op 
            //SocketAsyncEventArgs objects
            SocketAsyncEventArgs receiveSendEventArgs = this.poolOfRecSendEventArgs.Pop();

            //Create sessionId in UserToken.
            ((DataHoldingUserToken)receiveSendEventArgs.UserToken).CreateSessionId();

            //A new socket was created by the AcceptAsync method. The 
            //SocketAsyncEventArgs object which did the accept operation has that 
            //socket info in its AcceptSocket property. Now we will give
            //a reference for that socket to the SocketAsyncEventArgs 
            //object which will do receive/send.
            receiveSendEventArgs.AcceptSocket = acceptEventArgs.AcceptSocket;

            //We have handed off the connection info from the
            //accepting socket to the receiving socket. So, now we can
            //put the SocketAsyncEventArgs object that did the accept operation 
            //back in the pool for them. But first we will clear 
            //the socket info from that object, so it will be 
            //ready for a new socket when it comes out of the pool.
            acceptEventArgs.AcceptSocket = null;
            this.poolOfAcceptEventArgs.Push(acceptEventArgs);            

            StartReceive(receiveSendEventArgs);
        }

        //____________________________________________________________________________
        //LoopToStartAccept method just sends us back to the beginning of the 
        //StartAccept method, to start the next accept operation on the next 
        //connection request that this listening socket will pass of to an 
        //accepting socket. We do NOT actually need this method. You could
        //just call StartAccept() in ProcessAccept() where we called LoopToStartAccept().
        //This method is just here to help you visualize the program flow.
        private void LoopToStartAccept()
        {                        
            StartAccept();
        }
                

        //____________________________________________________________________________
        //Post a receive.        
        private void StartReceive(SocketAsyncEventArgs receiveSendEventArgs)
        {   
            // Post async receive operation on the socket.
            bool willRaiseEvent = 
                 receiveSendEventArgs.AcceptSocket.ReceiveAsync(receiveSendEventArgs);

            //Socket.ReceiveAsync returns true if the I/O operation is pending. The 
            //SocketAsyncEventArgs.Completed event on the e parameter will be raised 
            //upon completion of the operation. So, true will cause the IO_Completed
            //method to be called when the receive operation completes. 
            //That's because of the event handler we created when building
            //the pool of SocketAsyncEventArgs objects that perform receive/send.
            //It was the line that said
            //eventArgObjectForPool.Completed += 
                 new EventHandler<SocketAsyncEventArgs>(IO_Completed);

            //Socket.ReceiveAsync returns false if I/O operation completed synchronously. 
            //In that case, the SocketAsyncEventArgs.Completed event on the e parameter 
            //will not be raised and the e object passed as a parameter may be 
            //examined immediately after the method call 
            //returns to retrieve the result of the operation.
            // It may be false in the case of a socket error.
            if (!willRaiseEvent)
            {
                //If the op completed synchronously, we need to call ProcessReceive 
                //method directly. This will probably be used rarely, as you will 
                //see in testing.
                ProcessReceive(receiveSendEventArgs);                
            }            
        }

        //____________________________________________________________________________
        // This method is called whenever a receive or send operation completes.
        // Here "e" represents the SocketAsyncEventArgs object associated 
        //with the completed receive or send operation
        void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            //Any code that you put in this method will NOT be called if
            //the operation completes synchronously, which will probably happen when
            //there is some kind of socket error.

            DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)e.UserToken;
            
            // determine which type of operation just completed and call the associated handler
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;

                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;

                default:
                    //This exception will occur if you code the Completed event of some
                    //operation to come to this method, by mistake.
                    throw new ArgumentException("The last operation completed on 
                           the socket was not a receive or send");
            }
        }


        //____________________________________________________________________________
        // This method is invoked by the IO_Completed method
        //when an asynchronous receive operation completes. 
        // If the remote host closed the connection, then the socket is closed.
        // Otherwise, we process the received data. And if a complete message was
        //received, then we do some additional processing, to 
        //respond to the client.
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)e.UserToken;
            
            bool incomingTcpMessageIsReady = false;

            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                //This variable will be needed to calculate the value of the
                //receiveMessageOffset variable in one situation below. Notice that the
                //name is similar but the usage is different from the variable
                //receiveSendToken.receivePrefixBytesDone.
                Int32 recPrefixBytesDoneThisOp = 0;

                //The BytesTransferred property tells us how many bytes 
                //we need to process.
                Int32 remainingBytesToProcess = e.BytesTransferred;

                //If we have not got all of the prefix already, 
                //then we need to work on it here.                                
                if (receiveSendToken.receivedPrefixBytesDoneCount < 
                 this.socketListenerSettings.ReceivePrefixLength)
                {
                    //receivedPrefixBytesDoneCount tells us how many prefix bytes were
                    //processed during previous receive ops which contained data for 
                    //this message. Usually there will NOT have been any previous 
                    //receive ops here. So in that case,
                    //receiveSendToken.receivedPrefixBytesDoneCount would equal 0.
                    //Create a byte array to put the new prefix in, if we have not
                    //already done it in a previous loop.
                    if (receiveSendToken.receivedPrefixBytesDoneCount == 0)
                    {
                        receiveSendToken.byteArrayForPrefix = 
                           new Byte[this.socketListenerSettings.ReceivePrefixLength];
                    }

                    //If this next if-statement is true, then we have received at 
                    //least enough bytes to have the prefix. So we can determine the 
                    //length of the message that we are working on.
                    if (remainingBytesToProcess >= 
                           this.socketListenerSettings.ReceivePrefixLength - 
                           receiveSendToken.receivedPrefixBytesDoneCount)
                    {
                        //Now copy that many bytes to byteArrayForPrefix.
                        //We can use the variable receiveMessageOffset as our main
                        //index to show which index to get data from in the TCP
                        //buffer.
                        Buffer.BlockCopy(e.Buffer, receiveSendToken.receiveMessageOffset 
                           - this.socketListenerSettings.ReceivePrefixLength 
                           + receiveSendToken.receivedPrefixBytesDoneCount, 
                             receiveSendToken.byteArrayForPrefix, 
                             receiveSendToken.receivedPrefixBytesDoneCount, 
                             this.socketListenerSettings.ReceivePrefixLength 
                           - receiveSendToken.receivedPrefixBytesDoneCount);

                        remainingBytesToProcess = remainingBytesToProcess 
                           - this.socketListenerSettings.ReceivePrefixLength
                           + receiveSendToken.receivedPrefixBytesDoneCount;

                        recPrefixBytesDoneThisOp = 
                           this.socketListenerSettings.ReceivePrefixLength 
                           - receiveSendToken.receivedPrefixBytesDoneCount;

                        receiveSendToken.receivedPrefixBytesDoneCount = 
                           this.socketListenerSettings.ReceivePrefixLength;

                        receiveSendToken.lengthOfCurrentIncomingMessage 
                           = BitConverter.ToInt32(receiveSendToken.byteArrayForPrefix, 0);

                    }

                    //This next else-statement deals with the situation 
                    //where we have some bytes
                    //of this prefix in this receive operation, but not all.
                    else
                    {
                        //Write the bytes to the array where we are putting the
                        //prefix data, to save for the next loop.
                        Buffer.BlockCopy(e.Buffer, receiveSendToken.receiveMessageOffset 
                           - socketListenerSettings.ReceivePrefixLength 
                           + receiveSendToken.receivedPrefixBytesDoneCount, 
                             receiveSendToken.byteArrayForPrefix, 
                             receiveSendToken.receivedPrefixBytesDoneCount, 
                             remainingBytesToProcess);

                        recPrefixBytesDoneThisOp = remainingBytesToProcess 
                           - receiveSendToken.receivedPrefixBytesDoneCount;
                        receiveSendToken.receivedPrefixBytesDoneCount += 
                           remainingBytesToProcess;
                        remainingBytesToProcess = 0;
                        receiveSendToken.receiveMessageOffset = 
                              receiveSendToken.receiveMessageOffset 
                            - receiveSendToken.receivedPrefixBytesDoneCount;
                        StartReceive(e);
                    }
                }

                //If we have processed the prefix, we can work on the message now,
                //if there are more bytes to work on.
                if ((receiveSendToken.receivedPrefixBytesDoneCount == 
                           this.socketListenerSettings.ReceivePrefixLength) 
                         & (remainingBytesToProcess > 0))
                {
                    //Create the array where we'll store the complete message, 
                    //if it has not been created on a previous receive op or loop
                    //through the while loop.
                    if (receiveSendToken.theDataHolder.dataMessageReceived == null)
                    {
                        //We'll be inside this if-statement the first time that we 
                        //receive enough data to have the 
                        //prefix, which carries the length information for the message
                        //that we are currently working on.                        
                        receiveSendToken.theDataHolder.dataMessageReceived = 
                           new Byte[receiveSendToken.lengthOfCurrentIncomingMessage];
                    }

                    //Remember there is a receiveSendToken.receivedPrefixBytesDoneCount
                    //variable, which allows us to handle the prefix even when it
                    //requires multiple receive ops. in the same way, we have a 
                    //receiveSendToken.receivedMessageBytesDoneCount variable, which
                    //helps us handle message data, whether it requires one receive
                    //operation or many.
                    if (remainingBytesToProcess + 
                           receiveSendToken.receivedMessageBytesDoneCount == 
                             receiveSendToken.lengthOfCurrentIncomingMessage)
                    {
                        //If we are inside this if-statement, then we got 
                        //the end of the message. In other words,
                        //the number of bytes we received matched the 
                        //message length value that we got from the prefix.

                        //Write/append the bytes received to the byte array in the 
                        //DataHolder object that we are using to store our data.
                        Buffer.BlockCopy(e.Buffer, receiveSendToken.receiveMessageOffset, 
                            receiveSendToken.theDataHolder.dataMessageReceived, 
                            receiveSendToken.receivedMessageBytesDoneCount, 
                            remainingBytesToProcess);

                        incomingTcpMessageIsReady = true;

                        //Reset the variables in the UserToken, to be ready for the
                        //next message that will be received on the socket in this
                        //SAEA object.
                        receiveSendToken.receivedPrefixBytesDoneCount = 0;
                        receiveSendToken.receivedMessageBytesDoneCount = 0;
                        remainingBytesToProcess = 0;
                        receiveSendToken.receiveMessageOffset = 
                           receiveSendToken.bufferOffsetReceive 
                             + this.socketListenerSettings.ReceivePrefixLength;
                    }

                    else if (remainingBytesToProcess 
                           + receiveSendToken.receivedMessageBytesDoneCount 
                           < receiveSendToken.lengthOfCurrentIncomingMessage)
                    {
                        //If we are inside this else=statement, then 
                        //that means that we need another receive op.
                        //We still haven't got the whole message, even though we
                        //have examined all the data that was received.
                        //Oh well, it's not a problem.
                        //We'll just do another receive op to receive more data.

                        Buffer.BlockCopy(e.Buffer, receiveSendToken.receiveMessageOffset, 
                             receiveSendToken.theDataHolder.dataMessageReceived, 
                             receiveSendToken.receivedMessageBytesDoneCount, 
                             remainingBytesToProcess);

                        receiveSendToken.receiveMessageOffset = 
                             receiveSendToken.receiveMessageOffset 
                           - recPrefixBytesDoneThisOp;
                        // Do NOT reset receiveSendToken.receivedPrefixBytesDoneCount here.
                        receiveSendToken.receivedMessageBytesDoneCount += 
                           remainingBytesToProcess;
                        remainingBytesToProcess = 0;
                        StartReceive(e);
                    }
                }

                if (incomingTcpMessageIsReady == true)
                {
                    //Pass the DataHolder object to the Mediator here.
                    // The data in this DataHolder can be used for all kinds of 
                    //things that an intelligent and
                    //creative person like you might think of.                        
                    receiveSendToken.theMediator
                           .HandleData(receiveSendToken.theDataHolder);

                    //Create a new DataHolder for next message.
                    receiveSendToken.CreateNewDataHolder();
                    incomingTcpMessageIsReady = false;
                    receiveSendToken.theMediator.PrepareOutgoingData();
                    StartSend(receiveSendToken.theMediator.GiveBack());
                }
            }
                //If no data was received, close the connection.
            else
            {                
                CloseClientSocket(e);
            }
        }

        //____________________________________________________________________________
        //Post a send op.   
        private void StartSend(SocketAsyncEventArgs receiveSendEventArgs)
        {
            DataHoldingUserToken receiveSendToken = 
                           (DataHoldingUserToken)receiveSendEventArgs.UserToken;

            //Set the buffer. You can see on Microsoft's page at 
            //http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.setbuffer.aspx
            //that there are two overloads. One of the overloads has 3 parameters.
            //When setting the buffer, you need 3 parameters the first time you set it,
            //which we did in the Init method. The first of the three parameters
            //tells what byte array to use as the buffer. After we tell what byte array

            //to use we do not need to use the overload with 3 parameters any more.
            //(That is the whole reason for using the buffer block. You keep the same
            //byte array as buffer always, and keep it all in one block.)
            //Now we use the overload with two parameters. We tell 
            // (1) the offset and
            // (2) the number of bytes to use, starting at the offset.

            //The number of bytes to send depends on whether the message is larger than
            //the buffer or not. If it is larger than the buffer, then we will have
            //to post more than one send operation. If it is less than or equal to the
            //size of the send buffer, then we can accomplish it in one send op.
            if (receiveSendToken.sendBytesRemainingCount 
                           <= this.socketListenerSettings.BufferSize)
            {
                receiveSendEventArgs.SetBuffer(receiveSendToken.bufferOffsetSend, 
                           receiveSendToken.sendBytesRemainingCount);
                //Copy the bytes to the buffer associated with this SAEA object.
                Buffer.BlockCopy(receiveSendToken.dataToSend, 
                           receiveSendToken.bytesSentAlreadyCount, 
                      receiveSendEventArgs.Buffer, receiveSendToken.bufferOffsetSend, 
                      receiveSendToken.sendBytesRemainingCount);
            }
            else
            {
                //We cannot try to set the buffer any larger than its size.
                //So since receiveSendToken.sendBytesRemainingCount > BufferSize, we just
                //set it to the maximum size, to send the most data possible.
                receiveSendEventArgs.SetBuffer(receiveSendToken.bufferOffsetSend,
                            this.socketListenerSettings.BufferSize);
                //Copy the bytes to the buffer associated with this SAEA object.
                Buffer.BlockCopy(receiveSendToken.dataToSend, 
                           receiveSendToken.bytesSentAlreadyCount, 
                      receiveSendEventArgs.Buffer, receiveSendToken.bufferOffsetSend, 
                      this.socketListenerSettings.BufferSize);

                //We'll change the value of sendUserToken.sendBytesRemainingCount
                //in the ProcessSend method.
            }

            //post asynchronous send operation
            bool willRaiseEvent = 
                 receiveSendEventArgs.AcceptSocket.SendAsync(receiveSendEventArgs);
            
            if (!willRaiseEvent)
            {
                ProcessSend(receiveSendEventArgs);
            }
        }

        //____________________________________________________________________________
        // This method is called by I/O Completed() when an asynchronous send completes.  
        // If all of the data has been sent, then this method calls StartReceive
        //to start another receive op on the socket to read any additional 
        // data sent from the client. If all of the data has NOT been sent, then it 
        //calls StartSend to send more data.        
        private void ProcessSend(SocketAsyncEventArgs receiveSendEventArgs)
        {
            DataHoldingUserToken receiveSendToken = 
                           (DataHoldingUserToken)receiveSendEventArgs.UserToken;
            
            receiveSendToken.sendBytesRemainingCount = 
                             receiveSendToken.sendBytesRemainingCount 
                           - receiveSendEventArgs.BytesTransferred;
            receiveSendToken.bytesSentAlreadyCount += 
                           receiveSendEventArgs.BytesTransferred;           

            if (receiveSendEventArgs.SocketError == SocketError.Success)
            {
                if (receiveSendToken.sendBytesRemainingCount == 0)
                {
                    //If we are within this if-statement, then all the bytes in the message
                    //have been sent. So now we need to reset the buffer for the receive
                    //operation.
                    receiveSendEventArgs.SetBuffer(receiveSendToken.bufferOffsetReceive,
                            this.socketListenerSettings.BufferSize);

                    StartReceive(receiveSendEventArgs);
                }
                else
                {
                    //If some of the bytes in the message have NOT been sent,
                    //then we will need to post another send operation.
                    //So let's loop back to StartSend().
                    StartSend(receiveSendEventArgs);
                }
            }
            else
            {
                //If we are in this else-statement, there was a socket error.
                //In this example we'll just close the socket if there was a socket error
                //when receiving data from the client.
                CloseClientSocket(receiveSendEventArgs);
            }
       }


        //____________________________________________________________________________
        private void CloseClientSocket(SocketAsyncEventArgs e)
        {
            DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)e.UserToken;

            // do a shutdown before you close the socket
            try
            {
                e.AcceptSocket.Shutdown(SocketShutdown.Send);
                
            }
            // throws if client process has already closed
            catch 
            {                
            }

            //This method closes the socket and releases all resources, both
            //managed and unmanaged. It internally calls Dispose.           
            e.AcceptSocket.Close();

            //Make sure the new DataHolder has been created for the next connection.
            //If it has, then dataMessageReceived should be null.
            if (receiveSendToken.theDataHolder.dataMessageReceived != null)
            {
                receiveSendToken.CreateNewDataHolder();
            }

            // Put the SocketAsyncEventArg back into the pool,
            // to be used by another client. This 
            this.poolOfRecSendEventArgs.Push(e);

            //Release Semaphore so that its connection counter will be decremented.
            //This must be done AFTER putting the SocketAsyncEventArg back into the pool,
            //or you can run into problems.
            this.theMaxConnectionsEnforcer.Release();      
        }

 

 Collapse
class BufferManager
    {
        // This class creates a single large buffer which can be divided up 
        // and assigned to SocketAsyncEventArgs objects for use with each 
        // socket I/O operation.  
        // This enables buffers to be easily reused and guards against 
        // fragmenting heap memory.
        // 
        //This buffer is a byte array which the Windows TCP buffer can copy its data to.

        // the total number of bytes controlled by the buffer pool
        Int32 totalBytesInBufferBlock;  
        
        // Byte array maintained by the Buffer Manager.
        //Theoretical maximum size for the buffer block is 2.147 GB. And you would 
        //probably really want less than 500 MB, if on 32 bit Windows. This limitation
        //should only be relevant if you use a large buffer size and/or have a large
        //number of simultaneous connections. For example, if you use a buffer size of
        //50,000 bytes, and have a separate buffer for send and receive, then that is
        //100,000 bytes per connection. 2.147 GB divided by 100,000 bytes = 21,470, 
        //which would be the maximum number of connections that could use this buffer
        //block with this buffer size and design.
        byte[] bufferBlock; 
        Stack<int> freeIndexPool;     
        Int32 currentIndex;
        Int32 bufferSize;

        //Microsoft's example included variable opsToPreAlloc in the calculation
        //for totalBytes, but not of bufferSize. (Maybe that's a mistake.) Fixed it         
        // in SocketListener. A reason to
        //use the opsToPreAlloc variable would be to have a separate buffer space for 
        //receive and send operations. Otherwise, if you reuse the same buffer space for
        //both receive and send, like Microsoft's example did, then you would not need 
        //to include opsToPreAlloc in creation of the one large buffer block.

        public BufferManager(Int32 totalBytes, Int32 bufferSize)
        {
            totalBytesInBufferBlock = totalBytes;
            this.currentIndex = 0;
            this.bufferSize = bufferSize;
            this.freeIndexPool = new Stack<int>();
        }

        // Allocates buffer space used by the buffer pool
        internal void InitBuffer()
        {
            // Create one large buffer block.
            this.bufferBlock = new byte[totalBytesInBufferBlock];
        }

        //Now divide that one large buffer block out to each SocketAsyncEventArg object.
        // Assign a buffer space from the buffer block to the 
        // specified SocketAsyncEventArgs object.
        //
        // returns true if the buffer was successfully set, else false
        internal bool SetBuffer(SocketAsyncEventArgs args)
        {            
            if (this.freeIndexPool.Count > 0)
            {
                //This if-statement is only true if you have called the FreeBuffer
                //method previously, which would put an offset for a buffer space 
                //back into this stack.
                args.SetBuffer(this.bufferBlock, this.freeIndexPool.Pop(), 
                           this.bufferSize);
            }
            else
            {
                //Inside this else-statement is the code that is used to set the 
                //buffer for each SAEA object when the pool of SAEA objects is built
                //in the Init method.
                if ((totalBytesInBufferBlock - this.bufferSize) < this.currentIndex)
                {
                    return false;
                }
                args.SetBuffer(this.bufferBlock, this.currentIndex, this.bufferSize);
                this.currentIndex += this.bufferSize;
            }
            return true;
        }

        // Removes the buffer from a SocketAsyncEventArg object.  
        // This frees the buffer back to the buffer pool.
        // Try NOT to use the FreeBuffer method, unless you need to destroy the SAEA
        //object, or maybe in the case of some exception handling. instead, on the server
        //keep the same buffer space assigned to one SAEA object for the duration of
        //this app's running. On the client you may find some use for this method.
        internal void FreeBuffer(SocketAsyncEventArgs args)
        {
            this.freeIndexPool.Push(args.Offset);
            args.SetBuffer(null, 0, 0);
        }
    } 

 

 Collapse
class DataHoldingUserToken
    {
        internal Mediator theMediator;
        internal DataHolder theDataHolder;
     
        internal Int32 bufferOffsetReceive;
        internal Int32 bufferOffsetSend;
               
        internal Int32 lengthOfCurrentIncomingMessage;
        
        //receiveMessageOffset is used to mark the byte position where the message
        //begins in the receive buffer. This value can sometimes be out of
        //bounds for the data stream just received. But, if it is out of bounds, the 
        //code will not access it.
        internal Int32 receiveMessageOffset;        
        internal Byte[] byteArrayForPrefix;        
        internal Int32 receivePrefixLength;
        internal Int32 receivedPrefixBytesDoneCount = 0;
        internal Int32 receivedMessageBytesDoneCount = 0;

        internal Int32 sendBytesRemainingCount;
        internal Int32 sendPrefixLength;
        internal Byte[] dataToSend;
        internal Int32 bytesSentAlreadyCount;

        //The session ID correlates with all the data sent in a connected session.
        //It is different from the transmission ID in the DataHolder, which relates
        //to one TCP message. A connected session can have many messages, if you
        //allow it to.
        private Int32 sessionId;                

        public DataHoldingUserToken(SocketAsyncEventArgs e, 
                           Int32 rOffset, Int32 sOffset, 
             Int32 receivePrefixLength, Int32 sendPrefixLength, Int32 identifier)
        {           
            //Create a Mediator that has a reference to the SAEA object.
            this.theMediator = new Mediator(e);
            this.bufferOffsetReceive = rOffset;
            this.bufferOffsetSend = sOffset;
            this.receivePrefixLength = receivePrefixLength;
            this.sendPrefixLength = sendPrefixLength;
            this.receiveMessageOffset = rOffset + receivePrefixLength;
        }

        //Let's use an ID for this object during testing, just so we can see what
        //is happening better if we want to.
        public Int32 TokenId
        {
            get
            {
                return this.idOfThisObject;
            }
        }

        internal void CreateNewDataHolder()
        {
            theDataHolder = new DataHolder();
        }
                
        //Used to create sessionId variable in DataHoldingUserToken.
        //Called in StartReceive().
        internal void CreateSessionId()
        {            
            lock (Program.lockerForSesId)
            {
                sessionId = Program.mainSessionId;
                Program.mainSessionId++;
            }            
        }

        public Int32 SessionId
        {
            get
            {
                return this.sessionId;
            }
        }
    }

 Collapse
class Mediator
    {
        private IncomingDataPreparer theIncomingDataPreparer;
        private OutgoingDataPreparer theOutgoingDataPreparer;
        private DataHolder theDataHolder;
        private SocketAsyncEventArgs saeaObject;     
       
        public Mediator(SocketAsyncEventArgs e)
        {
            this.saeaObject = e;
            this.theIncomingDataPreparer = new IncomingDataPreparer(saeaObject);
            this.theOutgoingDataPreparer = new OutgoingDataPreparer();            
        }
        
        internal void HandleData(DataHolder incomingDataHolder)
        {   
            theDataHolder = theIncomingDataPreparer.HandleReceivedData
                           (incomingDataHolder, this.saeaObject);
        }

        internal void PrepareOutgoingData()
        {
            theOutgoingDataPreparer.PrepareOutgoingData(saeaObject, theDataHolder);            
        }
        
        internal SocketAsyncEventArgs GiveBack()
        {            
            return saeaObject;
        }
    }
 Collapse
class IncomingDataPreparer
    {        
        private DataHolder theDataHolder;
        private SocketAsyncEventArgs theSaeaObject;

        public IncomingDataPreparer(SocketAsyncEventArgs e)
        {
            this.theSaeaObject = e;
        }
                
        private Int32 ReceivedTransMissionIdGetter()
        {
            Int32 receivedTransMissionId;
            lock (Program.lockerForTid)
            {
                receivedTransMissionId = Program.mainTransMissionId;
                Program.mainTransMissionId++;
            }            
            return receivedTransMissionId;
        }

        private EndPoint GetRemoteEndpoint()
        {   
            return this.theSaeaObject.AcceptSocket.RemoteEndPoint;
        }

        internal DataHolder HandleReceivedData(DataHolder incomingDataHolder, 
                           SocketAsyncEventArgs theSaeaObject)
        {
            DataHoldingUserToken receiveToken = 
                           (DataHoldingUserToken)theSaeaObject.UserToken;
            theDataHolder = incomingDataHolder;
            theDataHolder.sessionId = receiveToken.SessionId;
            theDataHolder.receivedTransMissionId = this.ReceivedTransMissionIdGetter();            
            theDataHolder.remoteEndpoint = this.GetRemoteEndpoint();
            this.AddDataHolder();
            return theDataHolder;
        }

        private void AddDataHolder()
        {
            lock (Program.lockerForList)
            {
                Program.listOfDataHolders.Add(theDataHolder);
            }
        }        
    }

 

 Collapse
class OutgoingDataPreparer
    {
        private DataHolder theDataHolder;

        internal void PrepareOutgoingData(SocketAsyncEventArgs e, 
                           DataHolder handledDataHolder)
        {
            DataHoldingUserToken theUserToken = (DataHoldingUserToken)e.UserToken;
            theDataHolder = handledDataHolder;

            //In this example code, we will send back the receivedTransMissionId,
            // followed by the
            //message that the client sent to the server. And we must
            //prefix it with the length of the message. So we put 3 
            //things into the array.
            // 1) prefix,
            // 2) receivedTransMissionId,
            // 3) the message that we received from the client, which
            // we stored in our DataHolder until we needed it.
            //That is our communication protocol. The client must know the protocol.

            //Convert the receivedTransMissionId to byte array.
            Byte[] idByteArray = BitConverter.GetBytes
                           (theDataHolder.receivedTransMissionId);

            //Determine the length of all the data that we will send back.
            Int32 lengthOfCurrentOutgoingMessage = idByteArray.Length 
                           + theDataHolder.dataMessageReceived.Length;

            //So, now we convert the length integer into a byte array.
            //Aren't byte arrays wonderful? Maybe you'll dream about byte arrays tonight!
            Byte[] arrayOfBytesInPrefix = BitConverter.GetBytes
                           (lengthOfCurrentOutgoingMessage);
            
            //Create the byte array to send.
            theUserToken.dataToSend = new Byte[theUserToken.sendPrefixLength 
                           + lengthOfCurrentOutgoingMessage];
            
            //Now copy the 3 things to the theUserToken.dataToSend.
            Buffer.BlockCopy(arrayOfBytesInPrefix, 0, theUserToken.dataToSend, 
                           0, theUserToken.sendPrefixLength);
            Buffer.BlockCopy(idByteArray, 0, theUserToken.dataToSend, 
                           theUserToken.sendPrefixLength, idByteArray.Length);
            //The message that the client sent is already in a byte array, in DataHolder.
            Buffer.BlockCopy(theDataHolder.dataMessageReceived, 0, 
                   theUserToken.dataToSend, theUserToken.sendPrefixLength 
                   + idByteArray.Length, theDataHolder.dataMessageReceived.Length);
            
            theUserToken.sendBytesRemainingCount = 
                    theUserToken.sendPrefixLength + lengthOfCurrentOutgoingMessage;
            theUserToken.bytesSentAlreadyCount = 0;
        }
    }

 

 Collapse
class DataHolder
    {
        //Remember, if a socket uses a byte array for its buffer, that byte array is
        //unmanaged in .NET and can cause memory fragmentation. So, first write to the
        //buffer block used by the SAEA object. Then, you can copy that data to another
        //byte array, if you need to keep it or work on it, and want to be able to put
        //the SAEA object back in the pool quickly, or continue with the data 
        //transmission quickly.         
        //DataHolder has this byte array to which you can copy the data.
        internal Byte[] dataMessageReceived;

        internal Int32 receivedTransMissionId;

        internal Int32 sessionId;

        //for testing. With a packet analyzer this can help you see specific connections.
        internal EndPoint remoteEndpoint;
    }

 

 Collapse
internal sealed class SocketAsyncEventArgsPool
    {
        //just for assigning an ID so we can watch our objects while testing.
        private Int32 nextTokenId = 1000001;
        object lockerForTokenId = new object();

        // Pool of reusable SocketAsyncEventArgs objects.        
        Stack<SocketAsyncEventArgs> pool;
        
        // initializes the object pool to the specified size.
        // "capacity" = Maximum number of SocketAsyncEventArgs objects
        internal SocketAsyncEventArgsPool(Int32 capacity)
        {
            this.pool = new Stack<SocketAsyncEventArgs>(capacity);
        }

        // The number of SocketAsyncEventArgs instances in the pool.         
        internal Int32 Count
        {
            get { return this.pool.Count; }
        }

        internal Int32 GetTokenId()
        {
            Int32 tokenId = 0;
            lock (lockerForTokenId)
            {
                tokenId = nextTokenId;
                nextTokenId++;
            }
            return tokenId;
        }

        // Removes a SocketAsyncEventArgs instance from the pool.
        // returns SocketAsyncEventArgs removed from the pool.
        internal SocketAsyncEventArgs Pop()
        {
            lock (this.pool)
            {
                return this.pool.Pop();
            }
        }

        // Add a SocketAsyncEventArg instance to the pool. 
        // "item" = SocketAsyncEventArgs instance to add to the pool.
        internal void Push(SocketAsyncEventArgs item)
        {
            if (item == null) 
            { 
                throw new ArgumentNullException("Items added to a 
                           SocketAsyncEventArgsPool cannot be null"); 
            }
            lock (this.pool)
            {
                this.pool.Push(item);
            }
        }
    }

 

 Collapse
class SocketListenerSettings
    {
        // the maximum number of connections the sample is designed 
        //to handle simultaneously 
        private Int32 maxConnections;

        //max # of pending connections the listener can hold in queue
        private Int32 backlog;

        //tells us how many objects to put in pool for accept operations
        private Int32 maxSimultaneousAcceptOps;

        // buffer size to use for each socket receive operation
        private Int32 receiveBufferSize;

        //length of message prefix for receive ops
        private Int32 receivePrefixLength;

        //length of message prefix for send ops
        private Int32 sendPrefixLength;

        //See comments in buffer manager.
        private Int32 opsToPreAllocate;

        // Endpoint for the listener.
        private IPEndPoint localEndPoint;

        public SocketListenerSettings(Int32 maxConnections, Int32 backlog, 
               Int32 maxSimultaneousAcceptOps, Int32 receivePrefixLength, 
               Int32 receiveBufferSize, Int32 sendPrefixLength, 
               Int32 opsToPreAlloc, IPEndPoint theLocalEndPoint)
        {
            this.maxConnections = maxConnections;
            this.backlog = backlog;
            this.maxSimultaneousAcceptOps = maxSimultaneousAcceptOps;
            this.receivePrefixLength = receivePrefixLength;
            this.receiveBufferSize = receiveBufferSize;
            this.sendPrefixLength = sendPrefixLength;
            this.opsToPreAllocate = opsToPreAlloc;
            this.localEndPoint = theLocalEndPoint;
        }

        public Int32 MaxConnections
        {
            get
            {
                return this.maxConnections;
            }
        }
        public Int32 Backlog
        {
            get
            {
                return this.backlog;
            }
        }
        public Int32 MaxAcceptOps
        {
            get
            {
                return this.maxSimultaneousAcceptOps;
            }
        }
        public Int32 ReceivePrefixLength
        {
            get
            {
                return this.receivePrefixLength;
            }
        }
        public Int32 BufferSize
        {
            get
            {
                return this.receiveBufferSize;
            }
        }
        public Int32 SendPrefixLength
        {
            get
            {
                return this.sendPrefixLength;
            }
        }
        public Int32 OpsToPreAllocate
        {
            get
            {
                return this.opsToPreAllocate;
            }
        }
        public IPEndPoint LocalEndPoint
        {
            get
            {
                return this.localEndPoint;
            }
        }    
    }   

 

The server app 

After downloading the zip file that contains the code, in order not to have problems using it in Visual Studio, before extracting it, right-click on the zip file and choose Properties, and then Unblock, then OK. Then extract it. If you do not do that, you may get security warning errors from Visual Studio.

Before running the server code the first time, you may want to change the folder where the logs are written. The default path is c:/LogForSaeaTest/, which will be created at server startup, if it does not exist. If you do not want to use the default folder, change the path in TestFileWriter class before running the app the first time.  (TestFileWriter code is not included above, but is in the source code.) For the most part I have not set the server application up so that the SocketListenerSettings and other variables can be controlled from the Console. You'll need to change the source code and rebuild to make most changes during testing.  

It's much better to run the client on one machine and server on another. If you try to run the client and server on the same machine, as the value of the "host" variable on the client, try to use the computer name first. If that does not work, try "localhost" as the value of the "host" variable on the client. When trying to connect the client to the server, if you get a "connection actively refused" message, check to see if you have a firewall that is blocking the transmissions on the incoming port of the server. You might have to allow incoming transmissions on that port on your local network. And if you have a firewall that blocks outgoing transmission on the client, then you would need to change settings for that too.  When testing any network application, I suggest learn how to you use Wireshark or something like it.
 

 

The client app 

A lot of the code in the client app is very similar to the server app. The server code is fully commented. So I did not always fully comment the client code. If in the client you find code that you do not understand and it is not commented on, then check similar portions of the server app for code comments.

The client app is NOT a normal client, but an app designed to test the server. It is set up to deliver as many connections as you want to throw at the server. The client app is set up to build all the messages for all of those connections before the test is started. It sends a different message each time for each client. And all the messages are put in memory before the test starts, so message creation won't be a drag on the client app during the test. If you choose to have 5000 connections sending 5000 messages per connection, then that is 25 million messages. So that is too many for memory probably. If you want to do a long test like that, then in the client app change runLongTest to true. In that case, instead of sending a separate message for each message from each client, it will send the same array of messages over and over for each client. That way the messages can fit in memory. (If you are doing a long test like that, also set runLongTest to true on the server. That will keep the server app from writing the received data to a dictionary. Otherwise, you'll run the server out of memory on the server probably.)

How many connections can this application handle? It depends on your hardware and configuration. In some testing I did, when running the server on an older single processor Dell desktop with Windows XP Pro 32 bit on a wired local area network with 100 MB NICs, it could handle a few thousand connections sending/receiving messages continually.
  1. The client app is set up so that you can do the following from the Console: (a) put in the method for finding the network address of the host machine, either machine name or IP address, (b) put in the correct string for the host machine name or IP address, depending on what method you chose for getting the network address of the host, (c) type in the port number of the host app, or accept the default, (d) specify a folder name for the log file to be written to, or accept the default, (e) specify the buffer size, or accept the previous value, (f) specify the number of client connections to attempt, or accept the previous value, (g) indicate the number of TCP messages to send per connection or accept the previous value.  
  2. In the downloadable source code, there is plenty of capability to visualize what is happening by writing to a log and the Console.  The things that you can visualize are (a) program flow, as it moves from method to method, (b) connects and disconnects, (c) the data which was sent from client to server and server to client, (d) threads, only in the server app.  Save the thread watching for last. 

 

Simple process to understand the code well

First, start the server app, and make sure you see "Server is listening" in the console. Then start the client app. You'll be asked to specify the host in the console. For the other items it will ask you, hopefully you can just accept the defaults. When it displays "Press Enter to begin socket test," press Enter. It should finish quickly. Then close both client and server, to make the logs finish writing. You just sent one message from one client connection to the server, and a response message from the server back to that client connection.  

Now look at the log files from both server and client. (It's easier if you print them.) Compare them to the code, and think about it a long time. You are looking at one message sent from one connection, and the response to that message. You should be able to see and understand the program flow very well from it.

Now go thru that same for each of the tests below. Start the server, and then the client. The client will ask you about buffer size, number of connections and number of messages.  You'll make some selections for those things on the client. When you change the number of connections you are changing the number of simulated users. One connection is like one client user running on one machine on the Internet. Run the test. And then after each test close client and server apps to write the logs. Look at the logs and make sure you understand what is happening after each test. Then go to the next test in the list.

test 1) buffer size stays same (25), number of connections = 2, number of messages = 2. 

test 2) buffer size stays same (25), number of connections = 3, number of messages = 5. 

test 3) buffer size = 5 on client, number of connections = 3, number of messages = 5. (In this test the buffer is smaller than the message with its prefix. So the client will require multiple send ops to send one message. And the client will require multiple receive ops to receive one message. You can see that in the client log. What happens in the server logs can vary, since one send op from client does NOT necessarily correlate with one receive op on the server, due to the way that TCP works.)

test 4) buffer = 3 on client, number of connections = 3, number of messages = 5. (In this test the buffer is smaller than even the prefix by itself. Multiple send ops will be required.)

test 5) put buffer back to 25 on client, number of connections = 3, number of messages = 5. Now on the server, in the Program.cs code changetestBufferSize from 25 to 5. Build the server app. Run the test. You'll see that multiple receive ops are required to receive a message from the client. And multiple send ops are required to send from server back to client.

test 6) On server put testBufferSize back to 25 on server, change watchThreads from false to true, and build. Leave settings the same as they were on the client, and run the test again. Now after you close the server app the server log file will show info on threads. In Notepad, or whatever program you use to look at the logs, search for the phrase "New managed thread". You'll see that phrase every time a new thread starts. Usually there are only 2-4 managed threads running for the app. The managed thread numbers and socket handle identifiers are displayed throughout the log file now.

test 7) On server change watchThreads to false, watchProgramFlow to false, maxNumberOfConnections to 10,000, and build. On client change watchProgramFlow to false, and build. In client console at startup make number of connections = 1000, number of messages = 500. Run the test. (If this crashes your client app, change runLongTest to true on the client, and build it again. If you change runLongTest to true on the client, there will not be a huge array of arrays of messages created on the client before starting the test. Instead just one array of messages will be created and it will be sent over and over, the same array being used for all of the connections. When runLongTest is false on the client, every connection has its own unique array of messages.)  

test 8) To run bigger tests, I suggest you change runLongTest to true on the server also. Try setting number of connections = 8000, number of messages = 5000 on the client, and see if it crashes your server app. On the client there is also a tickDelayBeforeNextConn variable which is set to delay the next connection by 1 millisecond (10000 ticks). You can play around with that some. If you send too many new connection requests at once, you'll overwhelm the server. It's kind of fun to do once or twice. 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的C# Socket服务端使用SocketAsyncEventArgs的示例代码: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Threading; public class SocketServer { private Socket m_serverSocket; private Semaphore m_maxAcceptedClients; private SocketAsyncEventArgsPool m_readWritePool; public SocketServer(int maxConnections) { m_serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); m_serverSocket.Bind(new IPEndPoint(IPAddress.Any, 1234)); m_serverSocket.Listen(maxConnections); m_maxAcceptedClients = new Semaphore(maxConnections, maxConnections); m_readWritePool = new SocketAsyncEventArgsPool(maxConnections); for (int i = 0; i < maxConnections; i++) { SocketAsyncEventArgs readEventArgs = new SocketAsyncEventArgs(); readEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed); readEventArgs.UserToken = new AsyncUserToken(); m_readWritePool.Push(readEventArgs); } } public void Start() { StartAccept(null); } private void StartAccept(SocketAsyncEventArgs acceptEventArgs) { if (acceptEventArgs == null) { acceptEventArgs = new SocketAsyncEventArgs(); acceptEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(Accept_Completed); } else { acceptEventArgs.AcceptSocket = null; } m_maxAcceptedClients.WaitOne(); if (!m_serverSocket.AcceptAsync(acceptEventArgs)) { ProcessAccept(acceptEventArgs); } } private void ProcessAccept(SocketAsyncEventArgs acceptEventArgs) { SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop(); AsyncUserToken userToken = (AsyncUserToken)readEventArgs.UserToken; userToken.Socket = acceptEventArgs.AcceptSocket; userToken.ReadEventArgs = readEventArgs; readEventArgs.AcceptSocket = userToken.Socket; if (!userToken.Socket.ReceiveAsync(readEventArgs)) { ProcessReceive(readEventArgs); } StartAccept(acceptEventArgs); } private void Accept_Completed(object sender, SocketAsyncEventArgs e) { ProcessAccept(e); } private void IO_Completed(object sender, SocketAsyncEventArgs e) { switch (e.LastOperation) { case SocketAsyncOperation.Receive: ProcessReceive(e); break; case SocketAsyncOperation.Send: ProcessSend(e); break; default: throw new ArgumentException("The last operation completed on the socket was not a receive or send"); } } private void ProcessReceive(SocketAsyncEventArgs e) { AsyncUserToken userToken = (AsyncUserToken)e.UserToken; if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success) { userToken.Data.AddRange(e.Buffer.Take(e.BytesTransferred)); if (userToken.Data.Count >= 4) { int len = BitConverter.ToInt32(userToken.Data.Take(4).ToArray(), 0); if (userToken.Data.Count >= 4 + len) { byte[] buffer = userToken.Data.Skip(4).Take(len).ToArray(); userToken.Data.RemoveRange(0, 4 + len); // 处理请求并响应 byte[] response = HandleRequest(buffer); Send(userToken.Socket, response); return; } } if (!userToken.Socket.ReceiveAsync(e)) { ProcessReceive(e); } } else { CloseClientSocket(userToken); } } private void ProcessSend(SocketAsyncEventArgs e) { AsyncUserToken userToken = (AsyncUserToken)e.UserToken; if (e.SocketError == SocketError.Success) { if (!userToken.Socket.ReceiveAsync(userToken.ReadEventArgs)) { ProcessReceive(userToken.ReadEventArgs); } } else { CloseClientSocket(userToken); } } private void Send(Socket socket, byte[] buffer) { SocketAsyncEventArgs sendEventArgs = new SocketAsyncEventArgs(); sendEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed); sendEventArgs.SetBuffer(buffer, 0, buffer.Length); sendEventArgs.UserToken = socket; if (!socket.SendAsync(sendEventArgs)) { ProcessSend(sendEventArgs); } } private void CloseClientSocket(AsyncUserToken userToken) { try { userToken.Socket.Shutdown(SocketShutdown.Send); } catch (Exception) { } userToken.Socket.Close(); m_readWritePool.Push(userToken.ReadEventArgs); m_maxAcceptedClients.Release(); } private byte[] HandleRequest(byte[] request) { // 处理请求并响应 return null; } } public class AsyncUserToken { public Socket Socket { get; set; } public List<byte> Data { get; set; } public SocketAsyncEventArgs ReadEventArgs { get; set; } public AsyncUserToken() { Data = new List<byte>(); } } ``` 以上示例代码演示了如何使用SocketAsyncEventArgs实现一个简单的Socket服务端,其中包含了异步接收、异步发送和连接池等功能。当有客户端连接时,它会从连接池中获取一个SocketAsyncEventArgs对象,并使用它来进行通信。在通信完成后,它会将SocketAsyncEventArgs对象放回连接池中,以便于重用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值