测试Linux下socket每次发送数据和接受数据的最大值

本文探讨了客户端和服务端在网络编程中的一次性数据传输上限问题,并通过实验确定了本机的最大发送和接收数据量分别为384KB和64KB。此外,还提供了C语言实现的客户端和服务端代码示例。

经试验发现,客户端一次发送数据大小有个上线,本机试验数据最大是384KB,超出最大值的时候,客户端会阻塞到发送语句哪里等待服务端接受数据。服务多也是一样,有一个最大的一次性接受数据的上线,本机试验最大接受数据是64KB,但是这个服务端好像不确定具体接受多少

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

#define MAXBUF 1024 * 1024

void* recvsocket(void *arg)
{
	int st = *(int *)arg;
	char s[1024];

	while (1)
	{
		memset(s, 0, sizeof(s));
		int rc = recv(st, s, sizeof(s), 0);
		if (rc <= 0)
			break;
		printf("%s\n", s);
	}
	return NULL;
}

void* sendsocket(void *arg)
{
	int st = *(int *)arg;
	char s[1024];
	char *p = malloc(MAXBUF);//1M

	while (1)
	{
		memset(s, 0, sizeof(s));
		read(STDIN_FILENO, s, sizeof(s));
		int rc = send(st, p, MAXBUF, 0);
		printf("rc = %d\n", rc);
	}

	return NULL;
}


int main(int arg, char *args[])
{
	if (arg < 3)
	{
		return -1;
	}

	int port = atoi(args[2]);
	int st = socket(AF_INET, SOCK_STREAM, 0);//初始化socket

	struct sockaddr_in addr; // 定义一个IP地址的结构
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;// 设置结构地址类型为TCP/IP地址
	addr.sin_port = htons(port); // 制定一个端口号:8080,htons:将short类型从host字节类型转到net字节类型
	// 将字符类型的IP地址转化为int,赋给addr结构
	//addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	addr.sin_addr.s_addr = inet_addr(args[1]);

	//
	if (connect(st, (struct sockaddr *)&addr, sizeof(addr)) == -1)
	{
		printf("connect failed %s\n", strerror(errno));
		return EXIT_FAILURE;
	}

	pthread_t thrd1, thrd2;

	pthread_create(&thrd1, NULL, recvsocket, &st);
	pthread_create(&thrd2, NULL, sendsocket, &st);
	pthread_join(thrd1, NULL);
	//pthread_join(thrd2, NULL);// 加上这一句话会导致线程如法退出,主线程会一直等待子线程,子线程一直是退步出来的

	close(st);
	return EXIT_SUCCESS;
}


#define MAXBUF 1024 * 128

struct ps
{
	int st;
	pthread_t *thr;
};

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int status = 0;

void* recvsocket(void *arg)
{
	struct ps *p = (struct ps *)arg;

	int st = p->st;
	char s[1024];
	char *pp = malloc(MAXBUF);
	while (1)
	{
		char buf[100];
		read(STDIN_FILENO, buf, sizeof(buf));
		memset(s, 0, sizeof(s));
		int rc = recv(st, pp, MAXBUF, 0);
		printf("rc = %d\n", rc);
		if (rc <= 0)//如果recv返回小于等于0,代表socket已经关闭或者出错了
			break;
		//printf("%s\n", s);
	}
	pthread_mutex_lock(&mutex);
	status = 0;
	pthread_mutex_unlock(&mutex);
	pthread_cancel(*(p->thr));
	return NULL;
}

void* sendsocket(void *arg)
{
	int st = *(int *)arg;
	char s[1024];

	while (1)
	{
		memset(s, 0, sizeof(s));
		read(STDIN_FILENO, s, sizeof(s));
		send(st, s, strlen(s), 0);
	}

	return NULL;
}


