Socket编程(网络编程)中TCP粘包的实例,含代码演示

利用网络通信中,经常会出现粘包的问题,围绕着这个问题说原因和解决的蛮多帖子的,但是给出粘包代码的就好少,为了便于大家更好的理解粘包的问题,这里对客户端和服务器端出现的粘包问题进行模拟,以方便更好的理解这个问题的出现原因。在开始之前还是需要理解几个基础概念。

如果需要源代码请点击此处下载

1、什么是粘包?

粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。只有TCP有粘包现象,UDP不会。

2、哪里会发生粘包?

其实粘包主要分为两个方面,客户端和服务器端都可能会出现:

a、客户端发生:当连续发送数据时,由于tcp协议的nagle算法,会将较小的内容拼接成大的内容,一次性发送到服务器端,因此造成粘包

b、服务器端发生:当发送内容较大时,由于服务器端的recv(buffer_size)方法中的buffer_size较小,不能一次性完全接收全部内容,因此在下一次请求到达时,接收的内容依然是上一次没有完全接收完的内容,因此造成粘包现象。

好了说了这些之后,我们简单通过一个图来介绍下我们要模拟的粘包的场景:简单说就是客户端向服务器端不停的发送两个包,一个数据包的内容是一个整形的数:4,另一个包是一个字符串:你好!

理想情况下,我们应该是客户端发送什么,发送多少服务器端就接收对应数量的数据:

然而,当我们加快客户端的发送速度时,服务器端接收的数据包就不正常了,如图所示:

很显然,客户端还是正常的,不停的发送21和40两个数据包,但是在服务器端除了21,40正常的包外还出现了101和61两种包类型,其中101我们可以分析的出是两个40的包和一个21的包粘到一起了,而61的包是一个21的包和一个40的包粘到一起了

 这里为了方便大家大家复现,先把客户端的代码贴出来:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using LitJson;

class client
{
    //声明socket
    Socket socket;
    //声明一个地址类
    IPEndPoint iep;
    public void connect()
    {
        try
        {
            //设定Socket的模式
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //指定服务端的地址和端口
            iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 30000);
            //建立连接
            socket.Connect(iep);
            Console.WriteLine("服务器连接成功");
            //定义一个Json来存放数据
            JsonData jd_int = new JsonData();
            jd_int["code"] = 1;
            jd_int["result"] = 4;
            //定义一个byte数组存放数据
            byte[] jsByte_int = Encoding.UTF8.GetBytes(jd_int.ToJson());
            
            //发送数据
            JsonData jd_string = new JsonData();
            jd_string["code"] = 2;
            jd_string["result"] = "你好!";
            byte[] jsByte_string = Encoding.UTF8.GetBytes(jd_string.ToJson());
            //发送数据
            while (true)
            {
                Thread.Sleep(1);  //设置发送消息的间隔
                socket.Send(jsByte_int);
                Console.WriteLine("发送了" + jsByte_int.Length + "个字节");
                socket.Send(jsByte_string);
                Console.WriteLine("发送了" + jsByte_string.Length + "个字节");
            }

        }
        catch(Exception e)
        {
            Console.WriteLine("服务器连接失败");
            Console.WriteLine(e.ToString());
        }
    }
}

这里各位可以根据自己电脑的情况设置发送的间隔,在不同的电脑里可能出现粘包的间隔不同,性能较差的电脑可能sleep时间较长就会出现粘包现象,而笔者的电脑是设置为1毫秒时出现了粘包的情况,具体代码如下:

            //发送数据
            while (true)  //循环发送数据,模拟客户端较大的数据量
            {
                Thread.Sleep(1);  //设置发送消息的间隔,越小越容易出现粘包现象(和电脑配置有关)
                socket.Send(jsByte_int);
                Console.WriteLine("发送了" + jsByte_int.Length + "个字节");
                socket.Send(jsByte_string);
                Console.WriteLine("发送了" + jsByte_string.Length + "个字节");
            }

服务器端的代码就相对简单了,就是一个简单的异步接收:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using LitJson;
public class demo
{
    //需要添加System.Net.Sockets的支持
    Socket socket;
    private string ip;
    private int port;
    //定义每次接收的最大字节数和接收数组
    private int maxBuffer = 1024;
    private byte[] buffer;
    //定义一个套接字代表客户端
    private Socket client;
    public demo(string _ip,int _port)
    {
        this.ip = _ip;
        this.port = _port;
    }
    //服务器连接函数
    public void StartConnect()
    {
        try
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint iep = new IPEndPoint(IPAddress.Parse(ip), port);
            //绑定地址
            socket.Bind(iep);
            //设置客户端连接数量
            socket.Listen(1000);
            Console.WriteLine("服务器开启成功!");

            //监听客户端连接
            Console.WriteLine("监控客户端连接!");
            client = socket.Accept();

            Console.WriteLine("有客户端连接了!");
            Receive();
        }
        catch(Exception e)
        {
            Console.WriteLine("服务器开启失败!");
            //显示出异常详情
            Console.WriteLine(e.ToString());
        }
        
    }
    //接收客户端信息
    public void Receive()
    {
        //初始化接收的数组
        buffer = new byte[maxBuffer];
        try
        {
            //异步接收,接收的数据放到回调函数BegRece里面去处理,无论客户端是否发送消息都会执行这一步
            client.BeginReceive(buffer, 0, maxBuffer, 0, new AsyncCallback(BegRece), client);
        }
        catch (Exception e)
        {
            Console.WriteLine("接收异常!");
            Console.WriteLine(e.ToString());
        }
    }
    private void BegRece(IAsyncResult ar)
    {
        Socket so = (Socket)ar.AsyncState;
        //接收字节的数据长度
        int length = so.EndReceive(ar);
        Console.WriteLine("收到了客户端发来的信息,长度为:" + length);
        try
        {
            //接收数据
            string content = Encoding.UTF8.GetString(buffer, 0, length);
            //Json的解析
            JsonData jd = JsonMapper.ToObject<JsonData>(content);
            Console.WriteLine("code: " + (int)jd["code"] + " result: " + jd["result"]);
            buffer = new byte[maxBuffer];
        }
        catch(Exception e)
        {
            Console.WriteLine("解码错误!");
            Console.WriteLine(e.ToString());
        }

        //为了实现不停地接收数据,再次调用并对buffer重新赋值即可

        client.BeginReceive(buffer, 0, maxBuffer, 0, new AsyncCallback(BegRece), client);

    }
}

 

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值