【问题标题】:TCPClient and TCPListener - NetworkStream - order of messagesTCPClient 和 TCPListener - NetworkStream - 消息的顺序
【发布时间】:2017-04-09 13:57:19
【问题描述】:

我有一个关于通过 TCP 类发送和接收消息的顺序的简单问题,我找不到任何 100% 的答案,而且我的英语还不够好。

如果我有以下示例:

服务器:

        IPAddress IP = IPAddress.Parse("127.0.0.1");
        int Port = 13000;

        TcpListener Server = new TcpListener(IP, Port);
        TcpClient Client = Server.AcceptTcpClient();
        NetworkStream Stream = Client.GetStream();

        Stream.Write(Buffer1, 0, 4);
        //random time
        Stream.Write(Buffer2, 0, 4);
        //random time
        Stream.Write(Buffer3, 0, 4);

和客户:

        TCPClient Client = new TcpClient("127.0.0.1", 13000);
        NetworkStream Stream = Client.GetStream();

        Stream.Read(A, 0, 4);
        //random time
        Stream.Read(B, 0, 4);
        //random time
        Stream.Read(C, 0, 4);

是否 100% 确定我会得到 A = Buffer1、B = Buffer2、C = Buffer3?

【问题讨论】:

  • 不!您必须检查Stream.Read 的返回值以查看您实际获得了多少字节。 TCP 是 ordered 的(意思是,您可以保证按照发送它们的顺序接收字节)而且也是 stream-based 的(意思是,不保证字节的打包)。更明确地说:TCP 没有消息。如果你想要消息,你必须自己构建它们(用长度作为前缀是最常见的方法)。请参阅任何有关网络代码的教程,了解如何编写正确的接收循环。
  • 我已经实现了这个前缀,我的消息的前 4B 是消息的长度,所以在另一边我读取了前 4B,然后将 NumberOfBytesToRead 设置为该数字以进行另一次读取。我只是想确认您可能在评论的第一部分中回答的消息的顺序:)
  • 小心:如果你要求 4 个字节,你甚至不能保证得到 4 个字节(它们可以跨数据包拆分),所以即使读取长度也必须在循环中完成,直到你有那些4字节。然后您需要再次循环以获取完整消息。弄错这个错误可能是网络编程中最常见的错误,而且特别狡猾,因为这种代码可能在意外或在测试环境中运行良好,然后在生产中失败。
  • 这可能是我的例子,我正在发送消息
  • @JeroenMostert 即使客户端请求 4 个字节并且服务器在不同的数据包中发送 2 和 2,客户端也会等到他读取 4 个字节。

标签: c# tcpclient tcplistener networkstream


【解决方案1】:

无法保证对于每个 NetworkStream.Write 操作,它都会对应 NetworkStream.Read 操作,这些操作将返回已写入流的确切数据。 下面是一个简单的 TcpListner 和 TcpClient 连接示例:

public class NetworkUtils{

    //Client
    TcpClient client = null;
    int port = 40555;
    string serverIpAddress = "127.0.0.1";
    public Mutex mut = new Mutex();
    int byteToExpecting = 0;
    int savedBufferOffset = 0;
    Byte[] saveDataBuffer = new Byte[20000];
    NetworkStream stream;

    public string ServerIpAddress
    {
        get { return serverIpAddress; }
        set { serverIpAddress = value;}
    }

    string lastSentMsg = String.Empty;
    public string LastSentMsg
    {
        get { return lastSentMsg; }
        set { lastSentMsg = value;}
    }

    //Server
    string clientMsg = String.Empty;
    public string ClientMsg
    {
        get { return clientMsg; }
    }
    public void ClearClientMsg()
    {
        clientMsg = String.Empty;
    }

    TcpListener server=null;

    private string errMsg = String.Empty;
    public string ErrMsg
    {
        get { return errMsg; }
        set { errMsg = value;}
    }

    void ConnectToServer()
    {
        client = new TcpClient(serverIpAddress, port);  
    }

