TCP 服务器在 SocketAsyncEventArgs 实例上重置 AcceptSocket.ReceiveAsync 操作

Posted

技术标签:

【中文标题】TCP 服务器在 SocketAsyncEventArgs 实例上重置 AcceptSocket.ReceiveAsync 操作【英文标题】:TCP Server Resetting AcceptSocket.ReceiveAsync operation on a SocketAsyncEventArgs instance 【发布时间】:2015-10-14 06:35:33 【问题描述】:

我目前有一个使用 C# SAEA 实现的 TCP 服务器。我想做的是在连接到服务器的 2 个 TCP 客户端(Client 1Client 2)之间转发消息。

服务器使用 receiveSendEventArgs.AcceptSocket.ReceiveAsyncreceiveSendEventArgs.AcceptSocket.SendAsync 命令发送和 从每个连接的客户端接收信息 问题。 服务器对 Client 1Client 2 都处于 receiveSendEventArgs.AcceptSocket.ReceiveAsync 操作中。 客户端 1 发送一条消息,服务器接受该消息。服务器看到 Client 2 也已连接,因此需要获取 receiveSendEventArgsClient 2 的引用来转发消息。

但是,Server 参考 Client 2receiveSendEventArgs 并开始准备缓冲区(SetBuffer)来发送消息,我相信因为 Socket 仍然在 Client 2 的“ReceiveSync”状态下,它会爆炸并显示以下消息:

"已使用此 SocketAsyncEventArgs 实例进行异步套接字操作。"

有没有办法将服务器上的 Client 2 状态从“ReceiveAsync”切换到“SendAsync”,这样当我尝试将 SendData 发送到 Client 2?我知道发送或接收操作完成时会触发 Completed 事件,但是,直接调用我的 IO_Completed 方法不会更改操作。

在 for 循环中为 SocketAsyncEventArgs 的 Completed 事件设置 EventHandlers: eventArgObjectForPool.Completed += new EventHandler(IO_Completed);

void IO_Completed(object sender, SocketAsyncEventArgs e)

        DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)e.UserToken;
        //More business logic here ...
        // determine which type of operation just completed and call the associated handler
        switch (e.LastOperation)
        
            case SocketAsyncOperation.Receive:
                if (Program.watchProgramFlow == true)   //for testing
                
                    Program.testWriter.WriteLine("IO_Completed method in Receive, receiveSendToken id " + receiveSendToken.TokenId);
                                    
                ProcessReceive(e);
                break;

            case SocketAsyncOperation.Send:
                if (Program.watchProgramFlow == true)   //for testing
                
                    Program.testWriter.WriteLine("IO_Completed method in Send, id " + receiveSendToken.TokenId);
                

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

private void StartReceive(SocketAsyncEventArgs receiveSendEventArgs)

        DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)receiveSendEventArgs.UserToken;

        if (Program.watchProgramFlow == true)   //for testing
                        
            Program.testWriter.WriteLine("StartReceive(), receiveSendToken id " + receiveSendToken.TokenId);
        

        switch (receiveSendToken.clientInfo.currentState)
        
            case MyClient.ClientState.Connecting://This occurs when we get client to connect for first time. However, it will automatically disconnect

                receiveSendToken.theMediator.HandleData(receiveSendToken.theDataHolder);

                // Create a new DataHolder for next message.
                receiveSendToken.CreateNewDataHolder();

                //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.Reset(true);

                receiveSendToken.theMediator.PrepareOutgoingData();
                StartSend(receiveSendToken.theMediator.GiveBack());

                //******************************************************************
                break;

            default:
                //Set the buffer for the receive operation.
                receiveSendEventArgs.SetBuffer(receiveSendToken.bufferOffsetReceive, this.socketListenerSettings.BufferSize);                    

                // 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 

                if (!willRaiseEvent)
                
                    if (Program.watchProgramFlow == true)   //for testing
                    
                        Program.testWriter.WriteLine("StartReceive in if (!willRaiseEvent), receiveSendToken id " + receiveSendToken.TokenId);
                    

                    ProcessReceive(receiveSendEventArgs);
                
                break;
        
    

private void StartSend(SocketAsyncEventArgs receiveSendEventArgs) DataHoldingUserToken receiveSendToken = (DataHoldingUserToken)receiveSendEventArgs.UserToken;

        if (Program.watchProgramFlow == true)   //for testing
        
            Program.testWriter.WriteLine("StartSend, id " + receiveSendToken.TokenId);
        
        if (Program.watchThreads == true)   //for testing
        
            DealWithThreadsForTesting("StartSend()", receiveSendToken);
        

        if (receiveSendToken.sendBytesRemainingCount <= this.socketListenerSettings.BufferSize)
        
            Program.testWriter.WriteLine("blocking:?(" + receiveSendEventArgs.AcceptSocket.Blocking + ")");
            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)
        
            if (Program.watchProgramFlow == true)   //for testing
            
                Program.testWriter.WriteLine("StartSend in if (!willRaiseEvent), receiveSendToken id " + receiveSendToken.TokenId);
            

            ProcessSend(receiveSendEventArgs);
                    
    

【问题讨论】:

您能否更好地格式化您的问题,并可能提供一些示例代码和您得到的任何错误/日志? 您所做的格式更新很好,但不如在代码中添加Minimal, Complete, and Verifiable example 重要。 【参考方案1】:

这不是我想要做的,但我可以调用以下内容:

receiveSendEventArgs.AcceptSocket.Write(strMyBuffer);

这让我可以写入同一个套接字并且我没有任何错误。我想坚持使用异步命令,但屈服于同步写入,它并没有影响我的服务器。也许还有其他解决方案。

【讨论】:

可能你不需要异步 IO,所以这个解决方案可能没问题。在任何情况下,SAEA 都是过时的 API。使用带有等待的套接字。如果您只想转发数据,那么您只需要两个 NetworkStreams 和一个对 CopyTo(Async) 的调用。

以上是关于TCP 服务器在 SocketAsyncEventArgs 实例上重置 AcceptSocket.ReceiveAsync 操作的主要内容,如果未能解决你的问题,请参考以下文章

TCP协议与TCP通信

到底使用UDP还是TCP

jmeter测试TCP服务器/模拟发送TCP请求

如何调试涉及 TCP 中继的慢速 TCP 连接

【TCP】单台服务器并发 TCP 连接数到底可以有多少 ?

一台服务器​最大并发 tcp 连接数多少?65535?