recv/send/recvfrom/sendto/recvmsg/sendmsg小结
这篇笔记总结一下recv/send
:
首先使用read
以及write
函数也可以对网络的套接字进行读写的操作:
#include<unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int filedes, void *buf, size_t nbytes);
此时fd
就表示为对应的套接字描述符,但是write成功返回,只是buf中的数据被复制到了kernel中的TCP发送缓冲区。至于数据什么时候被发往网络,什么时候被对方主机接收,什么时候被对方进程读取,系统调用层面不会给予任何保证和通知。
那么write
函数在什么时候会发生一个阻塞呢? 当kernel
的该socket
的发送缓冲区已满时。对于每个socket
,拥有自己的send buffer
和receive buffer
。
然后是在TCP数据读写部分,我们常用的时recv
和send
函数,它们增加了对数据读写的控制:
#incldue <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, viod *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
send
往sockfd
上写入数据,buf
和len
参数分别指定写缓冲区的位置和大小。send
成功时候返回实际写入的数据长度,失败则返回-1
,并且设置errno
。
flags
参数为数据的收发提供了额外的控制, 它可以取表5-4所示选项中的一个或者几个的逻辑或:
在UDP的读写部分,比较常用的函数是recvfrom
和sendto
函数:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
recvfrom
读取sockfd
上的数据,buf
和len
参数分别指定缓冲区的位置和大小。因为UDP
通信没有连接的概念,所以我们读取数据都需要获取发送端的socket
地址。
sendto
函数想sockfd
上写入数据,buf
和len
参数分别指定写缓冲区的位置和大小,dest_addr
参数指定接收端的socket
地址,addrlen
参数则指定该地址的长度。
这两个系统调用的flags
参数以及返回值的含义均与send/recv
系统调用的flags
参数及返回值相同。
值得一提的是,recvfrom/sendto
系统调用也可以用于面向连接(STREAM
)的socket的数据读写,只需要把最后两个参数都设置为NULL
以忽略发送端/接收端的socket
地址(因为我们以及和对方建立了连接,所以已经知道其socket
的地址了)
下面我们介绍通用数据读写函数,它们不仅能用于TCP流数据,而且也能用于UDP的数据:
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
sockfd
参数指定被操作的目标socket
。msg
参数是msghdr
结构体类型的指针,msghdr
结构体的定义如下:
struct msghdr {
void* msg_name; // socket地址
socklen_t msg_namelen; // socket地址的长度
struct iovec* msg_iov; // 分散的内存块
int msg_iovlen; // 分散内存块的数量
void* msg_control; // 指向辅助数据的起始位置
socklen_t msg_controllen; // 辅助数据的大小
int msg_flags; // 复制函数中的flags参数,并且在调用过程当中更新
};
msg_name
成员指向一个socket
地址结构变量。它指定通信对方的socket
地址。对于面向连接的TCP协议,该成员没有意义,必须被设置为NULL
(因为之前已经connect
三次握手过了)。
msg_iov
成员是iovec
结构体类型的指针,iovec
结构体的定义如下所示:
struct iovec {
void *iov_base; // 内存起始地址
size_t iov_len; // 这块内存的长度
}
由上可见,iovec
结构体封装了一块内存的起始位置和长度。msg_iovlen
指定这样的iovec
结构对象有多少个。对于recvmsg
而言,数据将被读取并存放在msg_iovlen
块分散的内存中,这些内存的位置和长度则由msg_iov
指向的数组指定,这称之为分散读,对于sendmsg
而言,msg_iovlen
分散内存中的数据将被一并发送,这称之为集中写。
对于msg_control
和msg_controllen
成员用于辅助数据的传送,辅助数据我平时用的也比较少,《Linux高性能服务器编程》当中也只是介绍了使用其传递文件描述符(13.9部分),具体的辅助数据可以参考这个博客:
msg_flags
成员无需设定,它会复制recvmsg/sendmsg
的flags
参数的内容影响读写的过程。recvmsg
还会在调用结束前,将某些更新后的标志设置到msg_flags
中。
这两个函数的recvmsg
函数的flags
字段的含义和send
以及recv
函数相同。