socket 连接参数探究

探究Linux环境下的socket连接参数的设置。

设置SO_KEEPALIVE选项,将这个选项设置为1,代表打开KeepAlive机制。
设置TCP_KEEPIDLE选项,值为5秒,代表如果TCP连接上有五秒钟没有任何数据包传输,
                    则启动保活机制,发送TCP Keep-alive机制。默认为2小时。
设置TCP_KEEPINTVL选项,值为1秒,代表如果启动保活机制,
                    则每隔1秒发送一个Keep-alive包。默认为75秒。
设置TCP_KEEPCNT选项,值为3,代表如果对端对3次Keep-alive数据包都没有正常响应,
                  则判断对端已经崩溃。默认为9。

使用举例

import redis
import socket

rds = redis.Redis(
    socket_timeout=1,
    socket_connect_timeout=0.5,

    # 代表打开KeepAlive机制
    socket_keepalive=True,

    socket_keepalive_options={
        # 代表如果TCP连接上有五秒钟没有任何数据包传输, 则启动保活机制,发送TCP Keep-alive机制
        socket.TCP_KEEPIDLE: 5,
        # 代表如果对端对3次Keep-alive数据包都没有正常响应,则判断对端已经崩溃。
        socket.TCP_KEEPCNT: 5,
        # 代表如果启动保活机制,则每隔1秒发送一个Keep-alive包
        socket.TCP_KEEPINTVL: 1
    },
    # 连接超时,或者执行超时,是否重试
    retry_on_timeout=True
)
TCP_USER_TIMEOUT选项是TCP层的socket选项,选项接受unsigned int类型的值。
                值为数据包被发送后未接收到ACK确认的最大时长,以毫秒为单位,
                例如设置为10000时,代表如果发送出去的数据包在十秒内未收到ACK确认,
                则下一次调用send或者recv,则函数会返回-1,
                errno设置为ETIMEOUT,代表connection timeout。

