一、引言
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());
2.2 检查输入缓存
MSG_PEEK 可以读取输入缓存中的数据而不会删除里边的数据,MSG_DONTWAIT 可以设置 IO 函数不阻塞,即使输入缓存没有数据也不会阻塞。两者结合可以用来验证输入缓存中是否存在可以接收的数据。
三、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.c 和 oob_send_win.c。
此外,Windows 中没有 writev 函数 和 readv 函数,但可以通过 “重叠 I/O” 实现同样的效果。