TCP的半链接和完全连接队列

TCP通过三次握手建立连接的过程应该都不陌生了。从服务器的角度看,它分为以下几步

  1. TCP状态设置为LISTEN状态,开启监听客户端的连接请求
  2. 收到客户端发送的SYN报文后,TCP状态切换为SYN RECEIVED,并发送SYN ACK报文
  3. 收到客户端发送的ACK报文后,TCP三次握手完成,状态切换为ESTABLISHED

Unix系统中,开启监听是通过listen完成。

int listen(int sockfd, int backlog)

listen有两个参数,第一个参数sockfd表示要设置的套接字,本文主要关注的是其第二个参数backlog

<Unix 网络编程>将其描述为已完成的连接队列(ESTABLISHED)与未完成连接队列(SYN_RCVD)之和的上限。

一般我们将ESTABLISHED状态的连接称为全连接,而将SYN_RCVD状态的连接称为半连接

图片描述

当服务器收到一个SYN后,它创建一个子连接加入到SYN_RCVD队列。在收到ACK后,它将这个子连接移动到ESTABLISHED队列。最后当用户调用accept()时,会将连接从ESTABLISHED队列取出。


是 Posix 不是 TCP

listen只是posix标准,不是TCP的标准!不是TCP标准就意味着不同的内核可以有自己独立的实现

POSIX是这么说的:

The  backlog argument provides a hint to the implementation which the implementation shall use to limit the number of outstanding connections in the socket's listen queue.

Linux是什么行为呢 ? 查看listenman page

The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests.

什么意思呢?就是说的在Linux 2.2以后, backlog只限制完成了三次握手,处于ESTABLISHED状态等待accept的子连接的数目了。

真的是这样吗?于是我决定抄一个小程序验证一下:

服务器监听50001端口,并且设置backlog = 4。注意,我为了将队列塞满,没有调用accept

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define BACKLOG 4

int main(int argc, char **argv)
{
    int listenfd;
    int connfd;
    struct sockaddr_in servaddr;

    listenfd = socket(PF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(50001);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, BACKLOG);
    while(1)
    {
        sleep(1);
    }
    
    return 0;
}

客户端的代码

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr;

    sockfd = socket(PF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(50001);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (0 != connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)))
    {
         printf("connect failed!\n");
    }
    else
    {
         printf("connect succeed!\n");
    }

    sleep(30);

    return 1;
}

为了排除syncookie的干扰,我首先关闭了syncookie功能

echo 0 > /proc/sys/net/ipv4/tcp_syncookies

由于我设置的backlog = 4并且服务器始终不会accept。因此预期会建立 4 个全连接, 但实际却是

root@ubuntu-1:/home/user1/workspace/client# ./client &
[1] 12798
root@ubuntu-1:/home/user1/workspace/client# connect succeed!
./client &
[2] 12799
root@ubuntu-1:/home/user1/workspace/client# connect succeed!
./client &
[3] 12800
root@ubuntu-1:/home/user1/workspace/client# connect succeed!
./client &
[4] 12801
root@ubuntu-1:/home/user1/workspace/client# connect succeed!
./client &
[5] 12802
root@ubuntu-1:/home/user1/workspace/client# connect succeed!
./client &
[6] 12803
root@ubuntu-1:/home/user1/workspace/client# connect succeed!
./client &
[7] 12804
root@ubuntu-1:/home/user1/workspace/client# connect succeed!
./client &
[8] 12805
root@ubuntu-1:/home/user1/workspace/client# connect succeed!
./client &
[9] 12806
root@ubuntu-1:/home/user1/workspace/client# connect succeed!
./client &
[10] 12807
root@ubuntu-1:/home/user1/workspace/client# connect failed!

看!客户器竟然显示成功建立了 9 次连接!

netstat看看TCP连接状态

