Overview面试的时候,面试者提到了半连接,全连接队列是由内核中的常量和backlog参数决定的。之前只知道一个queue,于是看代码一探究竟。源码分析源码版本:https://github.com/torvalds/l...d992fe5318d8d7af9510b879439a3c7f283da442代码中有大量的函数指针,对应不同的协议簇,看的时候肯定有很多猜测源代码,但最终结论是可信的,主要是这个实现是有意义的。在入口处搜索“sys_listen”,可以看到backlog确实取了用户输入和sysctl_somaxconn的最小值。int__sys_listen(intfd,intbacklog){structsocket*sock;内部错误,fput_needed;诠释somaxconn;sock=sockfd_lookup_light(fd,&err,&fput_needed);if(sock){somaxconn=sock_net(sock->sk)->core.sysctl_somaxconn;if((unsignedint)backlog>somaxconn)backlog=somaxconn;err=security_socket_listen(sock,backlog);如果(!err)err=sock->ops->listen(sock,backlog);fput_light(袜子->文件,fput_needed);}returnerr;}listenimplementationbacklog写入sk_max_ack_backlog,可以看到sock->ops是一个函数指针结构,搜索“sock->ops=”,可以找到一系列proto_ops。选择base_sock_ops继续观看。staticconststructproto_opsbase_sock_ops={.family=PF_ISDN,.owner=THIS_MODULE,.release=base_sock_release,.ioctl=base_sock_ioctl,.bind=base_sock_bind,.getname=sock_no_getname,.sendmsg=sock_no_sendmsg,.recvmsg=sock_nog,.recvmsg=sock_nog,.sock_no_listen,.shutdown=sock_no_shutdown,.connect=sock_no_connect,.socketpair=sock_no_socketpair,.accept=sock_no_accept,.mmap=sock_no_mmap};发现大部分proto_ops都没有定义listen的实现。搜索“.listen=”,可以找到svc_listen、dn_listen、unix_listen、vsock_listen、x25_listen等。从命名和文件头的描述,猜测inet_listen才是真正的listen,即inet中指定的协议套接字地址继续看inet_listen。可以看到backlog赋值给了sk->sk_max_ack_backlog,其他listen函数也是如此。intinet_listen(structsocket*sock,intbacklog){structsock*sk=sock->sk;无符号字符旧状态;内部错误,tcp_fastopen;锁袜子(sk);错误=-EINVAL;if(sock->state!=SS_UNCONNECTED||sock->type!=SOCK_STREAM)gotoout;old_state=sk->sk_state;如果(!((1<sk_max_ack_backlog,积压);/*真的,如果套接字已经处于侦听状态*我们只能允许调整积压。*/sk_max_ack_backlog的含义搜索sk_max_ack_backlog,可以看到sk_max_ack_backlog控制的就是既定队列的大小:include/net/sock.h:930staticinlinevoidsk_acceptq_removed(structsock*sk){WRITE_ONCE(sk->sk_ack_sklog_,ack_sklog->-1);}staticinlinevoidsk_acceptq_added(structsock*sk){WRITE_ONCE(sk->sk_ack_backlog,sk->sk_ack_backlog+1);}/*注意:如果你认为测试应该是:*returnREAD_ONCE(sk->sk_ack_backlog)>=READ_ONCE(sk->sk_max_ack_backlog);*然后请查看提交64a146513f8f(“[NET]:恢复不正确的接受队列积压更改。”)*/staticinlineboolsk_acceptq_is_full(conststructsock*sk){returnREAD_ONCE(sk->sk_ack_backlog)>READ_ONCE(sk->sk_max_ack_backlog);}此外,还有其他代码可以说明这一点:如inet_connection_sock.c:501accept后,调用reqsk_queue_remove从sk_ack_backlog中减去1tcp_check_req(通过SYN_RECV->ESTABLISHED)->inet_csk_complete_hashdance->inet_csk_reqsk_queue_addadd1sk_ack_backlog。net/ipv4/tcp_minisocks.c:772当接收到sync时,会调用一个函数指针判断sync_recv_sock队列是否满/*OK,ACK有效,创建bigsocket并*把这个segment喂给它。它将重复所有*测试。该段必须将套接字移动到*已建立状态。如果*socket创建后会被丢弃,等着麻烦吧。*/child=inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk,skb,req,NULL,req,&own_req);if(!child)gotolisten_overflow;net/ipv4/tcp_input.c:6772可以看到如果accept队列超过上限,有新的握手完成包,就会触发listen_overflowlisten_overflow的进程,状态为已记录,不会通知客户。net/ipv4/tcp_minisocks.c:788listen_overflow:if(sk!=req->rsk_listener)__NET_INC_STATS(sock_net(sk),LINUX_MIB_TCPMIGRATEREQFAILURE);如果(!sock_net(sk)->ipv4.sysctl_tcp_abort_on_overflow){inet_rsk(req)acked=1;返回空值;}如果正常,inet_csk_complete_hashdance会被调用加入到建立的队列中。真的有syn_recv队列吗?在处理第一次sync握手请求时,只需将request_sock_queue结构的qlen加1,从sync握手请求包开始:net/ipv4/tcp_input.c:6357caseTCP_LISTEN:if(th->ack)return1;如果(th->rst)转到丢弃;如果(th->syn){如果(th->fin)转到丢弃;/*我们有可能处理积压的SYN数据包,*所以我们需要确保在那里禁用BH和RCU。*/rcu_read_lock();local_bh_disable();可接受的=icsk->icsk_af_ops->conn_request(sk,skb)>=0;可以看到,当建立的队列满了,新的sync请求将不会被接受,有意思的是reqsk_queue的判断使用了同样的sk_max_ack_backlog作为上限:net/ipv4/tcp_input.c:6772inttcp_conn_request(structrequest_sock_ops*rsk_ops,conststructtcp_request_sock_ops*af_ops,structsock*sk,structsk_buff*skb{)structtcp_fastopen_cookiefoc={.len=-1};__u32=TCP_SKB_CB(skb)->tcp_tw_isn;结构tcp_options_receivedtmp_opt;结构tcp_sock*tp=tcp_sk(sk);结构网*net=sock_net(sk);结构袜*fastopen_sk=NULL;结构请求袜子*请求;boolwant_cookie=false;结构dst_entry*dst;结构流;/*TWbuckets被转换为没有限制的开放请求,它们节省资源并且peer是*显然是真实的。*/if((net->ipv4.sysctl_tcp_syncookies==2||inet_csk_reqsk_queue_is_full(sk))&&!isn){want_cookie=tcp_syn_flood_action(sk,rsk_ops->slab_name);如果(!want_cookie)转到下降;}if(sk_acceptq_is_full(sk)){NET_INC_STATS(sock_net(sk),LINUX_MIB_LISTENOVERFLOWS);转到下降;}另外也和sysctl_max_syn_backlog值做了比较,当队列长度达到sysctl_max_syn_backlog的半时,会直接drop:net/ipv4/tcp_input.c:6840if(!want_cookie&&!isn),{/*Killthefollowingclause不喜欢这种方式。*/if(!net->ipv4.sysctl_tcp_syncookies&&(net->ipv4.sysctl_max_syn_backlog-inet_csk_reqsk_queue_len(sk)<(net->ipv4.sysctl_max_syn_backlog>>2))&&!tcp_peer_is_proven(req,dst)){/*最后没有syncookies四分之一*积压的都是目的地,*证明是活的。*这意味着我们继续*与目的地沟通,已经记住*到同步洪水的时刻。*/pr_drop_req(req,ntohs(tcp_hdr(skb)->source),rsk_ops->family);转到drop_and_release;}is=af_ops->init_seq(skb);}最后只是将qlen加1:inet_connection_sock.c:923voidinet_csk_reqsk_queue_hash_add(structsock*sk,structrequest_sock*req,unsignedlongtimeout){reqsk_queue_hash_req(req,timeout);inet_csk_reqsk_queue_added(sk);}第三步处理syncack时,从哈希表中获取sockinet_hashtables.h:342staticinlinestructsock*__inet_lookup(structnet*net,structinet_hashinfo*hashinfo,structsk_buff*skb,intdoff,const__be32saddr,const__be16sport,const__be32daddr,const__be16dport,constintdif,constintsdif,bool*refcounted){u16hnum=ntohs(dport);结构袜子*sk;sk=__inet_lookup_established(net,hashinfo,sadd,sport,daddr,hnum,dif,sdif);*refcounted=true;如果(sk)返回sk;*refcounted=false;return__inet_lookup_listener(net,hashinfo,skb,doff,saddr,sport,daddr,hnum,dif,sdif);}inet_hashtable.c:327在建立链接之前,从net结构中计算出hash值(好像是相关的到网络设备)、源地址和主机(目标)端口structsock*__inet_lookup_listener(structnet*net,structinet_hashinfo*hashinfo,structsk_buff*skb,intdoff,const__be32saddr,__be16sport,const__be32daddr,constunsignedshorthnum,constintinthashet_dif,constintstr_b列表)*ilb2;结构袜*结果=NULL;无符号整数哈希2;/*从BPF查找重定向*/如果(结果)转到完成;}hash2=ipv4_portaddr_hash(net,daddr,hnum);ilb2=inet_lhash2_bucket(hashinfo,hash2);inet_hashtable.c:390建立链接后的哈希值由net结构和源、目的地址决定,端口计算。structsock*__inet_lookup_established(structnet*net,structinet_hashinfo*hashinfo,const__be32saddr,const__be16sport,const__be32daddr,constu16hnum,constintdif,constintaddsdif){const_ADDR_COOKIEra(__portpairports=INET_COMBINED_PORTS(运动,hnum);structsock*sk;conststructhlist_nulls_node*node;/*优化这里直接命中,只有监听连接可以*有通配符。*/unsignedinthash=inet_ehashfn(net,daddr,hnum,saddr,sport);unsignedintslot=hash&hashinfo->ehash_mask;structinet_ehash_bucket*head=&hashinfo->ehash[slot];conclusionbacklog决定了icsk_accept_queue队列的大小,这个队列中ESTABLISHTED状态的链接,物理上没有syn_recv队列sense,但是抽象意义上有一个syn_recv队列,backlog也决定了virtualreqsk(sync_recv)队列的大小,实际实现是一个全局哈希表,单独统计每个listensock的大小,这个队列大小为也由tcp_max_syn_backlog控制。fastopen可以跳过上面的限制。服务器应尽可能减少连接数。当全局sock哈希表的冲突比较多时,性能必然会受到影响。reqsk队列中的sock有超时和重传机制。看reqsk_timer_handler,还有newgenerationoldgeneration的优化。因为和backlog参数没有直接关系,这里就不展开了。