天天看點

伺服器與用戶端菜鳥初學(二)之粘包和分包

對我而言。粘包是由于短時間傳送的資料多且小,socket會進行優化,使其一部分資料合并發送。而分包是由于傳送的資料大,消耗資源大,且丢失重新發送又麻煩,是以系統自動會将資料進行拆分發送。

不過,還是舉例說明好點。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse("127.0.0.1");
            IPEndPoint point = new IPEndPoint(ip, 88);
            client.Connect(point);
            //接收
            byte[] buffer = new byte[1024];
            int count = client.Receive(buffer);
            Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, count));
            //發送
           /* while (true)
            {
                string s = Console.ReadLine();
                if (s=="c")
                {
                    //主動關閉
                    client.Close();
                    return;
                }
                client.Send(Encoding.UTF8.GetBytes(s));
            }*/

            for(int i = 0; i < 100; i++)
            {
                client.Send(Encoding.UTF8.GetBytes(i.ToString()));
            }
            Console.ReadKey();
            client.Close();
            
        }
    }
}

           

在用戶端傳送100個資料給伺服器。按理說,服務端應該分批接收100個資料,然而并沒有。

伺服器與用戶端菜鳥初學(二)之粘包和分包

系統将後面一部分資料合并了。

而分包呢

string i = "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和" +
                "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和" +
                "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和" +
                "" +
                "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和" +
                "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和" +
                "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和" +
                "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和" +
                "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和" +
                "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和" +

                "會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和會計師大後方士大夫實打實附上附件是覅u是覅是isfi覅是uiui是hi和毒素還是粉色粉色ui和i薩芬和";
            client.Send(Encoding.UTF8.GetBytes(i.ToString()));
           

請無視内容,随便打的。正常來說服務端應該接收一次資料。然而

伺服器與用戶端菜鳥初學(二)之粘包和分包

服務端接收了兩次。

對于這個問題,我采取一種方法。首先将要傳送的資料的長度記錄下來,并将轉化為位元組。而這個過程我用的BitConverter.ToInt32函數,因為不管資料長度多少,都能轉化為4個位元組,使其結構固定,最後将長度位元組與資訊結合傳送,這樣的話,對方就可以知道該資訊要讀取多少。

廢話少說,直接上代碼

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp1
{
    class Message
    {
        public static byte[] GetByte(string str)
        {
            byte[] str_byte = Encoding.UTF8.GetBytes(str);
            int count = str_byte.Length;
            byte[] start_byte = BitConverter.GetBytes(count);
            byte[] new_byte = start_byte.Concat(str_byte).ToArray();
            return new_byte;
        }
    }
}

           

此腳本位于用戶端,将要傳送的資料序列化。同時用粘包例子進行修改。

for (int i = 0; i < 100; i++)
            {

                client.Send(Message.GetByte(i.ToString ()));
              
            }
           

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

2.然後這個腳本位于服務端,将傳來的資料進行解析。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sever
{
    class Message
    {
        private byte[] date = new byte[1024];
        private int startindex = 0;
        public void add_byte(int count)
        {
            startindex += count;
        }
        public byte[] set_byte
        {
            get { return date; }
        }
        public int Startindex
        {
            get { return startindex; }
        }
        public int remove_byte
        {
            get { return date.Length - startindex; }
        }
        public void receivedata()
        {
            while (true)
            {
                if (startindex <= 4) return;
                int count = BitConverter.ToInt32(date, 0);//表示資訊長度
                if(startindex - 4 > count)
                {
                    string s = Encoding.UTF8.GetString(date, 4, count);
                    Console.WriteLine("接收的資料:" + s);
                    Array.Copy(date, count + 4, date, 0, startindex - 4 - count);
                    startindex -= (count + 4);
                }
                else
                {
                    break;
                }
            }
        }
    }
}

           

然後對服務端的腳本也修改

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace Sever
{
    class Program
    {
        static byte[] buffer = new byte[1024];  
        static void Main(string[] args)
        {
            Socket severSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ip = IPAddress.Parse("127.0.0.1");
            IPEndPoint severpoint = new IPEndPoint(ip,88);
            severSocket.Bind(severpoint);
            severSocket.Listen(10);
            severSocket.BeginAccept(accept,severSocket);
            
            Console.ReadKey();
            
        }
        static  Message mesg = new Message();
        static void accept(IAsyncResult ar)
        {
            Socket severSocket = ar.AsyncState as Socket;
            Socket client = severSocket.EndAccept(ar);
           //發資料
            string str = "hello";
            client.Send(Encoding.UTF8.GetBytes(str));
            //接收 
            client.BeginReceive (mesg.set_byte, mesg.Startindex, mesg.remove_byte , SocketFlags.None, receive, client);
            severSocket.BeginAccept(accept, severSocket);
        }
        static void receive(IAsyncResult ar)
        {
            Socket client = null;
            try {
                client = ar.AsyncState as Socket;
                int count = client.EndReceive(ar);
                if (count == 0)
                {
                    //正常關閉
                    client.Close();return;
                }
                mesg.add_byte(count);
                /*string str = Encoding.UTF8.GetString(buffer, 0, count);
                Console.WriteLine("接收的資料:"+str);*/
                mesg.receivedata();
                client.BeginReceive(mesg.set_byte, mesg.Startindex, mesg.remove_byte, SocketFlags.None, receive, client);
            }
            catch(Exception e)//非正常關閉
            {
                Console.WriteLine(e);
                if (client != null)
                {
                    client.Close();
                }
            }
           

        }
        
    }
}

           

最後的結果

伺服器與用戶端菜鳥初學(二)之粘包和分包

繼續閱讀