> netstat -t
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 localhost:50001         localhost:55792         ESTABLISHED
tcp        0      0 localhost:55792         localhost:50001         ESTABLISHED
tcp        0      0 localhost:55798         localhost:50001         ESTABLISHED   
tcp        0      1 localhost:55806         localhost:50001         SYN_SENT
tcp        0      0 localhost:50001         localhost:55784         ESTABLISHED
tcp        0      0 localhost:50001         localhost:55794         SYN_RECV
tcp        0      0 localhost:55786         localhost:50001         ESTABLISHED
tcp        0      0 localhost:55800         localhost:50001         ESTABLISHED
tcp        0      0 localhost:50001         localhost:55786         ESTABLISHED
tcp        0      0 localhost:50001         localhost:55800         SYN_RECV
tcp        0      0 localhost:55784         localhost:50001         ESTABLISHED
tcp        0      0 localhost:50001         localhost:55796         SYN_RECV
tcp        0      0 localhost:50001         localhost:55788         ESTABLISHED
tcp        0      0 localhost:55794         localhost:50001         ESTABLISHED
tcp        0      0 localhost:55788         localhost:50001         ESTABLISHED
tcp        0      0 localhost:50001         localhost:55790         ESTABLISHED
tcp        0      0 localhost:50001         localhost:55798         SYN_RECV
tcp        0      0 localhost:55790         localhost:50001         ESTABLISHED
tcp        0      0 localhost:55796         localhost:50001         ESTABLISHED   

整理一下就是下面这样
图片描述

从上面可以看出,一共有5条连接对是ESTABLISHED<->ESTABLISHED连接, 但还有4条连接对是SYN_RECV<->ESTABLISHED连接, 这表示对客户端三次握手已经完成了,但对服务器还没有! 回顾一下TCP三次握手的过程,造成这种连接对原因只有可能是服务器客户端最后发送的握手ACK被丢弃了!

还有一个问题,我明明设置的backlog的值是 4,可为什么还能建立5个连接 ?


去内核找原因

我实验用的机器内核是CentOS kernel-3.10.0-327.el

前面提到过已完成连接队列未完成连接队列这两个概念, Linux有这两个队列吗 ? Linux 既有又没有! 说有是因为内核中可以得到两种连接各自的长度; 说没有是因为 Linux只有已完成连接队列实际存在, 而未完成连接队列只有长度的记录!

每一个LISTEN状态的套接字都有一个struct inet_connection_sock结构, 其中的accept_queue从名字上也可以看出就是已完成三次握手的子连接队列.只是这个结构里还记录了半连接请求的长度!

握手过程中的数据结构

/** inet_connection_sock - INET connection oriented sock
 *  ...
 */
struct inet_connection_sock {
    ...
    struct request_sock_queue icsk_accept_queue;
    ...
}

struct request_sock_queue
{
    /*Points to the request_sock accept queue, when after 3 handshake will add the request_sock from syn_table to here*/
    struct request_sock *rskq_accept_head;
    struct request_sock *rskq_accept_tail;
    rwlock_t			 syn_wait_lock;
    u8					 rskq_defer_accept;
 
    /* 3 bytes hole, try to pack */
    struct listen_sock *listen_opt;
};
struct listen_sock
{
    u8 max_qlen_log; /*2^max_qlen_log is the length of the accpet queue, max of max_qlen_log is 10. (2^10=1024)*/
    /* 3 bytes hole, try to use */
    int					 qlen; 
    int					 qlen_young;
    int					 clock_hand;
    u32					 hash_rnd;
    u32					 nr_table_entries; /*nr_table_entries is the number of the syn_table,max is 512*/
    struct request_sock *syn_table[0];
};

们先简单的描述一下几个tcp的操作函数,下面针对的也是ip4协议的

TCP握手的几个阶段

收到客户端的syn请求 ->将这个请求放入syn_table中去->服务器端回复syn-ack->收到客户端的ack->放入accept queue中。

我们先简单的描述一下几个tcp的操作函数,下面针对的也是ip4协议的:

