服务器与客户端菜鸟初学(二)之粘包和分包

对我而言。粘包是由于短时间传送的数据多且小,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();
                }
            }
           

        }
        
    }
}

最后的结果
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
.版本 2 .支持库 spec .支持库 sock .程序集 窗口程序集_启动窗口 .子程序 _按钮2_被单击 客户1.发送数据 (取重复字节集 (10000, 到字节集 (“1”)) + 到字节集 (“分隔符”)) 客户1.发送数据 (取重复字节集 (20000, 到字节集 (“2”)) + 到字节集 (“分隔符”)) 客户1.发送数据 (取重复字节集 (30000, 到字节集 (“3”)) + 到字节集 (“分隔符”)) 客户1.发送数据 (取重复字节集 (40000, 到字节集 (“4”)) + 到字节集 (“分隔符”)) 客户1.发送数据 (取重复字节集 (50000, 到字节集 (“5”)) + 到字节集 (“分隔符”)) 客户1.发送数据 (取重复字节集 (60000, 到字节集 (“6”)) + 到字节集 (“分隔符”)) 客户1.发送数据 (取重复字节集 (70000, 到字节集 (“7”)) + 到字节集 (“分隔符”)) 客户1.发送数据 (取重复字节集 (80000, 到字节集 (“8”)) + 到字节集 (“分隔符”)) 客户1.发送数据 (取重复字节集 (90000, 到字节集 (“9”)) + 到字节集 (“分隔符”)) 客户1.发送数据 (取重复字节集 (100000, 到字节集 (“0”)) + 到字节集 (“结尾符”)) .子程序 _服务器1_数据到达 .局部变量 取回数据, 字节集 .局部变量 数据数组, 文本型, , "0" .局部变量 次数, 整数型 .局部变量 临时数据, 字节集, 静态 .局部变量 得到的封包, 文本型 取回数据 = 服务器1.取回数据 () .判断开始 (取字节集右边 (取回数据, 6) ≠ 到字节集 (“结尾符”))  ' 6为结尾符的长度;这里检测封包是否全部发送完毕,如果没有发送完毕就会把发送来的封包数据进行累加     临时数据 = 临时数据 + 取回数据 .默认     临时数据 = 临时数据 + 取回数据  ' 检测到结尾符出现,说明数据已经发送完毕,这里把最后发送来的带有结尾符的数据加上就OK了。     临时数据 = 子字节集替换 (临时数据, 到字节集 (“结尾符”), , 取字节集长度 (临时数据) - 6, 6)  ' 6为结尾符的长度;这里把结尾符替换尾空,剩余数据尾完整的纯净数据。     数据数组 = 分割文本 (到文本 (临时数据), “分隔符”, )  ' 这里把收到的数据进行分割处理,无论服务器发送了多少次,都统一按分隔符分割     调试输出 (“封包数量:” + 到文本 (取数组成员数 (数据数组)))     .计次循环首 (取数组成员数 (数据数组), 次数)         得到的封包 = 数据数组 [次数]  ' 这里得到分割后的封包文本。         调试输出 (“第” + 到文本 (次数) + “个封包的大小:” + 到文本 (取文本长度 (得到的封包)))  ' 这里的大小和上面发送封包的大小相同,可以看到封包的分割次数。     .计次循环尾 ()     临时数据 = {  } .判断结束 .子程序 __启动窗口_创建完毕 客户1.连接 (取本机名 (), 8888)
网络通信中,由于数据传输的特性,会出现粘包分包的问题。 粘包(Packet Sticking)是指发送方发送的数据包被接收方连续接收到,多个数据包被黏在一起,导致接收方无法正确解析数据。这可能是因为发送方发送的数据包没有明确的边界,接收方无法确定每个数据包的开始和结束位置。 分包(Packet Splitting)是指发送方发送的数据包被接收方拆分成多个数据包接收。这可能是因为发送方发送的数据包过大,导致网络传输过程中被拆分成多个小包进行传输。 出现粘包分包问题的原因主要有以下几点: 1. 发送方连续发送多个数据包时,底层传输协议可能会将多个数据包合并成一个大的数据块进行传输,导致接收方接收到的数据不完整。 2. 发送方发送的数据包大小超过了接收方的缓冲区大小,导致数据被拆分成多个小包进行传输。 3. 网络传输中存在延迟或拥塞,导致数据包到达顺序发生变化。 为了解决粘包分包问题,可以采用以下几种方法: 1. 使用固定长度的数据包:在每个数据包的前面添加固定长度的头部信息,指示该数据包的长度,接收方根据头部信息来切分数据包。 2. 使用特殊字符作为分隔符:在数据包之间添加特殊字符作为分隔符,接收方根据分隔符来切分数据包。 3. 使用长度字段:在数据包的头部添加一个表示数据包长度的字段,接收方根据长度字段来切分数据包。 4. 使用消息边界:在传输协议中定义消息边界,保证每个数据包都是一个完整的消息。 具体选择哪种方法取决于你的应用需求和实际情况。在实际开发中,可以根据协议规范或业界常用的做法来处理粘包分包问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值