int main(int arg, char *args[])
{
	if (arg < 2)
	{
		return -1;
	}
	int port = atoi(args[1]);
	int st = socket(AF_INET, SOCK_STREAM, 0); //初始化socket

	int on = 1;
	if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
	{
		printf("setsockopt failed %s\n", strerror(errno));
		return EXIT_FAILURE;
	}

	struct sockaddr_in addr; // 定义一个IP地址结构
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET; // 将addr结构的属性定位为TCP/IP地址
	addr.sin_port = htons(port); // 将本地字节顺序转化为网络字节顺序
	addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY代表这个server上所有的地址

	// 将ip与server程序绑定
	if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1)
	{
		printf("bind failed %s\n", strerror(errno));
		return EXIT_FAILURE;
	}

	// server端开始listen
	if (listen(st, 20) == -1)
	{
		printf("listen failed %s\n", strerror(errno));
		return EXIT_FAILURE;
	}
	//printf("listen success\n");
	int client_st = 0;
	struct sockaddr_in client_addr; // 表示client端的IP地址

	pthread_t thrd1, thrd2;
	while (1)
	{
		memset(&client_addr, 0, sizeof(client_addr));
		socklen_t len = sizeof(client_addr);
		client_st = accept(st, (struct sockaddr*) &client_addr, &len);

		pthread_mutex_lock(&mutex);
		status++;
		pthread_mutex_unlock(&mutex);
		if (status > 1)
		{
			close(client_st);
			continue;
		}
		if (client_st == -1)
		{
			printf("accept failed %s\n", strerror(errno));
			return EXIT_FAILURE;
		}
		printf("accept by %s\n", inet_ntoa(client_addr.sin_addr));
		struct ps ps1;
		ps1.st = client_st;
		ps1.thr = &thrd2;
		pthread_create(&thrd1, NULL, recvsocket, &ps1);
		pthread_detach(thrd1);//设置线程为可分离
		//pthread_create(&thrd2, NULL, sendsocket, &client_st);
		//pthread_detach(thrd2);//设置线程为可分离
	}
	close(st);
	return EXIT_SUCCESS;
}



