tcp遇到黏包怎么解决?

**@tcp遇到黏包怎么解决?

这段时间复习下网络协议相关的知识, 在网上偶然看到tcp黏包的问题,就好奇地自己就实验了下,总结了下面几点经验,写的不对的还请指正:

一: 什么情况下tcp会发生黏包现象?

  1. 发送数据方多次send之间的间隔太短。tcp为了提高系统效率,使用了Nagle算法,假如多次短时间内使用send函数发送数据, Nagle会先将这几次send的数据存在内核的缓冲区,等缓冲区满了后或者超时(200ms)就会发送出去,这样就减少了发送的次数,提升了效率。
  2. 网路环境不好时,数据没有发送出去,就会全部堆积在缓冲区,等网络好后,就会一股脑的全部发送出去,这也会导致黏包。
  3. 接受数据方处理不当。tcp中接受方会将接受到的数据先放到缓冲区,然后应用程序再读取缓冲区的数据进行操作。假如缓冲区已经有个两个大小各为32个字节的数据,而此时直接读取64字节的数据的话,必然会造成黏包的。

二 : 怎么避免黏包问题?

  1. 关闭Nagle算法, 使用TCP_NODELAY选项:
int enable = 1;
setsockopt(sockfd,IPROTO_TCP,TCP_NODELAY(void*)&enable,sizeof(enable));
但我不建议这种做法,如果要进行文件传输,Nagle算法能大大提高效率。
  1. 尽量避免两次send时间过短,使用sleep或者usleep进行延时。但这也会影响程序运行效率,所以也不建议。

上面两种方法都是在数据发送时避免黏包,但假如网路不好或者接受处理不当还是会黏包,所以我推荐使用下面的方法:

  1. 格式化数据,每次发送的都是固定长度,每次接收的也是固定长度。
    发送方:
char buf1[32] = "aaa";
char buf2[32] = "bbb";

send(sockfd, buf1, sizeof(buf1), 0); //每次固定发送32个字节
send(sockfd, buf2, sizeof(buf1), 0);

数组buf1里保存的数据长度只有3个字节,正常来说应该发送strlen(buf1) ,既3个字节,但因为此时连续发送了send了两次, 所以改为发送数组buf1长度,既32个字节。
接受方:

char buf[32] = {0};
ret = recv(fd, buf, sizeof(buf), 0);  //每次接收固定长度32个字节

也可以将数据放再结构体里面。收发双发都约定好用相同大小的结构体或数组收发数据,这样就会避免黏包。

  1. 如果发送的数据不能固定长度,那每次发送数据时可以在数据前面加上这条数据的长度。例如规定数据的前四位为数据的长度length,接收方每次先接收四个字节,这四个字节中保存的就是应该接收的数据长度length,然后再次接收时就接收length个字节,这样也可以避免黏包。
    发送方:
			char buf[1024] = {0};
 73         char send_msg[] = "aaa"; //需要发送的数据
 74         int length = strlen(send_msg);
 75 
 76         sprintf(buf, "%4d%s", length, send_msg);//length转化为字符串,并让length占4个字节
 79         send(sockfd, buf, strlen(buf), 0);

接收方:

 		char buf[1024] = {0};
 66     while(1)
 67     {
 68         //从TCP连接读取数据
 70      	ret = recv(fd, buf, 4, 0);  //接收4个字节的长度信息
 71         if(-1 == ret)
 72         {
 73             perror("recv1");
 74         }
 75         else if(0 == ret)
 76         {
 77             printf("客户端退出\n");
 78             break;
 79         }
 
 81         printf("buf_length = %s\n", buf);
 82         int length = atoi(buf); //字符串转化为整数
 83         printf("length = %d\n", length);
 84         memset(buf, 0, sizeof(buf));
 85 
 86         ret = recv(fd, buf, length, 0); //接收长度为length字节的数据信息
 87         if(-1 == ret)
 88         {
 				perror("recv2");
 89         }
 91         else if(0 == ret)
 92         {
 93             printf("客户端退出\n");
 94             break;
 95         }
 96         printf("buf_msg = %s\n", buf);
 			memset(buf, 0, sizeof(buf));
 		}			

打印结果:

等待客户端的连接....
接受客户端的连接fd = 4
buf_length =    3
length = 3
buf_msg = aaa
客户端退出

参考资料:
1.:https://baike.baidu.com/item/Nagle%E7%AE%97%E6%B3%95/5645172?fr=aladdin
2.https://blog.csdn.net/weixin_41047704/article/details/85340311

### Netty 框架使用教程与开发指南 Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的网络服务器和客户端。以下是关于 Netty 的一些核心概念以及如何入门学习的相关资源。 #### 一、Netty 基础概述 Netty 提供了一个抽象层来处理底层 NIO 复杂性[^1]。它不仅简化了 TCP 和 UDP socket 应用程序的开发流程,还通过其高效的线程模型支持高并发场景下的性能优化[^2]。对于初学者来说,理解 Reactor 线程模型是非常重要的,因为这是 Netty 实现高效 I/O 的基础[^3]。 #### 二、环境搭建与基本配置 要开始使用 Netty 进行项目开发,首先需要引入依赖项。如果是 Maven 工程,则可以在 `pom.xml` 文件中添加如下依赖: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.78.Final</version> </dependency> ``` 接着可以通过创建简单的 Client 或 Server 来熟悉 API 接口。例如下面是一个典型的 EchoServer 示例: ##### EchoServer.java ```java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new EchoServerInitializer()); ChannelFuture f = b.bind(port).sync(); System.out.println("Echo server started at port " + port); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new EchoServer(9090).start(); } } ``` 以上代码展示了如何启动一个监听指定端口的服务端实例,并设置初始化处理器链路[^4]。 #### 三、解决常见问题——TCP /分现象 当涉及到数据传输时,可能会遇到或者分的情况。Netty 提供了几种内置解码器帮助开发者轻松应对这些问题,比如 `DelimiterBasedFrameDecoder` 和 `FixedLengthFrameDecoder`。这些工具能够自动解析接收到的数据帧并将其分割成独立的消息单元,从而减少手动编码的工作量。 #### 四、高级功能扩展 随着需求的增长,可能还需要集成序列化机制或其他第三方库的支持。例如,在 Android 平台上利用 Protobuf 对象作为通信载体是一种常见的做法[^5]。此时就需要自定义编解码器以适配特定的应用场景。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值