下面学习Linux网络API的socket读写接口。
1、TCP数据读写
文件读写操作read和write同样可用于socket,但是socket编程接口提供了专门的系统调用。TCP读写系统调用为:
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
buf为缓冲区,len为大小,flags一般设为0。recv成功返回实际读取的数据长度,可能小于len,所以需要多次调用recv读取完整数据。返回0表示对方已关闭连接,出错返回-1并设置errno。send成功返回实际写入的数据长度,失败返回-1并设置errno。
flags提供了额外的控制选项,可选以下一个或多个标志(当前不明白其具体含义,后续研究):
MSG_CONFIRM:(send)指示数据链路层协议持续监听对方的回应,直到得到答复。仅用于SOCK_DGRAM和SOCK_RAW类型socket;
MSG_DONTROUTE:(send)不查看路由表,直接将数据发送给本地局域网主机,表示发送者确切知道目标主机就在本地网络;
MSG_DONTWAIT:(recv send )对socket的此次操作是非阻塞的;
MSG_MORE:(send)告诉内核应用程序还有更多数据要发送,内核将超时等待新数据写入TCP发送缓冲区后一并发送,防止TCP发送过多小的报文段,提高传输效率;
MSG_WAITALL:(recv)读操作仅在读取到指定数量的字节后才返回;
MSG_PEEK:(recv)窥探读缓存中的数据,此次读操作不会导致这些数据被清除;
MSG_OOB:(recv send)发送或接收紧急数据;
MSG_NOSIGNAL:(send)往读端关闭的管道或socket连接中写数据时不引发SIGPIPE信号;
2、UDP数据读写
UDP数据报读写系统调用:
#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);
UDP没有连接的概念,每次读写需要获取对端的socket地址。参数和返回值含义与send/recv相同。
recvfrom/sendto也可以用于面向连接的socket数据读写,只要把后两个参数设为NULL。
3、通用数据读写
对于TCP和UDP,socket编程接口提供了一对通用的系统调用:
#include<sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
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成员对TCP没有意义,必须设置为NULL。
iovec结构体定义:
struct iovec
{
void *iov_base;/*内存起始地址*/
size_t iov_len;/*内存长度*/
};
iovec封装了一块内存的起始地址和长度,msg_iovlen指定有多少个这样的结构体对象。
对于recvmsg,数据被读取并存放在msg_iovlen块分散的内存中,内存位置和长度由msg_iov数组指定,称为分散读(scatter read);
对于sendmsg,msg_iovlen块分散内存的数据被一并发送,称为集中写(gather write)。
flags参数和返回值跟recv/send相同。
4、带外标记
Linux内核检测到TCP紧急标志时将通知应用程序有带外数据需要接收,两种常见方式为I/O复用产生的异常事件和SIGURG信号。应用程序还需要知道带外数据在数据流中的具体位置:
#include<sys/socket.h>
int sockatmark(int sockfd);
sockatmark判断sockfd是否处于带外标记,即下一个被读到的数据是否是带外数据。如果是返回1,此时我们就可以利用带MSG_OOB标志的recv函数来接收带外数据;如果不是,返回0。
5、地址信息函数
获取一个连接socket的本端socket地址和远端socket地址:
#include<sys/socket.h>
int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len);
int getpeername(int sockfd, struct sockaddr* address, socklen_t* address_len);
getsockname获取sockfd的本端socket地址,存储在address指向的内存,长度存储在address_len中,若地址长度超过address内存区,截断。成功返回0,失败返回-1并设置errno。
getpeername获取远端socket地址,与getsockname类似。