简体   繁体   English

C# SocketAsyncEventArgs 未发送所有数据

[英]C# SocketAsyncEventArgs not sending all data

I have a problem with the SocketAsyncEventArgs class..the problem is when I try to send 8K of data for example over internet, the socket sometimes only sends 1K or 2K, I know that this is normal for TCP socket and that one send doesn't guarantee one receive.我的 SocketAsyncEventArgs class 有问题。问题是当我尝试通过 Internet 发送 8K 数据时,套接字有时只发送 1K 或 2K,我知道这对于 TCP 套接字是正常的,而一个发送没有保证一收。 now for this to work for me I modified my code to resend the remaining data, for example when I the SocketAsyncEventArgs.SendAsync completes, in the callback i check whether it sent all the 8K or not if it's not, I call the SocketAsyncEventArgs.SendAsync again with the remaining data untill I send it all.现在为了这对我有用,我修改了我的代码以重新发送剩余的数据,例如当我 SocketAsyncEventArgs.SendAsync 完成时,在回调中我检查它是否发送了所有 8K,如果不是,我调用 SocketAsyncEventArgs.SendAsync再次使用剩余的数据,直到我全部发送。 Now, when I looked at some SocketAsyncEventArgs code.. I saw that most people don't do so, and they just clean up when the send complete without checking if it sent all the data or not.现在,当我查看一些 SocketAsyncEventArgs 代码时。我看到大多数人不这样做,他们只是在发送完成时清理而不检查它是否发送了所有数据。 also when I looked at Microsoft's example.当我看微软的例子时也是如此。 they were saying the ONE call to SocketAsyncEventArgs.SendAsync guarantees that all the data will be sent.他们说对 SocketAsyncEventArgs.SendAsync 的 ONE 调用保证所有数据都将被发送。 I mean I tested it myself and NO all data will not be sent in one call to SocketAsyncEventArgs?SendAsync.我的意思是我自己测试了它,并且不会在一次调用 SocketAsyncEventArgs?SendAsync 时发送所有数据。 What I'm doing wrong ?我做错了什么? thanks in advance.提前致谢。

Edit: Here is the code which doesn't send all data(exactly like microsoft's) the SendAsyncComplete will be called when the socket sends for example 1Kb of data not all 8K!编辑:这是不发送所有数据的代码(就像微软的一样),当套接字发送例如 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);
}

There are many things I would change there.有很多东西我会在那里改变。 As a preamble, read this .作为序言,请阅读

First of all, whenever you sent any data on the socket, you must process that event: either you stop the whole send process, or you issue another socket send operation to send the remaining data.首先,每当您在套接字上发送任何数据时,您都必须处理该事件:要么停止整个发送过程,要么发出另一个套接字发送操作来发送剩余的数据。

So it makes sense to have 3 methods, like this:所以有 3 种方法是有意义的,如下所示:

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

This method will take care to apply any data formatting that you need, create (retrieve) a SocketAsyncEventArgs object, set the token to hold your buffer, and call the next method bellow:此方法将注意应用您需要的任何数据格式,创建(检索)一个 SocketAsyncEventArgs object,设置令牌以保存您的缓冲区,然后调用下面的下一个方法:

2) private void Send(SocketAsyncEventArgs e);

This one actually calls Socket.SendAsync(SocketAsyncEventArgs e) and copies the contents from the token (the buffer, remember?) to the SAEA object.这实际上调用了 Socket.SendAsync(SocketAsyncEventArgs e) 并将内容从令牌(缓冲区,还记得吗?)复制到 SAEA object。 Now that is why because method number (2) might be called several times to send the remaining data that couldn't be sent in one operation by the socket.现在这就是为什么因为方法号 (2) 可能会被调用多次以发送无法通过套接字在一次操作中发送的剩余数据。 So here you copy the remaining data from the token to the SAEA buffer.所以在这里你将剩余的数据从令牌复制到 SAEA 缓冲区。

3) private void ProcessSent(SocketAsyncEventArgs e);

This last method will examine the data that has been sent by the socket.最后一种方法将检查套接字已发送的数据。 If all the data has been sent, the SAEA object will be released.如果所有数据都发送完毕,SAEA object 将被释放。 If not, method (2) will be called again for the rest of the data.如果不是,则对数据的 rest 再次调用方法(2)。 In order to keep track of sent data, you use SAEA.BytesTransferred.为了跟踪发送的数据,您使用 SAEA.BytesTransferred。 You should add this value to a value stored in the custom token I advise you to create (so do not use "this" as a token).您应该将此值添加到存储在我建议您创建的自定义令牌中的值中(因此不要使用“this”作为令牌)。

