想了半天,也没想出个合适的标题,还是描述问题吧

(1)客户端向服务端发送指令,期望获得回答

        (2)服务端响应客户端请求,并返回答案


这看起来是一个非常简单的过程,比如客户端发送一条json格式的指令

{"id":"2342"}
        服务器收到消息后解析json数据,返回id为2342的用户的信息

{"name":"sheng","age":"29"}

很好哦,顺利通信。但是呢,由于客户端极为频繁的发送数据,且网络状况不佳,会出现这样的情况,两条请求同时到达服务端,之前,服务端调用recv函数只收到一个json格式的请求,但现在,却收到了两个,那么服务端就无法再像之前那样有效的解析这条命令了。到此为止你发现,之前的做法是有问题的,之前的做法默认一次recv函数调用从缓冲区里获得指令是一条,但现实的情况是也可能是两条。


        这个就是我们在工作中真真实实遇到的问题。还有一种情况,由于网络问题,一条完整的消息被分成了多段被接收。说到这里,有些同学已经疑惑了,客户端把一条指令发送过来,服务端怎么还分段来接收呢?这就涉及到socket的消息传输机制了。当我们调用send函数时,我们以为把一条消息发送出去了,但事实上呢,并没有。这个send函数仅仅是把我们的消息写入到了发送缓冲区,这个大小通常为64K,接下来,由socket协议自己来决定什么时候把这部分消息发送出去。当我们调用recv函数时,并不是说从客户端获得数据,客户端发来的数据要进入到服务端的接收缓冲区,recv只是说从那里获得数据。客户端发来的数据,一部分已经到了,一部分还在路上呢,这个时候你去接收,那么只是接收已经到了的部分,这种情况在网络状况不好的时候容易发生。

        

        现在,你明白了,socket通信可不像我们平时对话,我说一句,你就听到一句,而可能是我说了一句,你没听见,一秒钟以后我又说了一句,虽然两句话间隔了一秒钟,但是他们都被写入到发送缓冲区中然后首尾相连一起发送给你了,那么你收到的时候还以为是一句话呢!我们为了保证socket长连接,还要发送心跳包,同理,这些心跳包也可能与我们正常发送的数据掺杂在一起,网络状况好或者消息发送不频繁时这些问题不会出现,但网络状况不总是好,我们也不总是那么清闲。


       有什么简单的解决办法呢?


       既然消息可能分段到达,那么我们就用一种方法把分段到达的数据拼接起来;既然多个消息可能同时到达,我们就把他们拆开。如何做到,我们自己建立一个消息的缓冲区,同时,在消息的最前面加一个标识用来标识本条消息的长度。

  

       假设每一次发送的消息的长度都小于10万,那么我们用长度为5的标识位就足够了,假如一条消息本身长1088,那么就在这个消息的最前面加上标识位01088。消息到达时,我们先用recv函数接收,然后放入我们自己的缓冲区,然后再来解析缓冲区里的内容,第一次调用recv时,不管来了几条消息还是一条消息的某一部分,我们只要确定缓冲区里的数据的长度大于5就可以了,因为大于5,我们就知道了消息的长度,那么只需要判断省下来的数据长度到底够不够一条消息,如果够就从缓冲区里取出来,这就是消息的拆分,如果不够呢,就继续接收,这就是消息的拼接。

 

      下面是C#实现的代码

      

namespace TestUpLoadAndDownLoad
{
    public class CmdContainer
    {
        public byte[] m_CmdBuff { get; set; }               // 缓存收到的指令
        public int m_BuffLen { get; set; }                  // 缓存区指令的长度
        public int m_BuffSize { get; set; }                 // 缓存区容量
        public Queue<string> m_CmdQueue { set; get; }       // 存储已经收到的指令,该指令是多条完整的指令
        private const int m_iHead = 5;                      // 在每条指令的前端加5个数字标识该条指令的长度,00324 标识指令长324
        public CmdContainer(int iSize)
        {
            m_CmdBuff = new byte[iSize];
            m_BuffLen = 0;
            m_BuffSize = iSize;
            m_CmdQueue = new Queue<string>();
        }

        // 尝试获得指令,如果有指令可以获取就返回true
        public bool GetCmd(byte[] buff, int len)
        {
            // 装不下的时候,扩容
            if(m_BuffLen + len > m_BuffSize)
            {
                byte[] tmp = new byte[m_BuffLen + len];
                Array.Copy(m_CmdBuff, 0, tmp, 0, m_BuffLen);
                m_CmdBuff = tmp;
                m_BuffSize = m_BuffLen + len;
            }

            Array.Copy(buff, 0, m_CmdBuff, m_BuffLen, len);
            m_BuffLen += len;
            GetCmd();

            return m_CmdQueue.Count==0?false:true;
        }

        // 根据缓存区的内容提取指令
        private void GetCmd()
        {
            int iStart = 0;
            while(true)
            {
                //无法读取命令长度
                if (m_BuffLen - iStart < m_iHead)
                {
                    m_BuffLen = m_BuffLen - iStart;
                    return;
                }

                // 读取命令长度
                string strLenth = Encoding.GetEncoding("utf-8").GetString(m_CmdBuff, iStart, m_iHead);
                int iCmdLen = Int32.Parse(strLenth);
                // 剩余的字符串不能组成一个有效的命令
                if (m_BuffLen - iStart - m_iHead < iCmdLen)
                {
                    int tmpLen = m_BuffLen - iStart ;
                    byte[] tmp = new byte[tmpLen];
                    Array.Copy(m_CmdBuff, iStart, tmp, 0, tmpLen);
                    Array.Copy(tmp, 0, m_CmdBuff, 0, tmpLen);
                    m_BuffLen = tmpLen;
                    return;
                }
                else
                {
                    // 命令可以被截取
                    string strCmd = Encoding.GetEncoding("utf-8").GetString(m_CmdBuff, iStart + m_iHead, iCmdLen);
                    m_CmdQueue.Enqueue(strCmd);
                    iStart = iStart + m_iHead + iCmdLen;
                }
            }
            
        }
    }
}

       服务端代码

       

        private void processClient(object obj)
        {
            CmdContainer cmd = new CmdContainer(1024);
            Socket socket = (Socket)obj;
            while(true)
            {
                byte[] data = null;
                int len = 0;
                try
                {
                    data = new Byte[1024];//client.Available
                    len = socket.Receive(data, SocketFlags.None);//接收客户端套接字数据
                }
                catch (Exception e){
                }

                if(!cmd.GetCmd(data,len))
                {
                    continue;
                }

                while(cmd.m_CmdQueue.Count!=0)
                {
                    string strCmd = cmd.m_CmdQueue.Dequeue();
                    //从这里开始,我们就可以根据指令进行应答操作了
                }
                
            }
        }

抱歉我这里没有客户端代码,测试时我为了方便,用了一个socket工具,即便如此,如果你已经看懂了上面两端代码,客户端也就随手写出来了