const struct inet_connection_sock_af_ops ipv4_specific = {
	.queue_xmit	   = ip_queue_xmit,
	.send_check	   = tcp_v4_send_check,
	.rebuild_header	   = inet_sk_rebuild_header,
	.sk_rx_dst_set	   = inet_sk_rx_dst_set,
	.conn_request	   = tcp_v4_conn_request,
	.syn_recv_sock	   = tcp_v4_syn_recv_sock,
	.net_header_len	   = sizeof(struct iphdr),
	.setsockopt	   = ip_setsockopt,
	.getsockopt	   = ip_getsockopt,
	.addr2sockaddr	   = inet_csk_addr2sockaddr,
	.sockaddr_len	   = sizeof(struct sockaddr_in),
	.bind_conflict	   = inet_csk_bind_conflict,
#ifdef CONFIG_COMPAT
	.compat_setsockopt = compat_ip_setsockopt,
	.compat_getsockopt = compat_ip_getsockopt,
#endif
	.mtu_reduced	   = tcp_v4_mtu_reduced,
};
EXPORT_SYMBOL(ipv4_specific);

在刚才所说的两个步骤,也就是结构体中的 conn_request 和 syn_recv_sock,  所对应的函数是 tcp_v4_conn_request 和 tcp_v4_syn_recv_sock

我们所重点关注的主要是方法中的drop逻辑,由于我关闭了syncookie,所以一旦满足了下面代码中的两个条件之一就会丢弃报文。

tcp_v4_conn_request ->tcp_conn_reques函数


int tcp_conn_request(struct request_sock_ops *rsk_ops,
		     const struct tcp_request_sock_ops *af_ops,
		     struct sock *sk, struct sk_buff *skb)
{
     ......   
	/* TW buckets are converted to open requests without
	 * limitations, they conserve resources and peer is
	 * evidently real one.
	 */
	if ((sysctl_tcp_syncookies == 2 ||
	     inet_csk_reqsk_queue_is_full(sk)) && !isn) { // 条件1: 半连接 >= backlog
		want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);
		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.
	 */
	if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) { //  条件2: 全连接sock > backlog 并且 半连接队列的young字段 > 1
		NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
		goto drop;
	}
    ......
}

1. inet_csk_reqsk_queue_is_full(sk)

判断的是  queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;

这里有个 qlen 代表的是listen_opt的 syn_table的长度,那什么是max_qlen_log呢?

nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
nr_table_entries = max_t(u32, nr_table_entries, 8);
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
for (lopt->max_qlen_log = 3;
     (1 << lopt->max_qlen_log) < nr_table_entries;
     lopt->max_qlen_log++)
{
    ;
}

也就是max_qlen 是listen 传入的backlog和sysctl_max_syn_backlog最小值,并且一定大于16 , roudup_pow_of_two 代表着找最靠近nr_table_entries+1的2的倍数 sysctl_max_syn_backlog 就是我们熟悉的

/proc/sys/net/ipv4/tcp_max_syn_backlog

我们看一下listen 函数在kernel的实现

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
	struct socket *sock;
	int err, fput_needed;
	int somaxconn;

	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (sock) {
		somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
		if ((unsigned int)backlog > somaxconn)
			backlog = somaxconn;

		err = security_socket_listen(sock, backlog);
		if (!err)
			err = sock->ops->listen(sock, backlog);

		fput_light(sock->file, fput_needed);
	}
	return err;
}

我们清楚的看到backlog 并不是按照你调用listen的所设置的backlog大小,实际上取的是backlog和somaxconn的最小值

somaxconn的值定义在/proc/sys/net/core/somaxconn

2.sk_acceptq_is_full

static inline bool sk_acceptq_is_full(const struct sock *sk)
{
	return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
}

int inet_listen(struct socket *sock, int backlog)
{
    sk->sk_max_ack_backlog = backlog;
}

就是等于我们刚才在前面部分看到的listen中的值

3.inet_csk_reqsk_queue_young

在判断sk_acceptq_is_full 的情况下,同是也要求了判断inet_csk_reqsk_queue_young>1,也就是刚才的结构体listen_sock的qlen_young,qlen_young 是对syn_table的计数,进入 syn_table 加1,出了syn_table  -1

