Linux socket编程 套接字选项教程
- 套接字选项概述
===============================
有很多方法来获取和设置套接字的选项, 以影响套接字行为:
- getsockopt和setsocketopt;
- fcntl;
- ioctl;
- -
- 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选项名getset说明标志数据类型SOL\_SOCKETSO\_BROADCAST··允许发送广播数据报·intSOL\_SOCKETSO\_DEBUG··开启调试跟踪·intSOL\_SOCKETSO\_DONTROUTE··绕过外出路由表查询·intSOL\_SOCKETSO\_ERROR·获取待处理错误并清除intSOL\_SOCKETSO\_KEEPALIVE··周期性测试连接是否仍存活·intSOL\_SOCKETSO\_LINEGER··若有数据待发送, 则延迟关闭linger{}SOL\_SOCKETSO\_OOBINLINE··让接收到的带外数据继续在线留存·intSOL\_SOCKETSO\_RCVBUF··接收缓冲区大小intSOL\_SOCKETSO\_SNDBUF··发送缓冲区大小intSOL\_SOCKETSO\_RCVLOWAT··接收缓冲区低水位标记intSOL\_SOCKETSO\_SNDLOWAT··发送缓冲区低水位标记intSOL\_SOCKETSO\_RCVTIMEO··接收超时timeval{}SOL\_SOCKETSO\_SNDTIMEO··发送超时timeval{}SOL\_SOCKETSO\_REUSEADDR··允许重用本地地址·intSOL\_SOCKETSO\_REUSEPORT··允许重用本地端口·intSOL\_SOCKETSO\_TYPE·取得套接字类型intSOL\_SOCKETSO\_USELOOPBACK··路由套接字取得所发送数据的副本·intIPPROTO\_IPIP\_HDRINCL··随数据包含的IP首部·intIPPROTO\_IPIP\_OPTIONS··IP首部选项IPPROTO\_IPIP\_RECVDSTADDR··返回目的IP地址·intIPPROTO\_IPIP\_RECVIF··返回接收接口索引·intIPPROTO\_IPIP\_TOS··服务类型和优先权intIPPROTO\_IPIP\_TTL··存活时间intIPPROTO\_IPIP\_MULTICAST\_IP··指定外出接口in\_addr{}IPPROTO\_IPIP\_MULTICAST\_TTL··指定外出TTLu\_charIPPROTO\_IPIP\_MULTICAST\_LOOP··指定是否环回u\_charIPPROTO\_IPIP\_ADD\_MEMBERSHIP·加入多播组ip\_mreq{}IPPROTO\_IPIP\_DROP\_MEMBERSHIP·离开多播组ip\_mreq{}IPPROTO\_IPIP\_BLOCK\_SOURCE·阻塞多播源ip\_mreq\_source{}IPPROTO\_IPIP\_UNBLOCK\_SOURCE·开通多播源ip\_mreq\_source{}IPPROTO\_IPIP\_ADD\_SOURCE\_MEMBERSHIP·加入源特定多播组ip\_mreq\_source{}IPPROTO\_IPIP\_DROP\_SOURCE\_MEMBERSHIP·离开源特定多播组ip\_mreq\_source{}IPPROTO\_ICMPV6ICMP6\_FILTER··指定待传递的ICMPv6消息类型icmp6\_filter{}IPPROTO\_IPV6IPV6\_CHECKSUM··用于原始套接字的校验和字段偏移intIPPROTO\_IPV6IPV6\_DONTFRAG··丢弃大的分组而非将其分片·intIPPROTO\_IPV6IPV6\_NEXTHOP··指定下一跳地址sockaddr\_in6{}IPPROTO\_IPV6IPV6\_PATHMTU·获取当前路径MTUip6\_mtuinfo{}IPPROTO\_IPV6IPV6\_RECVDSTOPTS··接收目的地选项·intIPPROTO\_IPV6IPV6\_RECVHOPLIMIT··接收单播跳限·intIPPROTO\_IPV6IPV6\_RECVHOPOPTS··接收步跳选项·intIPPROTO\_IPV6IPV6\_RECVPATHMTU··接收路径MTU·intIPPROTO\_IPV6IPV6\_RECVPKTINFO··接收分组信息·intIPPROTO\_IPV6IPV6\_RECVRTHDR··接收源路径·intIPPROTO\_IPV6IPV6\_RECVTCLASS··接收流通类别·intIPPROTO\_IPV6IPV6\_UNICAST\_HOPS··默认单播跳限intIPPROTO\_IPV6IPV6\_USE\_MIN\_MTU··使用最小MTU·intIPPROTO\_IPV6IPV6\_V6ONLY··禁止v4兼容·intIPPROTO\_IPV6IPV6\_XXX··黏附性辅助数据IPPROTO\_IPV6IPV6\_MULTICAST\_IF··指定外出接口u\_intIPPROTO\_IPV6IPV6\_MULTICAST\_HOPS··指定外出跳限intIPPROTO\_IPV6IPV6\_MULTICAST\_LOOP··指定是否环回·u\_intIPPROTO\_IPV6IPV6\_JOIN\_GROUP·加入多播组ipv6\_mreq{}IPPROTO\_IPV6IPV6\_LEAVE\_GROUP·离开多播组ipv6\_mreq{}IPPROTO\_IP或
IPPROTO\_IPV6MCAST\_JOIN\_GROUP·加入多播组group\_req{}IPPROTO\_IP或
IPPROTO\_IPV6MCAST\_LEAVE\_GROUP·离开多播组group\_source\_req{}IPPROTO\_IP或
IPPROTO\_IPV6MCAST\_BLOCK\_GROUP·阻塞多播组group\_source\_req{}IPPROTO\_IP或
IPPROTO\_IPV6MCAST\_UNBLOCK\_GROUP·开通多播组group\_source\_req{}IPPROTO\_IP或
IPPROTO\_IPV6MCAST\_JOIN\_SOURCE\_GROUP·加入源特定多播组group\_source\_req{}IPPROTO\_IP或
IPPROTO\_IPV6MCAST\_LEAVE\_SOURCE\_GROUP·离开源特定多播组group\_source\_req{}IPPROTO\_TCPTCP\_MAXSEG··TCP最大分节大小intIPPROTO\_TCPTCP\_NODELAY··禁止Nagle算法·intIPPROTO\_SCTPSCTP\_ADAPTION\_LAYER··适配层指示stcp\_setadaption{}IPPROTO\_SCTPSCTP\_ASSOCINFO·检查并设置关联信息stcp\_assocparams{}IPPROTO\_SCTPSCTP\_AUTOCLOSE··自动关闭操作intIPPROTO\_SCTPSCTP\_DEFAULT\_SEND\_PARAM··默认发送参数stcp\_sndrcvinfo{}IPPROTO\_SCTPSCTP\_DISABLE\_FRAGMENTS··SCTP分片·intIPPROTO\_SCTPSCTP\_EVENTS··感兴趣事件的通知sctp\_event\_subscribe{}IPPROTO\_SCTPSCTP\_GET\_PEER\_ADDR\_INFO获取对端地址状态sctp\_paddrinfo{}IPPROTO\_SCTPSCTP\_I\_WANT\_MAPPED\_V4\_ADDR··映射的v4地址·intIPPROTO\_SCTPSCTP\_INTTMSG··默认的INIT参数sctp\_initmsg{}IPPROTO\_SCTPSCTP\_MAXBURST··最大猝发大小intIPPROTO\_SCTPSCTP\_MAXSEG··最大分片大小intIPPROTO\_SCTPSCTP\_NODELAY··禁止Nagle算法·intIPPROTO\_SCTPSCTP\_PEER\_ADDR\_PARAMS·对端地址参数sctp\_paddrparams{}IPPROTO\_SCTPSCTP\_PRIMARY\_ADDR·主目的地址sctp\_setprim{}IPPROTO\_SCTPSCTP\_RTOINFO·RTO信息sctp\_rtoinfo{}IPPROTO\_SCTPSCTP\_SET\_PEER\_PRIMARY\_ADDR·对端的主目的地址sctp\_setpeerprim{}IPPROTO\_SCTPSCTP\_STATUS·获取关联状态sctp\_status{}
说明:
- 数据类型形如linger{}, 表示struct linger;
- 标有"标志"的列指出一个选项是否为标志选项. 当给这些标志选项调用getsockopt函数时, *optval是一个整数. *optval中返回的值为0, 表示相应的选项被禁止, 不为0表示相应的选项被启用;
- setsockopt函数需要一个不为0 的optval值来启用选项, 为0 的optval来禁止选项;
- 如果"标志"列不含"·", 那么相应选项用于在用户进程与系统之间传递所指定数据类型的值;
- -
- 检查选项是否受支持并获取默认值
=======================================
思路: 利用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