<think>首先,用户的问题是关于Linux sendto函数的详细解释,并确认在使用UDP协议时,sendto函数每次发送数据的长度上限是否为64KB。我需要参考提供的引用来回答。 关键引用: - 引用[1]:提到UDP协议首部有一个16位的最大长度,所以一个UDP数据最大长度是64KB(包含UDP首部)。但64KB在互联网中是小的,如果需要传输超过64KB的数据,需要在应用层手动分包。 - 引用[2]:讨论了UDP套接口的发送缓冲区大小。它表示UDP套接口有发送缓冲区大小(通过SO_SNDBUF设置),但这只是写入套接口的UDP数据报的大小上限。如果应用程序写入的数据报大于套接口发送缓冲区大小,内核会返回EMSGSIZE错误。UDP不需要真正的发送缓冲区,因为数据发送后会被丢弃。 - 引用[3]:解释了socket()函数,其中type参数可以是SOCK_DGRAM用于UDP。 - 引用[4]:提到在UDP中使用SOCK_DGRAM。 - 引用[5]:详细解释了UDP数据报的最大长度。IP报文最大长度是65535字节(16位),减去IP头(20字节)UDP头(8字节),UDP数据部分最大是65535 - 20 - 8 = 65507字节。 从这些引用中,我可以总结: - UDP数据报的最大理论长度是65535字节(64KB),但这包括UDP首部(8字节)。所以,实际可发送数据部分最大是65535 - 8 = 65527字节(对于IPv4),但引用[5]说65507字节,因为IP头最小是20字节。 计算: - IP报文最大大小:65535字节。 - IP头最小大小:20字节(对于IPv4)。 - UDP头大小:8字节。 所以,UDP数据部分最大 = 65535 - 20 - 8 = 65507字节。 用户问的是"每次发送数据的长度上限",指的是数据部分(payload),不包括头。 现在,关于sendto函数: - sendto函数用于发送数据到指定的地址。 - 对于UDP,使用SOCK_DGRAM类型。 - 引用[2]提到,如果写入的数据报大于套接口的发送缓冲区大小,会返回EMSGSIZE错误。 套接口的发送缓冲区大小可以通过SO_SNDBUF选项设置。默认情况下,这个大小可能较小,比如8192字节或类似,但可以设置得更大。 但是,最大数据报大小受协议限制是65507字节(对于IPv4)。 所以,上限取决于两个因素: 1. 协议限制:UDP数据最大数据部分为65507字节。 2. 系统限制:套接口的发送缓冲区大小(SO_SNDBUF)。如果SO_SNDBUF小于要发送的大小,sendto会失败并返回EMSGSIZE。 此外,引用[5]支持65507字节作为最大数据大小。 引用[1]提到64KB包括首部,所以数据部分应该小于64KB。 64KB = 65536字节。 IP最大65535字节。 所以,UDP数据最大65535字节,包括头。 数据部分:65535 - 8 = 65527字节?但引用[5]说65507,因为IP头20字节。 在IPv4,IP头最小20字节,但可以有选项,所以最大IP头可能更大,但通常考虑最小20字节。 实践中,路径MTU(最大传输单元)也可能限制大小,因为IP需要分片。但UDP本身没有内置分片;IP层处理分片。 对于sendto函数,当发送UDP数据报时,最大的长度上限是min(SO_SNDBUF, 65507) 或者类似,但SO_SNDBUF可以设置为更大,但协议限制不能超过65507。 在Linux中,你可以尝试发送更大的数据,但sendto会失败。 现在,用户问详细解释sendto函数。 我需要提供sendto函数的详细解释,包括参数、用法等,基于引用。 引用[3]解释了socket(),但未详细说sendto。引用[2]提到recvfromsendto。 从一般知识: sendto原型:`ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);` - sockfd: socket文件描述符 - buf: 指向要发送数据的缓冲区 - len: 数据的长度 - flags: 可选标志 - dest_addr: 指向目标地址结构 - addrlen: 地址长度 对于UDP,dest_addr指定目标地址。 在UDP中,sendto发送一个数据报。 len参数是数据部分的大小。 最大len不能超过65507字节(对于IPv4),并且受SO_SNDBUF限制。 SO_SNDBUF是套接口发送缓冲区大小。对于UDP,它控制单个数据报的最大大小,因为UDP没有流控制。 引用[2]说:"如果应用程序写一个大于套接口发送缓冲区大小的数据报,内核将返回一个EMSGSIZE错误。" 所以,上限是SO_SNDBUF的值,但在协议层面,最大是65507字节。SO_SNDBUF不能设置为大于这个值;尝试设置更大的值可能会被裁剪。 在Linux中,你可以设置SO_SNDBUF,但有上限。 最大可能的数据报大小是65507字节。 回答用户问题: - sendto函数的详细解释:描述其功能参数。 - 对于UDP,sendto每次发送数据的长度上限:最大65507字节的数据部分。 系统指令要求: - 行内数学表达式用$...$:例如,$65535$字节。 - 独立公式用$$...$$:如果有需要。 - 使用中文回答。 - 生成相关问题。 - 引用的段落末尾添加引用标识,例如[^1]。 回答结构清晰,逐步解决问题。 首先,解释sendto函数。 然后,讨论UDP下的长度上限。 最后,生成相关问题。 参考的引用:[^1],[^2],[^5]等。 详细解释sendto: - 功能:sendto函数用于向指定的网络地址发送数据。它通常用于无连接套接口,如UDP。 - 参数: - sockfd: socket文件描述符,由socket()创建。 - buf: 指向要发送数据的指针。 - len: 要发送数据的长度(字节)。 - flags: 控制发送行为的标志,如MSG_DONTWAIT。 - dest_addr: 指向目标地址的指针,类型为struct sockaddr。 - addrlen: 目标地址的长度。 - 返回值:成功时返回发送的字节数,失败时返回-1并设置errno。 对于UDP: - 当用于UDP套接口(SOCK_DGRAM)时,sendto发送一个独立的数据报。 长度上限: - UDP协议规定,单个UDP数据报的最大长度为$65535$字节(包括UDP首部)。UDP首部为8字节,因此数据部分的最大长度为$65535 - 8 = 65527$字节。但由于IP层的限制,IP数据最大长度为$65535$字节,IP首部最小为20字节,因此UDP数据部分实际最大为$65535 - 20 - 8 = 65507$字节[^5]。 - 此外,套接口的发送缓冲区大小(通过SO_SNDBUF设置)也限制单个数据报的大小。如果len大于SO_SNDBUF的值,sendto将失败并返回EMSGSIZE错误[^2]。 - 因此,在Linux中,基于UDP协议时,sendto函数每次发送数据的长度上限受SO_SNDBUF协议限制,最大数据部分约为65507字节,但通常受系统配置影响。 确认:是的,上限是64KB级别的,但具体数据部分是65507字节。 用户问"是否为64KB",64KB=65536字节,但最大是65535包括头,所以数据小于64KB。 所以,不是严格64KB的数据,而是接近64KB的总大小。 在回答中澄清。 生成相关问题。 相关问题示例: 1. 如何在Linux中设置UDP套接口的发送缓冲区大小? 2. 如果UDP数据报超过路径MTU会发生什么? 3. sendto函数的错误处理有哪些常见情况? 基于上下文。 最终回答结构: 1. **sendto函数详细解释** - 功能参数说明。 2. **UDP协议下的数据长度上限** - 解释协议限制系统限制。 3. **结论** 4. **相关问题** 使用引用标识。 </think>### sendto函数详细解释 `sendto` 函数是 Linux 网络编程中用于发送数据的重要系统调用,常用于无连接协议如 UDP。它允许应用程序指定目标地址发送数据报。以下是函数的详细说明: - **函数原型**: ```c ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ``` 参数解析: - `sockfd`:套接口文件描述符,由 `socket()` 函数创建。对于 UDP,需使用 `SOCK_DGRAM` 类型(指定协议族如 `AF_INET` 或 `AF_INET6`)[^3][^4]。 - `buf`:指向要发送数据的缓冲区指针。 - `len`:发送数据的长度(字节数),即本次发送数据部分(payload)大小。 - `flags`:控制发送行为的标志位(如 `MSG_DONTWAIT` 表示非阻塞发送)。 - `dest_addr`:指向目标地址结构的指针(如 `struct sockaddr_in` 用于 IPv4)。 - `addrlen`:目标地址结构的长度。 返回值: - 成功时返回实际发送的字节数(通常等于 `len`)。 - 失败时返回 `-1`,并设置 `errno` 指示错误原因(如 `EMSGSIZE` 表示数据过大)[^2][^3]。 - **工作流程**: 1. 应用程序调用 `sendto`,传入数据缓冲区目标地址。 2. 内核将数据从用户空间拷贝到内核缓冲区(UDP 无持久化缓冲区,数据发送后内核可能丢弃拷贝)[^2]。 3. 数据被封装为 UDP 数据报,添加 UDP 首部(8 字节)。 4. IP 层进一步处理(如分片),最终通过网络接口发送[^2][^5]。 对于 UDP,`sendto` 每次调用发送一个独立的数据报,不保证可靠性或顺序性。 ### 基于 UDP 协议时 sendto 函数的数据长度上限 在 UDP 协议下,`sendto` 函数每次发送数据的长度上限 **不是固定的 64KB,而是受协议系统双重限制**,最大数据部分(payload)约为 **65507 字节**(约 64KB - 28 字节)。以下是详细分析: 1. **协议层限制(理论最大值)**: - UDP 数据报的总长度(包括 UDP 首部)由一个 16 位字段表示,最大值为 $2^{16} - 1 = 65535$ 字节(即 64KB)。 - UDP 首部固定为 8 字节,因此数据部分的最大理论长度为: $$ 65535 - 8 = 65527 \text{ 字节} $$ - 但 IP 层进一步限制:IP 数据最大长度也为 65535 字节(16 位长度字段),IP 首部最小为 20 字节(IPv4)。因此,UDP 数据部分实际最大长度为: $$ 65535 - 20 - 8 = 65507 \text{ 字节} $$ 这意味着每次 `sendto` 调用中,`len` 参数的最大值不超过 65507 字节[^1][^5]。例如,发送超过此大小的数据会导致 IP 层分片或错误。 2. **系统层限制(实际可配置值)**: - UDP 套接口有一个发送缓冲区大小(通过 `SO_SNDBUF` 选项设置),它定义了单个数据报的尺寸上限。默认值通常较小(如 8192 字节),但可通过 `setsockopt` 调整。 - 如果 `len` 大于 `SO_SNDBUF` 的值,`sendto` 会失败并返回 `EMSGSIZE` 错误(即使数据小于 65507 字节)。这是因为内核强制检查缓冲区边界[^2]。 - 实践中,`SO_SNDBUF` 可设置到接近 65507 字节,但不能超过协议极限(设置过大时内核自动裁剪)[^2][^5]。 3. **用户问题确认**: - **是否每次发送上限为 64KB?** 不完全正确。总数据报大小上限为 64KB(65535 字节),但数据部分上限仅为 65507 字节(约 63.999KB)。发送超过此值的数据需要在应用层手动分包拼接(如引用[1]所述)[^1][^5]。 - **测试建议**:在 Linux 中,尝试发送大于 65507 字节的数据(例如 `len = 65508`),`sendto` 将失败并返回 `EMSGSIZE` 或导致 IP 分片(依赖网络路径 MTU)。 **总结**:基于 UDP 协议时,`sendto` 函数的数据长度上限由 UDP/IP 协议栈固定为最大 65507 字节(数据部分),并受 `SO_SNDBUF` 影响。实际应用中,建议保持数据报较小以避免分片性能问题(如引用[1][5]强调)[^1][^5]。 ### 相关问题 1. 如何在 Linux 中修改 UDP 套接口的发送缓冲区大小(`SO_SNDBUF`)以优化数据传输? 2. 当 UDP 数据报超过网络路径 MTU(最大传输单元)时,IP 层如何处理分片? 3. `sendto` 函数在 UDP TCP 协议下的行为差异是什么?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值