send 和 recv 函数

Posted by KalosAner on January 22, 2025

一、引言

send 和 recv 函数是网络编程中基础的 I/O 函数,虽然在 Linux 上 write 和 read 函数也可以用来通信,但是 write 和 read 本质上是文件操作函数。send 和 recv 函数在 Linux 和 Windows 上很相似,兼容性更强。

二、Linux 中的 send 和 recv

函数原型:

1
2
3
4
5
#include <sys/socket.h>

// sockfd:套接字,buf:缓冲,nbytes:可接收的最大字节数,flags:可选项
ssize_t send(int sockfd, const void * buf, size_t nbytes, int flags);
ssize_t recv(int sockfd, void * buf, size_t nbytes, int flags);
可选项 含义 send recv
MSG_OOB 用于传输带外数据(Out-of-band data) 可用 可用
MSG_PEEK 验证输入缓存中是否存在可以接收的数据   可用
MSG_DONTROUTE 数据传输过程中不参照路由表,在本地网络中寻找目的地 可用  
MSG_DONTWAIT 调用 I/O 函数时不阻塞,用于使用非阻塞 I/O 可用 可用
MSG_WAITALL 防止函数返回,直到接收全部请求的字节数   可用

2.1 MSG_OOB:发送紧急数据

TCP 不存在真正意义上的“带外数据”,因此通过 MSG_OOB 发送数据时并不会加快数据传输速度,只会向目标进程发送一个 SIGURG 信号,目标进程可以设置信号处理函数进行处理。

真正意义上的 Out-of-band 需要通过单独的通信路径高速传输数据,但 TCP 不另外提供,只利用 TCP 的紧急模式(Urgent mode)进行传输。

处理函数如下:

1
2
3
4
5
6
7
void urg_handler(int signo) {
	int str_len;
	char buf[BUF_SIZE];
	str_len = recv(recv_sock, buf, sizeof(buf)-1, MSG_OOB);
	buf[str_len] = 0;
	printf("Urgent message: %s \n", buf);
}

处理函数中的 recv 函数也设置为 MSG_OOB 用来接收紧急数据,但其实这样只会接收到紧急数据的最后一个字符,其他字符还会通过普通的 recv 函数接收。

通过调用 fork 函数创建子进程并同时复制文件描述符,此时多个进程可以同时拥有同一个套接字的文件描述符。此时如果发生 SIGURG 信号应该调用哪个进程中的信号处理函数?可以肯定的是不会调用所有进程的信号处理函数,因此,处理 SIGURG 信号时必须指定处理信号的进程,而 getpid 函数返回调用此函数的进程 ID。

指定处理信号的进程的函数:

1
fcntl(recv_sock, F_SETOWN, getpid());

oob_send.coob_recv.c

2.2 检查输入缓存

MSG_PEEK 可以读取输入缓存中的数据而不会删除里边的数据,MSG_DONTWAIT 可以设置 IO 函数不阻塞,即使输入缓存没有数据也不会阻塞。两者结合可以用来验证输入缓存中是否存在可以接收的数据。

peek_send.cpeek_recv.c

三、readv 和 writev 函数

writev 函数可以将分散在多个缓冲中的数据一并发送,readv 函数可以由多个缓冲分别接收。

函数原型:

1
2
3
4
5
6
7
8
9
#include <sys/uio.h>

ssize_t writev(int filedes, const struct iovec * iov, int iovcnt);
ssize_t readv(int filedes, const struct iovec * iov, int iovcnt);

struct iovec {
    void * iov_base;	// 缓冲地址
    size_t iov_len;		// 缓冲大小
}

sendv.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <sys/uio.h>
#define BUF_SIZE 100

int main(int argc, char *argv[])
{
	struct iovec vec[2];
	char buf1[BUF_SIZE]={0,};
	char buf2[BUF_SIZE]={0,};
	int str_len;

	vec[0].iov_base=buf1;
	vec[0].iov_len=5;
	vec[1].iov_base=buf2;
	vec[1].iov_len=BUF_SIZE;

	str_len=readv(0, vec, 2);
	printf("Read bytes: %d \n", str_len);
	printf("First message: %s \n", buf1);
	printf("Second message: %s \n", buf2);
	return 0;
}

writev.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <sys/uio.h>

int main(int argc, char *argv[])
{
	struct iovec vec[2];
	char buf1[]="ABCDEFG";
	char buf2[]="1234567";
	int str_len;

	vec[0].iov_base=buf1;
	vec[0].iov_len=3;
	vec[1].iov_base=buf2;
	vec[1].iov_len=4;
	
	str_len=writev(1, vec, 2);
	puts("");
	printf("Write bytes: %d \n", str_len);
	return 0;
}

四、基于 Windows 的带外数据

Windows 中不存在 Linux 那样的信号处理机制。但是 Out-of-band 数据属于异常,所以可以使用 select 函数监测异常来接收 Out-of-band 数据。

oob_recv_win.coob_send_win.c

此外,Windows 中没有 writev 函数 和 readv 函数,但可以通过 “重叠 I/O” 实现同样的效果。