inet_csk_reqsk_queue_yong(sk)的含义是请求队列中有多少个握手过程中没有重传过的段。在第一次的时候,之前的握手过程都没有重传过,所以这个syn包server端会直接drop掉,之后client会重传syn,当inet_csk_reqsk_queue_yong(sk) < 1,那么这个syn被server端接受。server会回复synack给client。这样一来两边的状态就变为client(ESTABLISHED), server(SYN_SENT)

"半连接队列的young字段 > 1" 表示网络很忙,有 SYNACK 丢失了(没有收到对端的第三次握手的 ACK),但在我们的简单例子中,client 的 ACK 总是很及时的,所以这个条件不会满足,也就是说丢弃 SYN 报文的条件 2 只剩下全连接sock > backlog

一般情况下,它和 qlen 是共同变化的, 但有时不会,代码中的注释对这点有说明,即 1s 定时器超时都没有收到对端的 ACK。这个半连接就"成熟"了。

Normally all the openreqs are young and become mature(i.e. converted to established socket) for first timeout.If synack was not acknowledged for 1 second, it means one of the following things: synack was lost, ack was lost, rtt is high or nobody planned to ack (i.e. synflood).

所以一般情况下连接建立时,服务端的变化过程是这样的:

  1. 收到SYN报文,回SYN ACK后, tcp_conn_request->af_ops->queue_hash_add,qlen++, qlen_young++
  2. 收到ACK报文, 三次握手完成,将连接加入accept队列,qlen--,qlen_young--
  3. 用户使用accept,将连接从accept取出。
void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
				   unsigned long timeout)
{
    ......

	reqsk_queue_hash_req(&icsk->icsk_accept_queue, h, req, timeout);
	inet_csk_reqsk_queue_added(sk, timeout);
}
EXPORT_SYMBOL_GPL(inet_csk_reqsk_queue_hash_add);

static inline void reqsk_queue_hash_req(struct request_sock_queue *queue,
					u32 hash, struct request_sock *req,
					unsigned long timeout)
{
	struct listen_sock *lopt = queue->listen_opt;
	req->num_timeout = 0; // 注意正常的会syn ack后,这里num_timeout赋值为0
	req->sk = NULL;
	req->dl_next = lopt->syn_table[hash];

	write_lock(&queue->syn_wait_lock);
	lopt->syn_table[hash] = req; // 加入syn_table
	write_unlock(&queue->syn_wait_lock);
}

static inline int reqsk_queue_added(struct request_sock_queue *queue)
{
	struct listen_sock *lopt = queue->listen_opt;
	const int prev_qlen = lopt->qlen;

	lopt->qlen_young++;
	lopt->qlen++;
	return prev_qlen;
}

// 正常三次握手第三阶段流程:收到客户端的ack后,tcp_check_req--->(child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);正常接收客户端ack)--->inet_csk_reqsk_queue_removed--->reqsk_queue_removed
static inline int reqsk_queue_removed(struct request_sock_queue *queue,
				      struct request_sock *req)
{
	struct listen_sock *lopt = queue->listen_opt;

	if (req->num_timeout == 0)
		--lopt->qlen_young;

	return --lopt->qlen;
}

有的人可能会有疑问了

如果accept queue满了,那么qlen_young不就是一直增加,而新来的客户端都会被条件if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) 而drop syn的ack包,那么客户端会出现connected timeout

实际上linux在server起socket的时候会调用tcp_keepalive_timer启动tcp_synack_timer,会调用函数inet_csk_reqsk_queue_prune

if (sk->sk_state == TCP_LISTEN)
{
    tcp_synack_timer(sk);
    goto out;
}
 
static void tcp_synack_timer(struct sock *sk)
{
    inet_csk_reqsk_queue_prune(sk, TCP_SYNQ_INTERVAL,
                               TCP_TIMEOUT_INIT, TCP_RTO_MAX);
}

