1

I try to make small changes in this ms example I want to do "chat server" from this "echo server" example

So...

I add some blocks in class Server and this work fine but ... just between two telnet connection, when I add more than two clients, I get error "An asynchronous socket operation is already in progress using this SocketAsyncEventArgs instance.";

Can you tell me where is error ? Thanks a lot.

This is a my server class:

namespace AsyncSocketSample
{
    /// <summary>
    /// Implements the connection logic for the socket server.  After accepting a connection, all data read
    /// from the client is sent back to the client.  The read and echo back to the client pattern is continued 
    /// until the client disconnects.
    /// </summary>
    class Server
    {
        private int m_numConnections;   // the maximum number of connections the sample is designed to handle simultaneously 
        private int m_receiveBufferSize;// buffer size to use for each socket I/O operation 
        BufferManager m_bufferManager;  // represents a large reusable set of buffers for all socket operations
        const int opsToPreAlloc = 2;    // read, write (don't alloc buffer space for accepts)
        Socket listenSocket;            // the socket used to listen for incoming connection requests
        // pool of reusable SocketAsyncEventArgs objects for write, read and accept socket operations
        SocketAsyncEventArgsPool m_readWritePool;
        int m_totalBytesRead;           // counter of the total # bytes received by the server
        int m_numConnectedSockets;      // the total number of clients connected to the server 
        Semaphore m_maxNumberAcceptedClients;

        //The ClientInfo structure holds the required information about every
        //client connected to the server
        struct ClientInfo
        {
            public Socket socket;   //Socket of the client
            public AsyncUserToken token; // User Token
            //public string strName;  //Name by which the user logged into the chat room
        }

        //The collection of all clients logged into the room (an array of type ClientInfo)
        ArrayList clientList = new ArrayList();

        /// <summary>
        /// Create an uninitialized server instance.  To start the server listening for connection requests
        /// call the Init method followed by Start method 
        /// </summary>
        /// <param name="numConnections">the maximum number of connections the sample is designed to handle simultaneously</param>
        /// <param name="receiveBufferSize">buffer size to use for each socket I/O operation</param>
        public Server(int numConnections, int receiveBufferSize)
        {
            m_totalBytesRead = 0;
            m_numConnectedSockets = 0;
            m_numConnections = numConnections;
            m_receiveBufferSize = receiveBufferSize;
            // allocate buffers such that the maximum number of sockets can have one outstanding read and 
            //write posted to the socket simultaneously  
            m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc,
                receiveBufferSize);

            m_readWritePool = new SocketAsyncEventArgsPool(numConnections);
            m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections); 
        }

        /// <summary>
        /// Initializes the server by preallocating reusable buffers and context objects.  These objects do not 
        /// need to be preallocated or reused, by is done this way to illustrate how the API can easily be used
        /// to create reusable objects to increase server performance.
        /// </summary>
        public void Init()
        {
            // Allocates one large byte buffer which all I/O operations use a piece of.  This gaurds 
            // against memory fragmentation
            m_bufferManager.InitBuffer();

            // preallocate pool of SocketAsyncEventArgs objects
            SocketAsyncEventArgs readWriteEventArg;

            for (int i = 0; i < m_numConnections; i++)
            {
                //Pre-allocate a set of reusable SocketAsyncEventArgs
                readWriteEventArg = new SocketAsyncEventArgs();
                readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                readWriteEventArg.UserToken = new AsyncUserToken();

                // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
                m_bufferManager.SetBuffer(readWriteEventArg);

                // add SocketAsyncEventArg to the pool
                m_readWritePool.Push(readWriteEventArg);
            }

        }

        /// <summary>
        /// Starts the server such that it is listening for incoming connection requests.    
        /// </summary>
        /// <param name="localEndPoint">The endpoint which the server will listening for conenction requests on</param>
        public void Start(IPEndPoint localEndPoint)
        {
            // create the socket which listens for incoming connections
            listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            listenSocket.Bind(localEndPoint);
            // start the server with a listen backlog of 100 connections
            listenSocket.Listen(100);

            // post accepts on the listening socket
            StartAccept(null);            

            //Console.WriteLine("{0} connected sockets with one outstanding receive posted to each....press any key", m_outstandingReadCount);
            Console.WriteLine("Press any key to terminate the server process....");
            Console.ReadKey();
        }


        /// <summary>
        /// Begins an operation to accept a connection request from the client 
        /// </summary>
        /// <param name="acceptEventArg">The context object to use when issuing the accept operation on the 
        /// server's listening socket</param>
        public void StartAccept(SocketAsyncEventArgs acceptEventArg)
        {
            if (acceptEventArg == null)
            {
                acceptEventArg = new SocketAsyncEventArgs();
                acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
            }
            else
            {
                // socket must be cleared since the context object is being reused
                acceptEventArg.AcceptSocket = null;
            }

            m_maxNumberAcceptedClients.WaitOne();
            bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
            if (!willRaiseEvent)
            {
                ProcessAccept(acceptEventArg);
            }
        }

        /// <summary>
        /// This method is the callback method associated with Socket.AcceptAsync operations and is invoked
        /// when an accept operation is complete
        /// </summary>
        void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            ProcessAccept(e);
        }

        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            Interlocked.Increment(ref m_numConnectedSockets);
            Console.WriteLine("Client connection accepted. There are {0} clients connected to the server",
                m_numConnectedSockets);

            // Get the socket for the accepted client connection and put it into the 
            //ReadEventArg object user token
            SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
            ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket;

            // As soon as the client is connected, post a receive to the connection
            bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);
            if(!willRaiseEvent){
                ProcessReceive(readEventArgs);
            }

            ClientInfo clientInfo = new ClientInfo();
            clientInfo.socket = ((AsyncUserToken)readEventArgs.UserToken).Socket;
            clientInfo.token =  (AsyncUserToken)readEventArgs.UserToken;
            clientList.Add(clientInfo);

            // Accept the next connection request
            StartAccept(e);
        }

        /// <summary>
        /// This method is called whenever a receive or send opreation is completed on a socket 
        /// </summary> 
        /// <param name="e">SocketAsyncEventArg associated with the completed receive operation</param>
        void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            // 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:
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }       

        }

        /// <summary>
        /// This method is invoked when an asycnhronous receive operation completes. If the 
        /// remote host closed the connection, then the socket is closed.  If data was received then
        /// the data is echoed back to the client.
        /// </summary>
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            // check if the remote host closed the connection
            AsyncUserToken token = (AsyncUserToken)e.UserToken;
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                //increment the count of the total bytes receive by the server
                Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);
                Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead);
                ///////////////////////////////////////////////////////////////////////////////////////////////////


                try
                {
                    foreach (ClientInfo clientInfo in clientList)
                    {
                        if (clientInfo.token != token)
                        {                           
                            //Data received send to the all client but not for sender
                            bool willRaiseEvent = clientInfo.token.Socket.SendAsync(e); // token.Socket.SendAsync(e);
                            if (!willRaiseEvent)
                            {
                                ProcessSend(e);
                            }
                        }
                    }
                }
                catch (Exception x)
                {
                    Console.WriteLine(x.Message.ToString());
                    Console.ReadKey();
                }
                ///////////////////////////////////////////////////////////////////////////////////////////////////


            }
            else
            {
                CloseClientSocket(e);
            }
        }

        /// <summary>
        /// This method is invoked when an asynchronous send operation completes.  The method issues another receive
        /// on the socket to read any additional data sent from the client
        /// </summary>
        /// <param name="e"></param>
        private void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                // done echoing data back to the client
                AsyncUserToken token = (AsyncUserToken)e.UserToken;
                // read the next block of data send from the client
                bool willRaiseEvent = token.Socket.ReceiveAsync(e);
                if (!willRaiseEvent)
                {
                    ProcessReceive(e);
                }
            }
            else
            {
                CloseClientSocket(e);
            }
        }

        private void CloseClientSocket(SocketAsyncEventArgs e)
        {
            AsyncUserToken token = e.UserToken as AsyncUserToken;

            // close the socket associated with the client
            try
            {
                token.Socket.Shutdown(SocketShutdown.Send);
            }
            // throws if client process has already closed
            catch (Exception) { }
            token.Socket.Close();
            ///////////////////////////////////////////
            // Connection is terminated, either by force or
            // willingly
            int nIndex = 0;
            foreach (ClientInfo client in clientList)
            {
                if (client.token == token)
                {
                    clientList.RemoveAt(nIndex);
                    break;
                }
                ++nIndex;
            }
            ///////////////////////////////////////////
            // decrement the counter keeping track of the total number of clients connected to the server
            Interlocked.Decrement(ref m_numConnectedSockets);
            m_maxNumberAcceptedClients.Release();
            Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets);

            // Free the SocketAsyncEventArg so they can be reused by another client
            m_readWritePool.Push(e);
        }

    }
