吵吵   2014-05-31  阅读:4,658

前文讲到我用tcp通信来做金域的接口代理,做好了之后,却发现了一个问题,对于TCP通信来说,默认的数据包长度是1492,因此,当我发送的数据大于1492的时候,问题就出现了,数据被拆分了,要不就是没法判断数据都接收完了,要不就是粘包了。

1、如何判断数据是否接收完?

通常,我们通过socket来接收数据是使用一个循环去读取的,读到了数据就加入到缓存的数据中去。但是,如果仅仅是循环的话,你怎么知道什么时候数据就接收完了呢?

while (bLoop == true)
            {

                if (ASocket.Available > 0)
                {
netStream.Read(buffer, 0, buffer.Length);
}

}

要解决这个问题,构造数据包就显得十分必要了,通过得到包头中数据总量的大小,来判断,这一次的数据有没有接收完。当然会有更加方便的方法,比如定义一个包的ID和包数量,当这个包的ID和包数量相等时,就是最后一个数据包了。

下面两个函数就是用来构造和翻译数据包的:

        public DataPacket BytesToPacket(byte[] ABytes)
        {
            DataPacket dp = new DataPacket();
            byte[] btID = new byte[4];
            Array.Copy(ABytes, 0, btID, 0, btID.Length);
            dp.ID=BitConverter.ToInt32(btID, 0);

            byte[] btMaxID = new byte[4];
            Array.Copy(ABytes, 4, btMaxID, 0, btMaxID.Length);
            dp.MaxID =BitConverter.ToInt32(btMaxID,0);

            byte[] btLenth = new byte[4];
            Array.Copy(ABytes, 8, btLenth, 0, btLenth.Length);
            dp.Length = BitConverter.ToInt32(btLenth, 0);

            byte[] btTotalLength = new byte[4];
            Array.Copy(ABytes, 12, btTotalLength, 0, btTotalLength.Length);
            dp.TotalLength = BitConverter.ToInt32(btTotalLength, 0);



            dp.Content=new byte[1024];
            Array.Copy(ABytes, 16, dp.Content, 0, dp.Content.Length);

            return dp;
            
        }

 public byte[] PacketToBytes(DataPacket ADp)
        {
            byte[] btSend = new byte[1024 + 16];
            byte[] btID = new byte[4];
            BitConverter.GetBytes(ADp.ID).CopyTo(btID, 0);
            btID.CopyTo(btSend, 0);

            byte[] btMaxID = new byte[4];
            BitConverter.GetBytes(ADp.MaxID).CopyTo(btMaxID, 0);
            btMaxID.CopyTo(btSend, 4);

            byte[] btLength = new byte[4];
            BitConverter.GetBytes(ADp.Length).CopyTo(btLength, 0);
            btLength.CopyTo(btSend, 8);

            byte[] btTotalLength = new byte[4];
            BitConverter.GetBytes(ADp.TotalLength).CopyTo(btTotalLength, 0);
            btTotalLength.CopyTo(btSend, 12);
            
            ADp.Content.CopyTo(btSend, 16);

            return btSend;

        }

那么,如果我们需要发送数据的话,就得先计算数据有多大,要分多少个包去发送了:

public void SendString(Socket ASocket, string AStr)
        {
            Byte[] sendBytes = Encoding.Unicode.GetBytes(AStr);
            //计算包数
            int iBagNum = (sendBytes.Length / MaxLength) + 1;
            // MessageBox.Show(iBagNum.ToString());
            int iLastBagLength = sendBytes.Length % MaxLength;
            // MessageBox.Show(iLastBagLength.ToString());
            for (int i = 0; i < iBagNum; ++i)
            {
                //  MessageBox.Show(i.ToString());
                DataPacket dp = new DataPacket();
                if (i == (iBagNum - 1))
                {

                    dp.Length = iLastBagLength;

                }
                else
                {
                    dp.Length = MaxLength;
                }
                dp.ID = i + 1;
                dp.MaxID = iBagNum;
                dp.MaxLength = MaxLength;
                dp.TotalLength = sendBytes.Length;
                dp.Offset = i * 1024;

                dp.Content = new byte[1024];
                Array.Copy(sendBytes, dp.Offset, dp.Content, 0, dp.Length);

                SendData(ASocket, PacketToBytes(dp));
            }



        }

2、粘包问题。

什么是粘包问题?粘包就是在socket接收数据的时候,socket的缓存里面存有2个或者两个以上包大小的数据,如果你的包大小不一,或者压根就没有定义包的结构,那就会很麻烦,因为,完全不知道要读的数据有多大。但是当我们构造了有包头的数据包来传输数据的时候,就没有这个问题了,粘多少个包,就多读取多少次了:

 public string ReceiveString(Socket ASocket)
        {
            NetworkStream netStream =new NetworkStream( ASocket);
            bool bLoop = true;
            byte[] bySource=null;
            while (bLoop == true)
            {

                if (ASocket.Available > 0)
                {
                    int iNum = (ASocket.Available / 1040);
                    //MessageBox.Show(iNum.ToString());
                    for (int i = 0; i < iNum; ++i)
                    {

                        byte[] buffer = new byte[1040];
                        netStream.Read(buffer, 0, buffer.Length);
                        DataPacket dp = BytesToPacket(buffer);

                        if (dp.ID == 1)
                        {
                            bySource = new byte[dp.TotalLength];
                        }
                        //MessageBox.Show(dp.ID.ToString());
                        //MessageBox.Show(dp.Length.ToString());
                        Array.Copy(dp.Content, 0, bySource, (dp.ID - 1) * 1024, dp.Length);
                        //Debug.WriteLine(dp.MaxID);
                        if (dp.ID == dp.MaxID)
                        {
                            bLoop = false;
                        }
                    }
                }
            }
            string str = Encoding.Unicode.GetString(bySource);
            return str;
           
        }

这其中发现了socket通信的一个小bug,ASocket.Available代表的是缓存里面有多少字节可以读取,当你读取一半以后,这个参数并不会变成剩下一半,而是没有了,只要你读取,这个参数就变成0了,因此如果你的语句是这么写的:

while(ASocket.Available > 0)
{
//读取数据
}
粘包过来的后面的包,你就都丢掉了,这是个很恼火的问题,当我在其中加入MessageBox.show()等操作的时候,可能是因为等了一下数据,就不会丢包,这让调试起来,很是恼火!

吵吵微信朋友圈,请付款实名加入:

吵吵 吵吵

发表评论

电子邮件地址不会被公开。 必填项已用*标注