网络编程——wireshark抓包、tcp粘包

目录

一、前言

[1.1 什么是粘包](#1.1 什么是粘包)

[1.2 为什么UDP不会粘包](#1.2 为什么UDP不会粘包)

二、编写程序

文件树

客户端程序

服务器程序

tcp程序

头文件

makefile

[三、 实验现象](#三、 实验现象)

四、改进实验

五、小作业


一、前言

最近在做网络芯片的驱动,验证功能的时候需要借助wireshark这个工具,今天就来回顾下网络编程相关的知识。

1.1 什么是粘包

在网络通信过程中,数据包往往是连续发送的,尤其是在稳定且高速的网络连接中。这种连续传输可以提高数据传输的效率,减少因等待发送或接收数据包而产生的延迟。

在TCP/IP协议中,由于TCP是一个面向连接的、可靠的、基于字节流的传输层通信协议,它不保留消息边界。这意味着在发送端连续发送的多个数据包,在接收端可能会被合并成一个大的数据包接收(粘包),或者一个完整的数据包被拆分成多个小数据包接收(拆包)。

|eth header|IP header|tcp header| data |

12 bytes\] \| 20 bytes\| 20 bytes \|"abc" \| \| data只占4个字节, 而为了发送这4个字节,需要12+20+20,至少52个字节,会造成极大的资源浪费 ### 1.2 为什么UDP不会粘包 1. **独立的传输机制**:由于UDP数据报的独立性,每个数据报都是单独发送和接收的,不会与其他数据报混合在一起。因此,在接收端,每个UDP数据报都可以被清晰地识别和处理,不会出现TCP中可能遇到的粘包问题。 2. **没有面向连接的数据流**:UDP不像TCP那样提供面向连接的数据流服务。TCP为了保证数据的可靠传输,会对数据进行拆分、重排和合并等操作,这些操作可能会导致粘包现象。而UDP则没有这些操作,它直接发送和接收完整的数据报,因此不会出现粘包问题。 3. **基于数据报的传输模式**:UDP的传输模式是基于数据报的,即每个数据报都是一个完整的单元,具有独立的传输路径和生命周期。这种传输模式使得UDP能够避免TCP中可能出现的粘包和拆包问题。 了解了以上概念我们开始验证这个问题。 ## 二、编写程序 ### 文件树 这是我们的目录结构分为服务器和客户端 ![](https://i-blog.csdnimg.cn/direct/20cb65202cf446f3ab75b9183f6934a8.png) ### 客户端程序 ```cpp #include "tcp.h" int main(int argc, char *argv[]) { int fd; int ret, i = 2; char buf[BUFSIZ] = {"===test===\n"}; /*检查参数*/ Argment(argc, argv); fd = SocketInit(argv, false); /*发送数据*/ while(i--){ do { ret = send(fd, buf, strlen(buf), 0); }while(ret < 0 && errno == EINTR); //如果信号导致的错误,继续执行 if(ret < 0) ErrExit("recv"); else if(!ret) break; printf("send data:%s", buf); fflush(stdout); } close(fd); return 0; } ``` 我们进行两次连发看看效果是什么样的 ### 服务器程序 ```cpp #include "tcp.h" int main(int argc, char *argv[]) { int fd, newfd; int ret; char buf[BUFSIZ]; Addr_in client_addr; socklen_t addrlen = sizeof(Addr_in); /*检查参数*/ Argment(argc, argv); /*创建服务端套接字*/ fd = SocketInit(argv, true); /*接收客户端连接*/ do { newfd = accept(fd, (Addr *)&client_addr, &addrlen); }while(newfd < 0 && errno == EINTR); //如果信号导致的错误,继续执行 if(newfd < 0) ErrExit("accept"); /*接收客户端数据*/ while(1){ do { ret = recv(newfd, buf, BUFSIZ, 0); }while(ret < 0 && errno == EINTR); //如果信号导致的错误,继续执行 if(ret < 0) ErrExit("recv"); else if(!ret) break; else printf("[%s:%d]buf:%s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buf); printf("getchar()\n"); getchar(); } close(newfd); close(fd); return 0; } ``` ### tcp程序 ```cpp #include "tcp.h" void Argment(int argc, char *argv[]){ if(argc < 3){ fprintf(stderr, "%s \n", argv[0]); exit(EXIT_FAILURE); } } int SocketInit(char *argv[], bool server){ int fd; Addr_in addr; func_t func = server?bind:connect; /*创建套接字*/ if( (fd = socket(AF_INET, SOCK_STREAM, 0) ) < 0) ErrExit("socket"); /*设置通信结构体*/ bzero(&addr, sizeof(addr) ); addr.sin_family = AF_INET; addr.sin_port = htons( atoi(argv[2]) ); if (inet_aton(argv[1], &addr.sin_addr) == 0) { fprintf(stderr, "Invalid address\n"); exit(EXIT_FAILURE); } /*地址快速重用*/ int b_reuse = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int) ); /*发起连接请求或绑定地址*/ if( func(fd, (Addr *)&addr, sizeof(addr) ) ) ErrExit("connect or bind"); if(server){ /*监听模式*/ if( listen(fd, BACKLOG) ) ErrExit("listen"); } return fd; } ``` ### 头文件 ```cpp #ifndef _TCP_H_ #define _TCP_H_ #include #include #include #include #include #include #include #include #include #include #include #define BACKLOG 5 #define ErrExit(msg) do { perror(msg); \ exit(EXIT_FAILURE); } while(0) typedef struct sockaddr Addr; typedef struct sockaddr_in Addr_in; typedef int (* func_t)(int, const Addr *, socklen_t); void Argment(int argc, char *argv[]); int SocketInit(char *argv[], bool server); #endif ``` ### makefile ```cpp all:server client CC=gcc CFLAGS=-g -Wall server:tcp.c server.c client:tcp.c client.c clean: rm server client ``` ## 三、 实验现象 ![](https://i-blog.csdnimg.cn/direct/36fa7f7172dd4498a64130a854d81dc3.png) ![](https://i-blog.csdnimg.cn/direct/216848ff90c04b12be4c12fd52423f76.png) 在ubuntu22.04和ubuntu18.04上都能实现。 ![](https://i-blog.csdnimg.cn/direct/3aa4c3d69b744d53ae1fd4e3d56d5447.png) 但是发现有个问题 ![](https://i-blog.csdnimg.cn/direct/ed9afadce5094d7b957f26c1e1ab5d85.png) 还有发两次的现象 ![](https://i-blog.csdnimg.cn/direct/70f59984989b490a8b4f823b396b43f6.jpeg) ## 四、改进实验 ![](https://i-blog.csdnimg.cn/direct/553778db21fc474fa46ca82cde914177.png) 先ping下百度,看看百度的ip是多少 ```cpp #include #include #include #include #include #include #include int main(int argc, const char *argv[]) { int fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in sin = { .sin_family = AF_INET, .sin_port = htons(80), }; if (inet_aton("110.242.68.66", &sin.sin_addr) == 0) { fprintf(stderr, "Invalid address\n"); exit(EXIT_FAILURE); } if(connect(fd, (struct sockaddr *)&sin, sizeof(sin) ) < 0) { perror("connect"); exit(0); } send(fd, "hello", 5, 0); send(fd, "hello", 5, 0); send(fd, "hello", 5, 0); send(fd, "hello", 5, 0); send(fd, "hello", 5, 0); send(fd, "hello", 5, 0); close(fd); return 0; } ``` 写完程序后运行wireshark ![](https://i-blog.csdnimg.cn/direct/12fba23f1ac84a4e8ab4e11770243843.png) 选择上互联网用的网卡 ![](https://i-blog.csdnimg.cn/direct/948a77ee3c3b465980827e930eb09bd4.png) 我们会发现6个hello被划分到了两个包里一个1个hello另一个5个,但是我们用了6次send正常应该6个包的,这就是粘包现象。 ![](https://i-blog.csdnimg.cn/direct/f3afad3bd43242ce83af004be68b9882.png) ![](https://i-blog.csdnimg.cn/direct/04c60ee9fcdb4264ae36c75c392787e1.png) 注意:PSH代表有数据包,FIN代表没有数据包了 ## 五、小作业 兄弟们可以试试分包现象的验证,搞一个大的包看看是不是会被分开。

相关推荐
上海云盾第一敬业销售19 分钟前
高防IP是怎么防御的?高防IP的防御步骤又有哪些?
运维·网络·安全
magic 24521 分钟前
@RequestHeader(“Authorization“) 解析:HTTP 请求头中的 Authorization 字段
网络·网络协议·http
llwszx35 分钟前
“从HTTP到TCP/IP的旅程“-----深入浅出Java Web通信
java·websocket·网络协议·tcp/ip·spring·http
涟漪海洋10 小时前
基于Netty的UDPServer端和Client端解决正向隔离网闸数据透传问题
网络
小疆智控12 小时前
从离散控制到集成管理:Modbus TCP转CANopen网关重构烟丝膨胀生产线
网络协议·tcp/ip·重构
国际云,接待12 小时前
微软云注册被阻止怎么解决?
服务器·网络·microsoft·云原生·微软·云计算
创小匠12 小时前
创客匠人:AI重构知识IP定位与变现效率新范式
人工智能·tcp/ip·重构
laocooon52385788612 小时前
基于Python的TCP应用案例,包含**服务器端**和**客户端**的完整代码
网络·python·tcp/ip
Blossom.11814 小时前
基于区块链的去中心化身份验证系统:原理、实现与应用
运维·服务器·网络·人工智能·机器学习·去中心化·区块链
SZ17011023114 小时前
HTTP 请求报文 方法
网络·网络协议·http