1 概述
在I/O操作上设置超时,这里有三种方法。然后read和write这两个函数的三个变体:recv和send允许通过第四个参数从进程到内核传递标志;readv和writev允许指定往其中输入数据或从其中输出数据的缓冲区向量;recvmsg和sendmsg结合了其他I/O函数的所有特性,并具有接收和发送辅助数据的新能力。
2 套接字超时
在套解字I/O上设置超时的方法有以下三种;
-
调用alarm,它在指定超时期满时产生SIGALRM信号。这个方法涉及信号处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有alarm调用。
-
在select中阻塞等待I/O(select有内置的时间限制),以此代替直接阻塞在read或write调用上。
-
使用较新的SO_REVTIMEO和SO_SNDTIMEO套接字选项。这个方法的问题在于并非所有实现都支持这两个套接字选项。
3 recv和send函数
这两个函数类似标准read和write函数,不过需要一个额外的参数。
#include <socket.h>
/* Read N bytes into BUF from socket FD.
Returns the number read or -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t recv (int __fd, void *__buf, size_t __n, int __flags);
/* Send N bytes of BUF to socket FD. Returns the number sent or -1.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t send (int __fd, const void *__buf, size_t __n, int __flags);
I/O函数的flag值。
图1-1 I/O函数的flags参数
4 readv和writev函数
readv和writev允许单个系统调用读入到或写出自一个或多个缓冲区。这些操作分别称为分散读(scatter read)和集中写(gather write),因为来自读操作的输入数据被分散到多个应用缓冲区,而来自多个应用缓冲区的输出数据则被集中提供给单个写操作。
#include <bits/uio.h>
/* Structure for scatter/gather I/O. */
struct iovec
{
void *iov_base; /* Pointer to data. */
size_t iov_len; /* Length of data. */
};
#include <sys/uio.h>
/* Read data from file descriptor FD, and put the result in the
buffers described by IOVEC, which is a vector of COUNT 'struct iovec's.
The buffers are filled in the order specified.
Operates just like 'read' (see <unistd.h>) except that data are
put in IOVEC instead of a contiguous buffer.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t readv (int __fd, const struct iovec *__iovec, int __count)
__wur;
/* Write data pointed by the buffers described by IOVEC, which
is a vector of COUNT 'struct iovec's, to file descriptor FD.
The data is written in the order specified.
Operates just like 'write' (see <unistd.h>) except that the data
are taken from IOVEC instead of a contiguous buffer.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t writev (int __fd, const struct iovec *__iovec, int __count)
__wur;
writev是一个原子操作。
5 recvmsg和sendmsg函数
这两个函数是最通用的I/O函数。实际上我们可以把所有read、readv、recv和recvfrom调用替换成recvmsg调用。类似地,各种输出函数调用也可以替换成sendmsg调用。
#include <socket.h>
/* Send a message described MESSAGE on socket FD.
Returns the number of bytes sent, or -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t sendmsg (int __fd, const struct msghdr *__message,
int __flags);
/* Send a VLEN messages as described by VMESSAGES to socket FD.
Returns the number of datagrams successfully written or -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int sendmmsg (int __fd, struct mmsghdr *__vmessages,
unsigned int __vlen, int __flags);
这两个函数把大部分参数封装到一个mmshdr。
#include <socket.h>
/* For `recvmmsg' and `sendmmsg'. */
struct mmsghdr
{
struct msghdr msg_hdr; /* Actual message header. */
unsigned int msg_len; /* Number of received or sent bytes for the
entry. */
};
#include <sys/socket.h>
/* Structure describing messages sent by
`sendmsg' and received by `recvmsg'. */
struct msghdr
{
void *msg_name; /* Address to send to/receive from. */
socklen_t msg_namelen; /* Length of address data. */
struct iovec *msg_iov; /* Vector of data to send/receive into. */
size_t msg_iovlen; /* Number of elements in the vector. */
void *msg_control; /* Ancillary(辅助数据) data (eg BSD filedesc passing). */
size_t msg_controllen; /* Ancillary data buffer length.
!! The type should be socklen_t but the
definition of the kernel is incompatible
with this. */
int msg_flags; /* Flags on received message. */
};
图1-2汇总了内核为输入和输出函数检查的flags参数值以及recvmsg可能返回的msg_flag成员值。其中没有sendmsg msg_flags一栏,因为我们已提及本组合无效。
图1-2 各种I/O函数输入和输出标志的总结
图1-3展示了一个msghdr结构以及它指向的各种信息。
图1-3 对一个UDP套接字调用recvmsg时的数据结构
图1-4展示了recvmsg返回时msghdr结构中的所有信息。
图1-5汇总了我们已讲述的5组I/O函数之间的差异。
图1-5 5组I/O函数的比较
6 辅助数组
辅助数据(ancillary data)可通过调用sendmsg和recvmsg这两个函数,使用msghdr结构中的msg_control和msg_controllen这两个成员发送和接收。辅助数据的另一个称谓是控制信息(control information)。图1-6汇总了辅助数据的各种用途。
图1-6 辅助数据用途的总结
辅助数据由一个或多个辅助数据对象(ancillary data object)构成,每个对象以一个定义在头文件<bits/socket.h>中的cmsghdr结构开头。
/* Structure used for storage of ancillary data object information. */
struct cmsghdr
{
size_t cmsg_len; /* Length of data in cmsg_data plus length
of cmsghdr structure.
!! The type should be socklen_t but the
definition of the kernel is incompatible
with this. */
int cmsg_level; /* Originating protocol. */
int cmsg_type; /* Protocol specific type. */
#if (!defined __STRICT_ANSI__ && __GNUC__ >= 2) || __STDC_VERSION__ >= 199901L
__extension__ unsigned char __cmsg_data __flexarr; /* Ancillary data. */
#endif
};
图1-7展示了在一个控制缓冲区出现2个辅助数据对象的例子。
图1-7 包含两个辅助数据对象的辅助函数
图1-8显示了通过一个Unix域套接字传递描述符或传递凭证时所用cmshhdr结构的格式。
图1-8 用在Unix域套接字上的cmsghdr结构
既然由recvmsg返回的辅助数据可含有任意数目的辅助数据对象,为了对应用程序屏蔽可能出现的填充字节,头文件<bits/socket.h>中定义了以下5个宏,以简化对辅助数据的处理。
#if (!defined __STRICT_ANSI__ && __GNUC__ >= 2) || __STDC_VERSION__ >= 199901L
# define CMSG_DATA(cmsg) ((cmsg)->__cmsg_data)
#else
# define CMSG_DATA(cmsg) ((unsigned char *) ((struct cmsghdr *) (cmsg) + 1))
#endif
#define CMSG_NXTHDR(mhdr, cmsg) __cmsg_nxthdr (mhdr, cmsg)
#define CMSG_FIRSTHDR(mhdr) \
((size_t) (mhdr)->msg_controllen >= sizeof (struct cmsghdr) \
? (struct cmsghdr *) (mhdr)->msg_control : (struct cmsghdr *) 0)
#define CMSG_ALIGN(len) (((len) + sizeof (size_t) - 1) \
& (size_t) ~(sizeof (size_t) - 1))
#define CMSG_SPACE(len) (CMSG_ALIGN (len) \
+ CMSG_ALIGN (sizeof (struct cmsghdr)))
#define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len))
这些宏能以如下伪代码形式使用。
CMSG_SPACE要记可能出现的尾部的填充字节,而CMSG_LEN不会。
7 排队的数据量
有时候我们想知道在不真正读取数据的前提下直到一个套接字上已有多少数据排队等着读取。有3个技术可用于获悉已排队的数据量。
-
如果获悉已排队数据量的目的在于避免读操作阻塞在内核中,那么可以使用非阻塞式I/O。
-
如果我们既想查看数据,又想数据仍然留在接收队列中以供本进程其他部分稍后读取,那么可以使用MSG_PEEK标志。
-
一些实现支持ioctl的FINONREAD命令。
8 kqueue接口
本接口允许进程向内核注册描述所关注kqueue事件的事件过滤器(event filter)。事件除了与select所关注类似的文件I/O和超时外,还有异步I/O、文件修改通知(例如文件被删除或修改时发生的通知)、进程跟踪(例如进程调用exit或fork时发出通知)和信号处理。kqueue接口包括如下2个函数和1个宏。
kqueue函数返回一个新的kqueue描述符,用于后序调用的kevent调用中。kevent函数既用于注册所关注的事件,也用于确定是否有所关注事件发生。
其中flags成员在调用时指定过滤器更改行为,在返回时额外给出条件,如图1-9所示。
图1-9 kevent结构的flags成员
filter成员指定的过滤器类型如图1-10所示。
图1-10 kevent结构的filter成员