而inet_csk_reqsk_queue_prune会在去检查syn的table, 而删除一些这个request 过期后并且完成retry 的syn ack包的请求

为了提高inet_csk_reqsk_queue_prune的效率,在request_sock 里加入了 expires(才前面的结构体中已经提到过) , 这个expires初始值是hardcode的3HZ 时间, inet_csk_reqsk_queue_prune会轮训syn_table里的已经exprie request, 发现如果还没有到到retry的次数,那么会增加expire的时间直到重试结束,而expire的时间为剩余retry 次数*3HZ ,并且不大于120HZ

关于retry, retry的参数可以通过设置 

/proc/sys/net/ipv4/tcp_syn_retries

当然你可以通过设置

/proc/sys/net/ipv4/tcp_abort_on_overflow 为1 不允许syn ack 重试
4,下面是收到客户端ACK握手报文时的处理

struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,
                  struct request_sock *req,
                  struct dst_entry *dst,
                  struct request_sock *req_unhash,
                  bool *own_req) 
{
     // code omitted
     if (sk_acceptq_is_full(sk))           //  全连接 > backlog, 就丢弃
         goto exit_overflow;

     newsk = tcp_create_openreq_child(sk, req, skb); // 创建子套接字了
     // code omitted
}

所以这样就可以解释实验现象了!

  1. 4个连接请求都可以顺利创建子连接,全连接队列长度 = backlog = 4, 半连接数目 = 0
  2. 5个连接请求, 由于sk_acceptq_is_full的判断条件是>而不是>=,所以依然可以建立全连接
  3. 6-9个连接请求到来时,由于半连接的数目还没有超过backlog,所以还是可以继续回复SYNACK,但收到ACK后已经不能再创建子套接字了(该ACK在tcp_v4_syn_recv_sock中被从exit_overflow处丢掉),所以TCP状态依然为SYN_RECV。同时半连接的数目(6-9)也增加到backlog(4)。而对于客户端,它既然能收到SYNACK握手报文,因此它可以将TCP状态变为ESTABLISHED。
  4. 10个请求到来时,由于半连接的数目已经达到backlog(且cookie功能未打开,值为0),因此,这个SYN报文会被丢弃(该SYN在tcp_conn_request条件1处被丢弃)。

内核的问题

从以上的现象和分析中,我认为内核存在以下问题

  1. accept队列是否满的判断用>=>更合适, 这样才能体现backlog的作用
  2. accept队列满了,就应该拒绝半连接了,因为即使半连接握手完成,也无法加入accept队列,否则就会出现SYN_RECV--ESTABLISHED这样状态的连接对!这样的连接是不能进行数据传输的!

问题2在16年的补丁中已经修改了! 所以如果你在更新版本的内核中进行相同的实验, 会发现客户端只能连接成功5次了,当然这也要先关闭syncookie

但问题1还没有修改! 如果以后修改了,我也不会意

 

最新CentOS kernel-4.18.0-147.5.1.el8_1

client发送syn到server:

server端处理流程:tcp_v4_do_rcv--->tcp_rcv_state_process--->tcp_v4_conn_request--->tcp_conn_request

    if ((net->ipv4.sysctl_tcp_syncookies == 2 ||

         inet_csk_reqsk_queue_is_full(sk)) && !isn) {

        want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);

        if (!want_cookie)

            goto drop; // 只在半链接队列满,且net.ipv4.tcp_syncookies=0时,才会走到这。即:直接忽略syn包,不增加overflow的值。因为overflow的是针对全连接队列的。

    }

    if (sk_acceptq_is_full(sk)) {

        NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS); //

        goto drop; // 

    }
    ......
    drop:

        tcp_listendrop(sk);

        return 0;

    }

static inline void tcp_listendrop(const struct sock *sk)

{

    atomic_inc(&((struct sock *)sk)->sk_drops);

    __NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENDROPS);

}

