8.send()函数族
系统调用send、sendto(), and sendmsg()用于将消息发送给另一个socket。
8.1 包含头文件
#include <sys/types.h>
#include <sys/socket.h>
8.2 函数主体
1.ssize_t send(int sockfd, const void *buf, size_t len, int flags);
2.ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
3.ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
参数解释:
- int sockfd
发送套接字的文件描述符
- void *buf
send()函数簇读到的数据将保存到buf所指向的空间中,用法和write()同理。
- size_t len
该参数指明本次调用接收数据的最大长度。
- int flags
如果flags设置为0,那么send()和write()没有任何区别。send()只用于面向连接的socket,即收方IP是知道的,flags还可以通过OR设置如下参数:
标志位 | 解释 |
---|---|
MSG_CONFIRM | 只在SOCK_DGRAM和SOCK_RAW类型的socket有效,该标志位用于通知链路层收到了成功回复,以避免链路层定期发送ARP协议,减少流量。 |
MSG_DONTROUTE | 不使用网关发送数据包,只能发送到直接连接的网络上的主机,通常用于网络诊断的路由系列协议。 |
MSG_DONTWAIT | 设置本次调用为非阻塞,与O_NONBLOCK flag类似,但是该标志为作用域仅限于本次send调用,而后者则是对文件描述符操作。 |
MSG_MORE | 此标志指定会有更多的数据发送,与 TCP_CORK 套接字设置效果相同,此时内核将保留这些数据,仅在下一次调用未指定该标志时进行传输。 |
MSG_NOSIGNAL | 此标志指定调用send(),当对端的socket已经关闭时,不会产生SIGPIPE信号,但仍会返回EPIPE错误,作用范围为本次调用。这与sigaction忽略 SIGPIPE信号效果相同,但是后者会影响整个线程。 |
MSG_OOB | 此标志指定在正常数据流的接受中不会接收带外数据。某些协议将加急数据放在正常数据队列的头部,因此此标志不能与此类协议一起使用。 |
这里对flag:MSG_CONFIRM稍做补充:
根据IOS五层模型,网络层将IP数据包封装好后会经过数据链路层封装源MAC和目的MAC,而ARP缓存表中则存放有IP到MAC的映射,主机通过ARP缓存表可以查找到目的MAC。但当ARP缓存表中没有对应的表项时,主机会以广播的方式向局域网中发送ARP请求,目的主机则会以单播的方式将自己的MAC封装到APR回文报文中,这样本主机便可知道目的MAC,当两台主机不在一个网段时,则需要通过路由器转发,参考arp请求跨网段工作原理。
在LINUX内核中设置了ARP缓存的老化时间机制,用于管理ARP缓存表,MSG_CONFIRM标志位则用于通知链路层继续保持Reachable状态,而不需要在发送ARP请求报文来确认是否更新当前状态,减少了ARP报文在网络中传输,具体细节需要时在做补充,参考《Linux实现的ARP缓存老化时间原理解析》。
- const struct sockaddr *dest_addr
如果dest_addr非NULL,且协议提供消息的地址信息,则该目标地址将放在 dest_addr所指向的缓冲区中。
-
socklen_t *addrlen
addrlen 是一个value-result参数。在调用之前,应将其初始化为与dest_addr关联的缓冲区的大小。返回后,addrlen 将更新为包含源地址的实际大小。 如果提供的缓冲区太小,则返回的地址将被截断;在这种情况下,addrlen 将返回一个大于提供给调用的值。
如果不需要知道源地址,则可以将dest_addr和addrlen均设为NULL即可,此时以下两个调用等价,send(sockfd, buf, len, flags) 与sendto(sockfd, buf, len, flags, NULL, 0);。
-
struct msghdr *msg
//该结构体如下,等用到时,在进行补充
struct iovec { /* Scatter/gather array items */
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags on received message */
};
8.3 返回值
这些调用返回收到的字节数,如果发生错误,则返回 -1,如果消息不适合套接字的发送缓冲区时,send()通常会阻塞(未设置非阻塞时)。如果发生错误,设置了 errno 用来指示错误。错误类型如下(只列出socket层产生的错误,更底层(传输层及以下)的错误再前面文章种已经全部列出):
错误类型 | 解释 |
---|---|
EBADF | socket并非有效的文件描述符 |
ECONNREFUSED | 对端主机拒绝了网络连接 |
EFAULT | buff缓冲区指向非法内存 |
EINTR | 在接收数据之前,本次调用被信号中断 |
EINVAL | 传递了无效参数 |
ENOTCONN | socket为面向连接套接字,但没有成功连接 |
ENOTSOCK | sockfd引用的并不是一个socket |
ENOMEM | 无可用内存 |
EMSGSIZE | 套接字类型要求以原子方式发送消息,而要发送的消息太大而不可能实现。 |
EISCONN | 套接字以建立连接,但是又重新指定了接收端信息 |
ECONNRESET | 连接已被对方重置 |
8.4 结束
当用户调用recv()/send()后,数据会读/写到指定套接字的读/写缓冲区,这里需要注意的是,send、write系统调用只负责将数据存到指定的缓存区,便成功返回,而将数据传输到对端主机中的过程则是由内核调用TCP/IP协议完成,此过程则不由用户控制,send函数也不会返回对用的状态信息。在调试程序时,可以通过“netstat -an”命令查看socket读写缓存区中是否由未发送的数据。