This is where you also check for SocketError on the SAEA parameter.您还可以在此处检查 SAEA 参数上的 SocketError。

This last method will be called in two places:最后一个方法将在两个地方调用:

  • in the 2nd method, like this:在第二种方法中,像这样:

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

This bit is important any many people missed it even when using the more traditional Begin/EndXXX pattern (in that case, via IAsyncResult).这一点很重要,即使在使用更传统的 Begin/EndXXX 模式(在这种情况下,通过 IAsyncResult)时,很多人都错过了它。 If you don't place it, once in a while (quite rare), a StackOverflow exception will pop out of nowhere and will keep you puzzled for long time.如果你不放置它,偶尔(非常罕见)会突然弹出一个 StackOverflow 异常,让你困惑很长时间。

  • in the Completed event handler:在 Completed 事件处理程序中:

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

The tricky thing is to use one SocketAsyncEventArgs object per 1st Send(byte[]) operation, and release it in 3rd operation, if all data has been sent.棘手的是每次第一次 Send(byte[]) 操作使用一个 SocketAsyncEventArgs object,如果所有数据都已发送,则在第三次操作中释放它。

For that, you must create a custom token (class or immutable struct) to place in SocketAsyncEventArgs.UserToken inside the 1st method, and then keep track of how much data you have transferred on each Socket.SendAsync() operation.为此,您必须创建一个自定义令牌(类或不可变结构)以放置在第一个方法内的 SocketAsyncEventArgs.UserToken 中,然后跟踪您在每个 Socket.SendAsync() 操作中传输了多少数据。

When you are reading the article provided in the beginning, notice how the writer reuses the same SAEA object when at the end of Send() operation proceeds with a Receive() operation, if all the data has been sent.当您阅读开头提供的文章时,请注意作者如何重用相同的 SAEA object,如果所有数据都已发送,则在 Send() 操作结束时继续执行 Receive() 操作。 That is because his protocol is: each one of the parties (server and client) talk to each other in turns.那是因为他的协议是:每一方(服务器和客户端)轮流相互交谈。

Now, should multiple calls to the 1st Send() method occur at the same time, there is no rule in which order they will be handled by the OS.现在,如果同时发生对第一个 Send() 方法的多个调用,则操作系统将按什么顺序处理它们并没有规则。 If that is likely to happen and message order is important, and since any call to Send(byte[]) from an "outside entity" result in a Socket.SendAsync(), I suggest that the 1st method actually writes down the received bytes in an internal buffer.如果这很可能发生并且消息顺序很重要,并且由于从“外部实体”对 Send(byte[]) 的任何调用都会导致 Socket.SendAsync(),我建议第一种方法实际上写下接收到的字节在内部缓冲区中。 As long as this buffer is not empty, you keep sending this data internally.只要此缓冲区不为空,您就可以在内部继续发送此数据。 Think of it like a producer-consumer scenario, where the "outside entity" is the producer and the internal send op is the consumer.把它想象成一个生产者-消费者场景,其中“外部实体”是生产者,内部发送操作是消费者。 I prefer an optimistic concurrency scenario here.我更喜欢这里的乐观并发场景。

The documentation on the matter is rather shallow, with the exception of this article, and even in this case, when you start implementing your own application, some things turn out to be different.关于这件事的文档相当浅薄,除了这篇文章,即使在这种情况下,当您开始实现自己的应用程序时,有些事情会变得不同。 This SocketAsyncEventArgs model is arguably a bit counter-intuitive.这个 SocketAsyncEventArgs model 可以说有点违反直觉。

Let me know if you need more help, I had my times of struggle with this a while ago when I developed my own library.如果您需要更多帮助,请告诉我,不久前,当我开发自己的库时,我曾为此苦苦挣扎。

Edit:编辑:

If I were you, I would move如果我是你,我会搬家

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

to your ProcessSend(SAEA), and use args.BytesTransferred instead of "length".到您的 ProcessSend(SAEA),并使用 args.BytesTransferred 而不是“长度”。

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

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