繁体   English   中英

C# SocketAsyncEventArgs 未发送所有数据

[英]C# SocketAsyncEventArgs not sending all data

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

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

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

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

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

所以有 3 种方法是有意义的,如下所示:

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

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

2) private void Send(SocketAsyncEventArgs e);

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

3) private void ProcessSent(SocketAsyncEventArgs e);

最后一种方法将检查套接字已发送的数据。 如果所有数据都发送完毕,SAEA object 将被释放。 如果不是,则对数据的 rest 再次调用方法(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 异常,让你困惑很长时间。

  • 在 Completed 事件处理程序中:

     private void Completed(object sender, SocketAsyncEventArgs e) { // What type of operation did just completed? switch (e.LastOperation) { case SocketAsyncOperation.Send: { ProcessSent(e); break; } } }

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

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

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

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

关于这件事的文档相当浅薄,除了这篇文章,即使在这种情况下,当您开始实现自己的应用程序时,有些事情会变得不同。 这个 SocketAsyncEventArgs model 可以说有点违反直觉。

如果您需要更多帮助,请告诉我,不久前,当我开发自己的库时,我曾为此苦苦挣扎。

编辑:

如果我是你,我会搬家

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM