前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【游戏开发】网络编程之浅谈TCP粘包、拆包问题及其解决方案

【游戏开发】网络编程之浅谈TCP粘包、拆包问题及其解决方案

作者头像
马三小伙儿
发布于 2019-05-15 03:05:46
发布于 2019-05-15 03:05:46
1.1K00
代码可运行
举报
运行总次数:0
代码可运行

引子

现如今手游开发中网络编程是必不可少的重要一环,如果使用的是TCP协议的话,那么不可避免的就会遇见TCP粘包和拆包的问题,马三觉得haifeiWu博主的 TCP 粘包问题浅析及其解决方案 这篇博客讲得很不错,因此转载过来并稍作修改与大家分享,也留作自己时常温习和查阅,文章的版权归haifeiWu博主所有。

作者: haifeiWu

出处: http://www.hchstudio.cn/

关于作者:专注大后端,分布式,高并发等领域,请多多赐教!

原文链接:https://www.cnblogs.com/haifeiWu/p/9358499.html

TCP协议的简单介绍

TCP是面向连接的运输层协议

简单来说,在使用TCP协议之前,必须先建立TCP连接,就是我们常说的三次握手。在数据传输完毕之后,必须是释放已经建立的TCP连接,否则会发生不可预知的问题,造成服务的不可用状态。

每一条TCP连接都是可靠连接,且只有两个端点

TCP连接是从Server端到Client端的点对点的,通过TCP传输数据,无差错,不重复不丢失。

TCP协议的通信是全双工的

TCP协议允许通信双方的应用程序在任何时候都能发送数据。TCP 连接的两端都设有发送缓冲区和接收缓冲区,用来临时存放双向通信的数据。发送数据时,应用程序把数据传送给TCP的缓冲后,就可以做自己的事情,而TCP在合适的时候将数据发送出去。在接收的时候,TCP把收到的数据放入接收缓冲区,上层应用在合适的时候读取数据。

TCP协议是面向字节流的

TCP中的流是指流入进程或者从进程中流出的字节序列。所以向Java,golang等高级语言在进行TCP通信是都需要将相应的实体序列化才能进行传输。还有就是在我们使用Redis做缓存的时候,都需要将放入Redis的数据序列化才可以,原因就是Redis底层就是实现的TCP协议。

TCP并不知道所传输的字节流的含义,TCP并不能保证接收方应用程序和发送方应用程序所发出的数据块具有对应大小的关系(这就是TCP传输过程中产生的粘包问题)。但是应用程序接收方最终受到的字节流与发送方发送的字节流是一定相同的。因此,我们在使用TCP协议的时候应该制定合理的粘包拆包策略。

下图是TCP的协议传输的整个过程:

下面这个图是从老钱的博客里面取到的,非常生动

TCP粘包问题复现

理论推敲

如下图所示,出现的粘包问题一共有三种情况

第一种情况: 如上图中的第一根bar所示,服务端一共读到两个数据包,每个数据包都是完成的,并没有发生粘包的问题,这种情况比较好处理,服务器只需要简单的从网络缓冲区去读就好了,每次服务端读取到的消息都是完成的,并不会出现数据不正确的情况。

第二种情况: 服务端仅收到一个数据包,这个数据包包含客户端发出的两条消息的完整信息,这个时候基于第一种情况的逻辑实现的服务端就蒙了,因为服务端并不能很好的处理这个数据包,甚至不能处理,这种情况其实就是TCP的粘包问题。

第三种情况: 服务端收到了两个数据包,第一个数据包只包含了第一条消息的一部分,第一条消息的后半部分和第二条消息都在第二个数据包中,或者是第一个数据包包含了第一条消息的完整信息和第二条消息的一部分信息,第二个数据包包含了第二条消息的剩下部分,这种情况其实是发送了TCP拆包问题,因为发生了一条消息被拆分在两个包里面发送了,同样上面的服务器逻辑对于这种情况是不好处理的。

为什么会发生TCP粘包、拆包

  1. 应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。
  2. 应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。
  3. 进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包。
  4. 接收方法不及时读取套接字缓冲区数据,这将发生粘包。

如何处理粘包、拆包

通常会有以下一些常用的方法:

  1. 使用带消息头的协议、消息头存储消息开始标识及消息长度信息,服务端获取消息头的时候解析出消息长度,然后向后读取该长度的内容。
  2. 设置定长消息,服务端每次读取既定长度的内容作为一条完整消息,当消息不够长时,空位补上固定字符。
  3. 设置消息边界,服务端从网络流中按消息编辑分离出消息内容,一般使用‘\n’。
  4. 更为复杂的协议,例如楼主最近接触比较多的车联网协议808,809协议。

TCP粘包拆包的代码实践

下面代码楼主主要演示了使用规定消息头,消息体的方式来解决TCP的粘包,拆包问题。

server端代码: server端代码的主要逻辑是接收客户端发送过来的消息,重新组装出消息,并打印出来。

代码语言:javascript
代码运行次数:0
运行
复制
import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author wuhf
 * @Date 2018/7/16 15:50
 **/
public class TestSocketServer {
    public static void main(String args[]) {
        ServerSocket serverSocket;
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8089));
            while (true) {
                Socket socket = serverSocket.accept();
                new ReceiveThread(socket).start();

            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    static class ReceiveThread extends Thread {
        public static final int PACKET_HEAD_LENGTH = 2;//包头长度
        private Socket socket;
        private volatile byte[] bytes = new byte[0];

        public ReceiveThread(Socket socket) {
            this.socket = socket;
        }

        public byte[] mergebyte(byte[] a, byte[] b, int begin, int end) {
            byte[] add = new byte[a.length + end - begin];
            int i = 0;
            for (i = 0; i < a.length; i++) {
                add[i] = a[i];
            }
            for (int k = begin; k < end; k++, i++) {
                add[i] = b[k];
            }
            return add;
        }

        @Override
        public void run() {
            int count = 0;
            while (true) {
                try {
                    InputStream reader = socket.getInputStream();
                    if (bytes.length < PACKET_HEAD_LENGTH) {
                        byte[] head = new byte[PACKET_HEAD_LENGTH - bytes.length];
                        int couter = reader.read(head);
                        if (couter < 0) {
                            continue;
                        }
                        bytes = mergebyte(bytes, head, 0, couter);
                        if (couter < PACKET_HEAD_LENGTH) {
                            continue;
                        }
                    }
                    // 下面这个值请注意,一定要取2长度的字节子数组作为报文长度,你懂得
                    byte[] temp = new byte[0];
                    temp = mergebyte(temp, bytes, 0, PACKET_HEAD_LENGTH);
                    String templength = new String(temp);
                    int bodylength = Integer.parseInt(templength);//包体长度
                    if (bytes.length - PACKET_HEAD_LENGTH < bodylength) {//不够一个包
                        byte[] body = new byte[bodylength + PACKET_HEAD_LENGTH - bytes.length];//剩下应该读的字节(凑一个包)
                        int couter = reader.read(body);
                        if (couter < 0) {
                            continue;
                        }
                        bytes = mergebyte(bytes, body, 0, couter);
                        if (couter < body.length) {
                            continue;
                        }
                    }
                    byte[] body = new byte[0];
                    body = mergebyte(body, bytes, PACKET_HEAD_LENGTH, bytes.length);
                    count++;
                    System.out.println("server receive body:  " + count + new String(body));
                    bytes = new byte[0];
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

client端代码:客户端代码主要逻辑是组装要发送的消息,确定消息头,消息体,然后发送到服务端。

代码语言:javascript
代码运行次数:0
运行
复制
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * @author wuhf
 * @Date 2018/7/16 15:45
 **/
public class TestSocketClient {
    public static void main(String args[]) throws IOException {
        Socket clientSocket = new Socket();
        clientSocket.connect(new InetSocketAddress(8089));
        new SendThread(clientSocket).start();

    }

    static class SendThread extends Thread {
        Socket socket;
        PrintWriter printWriter = null;

        public SendThread(Socket socket) {
            this.socket = socket;
            try {
                printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            String reqMessage = "HelloWorld! from clientsocket this is test half packages!";
            for (int i = 0; i < 100; i++) {
                sendPacket(reqMessage);
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

        }

        public void sendPacket(String message) {
            try {
                OutputStream writer = socket.getOutputStream();
                writer.write(message.getBytes());
                writer.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

小结

最近一直在写一些框架性的博客,专门针对某些问题进行原理性的技术探讨的博客还比较少,所以楼主想着怎样能在自己学到东西的同时也可以给一同在技术这条野路子上奋斗的小伙伴们一些启发,是楼主一直努力的方向。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-05-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
TCP 粘包问题浅析及其解决方案
最近一直在做中间件相关的东西,所以接触到的各种协议比较多,总的来说有TCP,UDP,HTTP等各种网络传输协议,因此楼主想先从协议最基本的TCP粘包问题搞起,把计算机网络这部分基础夯实一下。
haifeiWu
2018/09/11
2.6K0
TCP 粘包问题浅析及其解决方案
Socket粘包问题的3种解决方案,最后一种最完美!
在 Java 语言中,传统的 Socket 编程分为两种实现方式,这两种实现方式也对应着两种不同的传输层协议:TCP 协议和 UDP 协议,但作为互联网中最常用的传输层协议 TCP,在使用时却会导致粘包和半包问题,于是为了彻底的解决此问题,便诞生了此篇文章。
磊哥
2021/01/06
1.4K0
Netty与TCP粘包拆包
TCP协议是个流协议,所谓流,就是指没有界限的一串数据。河里的流水,是连成一片的,没有分界线。TCP底层并不了解上层业务数据的具体意义,他会根据TCP缓冲区的实际情况进行包的划分,所以在业务上一个完整的包,有可能会被TCP拆分为多个包进行发送,也有可能把业务上多个小包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
黑洞代码
2021/01/14
1K0
Netty与TCP粘包拆包
面试突击70:什么是粘包和半包?怎么解决?
粘包和半包问题是数据传输中比较常见的问题,所谓的粘包问题是指数据在传输时,在一条消息中读取到了另一条消息的部分数据,这种现象就叫做粘包。 比如发送了两条消息,分别为“ABC”和“DEF”,那么正常情况下接收端也应该收到两条消息“ABC”和“DEF”,但接收端却收到的是“ABCD”,像这种情况就叫做粘包,如下图所示:
磊哥
2022/09/20
4030
面试突击70:什么是粘包和半包?怎么解决?
Java网络编程——粘包拆包出现的原因及解决方式
先来看个例子,还是上篇文章 《Java网络编程——NIO的阻塞IO模式、非阻塞IO模式、IO多路复用模式的使用》 中“IO多路复用模式”一节中的代码: 服务端
DannyHoo
2022/08/07
1.4K0
Java网络编程——粘包拆包出现的原因及解决方式
Netty 是如何解决 TCP 粘包拆包的?
那么数据在通信层传播其实就像河水一样并没有明显的分界线,而数据具体表示什么意思什么地方有句号什么地方有分号这个对于TCP底层来说并不清楚。应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段,之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。
Java技术栈
2021/07/16
8490
Netty 是如何解决 TCP 粘包拆包的?
Netty TCP解决粘包拆包
TCP(Transmission Control Protocol)是一种在计算机网络中广泛使用的协议,用于可靠的、面向连接的数据通信。
Jensen_97
2023/10/28
5720
Netty TCP解决粘包拆包
Netty中粘包和拆包的解决方案
粘包和拆包是TCP网络编程中不可避免的,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包问题。
CodingDiray
2019/09/29
7970
Netty中粘包和拆包的解决方案
什么是TCP粘包、拆包
【玩转 GPU】AI绘画、AI文本、AI翻译、GPU点亮AI想象空间-腾讯云开发者社区-腾讯云 (tencent.com)
疯狂的KK
2023/07/04
1.2K0
什么是TCP粘包、拆包
TCP粘包问题与解决方案详解及Java代码演示
TCP(Transmission Control Protocol)是一种可靠的、面向连接的传输层协议,用于在网络上可靠地传输数据。然而,在实际应用中,TCP协议可能会遇到粘包问题,这是由于TCP协议特性导致的,而不是协议本身的缺陷。本文将详细讲解TCP粘包问题的原因、常见解决方案,并通过Java代码演示一种解决方案。
GeekLiHua
2025/01/21
1730
20-Netty TCP 粘包和拆包及解决方案
假设客户端分别发送了两个数据包D1和D2给服务端, 由于服务端一次读取到字节数是不确定的,故有可能存在以下四种情况
彼岸舞
2022/02/18
6590
20-Netty TCP 粘包和拆包及解决方案
Netty解决TCP粘包/拆包的问题
什么是TCP粘包/拆包   首先要明确, 粘包问题中的 “包”, 是指应用层的数据包.在TCP的协议头中, 没有如同UDP一样的 “报文长度” 字段,但是有一个序号字段.   站在传输层的角度, T
用户4919348
2019/04/19
1.1K0
Netty解决TCP粘包/拆包的问题
粘包和拆包及Netty解决方案
在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接。由于微服务往对方发送信息的时候,所有的请求都是使用的同一个连接,这样就会产生粘包和拆包的问题。本文首先会对粘包和拆包问题进行描述,然后介绍其常用的解决方案,最后会对Netty提供的几种解决方案进行讲解。
BUG弄潮儿
2021/01/05
2.2K0
Netty 粘包和拆包问题及解决方案
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情
鳄鱼儿
2024/05/22
3060
Netty 粘包和拆包问题及解决方案
网络编程之粘包问题
粘包是一种现象 这种现象只出现在TCP中而不会出现在UDP中(TCP和UDP都是传输层中的协议)
全栈程序员站长
2022/07/21
5200
网络编程之粘包问题
Java 网络编程深度解析:从 Socket 到多线程通信实战
在现代互联网应用中,几乎所有服务都离不开网络通信。Java 提供了功能强大的网络编程 API,支持 TCP、UDP 等多种协议,可广泛应用于:
用户11690571
2025/06/08
910
C++网络编程:TCP粘包和分包的原因分析和解决
在学习粘包之前,先纠正一下读音,很多视频教程中将“粘”读作“nián”。经过调研,个人更倾向于读“zhān bāo”。
嵌入式Linux内核
2022/10/22
3.1K0
C++网络编程:TCP粘包和分包的原因分析和解决
Netty中数据包的拆分粘包处理方案,以及对protobuf协议中的拆包粘包方案自定义重写
TCP是个“流”协议,所谓流,就是没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
小勇DW3
2020/04/26
1.8K0
Netty中数据包的拆分粘包处理方案,以及对protobuf协议中的拆包粘包方案自定义重写
Netty中粘包/拆包处理
为突出 Netty 的粘包/拆包问题,这里通过例子进行重现问题,以下为突出问题的主要代码:
Bug开发工程师
2019/12/27
2.1K0
Netty中粘包/拆包处理
socket网络编程(五)——粘包拆包问题
假设一个这样的场景,客户端要利用send()函数发送字符“asd”到服务端,连续发送3次,但是服务端休眠10秒之后再去缓冲池中接收。那么请问10秒之后服务端从缓冲区接收到的信息是“asd”还是“asdasdasd”呢?如果大家有去做实验的话,可以知道服务端收到的是“asdasdasd”,为什么会这样呢?按正常的话,服务端收到的应该是“asd”,剩下的两个asd要不就是收不到要不就是下次循环收到,怎么会一次性收到“asdasdasd”呢?如果要说罪魁祸首的话就是那个休眠10秒,导致数据粘包了!
一点sir
2024/01/10
3620
socket网络编程(五)——粘包拆包问题
相关推荐
TCP 粘包问题浅析及其解决方案
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档