1
  • well, which line throws the error? Commented Feb 28, 2012 at 9:18

2 Answers 2

1

Here's the most likely:

foreach (ClientInfo clientInfo in clientList)
{
    if (clientInfo.token != token)
    {                           
        //Data received send to the all client but not for sender
        bool willRaiseEvent = clientInfo.token.Socket.SendAsync(e); // token.Socket.SendAsync(e);
        if (!willRaiseEvent)
        {
            ProcessSend(e);
        }
    }
}

You are using the same e to send async messages to multiple clients. That is doomed. Each operation needs a different args. This API does allow for pooling args to re-use them, but first I would say: get it working with a new arg each time. Then optimize it.

Also note that your ProcessReceive should probably be starting the next async-receive, otherwise they can only send one message. You need to watch out too, because you currently have send starting a receive; if you send 3 messages to a socket who is just listening, things are going to get very confused. Personally I would have completely separate receive and send handling. Send just sends; receive just receives.

Sign up to request clarification or add additional context in comments.

1 Comment

>> This API does allow for pooling args to re-use them, but first I would say: get it working with a new arg each time. Then optimize it. Yep, you absolutely right ! I did not notice that! Thanks.
0

As Mark wrote, the obvious problem is in the ProcessReceive method, which attempts to use the same EventArgs to send data to multiple clients. As a minimum-effort solution you can allocate EventArgs each time, but you should know this is not how such apps should be written. For send operations there should be a Queue of packets (in this case, simple byte arrays) used together with a synchronization primitives like ReaderWriterLockSlim or just a ConcurrentQueue. In a send operation callback you check the queue and if it's not empty, you dequeue next packet and send it. Since, it's a duplex scenario and not request/response, receive should not begin as a reaction to sending something, instead receive should work in a loop, in the loop condition you should check whether socket is still available.

The code you have is really suitable for request/response stories. For the problem you are trying to solve, I suggest you to look at something more advanced, like this framework.

3 Comments

I understood, but it seemed to me that such implementation should be the fastest. about the EventArgs, you're right, it is necessary to allocate each time a new one.
Well, it's definitely your call. Although, I don't see any performance drawbacks in the solution that I offered. My intention is to warn you, that in my opinion you chose not the best piece of code for the situation you described. One obvious example: you accept two sockets, both start receiving, then you receive message from one and send it to everybody. The second socket is still receiving data. Then in the send callback for the second socket you call ReceiveAsync another time. So now for the second socket "receive" has been called twice...
... Even if it's possible (which I think is not and you will probably receive a SocketException), you should avoid stuff like this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.