C# Socket.BeginReceive/EndReceive

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1388561/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 15:56:21  来源:igfitidea点击:

C# Socket.BeginReceive/EndReceive

c#.netsocketsbeginreceive

提问by akif

In what order is the Socket.BeginReceive/EndReceive functions called?

Socket.BeginReceive/EndReceive 函数的调用顺序是什么?

For instance, I call BeginReceivetwice, once to get the message length and the second time to get the message itself. Now the scenario is like that, for every message I send, I start waiting for its completion (actually acknowledgment of the message sent, also I wait for the action's completion after receiving the acknowledgment), so I call BeginReceivewith each BeginSend, but in each BeginReceive's callback, I check if I'm receiving the length or the message. If I'm receiving the message and have received it completely, then I call another BeginReceiveto receive the completion of the action. Now this is where things get out of sync. Because one of my receive callback is receiving bytes which it interprets as the length of them message when in fact it is the message itself.

例如,我调用BeginReceive两次,一次是获取消息长度,第二次是获取消息本身。现在的场景是这样的,对于我发送的每条消息,我开始等待它的完成(实际上是对发送的消息的确认,我也在收到确认后等待动作的完成),所以我用每个BeginSend调用BeginReceive,但是在每个BeginReceive的回调,我检查我是否收到了长度或消息。如果我收到了消息并且已经完全收到了,那么我会调用另一个BeginReceive接收动作的完成。现在这就是事情不同步的地方。因为我的接收回调之一是接收字节,它会将其解释为消息的长度,而实际上它是消息本身。

Now how do I resolve it?

现在我该如何解决?

EDIT:This is a C#.NET question :)

编辑:这是一个 C#.NET 问题:)

Here is the code, basically it is too big, sorry for that

这是代码,基本上它太大了,抱歉

public void Send(string message)
{
    try
    {
        bytesSent = 0;

        writeDataBuffer = System.Text.Encoding.ASCII.GetBytes(message);
        writeDataBuffer = WrapMessage(writeDataBuffer);
        messageSendSize = writeDataBuffer.Length;

        clientSocket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
                            new AsyncCallback(SendComplete), clientSocket);
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }
}

public void WaitForData()
{
    try
    {
        if (!messageLengthReceived)
        {
            clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, MESSAGE_LENGTH_SIZE - bytesReceived,
                                    SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
        }
}

public void Send(string message)
{
    try
    {
        bytesSent = 0;

        writeDataBuffer = System.Text.Encoding.ASCII.GetBytes(message);
        writeDataBuffer = WrapMessage(writeDataBuffer);
        messageSendSize = writeDataBuffer.Length;

        clientSocket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
                            new AsyncCallback(SendComplete), clientSocket);
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }
}

public void WaitForData()
{
    try
    {
        if (! messageLengthReceived)
        {
            clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, MESSAGE_LENGTH_SIZE - bytesReceived,
                                    SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
        }
        else 
        {
            clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, messageLength - bytesReceived,
                                    SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
        }
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }
}

public void RecieveComplete(IAsyncResult result)
{
    try
    {
        Socket socket = result.AsyncState as Socket;
        bytesReceived = socket.EndReceive(result);

        if (! messageLengthReceived)
        {
            if (bytesReceived != MESSAGE_LENGTH_SIZE)
            {
                WaitForData();
                return;
            }

            // unwrap message length
            int length = BitConverter.ToInt32(receiveDataBuffer, 0);
            length = IPAddress.NetworkToHostOrder(length);

            messageLength = length;
            messageLengthReceived = true;

            bytesReceived = 0;

            // now wait for getting the message itself
            WaitForData();
        }
        else
        {
            if (bytesReceived != messageLength)
            {
                WaitForData();
            }
            else
            {
                string message = Encoding.ASCII.GetString(receiveDataBuffer);

                MessageBox.Show(message);

                bytesReceived = 0;
                messageLengthReceived = false;

                // clear buffer
                receiveDataBuffer = new byte[AsyncClient.BUFFER_SIZE];

                WaitForData();
            }
        }
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }

}

public void SendComplete(IAsyncResult result)
{
    try
    {
        Socket socket = result.AsyncState as Socket;
        bytesSent = socket.EndSend(result);

        if (bytesSent != messageSendSize)
        {
            messageSendSize -= bytesSent;

            socket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
                            new AsyncCallback(SendComplete), clientSocket);
            return;
        }

        // wait for data
        messageLengthReceived = false;
        bytesReceived = 0;

        WaitForData();
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }
}

