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 bufferreceive buffer


然后是在TCP数据读写部分,我们常用的时recvsend函数,它们增加了对数据读写的控制:

#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);

sendsockfd上写入数据,buflen参数分别指定写缓冲区的位置和大小。send成功时候返回实际写入的数据长度,失败则返回-1,并且设置errno

flags参数为数据的收发提供了额外的控制, 它可以取表5-4所示选项中的一个或者几个的逻辑或:


在UDP的读写部分,比较常用的函数是recvfromsendto函数:

#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上的数据,buflen参数分别指定缓冲区的位置和大小。因为UDP通信没有连接的概念,所以我们读取数据都需要获取发送端的socket地址。

sendto函数想sockfd上写入数据,buflen参数分别指定写缓冲区的位置和大小,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参数指定被操作的目标socketmsg参数是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_controlmsg_controllen成员用于辅助数据的传送,辅助数据我平时用的也比较少,《Linux高性能服务器编程》当中也只是介绍了使用其传递文件描述符(13.9部分),具体的辅助数据可以参考这个博客:

msg_flags成员无需设定,它会复制recvmsg/sendmsgflags参数的内容影响读写的过程。recvmsg还会在调用结束前,将某些更新后的标志设置到msg_flags中。

这两个函数的recvmsg函数的flags字段的含义和send以及recv函数相同。

编辑于 2022-03-14 10:14