首页 文章

C#Socket.Receive消息长度

提问于
浏览
7

我目前正在开发一个可以接受来自多个客户端计算机的多个连接的C#Socket服务器 . 服务器的目标是允许客户端从服务器事件“订阅”和“取消订阅” .

到目前为止,我已经在这里采取了一个愉快的好看:http://msdn.microsoft.com/en-us/library/5w7b7x5f(v=VS.100).aspxhttp://msdn.microsoft.com/en-us/library/fx6588te.aspx的想法 .

我发送的所有消息都是加密的,所以我接收了我希望发送的字符串消息,将其转换为byte []数组,然后在将消息长度预先挂起到数据之前加密数据并通过连接发送出去 .

令我印象深刻的一件事是:在接收端,当收到消息的一半时,Socket.EndReceive()(或相关的回调)似乎可能会返回 . 是否有一种简单的方法可以确保每封邮件都“完整”并且一次只收到一封邮件?

EDIT: 例如,我认为.NET / Windows套接字没有"wrap"消息,以确保在一个Socket.Receive()调用中收到使用Socket.Send()发送的单个消息?或者是吗?

到目前为止我的实施:

private void StartListening()
{
    IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
    IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0], Constants.PortNumber);

    Socket listener = new Socket(localEP.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    listener.Bind(localEP);
    listener.Listen(10);

    while (true)
    {
        // Reset the event.
        this.listenAllDone.Reset();

        // Begin waiting for a connection
        listener.BeginAccept(new AsyncCallback(this.AcceptCallback), listener);

        // Wait for the event.
        this.listenAllDone.WaitOne();
    }
}

private void AcceptCallback(IAsyncResult ar)
{
    // Get the socket that handles the client request.
    Socket listener = (Socket) ar.AsyncState;
    Socket handler = listener.EndAccept(ar);

    // Signal the main thread to continue.
    this.listenAllDone.Set();

    // Accept the incoming connection and save a reference to the new Socket in the client data.
    CClient client = new CClient();
    client.Socket = handler;

    lock (this.clientList)
    {
        this.clientList.Add(client);
    }

    while (true)
    {
        this.readAllDone.Reset();

        // Begin waiting on data from the client.
        handler.BeginReceive(client.DataBuffer, 0, client.DataBuffer.Length, 0, new AsyncCallback(this.ReadCallback), client);

        this.readAllDone.WaitOne();
    }
}

private void ReadCallback(IAsyncResult asyn)
{
    CClient theClient = (CClient)asyn.AsyncState;

    // End the receive and get the number of bytes read.
    int iRx = theClient.Socket.EndReceive(asyn);
    if (iRx != 0)
    {
        // Data was read from the socket.
        // So save the data 
        byte[] recievedMsg = new byte[iRx];
        Array.Copy(theClient.DataBuffer, recievedMsg, iRx);

        this.readAllDone.Set();

        // Decode the message recieved and act accordingly.
        theClient.DecodeAndProcessMessage(recievedMsg);

        // Go back to waiting for data.
        this.WaitForData(theClient);
    }         
}

4 回答

  • 1

    是的,你可能每次接收只有部分信息,在传输过程中可能会更糟,只会发送部分信息 . 通常您可以在网络状况不佳或网络负载较重时看到 .

    要在网络级别上清楚,TCP保证按指定的顺序传输数据,但不能保证部分数据与您发送的数据相同 . 该软件有很多原因(例如,看看Nagle's algorithm),硬件(跟踪中的不同路由器),操作系统实现,所以一般情况下你不应该假设已经传输或接收了哪些数据 .

    很抱歉很长时间的介绍,下面是一些建议:

    • 尝试使用relatedvely "new" API用于高性能套接字服务器,此处示例Networking Samples for .NET v4.0

    • 不要以为你总是发送完整的数据包 . Socket.EndSend()返回实际调度发送的字节数,在网络负载较重的情况下甚至可以是1-2个字节 . 因此,您必须在需要时实现重新发送缓冲区的其余部分 .

    MSDN上有警告:

    无法保证您发送的数据会立即显示在网络上 . 为了提高网络效率,底层系统可能会延迟传输,直到收集到大量的传出数据 . 成功完成BeginSend方法意味着底层系统有足够的空间来缓冲网络发送的数据 .

    • 不要以为你总是收到完整的数据包 . 将接收到的数据加入某种缓冲区并在有足够数据时进行分析 .

    • 通常,对于二进制协议,我添加字段以指示传入的数据量,具有消息类型的字段(或者您可以使用每种消息类型的固定长度(通常不好,例如版本控制问题)),版本字段(如果适用)并添加CRC字段到消息结束 .

    • 它真的不需要阅读,有点旧并直接适用于Winsock但可能值得学习:Winsock Programmer's FAQ

    • 看看ProtocolBuffers,值得学习:http://code.google.com/p/protobuf-csharp-port/http://code.google.com/p/protobuf-net/

    希望能帮助到你 .

    附:遗憾的是,你提到的MSDN样本有效破坏了异步范式,如其他答案中所述 .

  • 2

    你的代码非常错误 . 像这样做循环会破坏异步编程的目的 . Async IO用于不阻止线程,但让他们继续做其他工作 . 通过这样循环,你阻止线程 .

    void StartListening()
    {
        _listener.BeginAccept(OnAccept, null);
    }
    
    void OnAccept(IAsyncResult res)
    {
        var clientSocket = listener.EndAccept(res);
    
        //begin accepting again
        _listener.BeginAccept(OnAccept, null);
    
       clientSocket.BeginReceive(xxxxxx, OnRead, clientSocket);
    }
    
    void OnReceive(IAsyncResult res)
    {
        var socket = (Socket)res.Asyncstate;
    
        var bytesRead = socket.EndReceive(res);
        socket.BeginReceive(xxxxx, OnReceive, socket);
    
        //handle buffer here.
    }
    

    请注意,我已删除所有错误处理以使代码更清晰 . 该代码不会阻止任何线程,因此更有效 . 我会在两个类中分解代码:服务器处理代码和客户端处理代码 . 它使维护和扩展更容易 .

    接下来要理解的是TCP是一种流协议 . 它不保证消息到达一个Receive . 因此,您必须知道消息的大小或消息何时结束 .

    第一种解决方案是为每条消息添加一个首先解析的 Headers ,然后继续阅读,直到获得完整的正文/消息 .

    第二种解决方案是在每条消息的末尾放置一些控制字符并继续读取,直到读取控制字符 . 请记住,如果该字符可以存在于实际消息中,则应对该字符进行编码 .

  • 10

    您需要发送固定长度的消息或在标头中包含消息的长度 . 尝试有一些东西可以让你清楚地识别数据包的开始 .

相关问题