【问题标题】:C# Socket.BeginReceive/EndReceiveC# Socket.BeginReceive/EndReceive
【发布时间】:2010-11-26 04:34:29
【问题描述】:

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

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

现在我该如何解决?

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

这里是代码,基本上是太大了,见谅

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

【问题讨论】:

  • 可以添加代码/数据示例吗?
  • 主要问题似乎是 (MESSAGE_LENGTH_SIZE - bytesReceived) 第一次可以正常工作 - 但之后不会是正确的消息长度大小,因为 bytesReceived 不再为 0。它有点难看当有此类事物的状态对象时,使用全局变量来传递这些东西。
  • 问题不在那个,我调试了很多次,问题出来了,我调用BeginReceive 3次,2次接收长度,1次消息,但是调用顺序是,首先调用用于接收长度的回调,然后调用消息,但随后消息被破坏。所以我只需要知道回调被调用的顺序。
  • 你能做到吗?我有同样的问题。你问这个问题已经很久了。我想你找到了解决办法。你能给我一些帮助吗?谢谢

标签: c# .net sockets beginreceive


【解决方案1】:

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

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

【讨论】:

  • 不,我希望它是异步的。谢谢
  • Manzoor,你可以在另一个线程上执行同步 I/O。更容易、更可靠。
【解决方案2】:

时间顺序应该是:

  1. BeginReceive 消息长度
  2. EndReceive #1 完成
  3. BeginReceive 用于消息正文
  4. EndReceive #3 完成

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

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

但是,您最好使用Receive

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

... 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
}

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


2012 年附录

.NET 4.5 版本

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

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

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

因此(更充分地实现 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);
}

【讨论】:

  • 你发现我做错了吗? :( 你能告诉我应该如何改进我当前的代码
  • @Manzoo:不是直接的,这就是为什么我建议使用一种更简单的方法来保留不同的操作(消息长度和消息正文)。更简单的代码会更容易调试。
  • @Richard:你能提供一个使用你提到的状态对象的例子
  • @Manzoo:这将比我花费更多的时间(尤其是验证它)。本质上:将所有状态(缓冲区、套接字、状态标志)放入辅助类型。将其从一个操作传递到另一个操作,而不是使用全局状态。
  • @NielsBrinch 我对此表示怀疑:这个答案正在使用展示如何从单独的 Begin/End 方法转换为与任务并行库方法匹配的方法,从而与 C#5 的异步和等待兼容。如果您了解我正在使用的 API 并查看代码上方的注释 #2,我不确定我会发表什么评论。
【解决方案3】:

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

伪代码:



// 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),-)
}

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      正如其他人所说,不要在此处使用全局变量 - 使用类作为套接字状态。比如:

      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
      

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

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

      Timothy Pratley 上面说过,一个错误将在第二次通过时以 bytesRecieved 为单位。每次您只测量来自该 EndReceive 的 bytesReceived,然后将其与 messageLength 进行比较。您需要保留所有 bytesRecieved 的总和。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-30
        • 1970-01-01
        • 2010-10-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多