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

目录

一、前言

1.1 什么是粘包

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中可能出现的粘包和拆包问题。

了解了以上概念我们开始验证这个问题。

二、编写程序

文件树

这是我们的目录结构分为服务器和客户端 

客户端程序

#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;
}

我们进行两次连发看看效果是什么样的 

服务器程序 

#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程序

#include "tcp.h"

void Argment(int argc, char *argv[]){
	if(argc < 3){
		fprintf(stderr, "%s <addr><port>\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;
}

头文件 

#ifndef _TCP_H_
#define _TCP_H_

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>

#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


all:server client
CC=gcc
CFLAGS=-g -Wall

server:tcp.c server.c

client:tcp.c client.c

clean:
	rm server client

三、 实验现象 

在ubuntu22.04和ubuntu18.04上都能实现。 

但是发现有个问题

还有发两次的现象

四、改进实验

 先ping下百度,看看百度的ip是多少

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>

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

选择上互联网用的网卡 

 

       我们会发现6个hello被划分到了两个包里一个1个hello另一个5个,但是我们用了6次send正常应该6个包的,这就是粘包现象。 

注意:PSH代表有数据包,FIN代表没有数据包了

五、小作业

兄弟们可以试试分包现象的验证,搞一个大的包看看是不是会被分开。

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇努力学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值