该系统调用函数的原型:
#include <sys/socket.h>
int setsockopt( int socket, int level, int option_name, const void *option_value, size_t option_len);
第一个参数socket是套接字描述符;第二个参数level是被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET。第三个参数option_name指定准备设置的选项,option_name可以有哪些取值,这取决于level,以linux 2.6内核为例(在不同的平台上,这种关系可能会有不同),在套接字级别上(SOL_SOCKET),option_name可以有以下取值:
1. SO_DEBUG,打开或关闭调试信息。
当option_value不等于0时,打开调试信息,否则,关闭调试信息。它实际所做的工作是在sock->sk->sk_flag中置SOCK_DBG(第10)位,或清SOCK_DBG位。
2. SO_REUSEADDR,打开或关闭地址复用功能。
当option_value不等于0时,打开,否则,关闭。它实际所做的工作是置sock->sk->sk_reuse为1或0。
3. SO_DONTROUTE,打开或关闭路由查找功能。
当option_value不等于0时,打开,否则,关闭。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。
4. SO_BROADCAST,允许或禁止发送广播数据。
当option_value不等于0时,允许,否则,禁止。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。
5. SO_SNDBUF,设置发送缓冲区的大小。
发送缓冲区的大小是有上下限的,其上限为256 * (sizeof(struct sk_buff) + 256),下限为2048字节。该操作将sock->sk->sk_sndbuf设置为val * 2,之所以要乘以2,是防止大数据量的发送,突然导致缓冲区溢出。最后,该操作完成后,因为对发送缓冲的大小作了改变,要检查sleep队列,如果有进程正在等待写,将它们唤醒。
6. SO_RCVBUF,设置接收缓冲区的大小。
接收缓冲区大小的上下限分别是:256 * (sizeof(struct sk_buff) + 256)和256字节。该操作将sock->sk->sk_rcvbuf设置为val * 2。
7. SO_KEEPALIVE,套接字保活。
如果协议是TCP,并且当前的套接字状态不是侦听(listen)或关闭(close),那么,当option_value不是零时,启用TCP保活定时器,否则关闭保活定时器。对于所有协议,该操作都会根据option_value置或清sock->sk->sk_flag中的 SOCK_KEEPOPEN位。
8. SO_OOBINLINE,紧急数据放入普通数据流。
该操作根据option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。
9. SO_NO_CHECK,打开或关闭校验和。
该操作根据option_value的值,设置sock->sk->sk_no_check。
10. SO_PRIORITY,设置在套接字发送的所有包的协议定义优先权。Linux通过这一值来排列网络队列。
这个值在0到6之间(包括0和6),由option_value指定。赋给sock->sk->sk_priority。
11. SO_LINGER,如果选择此选项, close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后>才会返回. 否则, 调用将立即返回。
该选项的参数(option_value)是一个linger结构:
struct linger {
int l_onoff;/* 延时状态(打开/关闭) */
int l_linger; /* 延时多长时间 */
};
如果linger.l_onoff值为0(关闭),则清sock->sk->sk_flag中的SOCK_LINGER位;否则,置该位,并赋sk->sk_lingertime值为linger.l_linger。
12. SO_PASSCRED,允许或禁止SCM_CREDENTIALS 控制消息的接收。
该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_PASSCRED位。
13. SO_TIMESTAMP,打开或关闭数据报中的时间戳接收。
该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_RCVTSTAMP位,如果打开,则还需设sock->sk->sk_flag中的SOCK_TIMESTAMP位,同时,将全局变量netstamp_needed加1。
14. SO_RCVLOWAT,设置接收数据前的缓冲区内的最小字节数。
在Linux中,缓冲区内的最小字节数是固定的,为1。即将sock->sk->sk_rcvlowat固定赋值为1。
15. SO_RCVTIMEO,设置接收超时时间。
该选项最终将接收超时时间赋给sock->sk->sk_rcvtimeo。
16. SO_SNDTIMEO,设置发送超时时间。
该选项最终将发送超时时间赋给sock->sk->sk_sndtimeo。
17. SO_BINDTODEVICE,将套接字绑定到一个特定的设备上。
该选项最终将设备赋给sock->sk->sk_bound_dev_if。
18. SO_ATTACH_FILTER和SO_DETACH_FILTER。
关于数据包过滤,它们最终会影响sk->sk_filter。
以上所介绍的都是在SOL_SOCKET层的一些套接字选项,如果超出这个范围,给出一些不在这一level的选项作为参数,最终会得到- ENOPROTOOPT的返回值。但以上的分析仅限于这些选项对sock-sk的值的影响,这些选项真正如何发挥作用,我们的探索道路将漫漫其修远。
如果不在套接字级别上设置选项,即setsockopt系统调用的参数level不设为SOL_SOCKET,那么sys_setsockopt的实现会直接调用sock->ops->setsockopt。对MY_PF_INET域的RAW协议来讲,sock->ops = myinet_sockraw_ops,而myinet_sockraw_ops.setsockopt = sock_common_setsockopt。
而sock_common_setsockopt直接调用sock->sk->sk_prot->setsockopt。对于RAW协议来讲,即myraw_setsockopt。
下面关注myraw_setsockopt的实现。对于RAW协议来讲,level还可以有两种取值:SOL_IP和SOL_RAW。 myraw_setsockopt首先检查level是否为SOL_IP,如果是,调用myip_setsockopt函数,该函数实现IP级别上的选项,否则,为SOL_RAW级别上的选项,SOL_RAW级别上只有一个选项,即ICMP_FILTER,在MY_IPPROTO_ICMP协议下有效。它激活绑定到MY_IPPROTO_ICMP协议的一个用于myraw socket特殊的过滤器。该值对每种ICMP消息都有一个位(掩码),可以把那种ICMP消息过滤掉,缺省时是不过滤ICMP消息。
对于ICMP_FILTER选项,myraw_setsockopt调用myraw_seticmpfilter函数,它把option_value赋给 sock->sk->filter,option_value是一个结构体:
struct icmp_filter {
uint_32 data;
};
它是一个32位的位掩码。
关于该位掩码,我们目前知道的是最低位为回显应答的位掩码,由于目前我们的MY_PF_INET域代码还没完善,我们在PF_INET域上进行测试,把下面的代码添加到一个ping程序中,ping程序就收不到来自服务器的回应包了:
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>

