首页 » 网站建设 » phpsocket_listen参数技巧_从Linux源码看Socket的listen及连接队列

phpsocket_listen参数技巧_从Linux源码看Socket的listen及连接队列

访客 2024-10-31 0

扫一扫用手机浏览

文章目录 [+]

代码如下:

void start_server(){ // server fd int sockfd_server; // accept fd int sockfd; int call_err; struct sockaddr_in sock_addr; ...... call_err=bind(sockfd_server,(struct sockaddr)(&sock_addr),sizeof(sock_addr)); if(call_err == -1){ fprintf(stdout,"bind error!\n"); exit(1); } // 这边便是我们本日的聚焦点listen call_err=listen(sockfd_server,MAX_BACK_LOG); if(call_err == -1){ fprintf(stdout,"listen error!\n"); exit(1); }}

首先我们通过socket系统调用创建了一个socket,个中指定了SOCK_STREAM,而且末了一个参数为0,也便是建立了一个常日所有的TCP Socket。
在这里,我们直接给出TCP Socket所对应的ops也便是操作函数。

phpsocket_listen参数技巧_从Linux源码看Socket的listen及连接队列

Listen系统调用

好了,现在我们直接进入Listen系统调用吧。

phpsocket_listen参数技巧_从Linux源码看Socket的listen及连接队列
(图片来自网络侵删)

#include <sys/socket.h>// 成功返回0,缺点返回-1,同时缺点码设置在errnoint listen(int sockfd, int backlog);

把稳,这边的listen调用是被glibc的INLINE_SYSCALL装过一层,其将返回值改动为只有0和-1这两个选择,同时将缺点码的绝对值设置在errno内。
这里面的backlog是个非常主要的参数,如果设置不好,是个很暗藏的坑。
对付java开拓者而言,基本用的现成的框架,而java本身默认的backlog设置大小只有50。
这就会引起一些奇妙的征象,这个在本文中会进行讲解。

接下来,我们就进入Linux内核源码栈吧

listen|->INLINE_SYSCALL(listen......)|->SYSCALL_DEFINE2(listen, int, fd, int, backlog)/ 检测对应的描述符fd是否存在,不存在,返回-BADF|->sockfd_lookup_light/ 限定传过来的backlog最大值不超出 /proc/sys/net/core/somaxconn|->if ((unsigned int)backlog > somaxconn) backlog = somaxconn|->sock->ops->listen(sock, backlog) <=> inet_listen

值得把稳的是,Kernel对付我们传进来的backlog值做了一次调度,让其无法>内核参数设置中的somaxconn。

须要C/C++ Linux高等做事器架构师学习资料后台私信“资料”(包括C/C++,Linux,golang技能,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)

inet_listen

接下来便是核心调用程序inet_listen了。

int inet_listen(struct socket sock, int backlog){/ Really, if the socket is already in listen state we can only allow the backlog to be adjusted. if ((sysctl_tcp_fastopen & TFO_SERVER_ENABLE) != 0 && inet_csk(sk)->icsk_accept_queue.fastopenq == NULL) { fastopen的逻辑 if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) != 0)err = fastopen_init_queue(sk, backlog);else if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT2) != 0)err = fastopen_init_queue(sk, ((uint)sysctl_tcp_fastopen) >> 16);elseerr = 0;if (err)goto out;}if(old_state != TCP_LISTEN) {err = inet_csk_listen_start(sk, backlog);}sk->sk_max_ack_backlog =backlog;......}

从这段代码中,第一个故意思的地方便是,listen这个别系调用可以重复调用!
第二次调用的时候仅仅只能修正其backlog行列步队长度(虽然觉得没啥必要)。

首先,我们看下除fastopen之外的逻辑(fastopen往后开单章详细谈论)。
也便是末了的inet_csk_listen_start调用。

