什么是使用 .NET 3.5 异步 System.Net.Sockets.Socket 停止数据流?

Posted

技术标签:

【中文标题】什么是使用 .NET 3.5 异步 System.Net.Sockets.Socket 停止数据流?【英文标题】:What is stopping data flow with .NET 3.5 asynchronous System.Net.Sockets.Socket? 【发布时间】:2010-05-26 00:43:30 【问题描述】:

我有一个使用异步方法的 .NET 3.5 客户端/服务器套接字接口。客户端连接到服务器,并且连接应保持打开状态,直到应用程序终止。该协议由以下模式组成:

    发送stx 收到确认 发送数据1 接收确认 发送数据2(重复5-6,同时更多数据) 收到确认 发送 etx

因此,具有上述两个数据块的单个事务将包含来自客户端的 4 次发送。发送 etx 后,客户端只需等待更多数据发送出去,然后使用 stx 开始下一次传输。我不想断开各个交易所之间或每个 stx/data/etx 有效负载之后的连接。

现在,连接后,客户端可以发送第一个 stx,并获得一个 ack,但之后我无法将更多数据放到网络上。双方都没有断开连接,套接字仍然完好无损。客户端代码严重缩写如下 - 我遵循在线代码示例中常见的模式。

private void SendReceive(string data) 
    // ...
    SocketAsyncEventArgs completeArgs;
    completeArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSend);
    clientSocket.SendAsync(completeArgs);
    // two AutoResetEvents, one for send, one for receive
    if ( !AutoResetEvent.WaitAll(autoSendReceiveEvents , -1) )
       Log("failed");
    else
       Log("success");
    // ...


private void OnSend( object sender , SocketAsyncEventArgs e ) 
// ...
    Socket s = e.UserToken as Socket;
    byte[] receiveBuffer = new byte[ 4096 ];
    e.SetBuffer(receiveBuffer , 0 , receiveBuffer.Length);
    e.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceive);
    s.ReceiveAsync(e);
// ...


private void OnReceive( object sender , SocketAsyncEventArgs e ) 
    // ...
    if ( e.BytesTransferred > 0 ) 
        Int32 bytesTransferred = e.BytesTransferred;
        String received = Encoding.ASCII.GetString(e.Buffer , e.Offset , bytesTransferred);
        dataReceived += received;
    
    autoSendReceiveEvents[ SendOperation ].Set(); // could be moved elsewhere
    autoSendReceiveEvents[ ReceiveOperation ].Set(); // releases mutexes

服务器上的代码非常相似,只是它先接收然后发送响应 - 服务器在发送响应后没有做任何事情(我可以​​说)来修改连接。问题是我在客户端第二次点击 SendReceive 时,连接已经处于奇怪的状态。

我是否需要在客户端中做一些事情来保留 SocketAsyncEventArgs,并在套接字/连接的整个生命周期内重复使用相同的对象?我不确定在连接或给定交换的生命周期内应该挂起哪个 eventargs 对象。

我是否需要在服务器中做某事,或者不做某事以确保它继续接收数据?

服务器设置和响应处理如下所示:

void Start() 
    // ...
    listenSocket.Bind(...);
    listenSocket.Listen(0);
    StartAccept(null); // note accept as soon as we start. OK?
    mutex.WaitOne();


void StartAccept(SocketAsyncEventArgs acceptEventArg) 
    if ( acceptEventArg == null )
    
        acceptEventArg = new SocketAsyncEventArgs();
        acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
    
    Boolean willRaiseEvent = this.listenSocket.AcceptAsync(acceptEventArg);
    if ( !willRaiseEvent )
        ProcessAccept(acceptEventArg);
    // ...


private void OnAcceptCompleted( object sender , SocketAsyncEventArgs e ) 
    ProcessAccept(e);


private void ProcessAccept( SocketAsyncEventArgs e ) 
    // ...
    SocketAsyncEventArgs readEventArgs = new SocketAsyncEventArgs();
    readEventArgs.SetBuffer(dataBuffer , 0 , Int16.MaxValue);
    readEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnIOCompleted);
    readEventArgs.UserToken = e.AcceptSocket;
    dataReceived = ""; // note server is degraded for single client/thread use
    // As soon as the client is connected, post a receive to the connection.
    Boolean willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);
    if ( !willRaiseEvent )
        this.ProcessReceive(readEventArgs);
    // Accept the next connection request.
    this.StartAccept(e);


private void OnIOCompleted( object sender , SocketAsyncEventArgs e ) 
    // switch ( e.LastOperation )
    case SocketAsyncOperation.Receive:
        ProcessReceive(e); // similar to client code
        // operate on dataReceived here
    case SocketAsyncOperation.Send:
        ProcessSend(e); // similar to client code


// execute this when a data has been processed into a response (ack, etc)
private SendResponseToClient(string response) 
    // create buffer with response
    // currentEventArgs has class scope and is re-used
    currentEventArgs.SetBuffer(sendBuffer , 0 , sendBuffer.Length);
    Boolean willRaiseEvent = currentClient.SendAsync(currentEventArgs);
    if ( !willRaiseEvent )
        ProcessSend(currentEventArgs);

发送 ABC 时,.NET 跟踪显示以下内容\r\n:

Socket#7588182::SendAsync()
套接字#7588182::SendAsync(True#1)
来自 Socket#7588182::FinishOperation(SendAsync) 的数据
00000000 : 41 42 43 0D 0A
套接字#7588182::ReceiveAsync()
退出 Socket#7588182::ReceiveAsync() -> True#1

它停在那里。它看起来就像来自客户端的第一次发送,但服务器没有显示任何活动。

我认为这可能是信息过载,但我很乐意根据需要提供更多详细信息。

谢谢!

【问题讨论】:

【参考方案1】:

首先,我确定您知道,但值得重复一遍; TCP 连接是一个字节流。每次读取可以返回 1 个字节和您使用的缓冲区大小,具体取决于已到达的数据量。仅仅因为您使用 3 次发送发送数据并不意味着您需要 3 个 recv 来读取它,您可以一次获得所有数据,或者每个 recv 可以返回一个字节。处理消息框架取决于您,因为您是唯一知道它的人。 TCP 是一个字节流。

还有你为什么要使用这些事件?如果您不想使用异步数据流,请不要使用异步函数,使用同步套接字函数编写一些非常简单的东西,并消除使用异步 API 并使用同步原语来阻碍它的所有无意义的复杂性。

【讨论】:

以上是关于什么是使用 .NET 3.5 异步 System.Net.Sockets.Socket 停止数据流?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 .NET 3.5 中使用 System.IdentityModel.Tokens.Jwt 5.2.1?

在 .Net 3.5 中使用 Ninject 时 System.Core 的 System.Io.FileNotFoundException

.NET 3.5 - System.DirectoryServices.AccountManagement - 组上的 AdvancedSearchFilter?

如何在 .NET 2.0/3.5 中将(字符串)消息从一个线程异步排队到另一个线程?

FileSystemWatcher .Net 3.5

Windows 服务在从 3.5 迁移的 Windows Server 2008、x64、.NET 4 上引发 System.BadImageFormatException