首页 文章

C#Socket.BeginReceive / EndReceive

提问于
浏览
9

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

例如,我调用 BeginReceive 两次,一次获取消息长度,第二次调用消息本身 . 现在的情况是这样的,对于我发送的每条消息,我开始等待它的完成(实际上确认发送的消息,我等待接收到确认后的动作完成),所以我用 BeginReceive 调用 BeginReceive ,但是每个 BeginReceive 's callback, I check if I' m接收长度或消息 . 如果我收到消息并完全收到消息,那么我再拨打另一个 BeginReceive 来接收完成的动作 . 现在这是事情不同步的地方 . 因为我的一个接收回调是接收字节,它将其解释为消息的长度,实际上它是消息本身 .

现在我该如何解决?

EDIT: 这是一个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);
    }
}

5 回答

  • 5

    时间顺序应该是:

    消息长度为

    • BeginReceive

    • EndReceive 完成#1
      消息正文

    • BeginReceive

    • 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中的异步支持,有一个新选项 . 这使用编译器从内联代码生成手动延续(单独的回调方法)和闭包(状态) . 但是有两件事要解决:

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

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

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

    伪代码:

    // 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以获取正确的示例

  • 0

    通常,BeginXXX方法表示异步操作,您似乎希望以同步方式执行此操作 .

    如果你确实想要一个同步客户端/服务器,那么这将有助于http://sharpoverride.blogspot.com/2009/04/another-tcpip-server-client-well-it.html

  • 1

    如果您描述要发送的消息的结构,它将有所帮助 .

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

  • 19

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

    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
    

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

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

    Timothy Pratley在上面说过,一个错误将以字节为单位第二次通过 . 每次只测量从EndReceive获取的bytesReceived,然后将它与messageLength进行比较 . 你需要保留所有bytesRecieved的总和 .

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

相关问题