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

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

二、编写程序

文件树

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

客户端程序

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

头文件

cpp 复制代码
#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

cpp 复制代码
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是多少

cpp 复制代码
#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代表没有数据包了

五、小作业

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

相关推荐
Hacker_xingchen36 分钟前
一些常见网络安全术语
网络·安全·web安全
夏天匆匆2过1 小时前
网络传输:网卡、IP、网关、子网掩码、MAC、ARP、路由器、NAT、交换机
linux·网络·网络协议·tcp/ip
chirrupy_hamal1 小时前
网络基础 - NAT 篇
网络
网络安全queen1 小时前
网络安全等级测评师
网络·数据库·学习·安全·web安全
solomonzw1 小时前
举例矢量路由协议-RIP
运维·服务器·网络·华为·智能路由器·github
Ljw...2 小时前
网络基础Linux
linux·网络·网络协议
网络安全queen2 小时前
网络安全与CTF在线学习资源网站
网络·安全·web安全·网络安全
fantasy_arch3 小时前
SRT拥塞控制分析
开发语言·网络·php
newxtc3 小时前
【天壤智能-注册安全分析报告-无验证纯IP限制存在误拦截隐患】
人工智能·tcp/ip·安全·网易易盾·ai写作·极验
Dklau-c4 小时前
探索Linux内核中的Runqueue:从O(n)到O(1)的演进与负载均衡应用
linux·服务器·网络·负载均衡