C# 套接字.BeginReceive/EndReceive

标签 c# .net sockets beginreceive

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

最佳答案

时间顺序应该是:

  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 的完成委托(delegate)访问中)可以使事情变得更容易,但确实改变了命令式代码的线性思维并完全拥抱事件驱动方法。


2012 年附录:

.NET 4.5版本

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

  1. 同时 System.Net.Sockets.Socket有各种…Async这些方法用于基于事件的异步模式,而不是 Task基于 C#5 的 await 的模式使用。解决方案:使用TaskFactory.FromAsync得到一个Task<T>来自Begin… End…对。

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

关于C# 套接字.BeginReceive/EndReceive,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1388561/

相关文章:

c# 如何在 DataGrid 中正确对齐数字

.net - 如何在数据绑定(bind)场景(即 BindingList)中验证自定义对象的属性?

c# - 如何扫描文件并显示当前扫描的详细信息而不使应用程序无响应?

c - c socket() 函数中的类型和协议(protocol)有什么区别?

http - 为什么 SNDBUF 太低会破坏 HTTP?

c# - 从网页中提取数据,针对特定部分进行解析并显示

c# - WPF 中的自动数据更新

c# - 从 C# 到 VB6 的 Rijndael/AES

c# - 这个 IDisposable 实现是否正确?

linux 套接字编程中发生了奇怪的事情