int inet_csk_listen_start(struct sock sk, const int nr_table_entries){......// 这里的nr_table_entries即为调度过后的backlog// 但是在此函数内部会进一步将nr_table_entries = min(backlog,sysctl_max_syn_backlog)这个逻辑int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);......inet_csk_delack_init(sk);// 设置socket为listen状态sk->sk_state = TCP_LISTEN;// 检讨端口号if (!sk->sk_prot->get_port(sk, inet->inet_num)){// 打消掉dst cachesk_dst_reset(sk);// 将当前sock链入listening_hash// 这样,当SYN到来的时候就能通过__inet_lookup_listen函数找到这个listen中的socksk->sk_prot->hash(sk);}sk->sk_state = TCP_CLOSE;__reqsk_queue_destroy(&icsk->icsk_accept_queue);// 端口已经被占用,返回缺点码-EADDRINUSEreturn -EADDRINUSE;}

这里最主要的一个调用sk->sk_prot->hash(sk),也便是inet_hash,其将当前sock链入全局的listen hash表,这样就可以在SYN包到来的时候探求到对应的listen sock了。
如下图所示:

如图中所示,如果开启了SO_REUSEPORT的话,可以让不同的Socket listen(监听)同一个端口,这样就能在内核进行创建连接的负载均衡。
在Nginx 1.9.1版本开启了之后,其压测性能达到3倍!

半连接行列步队hash表和全连接行列步队

在笔者一开始翻阅的资料里面,都提到。
tcp的连接行列步队有两个,一个是sync_queue,另一个accept_queue。
但笔者仔细阅读了一下源码,实在并非如此。
事实上,sync_queue实在是个hash表(syn_table)。
另一个行列步队是icsk_accept_queue。

以是在本篇文章里面,将其称为reqsk_queue(request_socket_queue的简称)。
在这里,笔者先给出这两个queue在三次握手时候的涌现机遇。
如下图所示:

当然了,除了上面提到的qlen和sk_ack_backlog这两个计数器之外,还有一个qlen_young,其浸染如下:

qlen_young: 记录的是刚有SYN到达,没有被SYN_ACK重传定时看重传过SYN_ACK同时也没有完成过三次握手的sock数量

如下图所示:

至于SYN_ACK的重传定时器在内核中的代码为下面所示:

static void tcp_synack_timer(struct sock sk){inet_csk_reqsk_queue_prune(sk, TCP_SYNQ_INTERVAL, TCP_TIMEOUT_INIT, TCP_RTO_MAX);}

这个定时器在半连接行列步队不为空的情形下,以200ms(TCP_SYNQ_INTERVAL)为间隔运行一次。
限于篇幅,笔者就在这里不多谈论了。

为什么要存在半连接行列步队

由于根据TCP协议的特点,会存在半连接这样的网络攻击存在,即一直的发SYN包,而从不回应SYN_ACK。
如果发一个SYN包就让Kernel建立一个花费极大的sock,那么很随意马虎就内存耗尽。
以是内核在三次握手成功之前,只分配一个占用内存极小的request_sock,以防止这种攻击的征象,再合营syn_cookie机制,只管即便抵御这种半连接攻击的风险。

半连接hash表和全连接行列步队的限定

由于全连接行列步队里面保存的是占用内存很大的普通sock,以是Kernel给其加了一个最大长度的限定。
这个限定为:

下面三者中的最小值1.listen系统调用中传进去的backlog2./proc/sys/inet/ipv4/tcp_max_syn_backlog3./proc/sys/net/core/somaxconn 即min(backlog,tcp_ma_syn_backlog,somaxcon)

如果超过这个somaxconn会被内核丢弃,如下图所示:

这种情形的连接丢弃会发生比较诡异的征象。
在不设置tcp_abort_on_overflow的时候,client端无法感知,就会导致即在第一笔调用的时候才会知道对端连接丢弃了。

那么,怎么让client端在这种情形下感知呢,我们可以设置一下tcp_abort_on_overflow

echo '1' > tcp_abort_on_overflow

设置后,如下图所示:

当然了,最直接的还是调大backlog!

listen(fd,2048)echo '2048' > /proc/sys/inet/ipv4/tcp_max_syn_backlogecho '2048' > /proc/sys/net/core/somaxconnbacklog对半连接行列步队的影响

这个backlog对半连接行列步队也有影响,如下代码所示:

/ TW buckets are converted to open requests without limitations, they conserve resources and peer is evidently real one. /// 在开启SYN cookie的情形下,如果半连接行列步队长度超过backlog,则发送cookie// 否则丢弃if (inet_csk_reqsk_queue_is_full(sk) && !isn) {want_cookie = tcp_syn_flood_action(sk, skb, "TCP");if (!want_cookie)goto drop;}/ Accept backlog is full. If we have already queued enough of warm entries in syn queue, drop request. It is better than clogging syn queue with openreqs with exponentially increasing timeout. /// 在全连接行列步队满的情形下,如果有young_ack,那么直接丢弃if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);goto drop;}

我们在dmesg里面常常看到的

Possible SYN flooding on port 8080

便是由于半连接行列步队满往后,Kernel发送cookie校验而导致。

标签:

相关文章

微信第三方登录便捷与安全的完美融合

社交平台已成为人们日常生活中不可或缺的一部分。微信作为我国最受欢迎的社交软件之一,拥有庞大的用户群体。为了方便用户在不同平台间切换...

网站建设 2025-02-18 阅读0 评论0

广东高速代码表解码高速公路管理智慧

高速公路作为国家交通动脉,连接着城市与城市,承载着巨大的物流和人流。广东作为我国经济大省,高速公路网络密布,交通流量巨大。为了更好...

网站建设 2025-02-18 阅读0 评论0