#include <linux/in.h>
#include <linux/icmp.h>
int main() 
{
struct icmp_filter filter; 
socklen_t size = sizeof( struct icmp_filter );
int fd = socket( PF_INET, SOCK_RAW, IPPROTO_ICMP );
if( fd < 0 )
perror("error: ");

getsockopt( fd, SOL_RAW, ICMP_FILTER, &filter, &size );
printf("the filter: %x\n", filter.data );

filter.data = 1;
int err = setsockopt( fd, SOL_RAW, ICMP_FILTER, &filter, sizeof(struct icmp_filter) );
if( err < 0 )
perror("error: ");

memset( &filter, 0, sizeof( struct icmp_filter ) );
getsockopt( fd, SOL_RAW, ICMP_FILTER, &filter, &size );
printf("new filter: %x\n", filter.data);

close(fd);
return 0;
}
继续讲关于myraw_setsockopt的实现,如果level是SOL_IP,则调用myip_setsockopt函数。 myip_setsockopt的操作对像是struct socket sock的成员struct sock sk。并把sk强制转化为struct inet_sock: inet = inet_sk(sk)。
如果option_name在MRT_BASE和MRT_BASE+10之间,则调用myip_mroute_setsockopt函数,关于mroute,后面再给出分析。
IP_OPTIONS:设置将由该套接字发送的每个包的IP选项。
其option_value是一个结构体struct ip_options。该选项首先分配一个这样的结构体,然后用这个结构体替代inet->opt指向的结构体。如果协议类型是 SOCK_STREAM的话,从struct tcp_sock *tp中,tp->ext_header_len减去旧的inet->opt->optlen, 再加上新的opt->optlen。最后调用tcp_sync_mss进行同步,有关TCP的一些细节,我们在实现TCP协议时再分析。
IP_PKTINFO:传递一条包含pktinfo结构(该结构提供一些来访包的相关信息)的IP_PKTINFO辅助信息。
这个选项只对数据报类的套接字有效。
struct in_pktinfo
{
unsigned int ipi_ifindex; /* 接口索引 */
struct in_addr ipi_spec_dst; /* 路由目的地址 */
struct in_addr ipi_addr; /* 头标识目的地址 */
};
ipi_ifindex指的是接收包的接口的唯一索引。ipi_spec_dst指的是路由表记录中的目的地址,而ipi_addr 指的是包头中的目的地址。如果给 sendmsg传递了IP_PKTINFO,那么外发的包会通过在ipi_ifindex中指定的接口发送出去,同时把ipi_spec_dst设置为目的地址。
myip_setsockopt的代码实现中只是根据option_value是否为0,置或清inet->cmsg_flags的IP_CMSG_PKTINFO位。
IP_RECVTTL:
该选项根据option_value的值是否为0,置或清inet->cmsg_flags的IP_CMSG_TTL位,具体用途,留待日后分析。
IP_RECVTOS:
如果打开了这个选项,则IP_TOS辅助信息会与来访包一起传递。它包含一个字节用来指定包头中的服务/优先>级字段的类型。该字节为一个布尔整型标识。该选项根据option_value的值是否为0,置或清inet->cmsg_flags的IP_CMSG_TOS位。
IP_RECVOPTS:
用一条IP_OPTIONS控制信息传递所有来访的IP选项给用户。路由头标识和其它选项已经为本地主机填好.此选项不支持SOCK_STREAM套接字。该选项根据option_value的值是否为0,置或清inet->cmsg_flags的IP_CMSG_RECVOPTS位。
IP_RETOPTS:
等同于IP_RECVOPTS但是返回的是带有时间戳的未处理的原始选项和在这段路由中未填入的路由记录项目。该>选项根据 option_value的值是否为0,置或清inet->cmsg_flags的IP_CMSG_RETOPTS位。
IP_TOS:
设置源于该套接字的每个IP包的Type-Of-Service(TOS 服务类型)字段。它被用来在网络上区分包的优先级>。TOS是单字节的字段。定义了一些的标准TOS标识:IPTOS_LOWDELAY用来为交互式通信最小化延迟时间,IPTOS_THROUGHPUT用来优化吞吐量,IPTOS_RELIABILITY用来作可靠性优化, IPTOS_MINCOST应该被用作“填充数据”,对于这些数据,低速传输是无关紧要的。至多只能声明这些 TOS 值中的一个,其它的都是无效的,应当被清除。缺省时,Linux首先发送IPTOS_LOWDELAY数据报,但是确切的做法要看配置的排队规则而定。一些高优先级的层次可能会要求一个有效的用户标识0或者CAP_NET_ADMIN能力。优先级也可以以于协议无关的方式通过( SOL_SOCKET, SO_PRIORITY )套接字选项来设置。
该选项的操作置inet->tos = val,sk->sk_priority = rt_tos2priority(val),同时,清sk->sk_dst_cache。
IP_TTL:设置从此套接字发出的包的当前生存时间字段。
该选项置inet->uc_ttl = option_value。
IP_HDRINCL:
该选项只对SOCK_RAW有效,如果提供的话,用户可在用户数据前面提供一个ip头。该选项的操作根据option_value是否为零,置inet->hdrincl为1或0。
IP_MTU_DISCOVER:
为套接字设置Path MTU Discovery setting(路径MTU发现设置)。该选项的操作置inet->pmtudisc = option_value,option_value只允许取值0,1,2。
IP_SOL层上余下的选项还有:
IP_RECVERR,IP_MULTICAST_TTL,IP_MULTICAST_LOOP,IP_MULTICAST_IF, IP_ADD_MEMBERSHIP,IP_DROP_MEMBERSHIP,IP_MSFILTER,IP_BLOCK_SOURCE, IP_UNBLOCK_SOURCE,IP_ADD_SOURCE_MEMBERSHIP,IP_DROP_SOURCE_MEMBERSHIP, MCAST_JOIN_GROUP,MCAST_LEAVE_GROUP,MCAST_JOIN_SOURCE_GROUP, MCAST_LEAVE_SOURCE_GROUP,MCAST_BLOCK_SOURCE,MCAST_UNBLOCK_SOURCE, MCAST_MSFILTER,IP_ROUTER_ALERT,IP_FREEBIND,IP_IPSEC_POLICY, IP_XFRM_POLICY。
在涉及到相关内容时,再进行一一分析

版权声明:除特别注明外,本站所有文章均为王晨曦个人站点原创

转载请注明:出处来自王晨曦个人站点 » socket 连接参数探究

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注