谈listen与backlog

int listen(int sockfd, int backlog);
一、listen函数仅由TCP服务器调用,它做两件事情:
  • 将一个主动套接字(且未连接的套接字)转化为被动套接字来指示内核“应该接受指向该套接字的连接请求”。
    ps:当socket函数创建一个套接字时,他是被假设为一个主动套接字的,所以才需要此处套接字的转化。 主动套接字是可以调用connect发起连接的客户端套接字。
  • 将套接字状态由CLOSED转为LISTEN状态。
问:那么根据第一条的结论我们可以提出一个问题:被动套接字还能去连接别的服务器吗?

答:不能。
让我们在server中调用完listen函数后再进行connect另一个服务器。
经测试,运行时会提示
conn: err:: Transport endpoint is already connected提示传输断点已连接。

二、整个流程分析

tcp三次握手

  • 客户端在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
listen1
2.AF_INEF协议族(af_inet.c)的listen实现函数为inet_listen,代码如下:
listen2
3.接下来进入inet_csk_listen_start,代码如下:
listen3
通过源码剖析,我们可看出在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: */
};
总结

目前先了解到这些记录下来,后续更深入再补充

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值