Ошибка асинхронного сокета - PullRequest
1 голос
/ 28 февраля 2012

Я пытаюсь внести небольшие изменения в этот пример MS Я хочу сделать "сервер чата" из этого примера "эхо-сервера"

Итак ...

Я добавляю несколько блоков в классе Server, и это прекрасно работает, но ... между двумя соединениями telnet, когда я добавляю более двух клиентов, я получаю сообщение об ошибке «Операция с асинхронным сокетом уже выполняется с использованием этого экземпляра SocketAsyncEventArgs». ;

Можете ли вы сказать мне, где ошибка? Большое спасибо.

Это мой класс сервера:

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);
        }

    }

Ответы [ 2 ]

1 голос
/ 28 февраля 2012

Вот наиболее вероятный вариант:

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);
        }
    }
}

Вы используете один и тот же e для отправки асинхронных сообщений нескольким клиентам.Это обречено.Каждая операция требует разных аргументов.Этот API позволяет объединять аргументы для повторного их использования, но сначала я бы сказал: заставляйте его работать с новым аргументом каждый раз.Затем оптимизируйте его.

Также обратите внимание, что ваш ProcessReceive, вероятно, должен начать следующий асинхронный прием, иначе они могут отправить только одно сообщение.Вы также должны остерегаться, потому что у вас есть отправка, начинающая прием;если вы отправите 3 сообщения в сокет, который просто слушает, все будет очень запутано.Лично у меня будет полностью отдельная обработка приёма и отправки.Отправить просто отправляет;получать только получает.

0 голосов
/ 28 февраля 2012

Как писал Марк, очевидная проблема заключается в методе ProcessReceive, который пытается использовать одни и те же EventArgs для отправки данных нескольким клиентам. В качестве решения с минимальными усилиями вы можете выделять EventArgs каждый раз, но вы должны знать, что такие приложения не следует писать. Для операций отправки должна быть Очередь пакетов (в данном случае, простые байтовые массивы), используемые вместе с примитивами синхронизации, такими как ReaderWriterLockSlim или просто ConcurrentQueue. При обратном вызове операции отправки вы проверяете очередь, и если она не пуста, вы удаляете следующий пакет из очереди и отправляете его. Поскольку это дуплексный сценарий, а не запрос / ответ, получение не должно начинаться как реакция на отправку чего-либо, вместо этого получение должно работать в цикле, в условии цикла вы должны проверить, доступен ли сокет.

Ваш код действительно подходит для историй запросов / ответов. Для решения проблемы, которую вы пытаетесь решить, я предлагаю вам взглянуть на что-то более продвинутое, например this framework .

...