回答by Mihai Lazar

Usually BeginXXX methods indicate an asynchronous operation, and you seem to want to do it in a synchronous manner.

通常 BeginXXX 方法表示异步操作,而您似乎希望以同步方式进行。

If indeed you want a synchronous client/server maybe this will help http://sharpoverride.blogspot.com/2009/04/another-tcpip-server-client-well-it.html

如果您确实想要一个同步的客户端/服务器,这可能会有所帮助http://sharpoverride.blogspot.com/2009/04/another-tcpip-server-client-well-it.html

回答by Richard

The order in time should be:

时间顺序应该是:

  1. BeginReceivefor message length
  2. EndReceivefor the completion of #1
  3. BeginReceivefor the message body
  4. EndReceivefor the completion of #3
  1. BeginReceive对于消息长度
  2. EndReceive完成#1
  3. BeginReceive对于消息正文
  4. EndReceive完成#3

E.g. not using callbacks you could have:

例如,不使用您可能拥有的回调:

var sync = socket.BeginReceive(....);
sync.AsyncWaitHandle.WaitOne();
var res = socket.EndReceive(sync);
sync = socket.BeginReceive(....);
sync.AsyncWaitHandle.WaitOne();
var res2 = socket.EndReceive(sync);

But then, you would be better just using Receive!

但是,您最好只使用Receive!

I think you might find it easier to use separate handlers for the two different receives:

我认为您可能会发现为两个不同的接收使用单独的处理程序更容易:

... Start(....) {
    sync = socket.BeginReceive(.... MessageLengthReceived, null);
}

private void MessageLengthReceived(IAsyncResult sync) {
  var len = socket.EndReceive(sync);
  // ... set up buffer etc. for message receive

 sync = socket.BeginReceive(... MessageReceived, null);
}

private void MessageReceived(IAsyncResult sync) {
  var len = socket.EndReceive(sync);
  // ... process message
}

Ultimately putting all the associated in a state object and passing that around (in the completion delegate access via IAsyncResult.AsyncState) from BeginReceive can make things easier, but does take a shift from the linear thinking of imperative code and fulling embracing a event driven approach.

最终将所有关联的内容放在一个状态对象中并IAsyncResult.AsyncState从 BeginReceive传递(在完成委托访问中)可以使事情变得更容易,但确实从命令式代码的线性思维转变为完全拥抱事件驱动的方法。



2012 Addendum:

2012 年附录

.NET 4.5 Version

.NET 4.5 版本

With the async support in C#5 there is a new option. This uses the compiler to generate the manual continuations (the separate callback methods) and closures (state) from inline code. However there are two things to work around:

通过 C#5 中的异步支持,有一个新选项。这使用编译器从内联代码生成手动延续(单独的回调方法)和闭包(状态)。但是,有两件事需要解决:

  1. While System.Net.Sockets.Sockethas various …Asyncmethods these are for the event based asynchronous pattern, not the Taskbased pattern that C#5's awaituses. Solution: use TaskFactory.FromAsyncto get a single Task<T>from a Begin…End…pair.

  2. TaskFactory.FromAsynconly supports passing up to three additional arguments (in addition to the callback and state) to Begin…. Solution: a lambda taking zero additional arguments has the right signature, and C# will give us the right closure to pass the arguments in.

  1. 虽然System.Net.Sockets.Socket有各种…Async方法,但这些方法适用于基于事件的异步模式,而不是TaskC#5await使用的基于模式的模式。解决方案:用于从一对中TaskFactory.FromAsync获取单个。Task<T>Begin…End…

  2. TaskFactory.FromAsync仅支持将最多三个附加参数(除了回调和状态之外)传递给Begin…. 解决方案:采用零个附加参数的 lambda 具有正确的签名,C# 将为我们提供正确的闭包来传递参数。

