前面已经说过, 对文件的操作函数read/write也同样可以操作socket描述符, 就把socket描述符当作一个文件来处理. 所以在socket上使用read/write是有意义的, 可以传递socket描述符到那些本来设计为处理本地文件的函数, 还可以安排socket描述符到执行进程的子进程, 该子进程也不需要了解socket.
虽然read/write可以执行socket上的数据传输, 但它不能指定选项, 不能从多个客户端接收数据, 也不能向多个客户端发送数据. 如果需要实现这些, 就需要使用socket提供的函数了.
1. 发送数据:
- ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
- 使用时必须已经连接.
- 参数buf, nbytes和write中的含义一致.
- flags是socket的调用标志, 可以是:
- MSG_DONTROUTE: 不走路由, 也就是说数据不可越出本地网络.
- MSG_DONTWAIT: 非阻塞操作, 等价于write使用O_NONBLOCK.
- MSG_EOR: 如果协议支持, 此为记录结束.
- MSG_OOB: 如果协议支持, 发送带外数据.
- send成功返回时, 仅仅表示数据已经准确无误地发送到了网络上, 而不关心接收端.
- ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags,
const struct sockaddr *destaddr, socklen_t destlen);
- destaddr指针指向目标地址的结构.
- 其他同send.
- ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
- msghdr的结构定义如下:
struct msghdr
... {
void *msg_name; /**//* optional address */
socklen_t *msg_namelen; /**//* address size in bytes */
struct iovec *msg_iov; /**//* array of I/O buffers */
int msg_iovlen; /**//* number of elements in array */
void *msg_control; /**//* ancillary of data */
socklen_t msg_controllen; /**//* number of ancillary bytes */
int msg_flags; /**//* flags for received message */
.
.
.
} ; - 该函数指定多重缓冲取传输数据, 类似于writev.
- msghdr的结构定义如下:
2. 接收数据:
- ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
- flags可以是:
- MSG_OOB: 如果协议支持, 接收带外数据.
- MSG_PEEK: 返回报文内容而不真正取走报文.
- MSG_TRUNC: 即使报文被截断, 返回的也是报文的实际长度.
- MSG_WAITALL: 等待, 直到所有的数据可用(仅限SOCK_STREAM).
- ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags,
struct sockaddr *restrict addr, socklen_t *restrict addrlen);
- ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
3. 总结:
函数sendto/recvfrom和函数send/recv很类似, 区别在于sendto/recvfrom允许在无连接的socket上指定一个目标地址.
- 对于面向连接的socket, 目标地址是忽略的, 因为目标地址已经包含在连接中了.
- 对于无连接的socket, 不能使用send/recv, 除非在调用connect时预先设定了目标地址. 否则, 需要使用sendto/recvfrom来完成报文的传输.
这里介绍了数据传输的所有函数, 在socket系列文章的后面会有实例来调用这些函数.