    public bool ClientSendMsg(string message)
    {
        try{

            ConnectToServer();

            Byte[] lengthByteArr = IntToByteArr(message.Length);
            client.GetStream().Write(lengthByteArr, 0, lengthByteArr.Length);

            Byte[] data = Encoding.ASCII.GetBytes(message);
            client.GetStream().Write(data, 0, data.Length);

            client.GetStream().Close();
        }
        catch (Exception e) 
        {
            errMsg = e.Message;
        }

        return errMsg.Length == 0;
    }

    public bool LaunchServer() {
        try {
            IPAddress localAddr = IPAddress.Parse("127.0.0.1");
            server = new TcpListener(localAddr, port);
            server.Start();
            ListenToClients();
        }
        catch(Exception e)
        {
            server.Stop();
        }

        return errMsg.Length == 0;
    }

    void ProcessInformation(IAsyncResult result)
    {
        try{

            TcpClient client;
            client = server.EndAcceptTcpClient(result);
            stream = client.GetStream();
            stream.BeginRead(saveDataBuffer, 0, sizeof(Int32), new AsyncCallback(callbackGetHeadrer), null);
            ListenToClients ();
        }
        catch(Exception e)
        {
            errMsg = e.Message;
            server.Stop();
        }
    }

    void callbackGetHeadrer (IAsyncResult asyncResult) { 
        int lenToRead = stream.EndRead(asyncResult);

        savedBufferOffset = 0;
        byteToExpecting = ByteArrToInt (saveDataBuffer);
        saveDataBuffer = new byte[byteToExpecting];
        stream.BeginRead (saveDataBuffer, 0, byteToExpecting, callback, null);
    }

    void callback (IAsyncResult asyncResult) { 

        int lenToRead = stream.EndRead(asyncResult);

        byteToExpecting -= lenToRead;
        savedBufferOffset += lenToRead;

        /*No one is gurentee that the 'lenToRead' will be correspanding to NetworkStream.Write execution order.
        We need to keep read from the stream until we will get waht we are expecting accrding 'byteToExpecting'
        So here we are keep calling 'stream.BeginRead'.*/
        if (byteToExpecting > 0) {
            stream.BeginRead (saveDataBuffer, savedBufferOffset, byteToExpecting, callback, null);
        } 
        else{
            mut.WaitOne();
            clientMsg = System.Text.Encoding.ASCII.GetString(saveDataBuffer,0, saveDataBuffer.Length);
            mut.ReleaseMutex();

            savedBufferOffset = 0;
            stream.Close();
            client.Close();
        }
    }

    bool ListenToClients()
    {
        try{
            server.BeginAcceptTcpClient( new AsyncCallback( ProcessInformation), server);
        }
        catch(Exception e)
        {
            errMsg = e.Message;
            server.Stop();
        }

        return errMsg.Length == 0;
    }

    public Byte[] IntToByteArr(Int32 intValue)
    {
        byte[] intBytes = BitConverter.GetBytes(intValue);

        if (BitConverter.IsLittleEndian)
            Array.Reverse(intBytes);
        return intBytes;
    }

    public Int32 ByteArrToInt(Byte[] intByteArr)
    {
        Int32 Int32_NUM_OF_BYTES = 4;
        Byte[] buffer = new Byte[Int32_NUM_OF_BYTES];

        for (int i = 0; i < Int32_NUM_OF_BYTES; ++i)
            buffer [i] = intByteArr [i];

        if (BitConverter.IsLittleEndian)
            Array.Reverse (buffer);

        return BitConverter.ToInt32 (buffer, 0);
    }
}

请注意,“callbackGetHeadrer”负责获取我们期望接收的数据大小。稍后,我们将继续使用“stream.BeginRead”从流中读取,直到我们得到我们所期望的,关于“stream.Write”操作的顺序。

【讨论】:

  • 是的,你是对的,我使用了类似的东西。顺便说一句,您可以使用 .NET 4.5 的异步等待。 NetworkStream 有 .ReadAsync.WriteAsync 这样的新方法,它们比旧的 .BeginRead 更加用户友好
猜你喜欢
  • 2016-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-02
  • 2014-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多