Hence (and more fully realised with Messagebeing another type that handles the conversion from an initial send of the length encoded in some fixed number of bytes then the content bytes into a length for the content's buffer):

因此(并且更完全地实现Message为另一种类型,该类型处理从以某些固定字节数编码的长度的初始发送然后将内容字节转换为内容缓冲区长度的转换):

private async Task<Message> ReceiveAMessage() {
  var prefix = new byte[Message.PrefixLength];

  var revcLen = await Task.Factory.FromAsync(
                         (cb, s) => clientSocket.BeginReceive(prefix, 0, prefix.Length, SocketFlags.None, cb, s),
                         ias => clientSocket.EndReceive(ias),
                         null);
  if (revcLen != prefix.Length) { throw new ApplicationException("Failed to receive prefix"); }

  int contentLength = Message.GetLengthFromPrefix(prefix);
  var content = new byte[contentLength];

  revcLen = await Task.Factory.FromAsync(
                         (cb, s) => clientSocket.BeginReceive(content, 0, content.Length, SocketFlags.None, cb, s),
                         ias => clientSocket.EndReceive(ias),
                         null);
  if (revcLen != content.Length) { throw new ApplicationException("Failed to receive content"); }

  return new Message(content);
}

回答by Timothy Pratley

Perhaps what you want to do is chain your call-backs :

也许您想要做的是链接您的回调:

pseudo code:

伪代码:



// read the first 2 bytes as message length
BeginReceive(msg,0,2,-,-,new AsyncCallback(LengthReceived),-)

LengthReceived(ar) {
  StateObject so = (StateObject) ar.AsyncState;
  Socket s = so.workSocket;
  int read = s.EndReceive(ar);
  msg_length = GetLengthFromBytes(so.buffer);
  BeginReceive(so.buffer,0,msg_length,-,-,new AsyncCallback(DataReceived),-)
}

DataReceived(ar) {
  StateObject so = (StateObject) ar.AsyncState;
  Socket s = so.workSocket;
  int read = s.EndReceive(ar);
  ProcessMessage(so.buffer);
  BeginReceive(so.buffer,0,2,-,-,new AsyncCallback(LengthReceived),-)
}

see: http://msdn.microsoft.com/en-us/library/system.asynccallback.aspxfor correct examples

有关正确示例,请参见:http: //msdn.microsoft.com/en-us/library/system.asynccallback.aspx

回答by feroze

It will help if you describe the structure of the message you are sending.

如果您描述了您发送的消息的结构,将会有所帮助。

As long as you have only one BeginReceive() outstanding, it will complete and give you the next available bytes of data on the wire. If you have more than one outstanding at the same time, then all bets are off, because .net does not guarantee that the completion will be in any given order.

只要您只有一个 BeginReceive() 未完成,它就会完成并为您提供线路上的下一个可用数据字节。如果您同时有多个未完成的投注,则所有投注都将取消,因为 .net 不保证按任何给定顺序完成。

回答by Jess

As the others have said, don't use global variables here - use a class for socket state. Something like:

正如其他人所说,不要在这里使用全局变量 - 使用套接字状态的类。就像是:

public class StateObject
{
    public const int DEFAULT_SIZE = 1024;           //size of receive buffer

    public byte[] buffer = new byte[DEFAULT_SIZE];  //receive buffer
    public int dataSize = 0;                        //data size to be received
    public bool dataSizeReceived = false;           //received data size?
    public StringBuilder sb = new StringBuilder();  //received data String
    public int dataRecieved = 0;

    public Socket workSocket = null;                //client socket.
    public DateTime TimeStamp;                      //timestamp of data
} //end class StateObject

Before trying to resend the message, you should verify the socket... you might have a socket exception.

在尝试重新发送消息之前,您应该验证套接字......您可能有一个套接字异常。

You should probably have a return; after your WaitForData call in the "if" block of ReceiveComplete.

你可能应该有回报;在 ReceiveComplete 的“if”块中调用 WaitForData 之后。

Timothy Pratley said it above, one error will be in bytesRecieved the second time through. Each time you are only measuring the bytesReceived from that EndReceive and then comparing it to messageLength. You need to keep a sum of all bytesRecieved.

Timothy Pratley 上面说过,一个错误会以 bytesRecieved 第二次通过。每次您只测量从 EndReceive 接收到的字节数,然后将其与 messageLength 进行比较。您需要保留所有字节数的总和。

And your biggest error is that in your first call to ReceiveComplete, you take account for the fact that the message might (most likely) contain more data than just the size of the message - it will probably contain half the message as well. You need to peel off the data size, and then also store the rest of the message in your message variable.

您最大的错误是,在您第一次调用 ReceiveComplete 时,您考虑到消息可能(最有可能)包含的数据不仅仅是消息的大小——它也可能包含一半的消息。您需要剥离数据大小,然后还将消息的其余部分存储在您的消息变量中。