int listen(int sockfd, int backlog);
一、listen函数仅由TCP服务器调用,它做两件事情:
- 将一个主动套接字(且未连接的套接字)转化为被动套接字来指示内核“应该接受指向该套接字的连接请求”。
ps:当socket函数创建一个套接字时,他是被假设为一个主动套接字的,所以才需要此处套接字的转化。 主动套接字是可以调用connect发起连接的客户端套接字。 - 将套接字状态由CLOSED转为LISTEN状态。
问:那么根据第一条的结论我们可以提出一个问题:被动套接字还能去连接别的服务器吗?
答:不能。
让我们在server中调用完listen函数后再进行connect另一个服务器。
经测试,运行时会提示
conn: err:: Transport endpoint is already connected
提示传输断点已连接。
二、整个流程分析
- 客户端在connect后激发三次握手,套接字状态由
CLOSED
转变为SYN_SENT
状态。 - 服务器在listen后,套接字状态由
CLOSED
转变为LISTEN
。 - 当来自客户的SYN到达时,TCP在未完成连接队列中创建一个新项,然后响应以三路握手的第二个分节:服务器的SYN响应,其中稍带对客户SYN的ACK。
- 如果三路握手正常,该项就从未完成队列移到已完成连接队列的队尾。
- 当进程调用accept时,已完成连接队列中的队头返回给进程,或者如果该队列为空,那么进程将被投入睡眠,知道TCP在该队列中放入一项才唤醒它。
具体三次握手的状态不做过多解释点击此处查看我的另一篇具体解析。
三、listen在服务器内核为每个给定的监听字描述符维护两个队列:未完成队列和已完成队列
(1)未完成队列:
- 当客户端完成三次握手的第一步,发出并到达服务器时,在未完成连接队列中创建一个新项,来自监听套接字的参数就复制到即将新建立的连接中。
- 套接字处于
SYN_RECV
状态。
(2)已完成队列
- 已完成TCP三路握手的客户,等待accept的调用
- 套接字处于
ESTABLISHED
状态
注:这两个队列都属于服务器内核的调用,真正回到服务器应用层的是accept从已完成队列取出队头
四、backlog
listen的第二个参数。跟系统的链接数量没有任何关系。相当于设置一个瞬间能够处理的阈值。
在Linux2.2以前指未完成队列和已完成队列的长度之和,而在Linux2.2以后指已完成队列长度。
内核会在自己的进程空间维护一个队列以跟踪已完成TCP的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
注意:
(1)backlog
是一个阀值,例如已完成队列最大长度为5,实际上不是限制处理的线程为5,而是当服务器忙不过来了,内核会hold住一个长度为5的已完成队列,一旦服务器忙过来了,就会从这个队列中拿出一个来处理,直到队列为空。
(2)注意不要把backlog
定义为0,因为不同的实现对此有不同的解释,操作系统不同实际队列最大数目会根据特定算法比backlog指定长度增加1个或2个。
如果你不想让任何客户连接到你的监听套接字上,那就直接关掉该监听套接字。
(3)backlog
有一个模糊因子,根据操作系统不同,实际队列最大数目会根据特定算法计算队列上限长度,总之比backlog值略大。(我的实验结果为backlog+1 读者可以自己尝试写一个简单的连接 然后自己指定端口号与backlog数目 通过运行netstat -nt|grep 端口号
查看listen监听队列内容)
我的测试代码放在这里有兴趣的可以查看测试
问:应用进程到底应该指定多大值的backlog呢?
答:
- 若指定一个比内核能够支持的值小的,根据自己情况而定。历史沿用的样例代码总是给出值为5的backlog,处理微小轻量级服务器是够用的。
- 若指定一个比内核能够支持值还大的值,可接受,但是内核会把指定的偏大的值截成自身支持的最大值,而不返回错误。
- 注:查看内核支持的最大backlog数
cat /proc/sys/net/core/somaxconn
五、backlog、somaxconn、tcp_max_syn_backlog
提到backlog就免不了提到这三个backlog、somaxconn、tcp_max_syn_backlog了。
backlog
指已完成队列长度somaxconn
指内核支持的最大长度。
如果定义backlog的长度大于somaxconn会被自动截断为somaxconn的值
(一般情况下为128 ,deepin下为521。
使用cat /proc/sys/net/core/somaxconn
查看)tcp_max_syn_backlog
指未完成队列长度。
(使用cat /proc/sys/net/ipv4/tcp_max_syn_backlog
查看)
问:如果一个请求到达队列时,队列的长度已经到达了阀值,那么这个请求该怎么处理?
答:如果一个请求到达队列时,队列的长度已经到达了阀值,那么这个请求就直接被忽略,而一般不发送信号。
因为如果客户在一定时间没有收到信号就会重发,此时队列说不定就会有空余,而若是回发信号会让客户端以为拒绝连接不会再发来连接请求了,而如果回发特定信号还需设计处理此信号,不如直接丢掉等待重发。
六、listen源码剖析
1.listen的源码入口为socket.c
2.AF_INEF协议族(af_inet.c)的listen实现函数为inet_listen,代码如下:
3.接下来进入inet_csk_listen_start,代码如下:
通过源码剖析,我们可看出在listen第二个参数backlog不超过系统限制的最大值somaxconn时,内核直接使用其作为已完成连接队列的最大长度。如果超过了,那么系统将采用somaxconn作为已完成连接队列的最大长度。
而在inet_listen函数中可以看到 struct sock *sk;探究源码我们便可以知道队列中放的究竟是什么东西。
struct sock {
/*
* Now struct inet_timewait_sock also uses sock_common, so please just
* don't add nothing before this first member (__sk_common) --acme
*/
struct sock_common __sk_common;//下面会说该结构体
#define sk_node __sk_common.skc_node
#define sk_nulls_node __sk_common.skc_nulls_node
#define sk_refcnt __sk_common.skc_refcnt
#define sk_tx_queue_mapping __sk_common.skc_tx_queue_mapping
#define sk_dontcopy_begin __sk_common.skc_dontcopy_begin
#define sk_dontcopy_end __sk_common.skc_dontcopy_end
#define sk_hash __sk_common.skc_hash
#define sk_portpair __sk_common.skc_portpair
#define sk_num __sk_common.skc_num
#define sk_dport __sk_common.skc_dport
#define sk_addrpair __sk_common.skc_addrpair
#define sk_daddr __sk_common.skc_daddr
#define sk_rcv_saddr __sk_common.skc_rcv_saddr
#define sk_family __sk_common.skc_family
#define sk_state __sk_common.skc_state
#define sk_reuse __sk_common.skc_reuse
#define sk_reuseport __sk_common.skc_reuseport
#define sk_ipv6only __sk_common.skc_ipv6only
#define sk_net_refcnt __sk_common.skc_net_refcnt
#define sk_bound_dev_if __sk_common.skc_bound_dev_if
#define sk_bind_node __sk_common.skc_bind_node
#define sk_prot __sk_common.skc_prot
#define sk_net __sk_common.skc_net
#define sk_v6_daddr __sk_common.skc_v6_daddr
#define sk_v6_rcv_saddr __sk_common.skc_v6_rcv_saddr
#define sk_cookie __sk_common.skc_cookie
#define sk_incoming_cpu __sk_common.skc_incoming_cpu
#define sk_flags __sk_common.skc_flags
#define sk_rxhash __sk_common.skc_rxhash
socket_lock_t sk_lock;
atomic_t sk_drops;
int sk_rcvlowat;
struct sk_buff_head sk_error_queue;//错误队列,用于重传
struct sk_buff_head sk_receive_queue;//接受队列
/*
* The backlog queue is special, it is always used with
* the per-socket spinlock held and requires low latency
* access. Therefore we special case it's implementation.
* Note : rmem_alloc is in this structure to fill a hole
* on 64bit arches, not because its logically part of
* backlog.
*/
struct {
atomic_t rmem_alloc;
int len;
struct sk_buff *head;
struct sk_buff *tail;
} sk_backlog;
#define sk_rmem_alloc sk_backlog.rmem_alloc
int sk_forward_alloc;
#ifdef CONFIG_NET_RX_BUSY_POLL
unsigned int sk_ll_usec;
/* ===== mostly read cache line ===== */
unsigned int sk_napi_id;
#endif
int sk_rcvbuf;
struct sk_filter __rcu *sk_filter;
union {
struct socket_wq __rcu *sk_wq;
struct socket_wq *sk_wq_raw;
};
#ifdef CONFIG_XFRM
struct xfrm_policy __rcu *sk_policy[2];
#endif
struct dst_entry *sk_rx_dst;
struct dst_entry __rcu *sk_dst_cache;
atomic_t sk_omem_alloc;
int sk_sndbuf;
/* ===== cache line for TX ===== */
int sk_wmem_queued;
refcount_t sk_wmem_alloc;
unsigned long sk_tsq_flags;
union {
struct sk_buff *sk_send_head;
struct rb_root tcp_rtx_queue;
};
struct sk_buff_head sk_write_queue;
__s32 sk_peek_off;
int sk_write_pending;
__u32 sk_dst_pending_confirm;
u32 sk_pacing_status; /* see enum sk_pacing */
long sk_sndtimeo;
struct timer_list sk_timer;
__u32 sk_priority;
__u32 sk_mark;
u32 sk_pacing_rate; /* bytes per second */
u32 sk_max_pacing_rate;
struct page_frag sk_frag;
netdev_features_t sk_route_caps;
netdev_features_t sk_route_nocaps;
int sk_gso_type;
unsigned int sk_gso_max_size;
gfp_t sk_allocation;
__u32 sk_txhash;
/*
* Because of non atomicity rules, all
* changes are protected by socket lock.
*/
unsigned int __sk_flags_offset[0];
#ifdef __BIG_ENDIAN_BITFIELD
#define SK_FL_PROTO_SHIFT 16
#define SK_FL_PROTO_MASK 0x00ff0000
#define SK_FL_TYPE_SHIFT 0
#define SK_FL_TYPE_MASK 0x0000ffff
#else
#define SK_FL_PROTO_SHIFT 8
#define SK_FL_PROTO_MASK 0x0000ff00
#define SK_FL_TYPE_SHIFT 16
#define SK_FL_TYPE_MASK 0xffff0000
#endif
unsigned int sk_padding : 1,
sk_kern_sock : 1,
sk_no_check_tx : 1,
sk_no_check_rx : 1,
sk_userlocks : 4,
sk_protocol : 8,
sk_type : 16;
#define SK_PROTOCOL_MAX U8_MAX
u16 sk_gso_max_segs;
u8 sk_pacing_shift;
unsigned long sk_lingertime;
struct proto *sk_prot_creator;
rwlock_t sk_callback_lock;
int sk_err,
sk_err_soft;
u32 sk_ack_backlog;
u32 sk_max_ack_backlog;
kuid_t sk_uid;
struct pid *sk_peer_pid;
const struct cred *sk_peer_cred;
long sk_rcvtimeo;
ktime_t sk_stamp;
u16 sk_tsflags;
u8 sk_shutdown;
u32 sk_tskey;
atomic_t sk_zckey;
struct socket *sk_socket;
void *sk_user_data;
#ifdef CONFIG_SECURITY
void *sk_security;
#endif
struct sock_cgroup_data sk_cgrp_data;
struct mem_cgroup *sk_memcg;
void (*sk_state_change)(struct sock *sk);
void (*sk_data_ready)(struct sock *sk);
void (*sk_write_space)(struct sock *sk);
void (*sk_error_report)(struct sock *sk);
int (*sk_backlog_rcv)(struct sock *sk,
struct sk_buff *skb);
void (*sk_destruct)(struct sock *sk);
struct sock_reuseport __rcu *sk_reuseport_cb;
struct rcu_head sk_rcu;
};
放的就是该sock_common结构体的内容
struct sock_common {
/* skc_daddr and skc_rcv_saddr must be grouped on a 8 bytes aligned
* address on 64bit arches : cf INET_MATCH()
*/
union {
__addrpair skc_addrpair;
struct {
__be32 skc_daddr;//目的地址
__be32 skc_rcv_saddr;//源地址
};
};
union {
unsigned int skc_hash;
__u16 skc_u16hashes[2];
};
/* skc_dport && skc_num must be grouped as well */
union {
__portpair skc_portpair;
struct {
__be16 skc_dport;//端口
__u16 skc_num;
};
};
unsigned short skc_family;
volatile unsigned char skc_state;
unsigned char skc_reuse:4;
unsigned char skc_reuseport:1;
unsigned char skc_ipv6only:1;
unsigned char skc_net_refcnt:1;
int skc_bound_dev_if;
union {
struct hlist_node skc_bind_node;
struct hlist_node skc_portaddr_node;
};
struct proto *skc_prot;//使用协议
possible_net_t skc_net;
#if IS_ENABLED(CONFIG_IPV6)
struct in6_addr skc_v6_daddr;
struct in6_addr skc_v6_rcv_saddr;
#endif
atomic64_t skc_cookie;
/* following fields are padding to force
* offset(struct sock, sk_refcnt) == 128 on 64bit arches
* assuming IPV6 is enabled. We use this padding differently
* for different kind of 'sockets'
*/
union {
unsigned long skc_flags;
struct sock *skc_listener; /* request_sock */
struct inet_timewait_death_row *skc_tw_dr; /* inet_timewait_sock */
};
/*
* fields between dontcopy_begin/dontcopy_end
* are not copied in sock_copy()
*/
/* private: */
int skc_dontcopy_begin[0];
/* public: */
union {
struct hlist_node skc_node;
struct hlist_nulls_node skc_nulls_node;
};
int skc_tx_queue_mapping;
union {
int skc_incoming_cpu;
u32 skc_rcv_wnd;
u32 skc_tw_rcv_nxt; /* struct tcp_timewait_sock */
};
refcount_t skc_refcnt;
/* private: */
int skc_dontcopy_end[0];
union {
u32 skc_rxhash;
u32 skc_window_clamp;
u32 skc_tw_snd_nxt; /* struct tcp_timewait_sock */
};
/* public: */
};
总结
目前先了解到这些记录下来,后续更深入再补充