【飞秋】TCP粘包

来源:互联网 发布:apache部署webservice 编辑:程序博客网 时间:2024/04/29 20:32

 首先申明一下,写的这个东西注重的是一个思想~,代码只是参考,并不能直接运行.下面进入正题

这两天在弄Silverlight版本的SOCKET网络编程,参考了菩提树下的杨过的例子,写了一段程序

自己也遇到了一些问题,例如TCP协议的粘包,想了个解决方案,兴冲冲的,GOOGLE了一下发现类似的思想很多,不过决定还是把代码贴出来吧

 

   /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="msgOrSql">消息类容</param>
        /// <returns></returns>
        public bool MsgTo(MsgOrSql msgOrSql)
        {
            try
            {
                SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
                MemoryStream ms = new MemoryStream();
                byte[] dataLen = new byte[sizeof(long)];
                ms.Write(dataLen, 0, dataLen.Length);//先占住前八位字符


                string jsonString = JsonConvert.SerializeObject(msgOrSql);//JSON的序列化方式,读者也可以用其它的序列化方式
                byte[] msgByte = Encoding.UTF8.GetBytes(jsonString);//写入需要的流
                ms.Write(msgByte,0,msgByte.Length);

                ms.Position = 0;//从头写
                dataLen = BitConverter.GetBytes(ms.Length - dataLen.Length);//有效长度
                ms.Write(dataLen,0,dataLen.Length);// 将有效长度写入流中

                byte[] data = ms.ToArray();//需要发送的字节流
                ms.Close();            
                socketEventArg.SetBuffer(data,0,data.Length);
                socketEventArg.RemoteEndPoint = clickSocket.RemoteEndPoint;
                clickSocket.SendAsync(socketEventArg);
            }
            catch (Exception ee)
            {
                Console.Write(ee);
            }
            return false;
        }

以上是发送代码,下面贴一下接收方的代码

 

    private int msgLength;//消息总长
        private int yishouLength;//已收消息长度
        byte[] lstReceiveBytes = new byte[] { };//已经接受的数据

    /// <summary>
        /// 接受服务端发来的数据-回调处理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnSocketReceiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.Buffer == null)
            {
                return;
            }
            MemoryStream ms = new MemoryStream();
            if (lstReceiveBytes.Length == 0)//判断缓存是否存在数据
            {

      //缓存中没有数据
                msgLength = int.Parse(BitConverter.ToInt64(e.Buffer, 0).ToString());//获得数据长度,也就是发送端写入的字符长度

                ms.Write(e.Buffer, 8, e.BytesTransferred - 8);//将剩余的字符流写入缓存,除了长度字符之外,所以是从第八位开始读取数据流的
            }
            else
            {

      //缓存中有数据了
                ms.Write(lstReceiveBytes,0,lstReceiveBytes.Length);//将原有的数据写入缓存
                ms.Write(e.Buffer, 0, e.BytesTransferred);//将当前接收数据写入缓存
            }
            yishouLength += e.BytesTransferred;//已收数据长度+=当前收的数据长度
            lstReceiveBytes = ms.ToArray();//缓存中的数据替换
            ms.Close();
            GetMsgByByte();//调用处理缓存数据的方法
            try
            {
                //继续异步地从服务端 Socket 接收数据(类似长连接)
                if (clickSocket != null && clickSocket.Connected)
                {
                    clickSocket.ReceiveAsync(e);
                }
                else
                {
                    Console.Write("无法连接到服务器...请刷新后再试...");
                }
            }
            catch (Exception ex)
            {
                Console.Write(ex.Message.ToString());
            }

        }
        private void GetMsgByByte()//处理缓存数据的方法
        {
            try
            {
                if (lstReceiveBytes.Length >= msgLength) //如果已接受的数据长度大于等于定义的数据长度 也就是说可以处理一条消息了
                {

       byte[] msgByte = new byte[] { };//当前需要处理的包信息
                    msgByte = lstReceiveBytes;//默认为缓存中的信息
                    if (lstReceiveBytes.Length > msgLength) //处于粘包状态
                    {

         //将字符流分为两个部分,一部分为当前的一条消息,分离出来当前处理,另一部分为下一步操作的数据流,写入缓存进行下一步操作,(或许写的有点模糊)   

         MemoryStream m = new MemoryStream();
                        m.Write(lstReceiveBytes,0,msgLength);//取字符流中的一条传送完毕的消息
                        msgByte = m.ToArray();
                        m.Close();
                        m = new MemoryStream();
                        m.Write(lstReceiveBytes, msgLength, lstReceiveBytes.Length - msgLength);//根据当前的数据长度读取一条数据,所属的所有字符流
                        byte[] bb = m.ToArray();
                        msgLength = int.Parse(BitConverter.ToInt64(bb, 0).ToString());//获得下一条信息的字符流的长度

                        m.Close();
                        m = new MemoryStream();
                        m.Write(lstReceiveBytes, msgLength, lstReceiveBytes.Length - msgLength);//获得下一条信息的字符流,(不包含长度的字符流)

                        lstReceiveBytes = m.ToArray();//替换缓存中的数据
                        m.Close();

             }
                    string jsonString = Encoding.UTF8.GetString(lstReceiveBytes.ToArray(), 0, msgLength);//JSON的序列化方式,大家可以不用理会
                    MsgOrSql msgOrSql = new MsgOrSql();
                    msgOrSql = JsonConvert.DeserializeObject<MsgOrSql>(jsonString);
                    if (msgOrSql.dbInfo != null)
                    {
                        if (msgOrSql.dbInfo.FangFaMin != null)
                        {
                            DBHelper.listDbInfo.Add(msgOrSql.dbInfo.FangFaMin, msgOrSql.dbInfo);
                        }
                    }
                    if (msgOrSql.msgInfo != null)
                    {
                        if (msgOrSql.msgInfo.Id != null)//发送者的ID不为空
                        {
                            UserHelper.listMsg.Add(msgOrSql.msgInfo);
                        }
                    }
                    if (lstReceiveBytes.Length > msgLength)
                    {
                        GetMsgByByte();//递归调用处理字符流的方法
                    }else

       {

         return; 

       }

       //字符流处理完毕,初始化接受消息的一些信息
                    msgLength = 0;
                    yishouLength = 0;
                    lstReceiveBytes=new byte[0];
                }
               
            }
            catch (Exception ex)
            {
                Console.Write(ex);
            }


        }

两个方法,一个是接受的回调函数,另外一个是处理字符流的方法

 

代码写的可能有些问题,COPY下去也运行不了,不过主要的思想应该是表现出来了,相信有些功底的人都能看得懂

发送消息之前,将消息打包,消息头之前添加该消息的字符长度,接收方接受消息之后根据字符长度,判断消息是否处理完毕,如果出现粘包,则继续根据下一条消息的长度,处理下一条消息

 

希望对读者有所启发吧

关注技术文章飞秋:http://www.freeeim.com/,24小时专业转载。

原创粉丝点击