可以看到,当全连接队列满了后,就会先统计增加队列overflow的个数,然后直接goto到drop,不再回sync ack,即忽略syn包。LINUX_MIB_LISTENDROPS而这些参数的值可以通过netstat -s 来查看到。

Client收到synack后回复ack给server:

server端处理流程: tcp_v4_do_rcv--->tcp_rcv_state_process--->tcp_check_req--->syn_recv_sock-->tcp_v4_syn_recv_sock

        if(sk_acceptq_is_full(sk))

                 goto exit_overflow;

......

exit_overflow:

    NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);

exit_nonewsk:

    dst_release(dst);

exit:

    tcp_listendrop(sk);

    return NULL;

put_and_exit:

    newinet->inet_opt = NULL;

    inet_csk_prepare_forced_close(newsk);

    tcp_done(newsk);

    goto exit;

}

tcp_v4_do_rcv--->tcp_rcv_state_process--->tcp_check_req

如果server端设置了sysctl_tcp_abort_on_overflow,那么server会发送rst给client,并删除掉这个链接;否则server端只是记录一下LINUX_MIB_LISTENOVERFLOWS,然后返回。默认情况下是不会设置的,server端只是标记连接请求块的acked标志,之后连接建立定时器,会遍历半连接表,重新发送synack,重复上面的过程(具体的函数是inet_csk_reqsk_queue_prune),如果重传次数超过synack重传的阀值(/proc/sys/net/ipv4/tcp_synack_retries),会把该连接从半连接链表中删除。

 

如果TCP连接队列溢出,有哪些指标可以看呢?

1,netstat -s

[root@server ~]#  netstat -s | egrep "listen|LISTEN" 
667399 times the listen queue of a socket overflowed
667399 SYNs to LISTEN sockets ignored

比如上面看到的 667399 times ,表示全连接队列溢出的次数,隔几秒钟执行下,如果这个数字一直在增加的话肯定全连接队列偶尔满了。

从上面的代码可知,当cookie为0时,drop的数量会大于等于overflow的数量。当cookie不为0时,drop的数量会等于overflow的数量

2,netstat和ss中, Recv-Q和Send-Q

man netstat是这么说的

   Recv-Q
       Established: The count of bytes not copied by the user program connected to this socket.
       Listening: Since Kernel 2.6.18 this column contains the current syn backlog.

   Send-Q
       Established: The count of bytes not acknowledged by the remote host.
       Listening: Since Kernel 2.6.18 this column contains the maximum size of the syn backlog.

Listen时:

ss的Recv-Q:“已完成三次握手建立成功(状态为ESTABLISHED),但尚未交付给应用的tcp连接的数量

该值最大为:Send-Q+1,即:min(backlog, somaxconn)+1。

之所以加1,是因为OS内核在判断队列是否已满时,用的是>(应该用>=),这导致当已创建成功的连接数量正好等于min(backlog, somaxconn)时,还会再多创建一个tcp连接,最终结果就是:min(backlog, somaxconn)+1

ss的Send-Q:listen时,backlog的大小。其值为min(backlog, somaxconn)

netstat的Recv-Q :同ss

netstat的Send-Q: 尽管文档中说是"Since Kernel 2.6.18 this column contains the maximum size of the syn backlog",但实验中看不出来,可能是0。

注:如果接收队列Recv-Q一直处于阻塞状态,可能是遭受了拒绝服务 denial-of-service 攻击。或应用进程处理的慢。

Established时:

netstat和ss 都

Recv-Q:“socket接收缓冲区,尚未被应用读取的数据的字节数

Send-Q:“socket发送缓冲区,已经发送给对端应用,但对端应用尚未ack的字节数。”

注:如果发送队列Send-Q不能很快的清零,可能是有应用向外发送数据包过快,或者是对方接收数据包不够快。

doc:

https://segmentfault.com/a/1190000019252960

https://segmentfault.com/a/1190000008224853

https://blog.csdn.net/whatday/article/details/103027414

https://blog.csdn.net/wangshuminjava/article/details/80926579

https://www.douban.com/note/178129553/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值