Linux socket编程 套接字选项
1. 套接字选项概述
有很多方法来获取和设置套接字的选项, 以影响套接字行为:
- getsockopt和setsocketopt;
- fcntl;
- ioctl;
2. getsockopt和setsockopt
2个函数仅用于套接字, 分别用于获取和设置套接字选项
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
- 参数
sockfd 一个打开的套接字描述符
level 级别, 指定系统中解释选项的代码或为通用套接字代码, 或为某个特定于协议的代码(e.g. IPv4/6, TCP, STCP)
optval 指向某个变量(optval)的指针, setsockopt从optval中取得选项待设置的新值, getsockopt则把已获得的选项当前值存放到*optval中
optlen 对setsockopt是值参数, 对getsockopt是值-结果参数, 用于指明optval的大小
套接字选项粗分为2个基本类型: 启用或进制某个特性的二元选项, 称为标志选项; 取得并返回我们可以设置或检查的特定值的选项, 称为值选项.
套接字层和IP层的套接字选项汇总表:
leve级别 | optname选项名 | get | set | 说明 | 标志 | 数据类型 |
---|---|---|---|---|---|---|
SOL_SOCKET | SO_BROADCAST | · | · | 允许发送广播数据报 | · | int |
SOL_SOCKET | SO_DEBUG | · | · | 开启调试跟踪 | · | int |
SOL_SOCKET | SO_DONTROUTE | · | · | 绕过外出路由表查询 | · | int |
SOL_SOCKET | SO_ERROR | · | 获取待处理错误并清除 | int | ||
SOL_SOCKET | SO_KEEPALIVE | · | · | 周期性测试连接是否仍存活 | · | int |
SOL_SOCKET | SO_LINEGER | · | · | 若有数据待发送, 则延迟关闭 | linger{} | |
SOL_SOCKET | SO_OOBINLINE | · | · | 让接收到的带外数据继续在线留存 | · | int |
SOL_SOCKET | SO_RCVBUF | · | · | 接收缓冲区大小 | int | |
SOL_SOCKET | SO_SNDBUF | · | · | 发送缓冲区大小 | int | |
SOL_SOCKET | SO_RCVLOWAT | · | · | 接收缓冲区低水位标记 | int | |
SOL_SOCKET | SO_SNDLOWAT | · | · | 发送缓冲区低水位标记 | int | |
SOL_SOCKET | SO_RCVTIMEO | · | · | 接收超时 | timeval{} | |
SOL_SOCKET | SO_SNDTIMEO | · | · | 发送超时 | timeval{} | |
SOL_SOCKET | SO_REUSEADDR | · | · | 允许重用本地地址 | · | int |
SOL_SOCKET | SO_REUSEPORT | · | · | 允许重用本地端口 | · | int |
SOL_SOCKET | SO_TYPE | · | 取得套接字类型 | int | ||
SOL_SOCKET | SO_USELOOPBACK | · | · | 路由套接字取得所发送数据的副本 | · | int |
IPPROTO_IP | IP_HDRINCL | · | · | 随数据包含的IP首部 | · | int |
IPPROTO_IP | IP_OPTIONS | · | · | IP首部选项 | ||
IPPROTO_IP | IP_RECVDSTADDR | · | · | 返回目的IP地址 | · | int |
IPPROTO_IP | IP_RECVIF | · | · | 返回接收接口索引 | · | int |
IPPROTO_IP | IP_TOS | · | · | 服务类型和优先权 | int | |
IPPROTO_IP | IP_TTL | · | · | 存活时间 | int | |
IPPROTO_IP | IP_MULTICAST_IP | · | · | 指定外出接口 | in_addr{} | |
IPPROTO_IP | IP_MULTICAST_TTL | · | · | 指定外出TTL | u_char | |
IPPROTO_IP | IP_MULTICAST_LOOP | · | · | 指定是否环回 | u_char | |
IPPROTO_IP | IP_ADD_MEMBERSHIP | · | 加入多播组 | ip_mreq{} | ||
IPPROTO_IP | IP_DROP_MEMBERSHIP | · | 离开多播组 | ip_mreq{} | ||
IPPROTO_IP | IP_BLOCK_SOURCE | · | 阻塞多播源 | ip_mreq_source{} | ||
IPPROTO_IP | IP_UNBLOCK_SOURCE | · | 开通多播源 | ip_mreq_source{} | ||
IPPROTO_IP | IP_ADD_SOURCE_MEMBERSHIP | · | 加入源特定多播组 | ip_mreq_source{} | ||
IPPROTO_IP | IP_DROP_SOURCE_MEMBERSHIP | · | 离开源特定多播组 | ip_mreq_source{} | ||
IPPROTO_ICMPV6 | ICMP6_FILTER | · | · | 指定待传递的ICMPv6消息类型 | icmp6_filter{} | |
IPPROTO_IPV6 | IPV6_CHECKSUM | · | · | 用于原始套接字的校验和字段偏移 | int | |
IPPROTO_IPV6 | IPV6_DONTFRAG | · | · | 丢弃大的分组而非将其分片 | · | int |
IPPROTO_IPV6 | IPV6_NEXTHOP | · | · | 指定下一跳地址 | sockaddr_in6{} | |
IPPROTO_IPV6 | IPV6_PATHMTU | · | 获取当前路径MTU | ip6_mtuinfo{} | ||
IPPROTO_IPV6 | IPV6_RECVDSTOPTS | · | · | 接收目的地选项 | · | int |
IPPROTO_IPV6 | IPV6_RECVHOPLIMIT | · | · | 接收单播跳限 | · | int |
IPPROTO_IPV6 | IPV6_RECVHOPOPTS | · | · | 接收步跳选项 | · | int |
IPPROTO_IPV6 | IPV6_RECVPATHMTU | · | · | 接收路径MTU | · | int |
IPPROTO_IPV6 | IPV6_RECVPKTINFO | · | · | 接收分组信息 | · | int |
IPPROTO_IPV6 | IPV6_RECVRTHDR | · | · | 接收源路径 | · | int |
IPPROTO_IPV6 | IPV6_RECVTCLASS | · | · | 接收流通类别 | · | int |
IPPROTO_IPV6 | IPV6_UNICAST_HOPS | · | · | 默认单播跳限 | int | |
IPPROTO_IPV6 | IPV6_USE_MIN_MTU | · | · | 使用最小MTU | · | int |
IPPROTO_IPV6 | IPV6_V6ONLY | · | · | 禁止v4兼容 | · | int |
IPPROTO_IPV6 | IPV6_XXX | · | · | 黏附性辅助数据 | ||
IPPROTO_IPV6 | IPV6_MULTICAST_IF | · | · | 指定外出接口 | u_int | |
IPPROTO_IPV6 | IPV6_MULTICAST_HOPS | · | · | 指定外出跳限 | int | |
IPPROTO_IPV6 | IPV6_MULTICAST_LOOP | · | · | 指定是否环回 | · | u_int |
IPPROTO_IPV6 | IPV6_JOIN_GROUP | · | 加入多播组 | ipv6_mreq{} | ||
IPPROTO_IPV6 | IPV6_LEAVE_GROUP | · | 离开多播组 | ipv6_mreq{} | ||
IPPROTO_IP或 IPPROTO_IPV6 |
MCAST_JOIN_GROUP | · | 加入多播组 | group_req{} | ||
IPPROTO_IP或 IPPROTO_IPV6 |
MCAST_LEAVE_GROUP | · | 离开多播组 | group_source_req{} | ||
IPPROTO_IP或 IPPROTO_IPV6 |
MCAST_BLOCK_GROUP | · | 阻塞多播组 | group_source_req{} | ||
IPPROTO_IP或 IPPROTO_IPV6 |
MCAST_UNBLOCK_GROUP | · | 开通多播组 | group_source_req{} | ||
IPPROTO_IP或 IPPROTO_IPV6 |
MCAST_JOIN_SOURCE_GROUP | · | 加入源特定多播组 | group_source_req{} | ||
IPPROTO_IP或 IPPROTO_IPV6 |
MCAST_LEAVE_SOURCE_GROUP | · | 离开源特定多播组 | group_source_req{} | ||
IPPROTO_TCP | TCP_MAXSEG | · | · | TCP最大分节大小 | int | |
IPPROTO_TCP | TCP_NODELAY | · | · | 禁止Nagle算法 | · | int |
IPPROTO_SCTP | SCTP_ADAPTION_LAYER | · | · | 适配层指示 | stcp_setadaption{} | |
IPPROTO_SCTP | SCTP_ASSOCINFO | · | 检查并设置关联信息 | stcp_assocparams{} | ||
IPPROTO_SCTP | SCTP_AUTOCLOSE | · | · | 自动关闭操作 | int | |
IPPROTO_SCTP | SCTP_DEFAULT_SEND_PARAM | · | · | 默认发送参数 | stcp_sndrcvinfo{} | |
IPPROTO_SCTP | SCTP_DISABLE_FRAGMENTS | · | · | SCTP分片 | · | int |
IPPROTO_SCTP | SCTP_EVENTS | · | · | 感兴趣事件的通知 | sctp_event_subscribe{} | |
IPPROTO_SCTP | SCTP_GET_PEER_ADDR_INFO | 获取对端地址状态 | sctp_paddrinfo{} | |||
IPPROTO_SCTP | SCTP_I_WANT_MAPPED_V4_ADDR | · | · | 映射的v4地址 | · | int |
IPPROTO_SCTP | SCTP_INTTMSG | · | · | 默认的INIT参数 | sctp_initmsg{} | |
IPPROTO_SCTP | SCTP_MAXBURST | · | · | 最大猝发大小 | int | |
IPPROTO_SCTP | SCTP_MAXSEG | · | · | 最大分片大小 | int | |
IPPROTO_SCTP | SCTP_NODELAY | · | · | 禁止Nagle算法 | · | int |
IPPROTO_SCTP | SCTP_PEER_ADDR_PARAMS | · | 对端地址参数 | sctp_paddrparams{} | ||
IPPROTO_SCTP | SCTP_PRIMARY_ADDR | · | 主目的地址 | sctp_setprim{} | ||
IPPROTO_SCTP | SCTP_RTOINFO | · | RTO信息 | sctp_rtoinfo{} | ||
IPPROTO_SCTP | SCTP_SET_PEER_PRIMARY_ADDR | · | 对端的主目的地址 | sctp_setpeerprim{} | ||
IPPROTO_SCTP | SCTP_STATUS | · | 获取关联状态 | sctp_status{} |
说明:
- 数据类型形如linger{}, 表示struct linger;
- 标有"标志"的列指出一个选项是否为标志选项. 当给这些标志选项调用getsockopt函数时, *optval是一个整数. *optval中返回的值为0, 表示相应的选项被禁止, 不为0表示相应的选项被启用;
- setsockopt函数需要一个不为0 的optval值来启用选项, 为0 的optval来禁止选项;
- 如果"标志"列不含"·", 那么相应选项用于在用户进程与系统之间传递所指定数据类型的值;
3. 检查选项是否受支持并获取默认值
思路: 利用getsockopt 获取每个level对应的选项名, 如果没有出错, 则证明支持该选项; 如果出错, 则说明不支持. 如果读取出来的值大小, 跟选项汇总表不一致, 则表明可能有哪里出问题, 打印出来.
一个sockopt的判断伪代码
sockfd = socket();
if (getsockopt(fd, opt_level, opt_name, &val, &len) < 0) {
// getsockopt error
...
}
else {
// no getsockopt error
// print opt_val_str
}
如果IPV6_DONTFRAG, SCTP_AUTOCLOSE等宏定义无法识别, 请确认安装好libsctp-dev, lksctp-tools库后, 然后#include <netinet/sctp.h>
安装命令, 详见 Unix下基于SCTP socket的通信:一对一场景 | 简书
$ sudo apt-get install libsctp-dev lksctp-tools
源代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/sctp.h>
/* UNP官方代码 */
#include "ourhdr.h"
union val {
int i_val;
long l_val;
struct linger linger_val;
struct timeval timeval_val;
} val;
int Socket(int domain, int type, int protocol);
static char *sock_str_flag(union val *, int);
static char *sock_str_int(union val *, int);
static char *sock_str_linger(union val *, int);
static char *sock_str_timeval(union val *, int);
/**
* 自定义结构sock_opts, 包含了获得或输出套接字选项的所有信息
*/
struct sock_opts {
const char *opt_str; /// 字符名称
int opt_level; /// 级别
int opt_name; /// 名称
char *(*opt_val_str)(union val *, int); /// 函数指针, 用于输出
} sock_opts[] = {
{"SO_BROADCAST", SOL_SOCKET, SO_BROADCAST, sock_str_flag},
{"SO_DEBUG", SOL_SOCKET, SO_DEBUG, sock_str_flag},
{"SO_DONTROUTE", SOL_SOCKET, SO_DONTROUTE, sock_str_flag},
{"SO_ERROR", SOL_SOCKET, SO_ERROR, sock_str_int },
{"SO_KEEPALIVE", SOL_SOCKET, SO_KEEPALIVE, sock_str_flag},
{"SO_LINGER", SOL_SOCKET, SO_LINGER, sock_str_linger},
{"SO_OOBINLINE", SOL_SOCKET, SO_OOBINLINE, sock_str_flag},
{"SO_RCVBUF", SOL_SOCKET, SO_RCVBUF, sock_str_int },
{"SO_SNDBUF", SOL_SOCKET, SO_SNDBUF, sock_str_int },
{"SO_RCVLOWAT", SOL_SOCKET, SO_RCVLOWAT, sock_str_int },
{"SO_SNDLOWAT", SOL_SOCKET, SO_SNDLOWAT, sock_str_int },
{"SO_RCVTIMEO", SOL_SOCKET, SO_RCVTIMEO, sock_str_timeval },
{"SO_SNDTIMEO", SOL_SOCKET, SO_SNDTIMEO, sock_str_timeval },
{"SO_REUSEADDR", SOL_SOCKET, SO_REUSEADDR, sock_str_flag },
#ifdef SO_REUSEPORT
{"SO_REUSEPORT", SOL_SOCKET, SO_REUSEPORT, sock_str_flag},
#else
{"SO_REUSEPORT", 0, 0, NULL},
#endif
{"SO_TYPE", SOL_SOCKET, SO_TYPE, sock_str_int },
#ifdef SO_USELOOPBACK
{"SO_USELOOPBACK",SOL_SOCKET, SO_USELOOPBACK, sock_str_flag},
#else
{"SO_USELOOPBACK", 0, 0, NULL},
#endif
{"IP_TOS", IPPROTO_IP, IP_TOS, sock_str_int },
{"IP_TTL", IPPROTO_IP, IP_TTL, sock_str_int },
{"IPV6_DONTFRAG", IPPROTO_IPV6, IPV6_DONTFRAG, sock_str_flag},
{"IPV6_UNICAST_HOPS", IPPROTO_IPV6, IPV6_UNICAST_HOPS, sock_str_int },
{"IPV6_V6ONLY", IPPROTO_IPV6, IPV6_V6ONLY, sock_str_flag},
{"TCP_MAXSEG", IPPROTO_TCP, TCP_MAXSEG, sock_str_int },
{"TCP_NODELAY", IPPROTO_TCP, TCP_NODELAY, sock_str_flag},
{"SCTP_AUTOCLOSE", IPPROTO_SCTP, SCTP_AUTOCLOSE, sock_str_int },
#ifdef SCTP_MAXBURST
{"SCTP_MAXBURST", IPPROTO_SCTP, SCTP_MAXBURST, sock_str_int },
#else
{"SCTP_MAXBURST", 0, 0, NULL},
#endif
{"SCTP_MAXSEG", IPPROTO_SCTP, SCTP_MAXSEG, sock_str_int },
{"SCTP_NODELAY", IPPROTO_SCTP, SCTP_NODELAY, sock_str_flag},
{NULL, 0, 0, NULL}
};
int main()
{
int fd;
socklen_t len;
struct sock_opts *ptr;
for (ptr = sock_opts; ptr->opt_str != NULL; ++ptr) {
printf("%s: ", ptr->opt_str);
if (ptr->opt_val_str == NULL) {
printf("(undefined)\n");
}
else {
switch(ptr->opt_level) {
case SOL_SOCKET:
case IPPROTO_IP:
case IPPROTO_TCP:
fd = Socket(AF_INET, SOCK_STREAM, 0);
break;
#ifdef IPV6
case IPPROTO_IPV6:
fd = Socket(AF_INET6, SOCK_STREAM, 0);
break;
#endif
#ifdef IPPROTO_SCTP
case IPPROTO_SCTP:
fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
break;
#endif
default:
// err_quit("Can't create fd for level %d\n", ptr->opt_level);
fprintf(stderr, "Can't create fd for level %d\n", ptr->opt_level);
}
len = sizeof(val);
// if (fd < 0) continue;
if (getsockopt(fd, ptr->opt_level, ptr->opt_name, &val, &len) == -1) {
err_ret("getsockopt error");
}
else {
printf("default = %s\n", (*ptr->opt_val_str)(&val, len));
}
close(fd);
}
}
return 0;
}
int Socket(int domain, int type, int protocol)
{
int fd = socket(domain, type, protocol);
if (fd < 0) {
perror("socket error");
return -1;
}
return fd;
}
static char strres[128];
static char *sock_str_flag(union val *ptr, int len)
{
if (len != sizeof(int))
snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
else
snprintf(strres, sizeof(strres), "%s", (ptr->i_val == 0) ? "off": "on");
return strres;
}
static char *sock_str_int(union val *ptr, int len)
{
if (len != sizeof(int))
snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
else
snprintf(strres, sizeof(strres), "%d\t", ptr->i_val);
return strres;
}
static char *sock_str_linger(union val *ptr, int len)
{
if (len != sizeof(struct linger))
snprintf(strres, sizeof(strres), "size (%d) not sizeof(struct linger)", len);
else
snprintf(strres, sizeof(strres), "l_onoff = %d, l_linger = %d\t", ptr->linger_val.l_onoff, ptr->linger_val.l_linger);
return strres;
}
static char *sock_str_timeval(union val *ptr, int len)
{
if (len != sizeof(struct timeval))
snprintf(strres, sizeof(strres), "size (%d) not sizeof(struct timeval)", len);
else
snprintf(strres, sizeof(strres), "%ld sec, %ldusec\t", ptr->timeval_val.tv_sec, ptr->timeval_val.tv_usec);
return strres;
}
运行结果:
SO_BROADCAST: default = off
SO_DEBUG: default = off
SO_DONTROUTE: default = off
SO_ERROR: default = 0
SO_KEEPALIVE: default = off
SO_LINGER: default = l_onoff = 0, l_linger = 0
SO_OOBINLINE: default = off
SO_RCVBUF: default = 131072
SO_SNDBUF: default = 16384
SO_RCVLOWAT: default = 1
SO_SNDLOWAT: default = 1
SO_RCVTIMEO: default = 0 sec, 0usec
SO_SNDTIMEO: default = 0 sec, 0usec
SO_REUSEADDR: default = off
SO_REUSEPORT: default = off
SO_TYPE: default = 1
SO_USELOOPBACK: (undefined)
IP_TOS: default = 0
IP_TTL: default = 64
Can't create fd for level 41
IPV6_DONTFRAG: getsockopt error: Bad file descriptor
Can't create fd for level 41
IPV6_UNICAST_HOPS: getsockopt error: Bad file descriptor
Can't create fd for level 41
IPV6_V6ONLY: getsockopt error: Bad file descriptor
TCP_MAXSEG: default = 536
TCP_NODELAY: default = off
SCTP_AUTOCLOSE: default = 0
SCTP_MAXBURST: (undefined)
SCTP_MAXSEG: default = size (8) not sizeof(int)
SCTP_NODELAY: default = off
4. 常用套接字选项
4.1 SO_REUSEADDR 选项
可通过设置SO_REUSEADDR 选项,强制使用被处于TIME_WAIT状态的连接占用的socket地址。即使sock处于TIME_WAIT状态,与之绑定的socket地址,也可以立即被重用。也可以通过修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle
,来快速回收被关闭的socket,从而使得TCP连接不进入TIME_WAIT状态,进而允许应用程序立即重用本地socket地址。
常见场景:bind地址失败,还能继续对该sock fd进行bind。因为bind会改变套接字内部状态,如果不加上SO_REUSEADDR 选项,无法立即用于重新bind。
重用本地址方法:
int sock = socket(AF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
int reuse = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int ret = bind(sock, (struct sockaddr*)&address, sizeof(address)); /* SO_REUSEADDR选项可以让bind地址失败后,还能继续bind */
4.2 SO_RCVBUF, SO_SNDBUF 选项
SO_RCVBUF表示TCP接收缓冲区大小;
SO_SNDBUF表示TCP发送缓冲区大小;
用setsockopt设置TCP接收缓冲区和发送缓冲区大小时,系统会将其值加倍,并且不得小于某个最小值。
TCP接收缓冲区最小值256byte, 发送缓冲区最小值2048byte(对于不同系统,值可能不一样)。
这样做的目的在于:确保一个TCP连接有足够的空闲缓冲区来处理拥塞(i.g. 快速重传算法就期望TCP接收缓冲区至少容纳4个大小为SMSS的TCP报文段)。
可以通过内核参数/proc/sys/net/ipv4/tcp_rmem
和/proc/sys/net/ipv4/tcp_wmem
,来修改TCP接收缓冲区、发送缓冲区的大小没有限制。
修改TCP发送缓冲区的客户端程序,关键代码:
typedef struct sockaddr SA;
int sendbuf = atoi(arg[3]);
int len = sizeof(sendbuf);
/* 先设置TCP发送缓冲区大小,然后立即读取 */
setsockopt(sock, SOL_SOCKET, &sendbuf, sizeof(sendbuf)); /* 单位byte */
getsockopt(sock, SOL_SOCKET, &sendbuf, (socklen_t *)&len);
printf("TCP send buffer size after setting is %d\n", sendbuf);
if (connect(sock, (SA *)&servaddr, sizeof(servaddr)) != -1) { /* tcp connect server */
...
}
...
修改TCP接收缓冲区的服务器程序,关键代码:
int recv = atoi(argv[3]);
int len = sizeof(len);
/* 先设置接收缓冲区大小,然后立即读取 */
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof(recvbuf)); /* 单位byte */
getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, (socklen_t *)&len);
printf("TCP receive buffer size after setting is %d\n", recvbuf);
bind(sock, (SA *)&servaddr, sizeof(servaddr));
...
4.3 SO_RCVLOWAT, SO_SNDLOWAT 选项
SO_RCVLOWAT 表示TCP接收缓冲区的低水平位标记,默认值1byte
SO_SNDLOWAT 表示TCP发送缓冲区的低水平位标记,默认值1byte
这2个选项,用于判断socket是否可读,或可写:
当TCP接收缓冲区中,可读数据的总数 > 低水平位标记时,IO系统将通知应用程序可从对应socket读取数据;
当TCP发送缓冲区中,空闲空间 > 低水平位标记时, IO系统将通知应用程序可写数据到socket;
4.4 SO_LINGER 选项
SO_LINGER 选项用于控制close关闭TCP连接时的行为。默认情况,调用close关闭socket时,会立即返回,TCP模块负责把socket对应的发送缓冲区残留数据发送给对方。
setsockopt/getsockopt 设置/获取SO_LINGER选项值时,要传递给一个linger类型的结构体,其定义如下:
#include <sys/socket.h>
struct linger {
int l_onoff; /* 开启(非0),关闭(0)该选项 */
int l_linger; /* 滞留时间 */
};
1)l_onoff值为0,此时SO_LINGER选项不起作用,close用默认行为来关闭socket;
2)l_onoff值非0,linger_为0,此时调用close立即返回,TCP模块将丢弃被关闭socket发送缓冲区中的数据,同时给对端发送一个RST分节。---- 终止异常连接。
3)l_onoff值非0,l_linger > 0,此时close行为取决于2个条件:
1.被关闭的socket对应发送缓冲区,是否还有残留数据;
2.该socket是阻塞,还是非阻塞的。
对于阻塞socket,close将等待一段长为l_linger的时间,直到TCP模块发送完所有残留数据,并得到对端确认,那么close将返回-1,且errno被设置为EWOULDBLOCK。 如果socket是非阻塞的,close将立即返回,此时需要根据其返回值和errno来判断残留数据是否已经发送完毕。
5. fcntl函数和ioctl
fcntl函数可执行各种描述符控制操作.
fcntl, ioctl和路由套接字操作汇总表:
操作 | fcntl | ioctl | 路由套接字 | POSIX |
---|---|---|---|---|
设置套接字为非阻塞式I/O型 | F_SETFL, O_NONBLOCK | FIONBIO | fcntl | |
设置套接字为信号驱动式I/O型 | F_SETFL, O_ASYNC | FIOASYNC | fcntl | |
设置套接字属主 | F_SETDOWN | SIOCSPGRP或 FIOSETOWN |
fcntl | |
获取套接字属主 | F_GETDOWN | SIOCSPGRP或 FIOSETOWN |
fcntl | |
获取套接字接收缓冲区的字节数 | FIONREAD | fcntl | ||
测试套接字是否处于带外标志 | SIOCATMARK | sockatmark | ||
获取接口列表 | SIOCGIFCONF | sysctl | ||
接口操作 | SIOC[GS]IFxxx | |||
ARP高速缓存操作 | SIOCxARP | RTM_xxx | ||
路由表操作 | SIOCxxxxRT | RTM_xxx |
说明:
1)前6个操作可由任何进程应用于套接字, 接着2个接口操作较少见, 最后2个操作(ARP和路由表操作)由ifconfig, route之类管理程序执行;
2)POSIX一列表明是POSIX规定的方法, 推荐使用;
3)sockatmark作为测试是否处于带外标志的首选方法;
5.1 fcntl与socket网络编程
fcntl提供了与socket编程相关的特性:
-
非阻塞式I/O
以F_SETFL方式设置选项O_NONBLOCK文件状态标志 -
信号驱动式I/O
以F_SETFL方式设置选项O_ASYNC, 套接字一旦状态变化, 内核就产生一个SIGIO信号 -
以F_SETDOWN指定用于接收SIGIO和SIGURG信号的套接字属主(pid或pgid).
F_GETDOWN返回套接字当前属主
5.2 fcntl函数
- 原型
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
-
参数
命令cmd有多种用途, 其中跟socket编程相关的:
1)获取文件标志: F_GETFL命令获取由F_SETFL命令设置的文件标志;
2)影响套接字描述符: O_NONBLOCK(非阻塞式IO), O_ASYNC(信号驱动式IO); -
返回值
成功时, 返回值为0, 参数输出取决于cmd命令; 失败时, 返回-1, errno被设置.
5.3 fcntl使用示例
fcntl开启非阻塞IO的典型代码:
int flags;
if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
err_sys("F_GETFL error");
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) {
err_sys("F_SETFL error");
}
关闭非阻塞IO标志的典型代码:
int flags;
if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
err_sys("F_GETFL error");
}
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) {
err_sys("F_SETFL error");
}
参考
《UNP》, 《Linux高性能服务器编程》