C#SocketAsyncEventArgs不发送所有数据

时间:2011-08-17 16:08:40

标签: c#

我的SocketAsyncEventArgs类有问题。问题是当我尝试发送8K数据时 例如通过互联网,套接字有时只发送1K或2K,我知道这是正常的 TCP套接字和那个发送不保证一个接收。 现在,为了让我能够工作,我修改了我的代码以重新发送剩余的数据 当我的SocketAsyncEventArgs.SendAsync完成时,在回调中我检查它是否发送了所有8K,如果不是,我再次调用SocketAsyncEventArgs.SendAsync,剩下的数据直到 我把它全部发送出去。 现在,当我查看一些SocketAsyncEventArgs代码时......我看到大多数人都没有这样做! 他们只是在发送完成时清理,而不检查它是否发送了所有数据! 当我查看微软的例子时,他们说的是对SocketAsyncEventArgs.SendAsync的ONE调用保证将发送所有数据。 我的意思是我自己测试过,并且不会在一次调用SocketAsyncEventArgs.SendAsync时发送所有数据。 我做错了什么? 提前谢谢。

编辑: 这是不发送所有数据的代码(完全像微软的) 当套接字发送例如1Kb的数据时,将调用SendAsyncComplete 不是全部8K!

public virtual void Send(byte[] packet, int offset, int length)
{
    if (_tcpSock != null && _tcpSock.Connected)
    {
        var args = SocketHelpers.AcquireSocketArg();
        if (args != null)
        {
            args.Completed += SendAsyncComplete;
            args.SetBuffer(packet, offset, length);
            args.UserToken = this;

            var willRaiseEvent = _tcpSock.SendAsync(args);
            if (!willRaiseEvent)
            {
            ProcessSend(args);
            }
            unchecked
            {
                _bytesSent += (uint)length;
            }
            Interlocked.Add(ref _totalBytesSent, length);
        }
        else
        {
            log.Error("Client {0}'s SocketArgs are null", this);
        }
    }
}

private static void ProcessSend(SocketAsyncEventArgs args)
{
      args.Completed -= SendAsyncComplete;
      SocketHelpers.ReleaseSocketArg(args);   
}

private static void SendAsyncComplete(object sender, SocketAsyncEventArgs args)
{
    ProcessSend(args);
}

1 个答案:

答案 0 :(得分:5)

我会在那里改变很多东西。作为序言,请阅读this

首先,无论何时在套接字上发送任何数据,都必须处理该事件:要么停止整个发送过程,要么发出另一个套接字发送操作来发送剩余数据。

因此有3个方法是有道理的,比如:

// This is the Send() to be used by your class' clients
1) public void Send(byte[] buffer);

此方法将注意应用您需要的任何数据格式,创建(检索)SocketAsyncEventArgs对象,设置令牌以保存缓冲区,并调用下一个方法:

2) private void Send(SocketAsyncEventArgs e);

这个实际上是打电话     Socket.SendAsync(SocketAsyncEventArgs e) 并将令牌中的内容(缓冲区,请记住吗?)复制到SAEA对象。现在这就是为什么因为方法号(2)可能被调用几次以发送无法通过套接字在一次操作中发送的剩余数据。所以在这里你将剩余的数据从令牌复制到SAEA缓冲区。

3) private void ProcessSent(SocketAsyncEventArgs e);

最后一种方法将检查套接字发送的数据。如果已发送所有数据,则将释放SAEA对象。如果不是,则将再次调用方法(2)以获得其余数据。为了跟踪发送的数据,您使用SAEA.BytesTransferred。您应该将此值添加到我建议您创建的自定义令牌中存储的值(因此不要将“this”用作令牌)。

这也是您在SAEA参数上检查SocketError的地方。

最后一个方法将在两个地方调用:

  • 在第二种方法中,如下所示:

        // Attempt to send data in an asynchronous fashion
        bool isAsync = this.Socket.SendAsync(e);
    
        // Something went wrong and we didn't send the data async
        if (!isAsync)
            this.ProcessSent(e);
    

即使使用更传统的Begin / EndXXX模式(在这种情况下,通过IAsyncResult),这一点也很重要。如果你没有放置它,偶尔(非常罕见),StackOverflow异常会突然冒出来让你长时间困惑。

    完成事件处理程序中的
  • private void Completed(object sender, SocketAsyncEventArgs e)
    {
        // What type of operation did just completed?
        switch (e.LastOperation)
        {
            case SocketAsyncOperation.Send:
                {
                    ProcessSent(e);
                    break;
                }
        }
    }
    

棘手的是每个1st Send(byte [])操作使用一个SocketAsyncEventArgs对象,如果所有数据都已发送,则在第3个操作中释放它。

为此,您必须创建一个自定义标记(类或不可变结构)以放置在第一个方法内的SocketAsyncEventArgs.UserToken中,然后跟踪每个Socket.SendAsync()操作传输的数据量。 / p>

当您阅读开头提供的文章时,请注意当Send()操作结束时,如果所有数据都已发送,则作者在Send()操作结束时如何重用相同的SAEA对象。这是因为他的协议是:每一方(服务器和客户端)轮流互相交谈。

现在,如果同时发生对第一个Send()方法的多次调用,那么操作系统将无法处理它们的处理顺序。如果这可能发生并且消息顺序很重要,并且因为从“外部实体”调用Send(byte [])会导致Socket.SendAsync(),我建议第一种方法实际写下接收到的字节在内部缓冲区中。只要此缓冲区不为空,您就可以在内部继续发送此数据。可以把它看作生产者 - 消费者情景,其中“外部实体”是生产者,内部发送操作是消费者。我更喜欢这里的乐观并发场景。

关于此事的文档相当浅,除了本文之外,即使在这种情况下,当您开始实现自己的应用程序时,有些事情会变得不同。这个SocketAsyncEventArgs模型可能有点反直觉。

如果你需要更多的帮助,请告诉我,不久之前,当我开发自己的图书馆时,我有时间与之斗争。

编辑:

如果我是你,我会搬家

unchecked
{
    _bytesSent += (uint)length;
}
Interlocked.Add(ref _totalBytesSent, length);

到您的ProcessSend(SAEA),并使用args.BytesTransferred而不是“length”。