深入理解Linux网络笔记(七):异常TCP连接建立情况、如何查看是否有连接队列溢出发生

news2024/12/26 16:12:53

本文为《深入理解Linux网络》学习笔记,使用的Linux源码版本是3.10,网卡驱动默认采用的都是Intel的igb网卡驱动

Linux源码在线阅读:https://elixir.bootlin.com/linux/v3.10/source

5、深度理解TCP连接建立过程(二)

4)、异常TCP连接建立情况
1)connect系统调用耗时失控

客户端在发起connect系统调用的时候,主要工作就是端口选择。在选择的过程中,有个大循环,从ip_local_port_range的一个随机位置开始把这个范围遍历一遍,找到可用端口则退出循环。如果端口很充足,那么循环只需要执行少数几次就可以退出。但假设端口消耗掉很多已经不充足,或者干脆就没有可用的了,那么这个循环就得执行很多遍。详细代码如下:

// net/ipv4/inet_hashtables.c
int __inet_hash_connect(struct inet_timewait_death_row *death_row,
		struct sock *sk, u32 port_offset,
		int (*check_established)(struct inet_timewait_death_row *,
			struct sock *, __u16, struct inet_timewait_sock **),
		int (*hash)(struct sock *sk, struct inet_timewait_sock *twp))
{
    ...
		inet_get_local_port_range(&low, &high);
		remaining = (high - low) + 1;
    ...
		for (i = 1; i <= remaining; i++) {
      // 其中offset是一个随机数
			port = low + (i + offset) % remaining;
			if (inet_is_reserved_local_port(port))
				continue;
			head = &hinfo->bhash[inet_bhashfn(net, port,
					hinfo->bhash_size)];
      // 加锁
			spin_lock(&head->lock);
      // 一大段的选择端口逻辑
      ...
      // 选择成功就goto ok
      // 不成功就goto next_port
		next_port:
      // 解锁
			spin_unlock(&head->lock);
		}
		...
}

在每次的循环内部需要等待锁以及在哈希表中进行多次的搜索。注意这里的锁是自旋锁,是一种非阻塞的锁,如果资源被占用,进程并不会被挂起,而是会占用CPU去不断尝试获取锁

但假设端口范围ip_local_port_range配置的是10000-30000,而且已经用尽了。那么每次当发起连接的时候都需要把循环执行两万遍才退出。这时会涉及大量的哈希表查找以及自旋锁等待开销,系统态CPU将会出现大幅度上涨

下图是线上截取到的正常时的connect系统调用耗时,是22微秒

下图是一台服务器在端口不足的情况下的connect系统调用耗时,是2581微秒

异常情况下的connect耗时是正常情况下的100多倍。虽然换算成毫秒只有2毫秒多一点儿,但是要知道这消耗的全是CPU时间。理解了时间产生的原因,解决起来就非常简单了,办法很多。修改内核参数net.ipv4.ip_local_port_range多预留一些端口号、改用长连接或者尽快回收TIME_WAIT都可以

2)第一次握手丢包

服务端在响应来自客户端的第一次握手请求的时候,会判断半连接队列和全连接队列是否溢出。如果发生溢出,可能会直接将握手包丢弃,而不会反馈给客户端。接下来我们来分别详细看一下

半连接队列满

来看看半连接队列在何种情况下会导致丢包

// net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
	...
  // 看看半连接队列是否满了
	if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
		want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
		if (!want_cookie)
			goto drop;
	}
  // 看看全连接队列是否满了
  ...
drop:
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
	return 0;
}

在以上代码中,inet_csk_reqsk_queue_is_full如果返回true就表示半连接队列满了,另外tcp_syn_flood_action判断是否打开了内核参数tcp_syncookies,如果未打开则返回false

// net/ipv4/tcp_ipv4.c
bool tcp_syn_flood_action(struct sock *sk,
			 const struct sk_buff *skb,
			 const char *proto)
{
	...
	bool want_cookie = false;
	...
	if (sysctl_tcp_syncookies) {
		...
		want_cookie = true;
		...
	}
  ...
	return want_cookie;
}

也就是说,如果半连接队列满了,而且ipv4.tcp_syncookies参数设置为0,那么来自客户端的握手包将直接丢弃

SYN Flooad攻击就是通过耗光服务端上的半连接队列来使得正常的用户连接请求无法被响应。不过在现在的Linux内核里只要打开tcp_syncookies,半连接队列满了仍然可以保证正常握手的进行

全连接队列满

当半连接队列判断通过以后,紧接着还有全连接队列满的相关判断。如果满了,服务器对握手包的处理还是会丢弃。源码如下:

// net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
	// 看看半连接队列是否满了
  ...
  // 看看全连接队列是否满了
	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;
	}
  ...
drop:
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
	return 0;
}

sk_acceptq_is_full判断全连接队列是否满了,inet_csk_reqsk_queue_young判断有没有young_ack(未处理完的半连接请求)

从这段代码可以看到,假如全连接队列满的情况下,且同时有young_ack,那么内核同样直接丢掉该SYN握手包

客户端发起重试

假设服务端侧发生了全/半连接队列溢出而导致的丢包,那么转换到客户端视角来看就是SYN包没有任何响应

好在客户端在发出握手包的时候,开启了一个重传定时器。如果收不到预期的synack,超时重传的逻辑就会开始执行,如下图所示。不过重传计时器的时间单位都是以秒来计算的,这意味着,如果有握手重传发生,即使第一次重传就能成功,那接口最快响应也是1秒以后得事情了。这对接口耗时影响非常大

来详细看看重传的相关逻辑。客户端在connect系统调用发出SYN握手信号后就开启了重传定时器

// net/ipv4/tcp_output.c
int tcp_connect(struct sock *sk)
{
  ...
	// 实际发出SYN
	err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
	      tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
	...
	// 启动重传定时器
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
				  inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
	return 0;
}

在定时器设置中传入的inet_csk(sk)->icsk_rto是超时时间,该值初始化的时候被设置为1秒

// net/ipv4/tcp_output.c
void tcp_connect_init(struct sock *sk)
{
	...
  // 初始化为TCP_TIMEOUT_INIT
	inet_csk(sk)->icsk_rto = TCP_TIMEOUT_INIT;
	...
}
// include/net/tcp.h
#define TCP_TIMEOUT_INIT ((unsigned)(1*HZ))

如果能正常接收到服务端响应的synack,那么客户端的这个定时器会清除。这段逻辑在tcp_rearm_rto离。调用顺序tcp_rcv_state_process=>tcp_rcv_synsent_state_process=>tcp_ack=>tcp_clean_rtx_queue=>tcp_rearm_rto

// net/ipv4/tcp_input.c
void tcp_rearm_rto(struct sock *sk)
{
	...
		inet_csk_clear_xmit_timer(sk, ICSK_TIME_RETRANS);
	...
}

如果服务端发生了丢包,那么定时器到时后会进入回调函数tcp_write_timer中进行重传

其实不只是握手,连接状态的超时重传也是在这里完成的

// net/ipv4/tcp_timer.c
static void tcp_write_timer(unsigned long data)
{
	...
		tcp_write_timer_handler(sk);
	...
}
// net/ipv4/tcp_timer.c
void tcp_write_timer_handler(struct sock *sk)
{
	...
  // 取出定时器类型
	event = icsk->icsk_pending;

	switch (event) {
	...
	case ICSK_TIME_RETRANS:
		icsk->icsk_pending = 0;
		tcp_retransmit_timer(sk);
		break;
	...
	}
  ...
}

tcp_retransmit_timer是重传的主要函数。在这里完成重传,以及下一次定时器到期时间的设置

// net/ipv4/tcp_timer.c
void tcp_retransmit_timer(struct sock *sk)
{
	...
  // 超过了重传次数则退出
	if (tcp_write_timeout(sk))
		goto out;
    ...
    // 重传
	if (tcp_retransmit_skb(sk, tcp_write_queue_head(sk)) > 0) {
		// 重传失败
    ...
	}
  ...
// 退出前置重新设置下一次超时时间    
out_reset_timer:
	// 计算超时时间
	if (sk->sk_state == TCP_ESTABLISHED &&
	    (tp->thin_lto || sysctl_tcp_thin_linear_timeouts) &&
	    tcp_stream_is_thin(tp) &&
	    icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) {
		icsk->icsk_backoff = 0;
		icsk->icsk_rto = min(__tcp_set_rto(tp), TCP_RTO_MAX);
	} else {
		icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);
	}
  // 设置
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
	...
}

tcp_write_timeout用来判断是否重试过多,如果是则退出重试逻辑

tcp_write_timeout的判断逻辑其实也有点复杂。对于SYN握手包主要的判断依据是net.ipv4.tcp_syn_retries,但其实也并不是简单对比次数,而是转换成了时间进行对比。所以如果在线上看到实际重传次数和对应内核参数不一致也不用太奇怪

接着在tcp_retransmit_timer函数中重传了发送队列里的元素。而且还设置了下一次超时的时间,为前一次的两倍(左移操作相当于乘2)

实际抓包结果

下图是因为服务端第一次握手丢包的抓包截图

在这里插入图片描述

可以看到,客户端在1秒以后进行了第一次握手重试。重试仍然没有响应,那么接下来依次又分别在3秒、7秒、15秒、31秒和63秒等时间共重试了6次(tcp_syn_retries设置的是6)

假如服务端第一次握手的时候出现了半/全连接队列溢出导致的丢包,那么接口响应时间将至少是1秒以上

3)第三次握手丢包

客户端在收到服务器的synack响应的时候,就认为链接建立成功了,然后会将自己的连接状态设置为ESTABLISHED,发出第三次握手请求。但服务端在第三次握手的时候,还有可能有意外发生

// net/ipv4/tcp_ipv4.c
struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
				  struct request_sock *req,
				  struct dst_entry *dst)
{
	...
  // 判断全连接队列是不是满了
	if (sk_acceptq_is_full(sk))
		goto exit_overflow;
  ...
exit_overflow:
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
  ...
}

从上述代码可以看出,第三次握手时,如果服务器全连接队列满了,来自客户端的ack握手包又被直接丢弃

想想也很好理解,三次握手完的请求是要放在全连接队列里的。但是假如全连接队列满了,三次握手也不会成功

不过有意思的是,第三次握手失败并不是客户端重试,而是由服务端来重发synack

下图是第三次握手丢包的抓包截图

在这里插入图片描述

第一个框内是第三次握手,其实这个握手请求在服务端已经被丢弃了。但是这时候客户端并不知情,它一直傻傻地以为三次握手已经妥了呢。不过还好,这时在服务端的半连接队列中仍然记录着第一次握手时存的握手请求

服务端等到半连接定时器到时后,向客户端重新发起synack,客户端收到后再重新恢复第三次握手ack。如果这期间服务端全连接队列一直都是满的,那么服务端重试5次(受内核参数net.ipv4.tcp_synack_retries控制)后就放弃了

在这种情况下还要注意另外一个问题。在实践中,客户端往往是以为连接建立成功就会开始发送数据,其实这时候连接还没有真的建立起来。它发出去的数据,包括重试将全部被服务端无视,直到连接真正建立成功后才行,如下图所示:

在这里插入图片描述

4)握手异常总结

如果端口不充足,会导致connect系统调用的时候过多地执行自旋锁等待与哈希查找,会引起CPU开销上涨。严重情况下会耗光CPU,影响用户业务逻辑的执行。出现这种问题处理起来的方法有这么几个:

  • 通过调整ip_local_port_range来尽量加大端口范围
  • 尽量复用连接,使用长连接来削减频繁的握手处理
  • 第三个有用,但是不太推荐的方法是开启tcp_tw_reuse和tcp_tw_recycle

服务端在第一次握手时,在如下两种情况下可能会丢包:

  • 半连接队列满,且tcp_syncookies为0
  • 全连接队列满,且有未完成的半连接请求

在这两种情况下,从客户端视角来看和网络断了没有区别,就是发出去的SYN包没有任何反馈,然后等待定时器到时后重传握手请求。第一次重传时间是1秒,接下来的等待间隔翻倍地增长,2秒、4秒、8秒……总的重传次数受net.ipv4.tcp_syn_retries内核参数影响

服务端在第三次握手时也可能出问题,如果全连接队列满,仍将发生丢包。不过第三次握手失败时,只有服务端知道(客户端误以为连接已经建立成功)。服务端根据半连接队列里的握手信息发起synack重试,重试次数由net.ipv4.tcp_synack_retries控制

如果出现了丢包的问题,该如何应对:

方法1:打开syncookie

可以通过打开tcp_syncookies来防止过多的请求打满半连接队列,包括SYN Flood攻击,来解决服务端因为半连接队列满而发生的丢包

方法2:加大连接队列长度

全连接队列的长度是min(backlog, net.core.somaxconn),半连接队列的长度是min(backlog, somaxconn, tcp_max_sync_backlog)+1再向上取整到2的N次幂,但最小不能小于16

如果需要加大全/半连接队列长度,请调节以上的一个或多个参数来达到目的。只要队列长度合适,就能很大程度降低握手异常概率的发生。其中全连接队列在修改完后可以通过ss命令输出的Send-Q来确认最终生效长度

$ ss -nlt
Recv-Q Send-Q Local Address:Port Address:Port                    
0      128    *:80               *:*

Recv-Q告诉我们当前该进程的全连接队列使用情况。如果Recv-Q已经逼近了Send-Q,那么可能不需要等到丢包也应该准备加大全连接队列了

方法3:尽快调用accept

这个虽然一般不会成为问题,但也要注意一下。应用程序应该尽快在握手成功之后通过accept把新连接取走。不要忙于处理其他业务逻辑而导致全连接队列塞满了

方法4:尽早拒绝

如果加大队列后仍然有非常偶发的队列溢出,我们可以暂且容忍。但如果仍然有较长时间处理不过来怎么办?另外一个做法就是直接报错,不要让客户端超时等待。例如将Redis、MySQL等服务器的内核参数tcp_abort_on_overflow设置为1。如果队列满了,直接发reset指令给客户端。告诉后端进程/线程不要傻等。这时候客户端会受到错误connection reset by peer

方法5:尽量减少TCP连接的次数

如果上述方法都未能根治问题,这个时候应该思考是否可以用长连接代替短连接,减少过于频繁的三次握手。这个烦那个发不但能降低握手出问题的可能性,而且还顺带砍掉了三次握手的各种内存、CPU、时间上的开销,对提升性能也有较大帮助

5)、如何查看是否有连接队列溢出发生
1)全连接队列溢出判断

全连接队列溢出都会记录到ListenOverflows这个MIB(Management Information Base,管理信息库),对应SNMP统计信息中的ListenDrops这一项。我们来展开看一下相关的源码

服务端在响应客户端的SYN握手包的时候,有可能会在tcp_v4_conn_request调用这里发生全连接队列溢出而丢包

// net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
	// 看看半连接队列是否满了
  ...
  // 看看全连接队列是否满了
	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;
	}
  ...
drop:
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
	return 0;
}

从上述代码可以看到,全连接队列满了以后调用NET_INC_STATS_BH增加了LINUX_MIB_LISTENOVERFLOWS和LINUX_MIB_LISTENDROPS这两个MIB

服务端在响应第三次握手的时候,会再次判断全连接队列是否溢出。如果溢出,一样会增加这两个MIB,源码如下:

// net/ipv4/tcp_ipv4.c
struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
				  struct request_sock *req,
				  struct dst_entry *dst)
{
	...
	if (sk_acceptq_is_full(sk))
		goto exit_overflow;
  ...
exit_overflow:
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
  ...
exit:
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
	return NULL;
  ...
}

在proc.c中,LINUX_MIB_LISTENOVERFLOWS和LINUX_MIB_LISTENDROPS都被整合进了SNMP统计信息

// net/ipv4/proc.c
static const struct snmp_mib snmp4_net_list[] = {
	...
	SNMP_MIB_ITEM("ListenOverflows", LINUX_MIB_LISTENOVERFLOWS),
	SNMP_MIB_ITEM("ListenDrops", LINUX_MIB_LISTENDROPS),
	...
};

netstat工具源码

在执行netstat -s的时候,该工具会读取SNMP统计信息并展现出来。netstat命令属于net-tools工具集,net-tools源码如下:

// https://github.com/giftnuss/net-tools/blob/master/statistics.c
struct entry Tcpexttab[] =
{
    ...
    { "ListenOverflows", N_("%u times the listen queue of a socket overflowed"),
      opt_number },
    { "ListenDrops", N_("%u SYNs to LISTEN sockets dropped"), opt_number },
    ...
};

以上这些就是执行netstat -s时会执行到的源码。它从SNMP统计信息中获取ListenDrops和ListenOverflows这两项并显示出来,分别对应LINUX_MIB_LISTENDROPS和LINUX_MIB_LISTENOVERFLOWS这两个MIB

$ watch 'netstat -s | grep overflowed'
 198 times the listen queue of a socket overflowed

所以,每当发生全连接队列满导致的丢包的时候,会通过上述命令的结果体现出来。而且幸运的是,ListenOverflows这个SNMP统计项只有在全连接队列满的时候才会增加,内核源码其他地方没有用到

所以,通过netstat -s输出中的xx times the listen queue如果查看到数字有变化,那么一定是你的服务端上发生了全连接队列溢出了

2)半连接队列溢出判断

再来看半连接队列,溢出时更新的是LINUX_MIB_LISTENDROPS这个MIB,对应到SNMP就是ListenDrops这个统计项

// net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
	...
  // 看看半连接队列是否满了
	if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
		want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
		if (!want_cookie)
			goto drop;
	}

	// 看看全连接队列是否满了
	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;
	}
  ...
drop:
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
	return 0;
}

从上述源码可见,半连接队列满的时候goto drop,然后增加了LINUX_MIB_LISTENDROPS这个MIB

但是问题在于,不只是在半连接队列发生溢出的时候会增加该值。所以根据netstat -s看半连接队列是否溢出是不靠谱的

从前序内容可知,即使半连接队列没问题,全连接队列满了该值也会增加。另外就是当在listen状态握手发生错误的时候,进入tcp_v4_err函数时也会增加该值

对于如何查看半连接队列溢出丢包这个问题,建议是不要纠结怎么看是否丢包了。直接看服务器上的tcp_syncookies是不是1就行

如果该值是1,那么下面代码中want_cookie就返回真,是根本不会发生半连接溢出丢包的

// net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
	...
  // 看看半连接队列是否满了
	if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
		want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
		if (!want_cookie)
			goto drop;
	}
  ...
}

如果tcp_syncookies不是1,则建议改成1就完事了

如果因为各种原因就是不想打开tcp_syncookies,就想看看是否有因为半连接队列满而导致的SYN丢弃,除了netstat -s的结果,建议同时查看当前listen端口上的SYN_RECV的数量

$ netstat -antp | grep SYN_RECV
256

在深入理解Linux网络笔记(六)中讨论了半连接队列的实际长度怎么计算。如果SYN_RECV状态的连接数量达到你算出来的队列长度,那么可以确定有半连接队列溢出了。如果想加大半连接队列的长度,方法也在深入理解Linux网络笔记(六)中讲过了

3)小结

对于全连接队列来说,使用netstat -s(最好再配合watch命令动态观察)就可以判断是否有丢包发生。如果看到xx times the listen queue中的数值在增长,那么就确定是全连接队列满了

$ watch 'netstat -s | grep overflowed'
 198 times the listen queue of a socket overflowed

对于半连接队列来说,只要保证tcp_syncookies这个内核参数是1就能保证不会有因为半连接队列满而发生的丢包。如果确实想看一看,使用watch 'netstat -s | grep “SYNs”'这种方式是错的,是没有办法说明问题的。需要自己计算半连接队列的长度,再看看当前SYN_RECV状态的连接的数量

$ watch 'netstat -s | grep "SYNs"'
 258209 SYNs to LISTEN sockets dropped 
$ netstat -antp | grep SYN_RECV | wc -l
5 
6)、总结

1)为什么服务端程序都需要先listen一下?

内核在响应listen调用的时候是创建了半连接、全连接两个队列,这两个队列是三次握手中很重要的数据结构,有了它们服务端才能正常响应来自客户端的三次握手。所以服务器提供服务前都需要先listen一下才行

2)半连接队列和全连接队列长度如何确定?

服务端在执行listen的时候确定好了半连接队列和全连接队列的长度

对于半连接队列来说,其最大长度是min(backlog, somaxconn, tcp_max_sync_backlog)+1再向上取整到2的N次幂,但最小不能小于16。如果需要加大半连接队列的长度,那么需要一并考虑backlog、somaxconn和tcp_max_sync_backlog

对于全连接队列来说,其最大长度是listen时传入的backlog和net.core.somaxconn之间较小的那个值。如果需要加大全连接队列长度,那么调整backlog和somaxconn

3)Cannot assign requested address这个报错你知道是怎么回事吗?该如何解决?

一条TCP连接由一个四元组构成:Server IP、Server Port、Client IP、Client Port。在连接建立前,前面的三个元素基本是确定了的,只有Client Port是需要动态选择出来的

客户端会在connect发起的时候自动选择端口号。具体的选择过程就是随机地从ip_local_port_range选择一个位置开始循环判断,跳过ip_local_reserved_ports里设置要规避的端口,然后挨个判断是否可用。如果循环完也没有找到可用端口,会报错Cannot assign requested address

理解了这个报错的原理,解决这个问题的办法就很多了。比如扩大可用端口范围、减少最大TIME_WAIT状态连接数量等方法都是可行的

# vi /etc/sysctl.conf
# 修改可用端口范围
net.ipv4.ip_local_port_range = 5000 65000
# 设置最大TIME_WAIT数量
net.ipv4.tcp_max_tw_buckets = 10000
# sysctl -p

4)一个客户端端口可以同时用在两条连接上吗?

connect调用在选择端口的时候如果端口没有被用过那么就是可用的。但是如果被用过并不是说这个端口就不能用了

如果用过,接下来进一步判断新连接和老连接四元组是否完全一致,如果不完全一致,该端口仍然可用。例如5000这个端口号是完全可以用于下面两条不同的连接的

  • 连接1:192.168.1.101 5000 192.168.1.100 8090
  • 连接2:192.168.1.101 5000 192.168.1.100 8091

在保证四元组不相同的情况下,一个端口完全可以用在两条,甚至更多条的连接上

5)服务端半/全连接队列满了会怎么样?

服务端响应第一次握手的时候,会进行半连接队列和全连接队列满的判断。如果半连接队列满了,并未开启tcp_syncookies这个内核参数,那么该握手包将直接被丢弃,所以建议不要关闭tcp_syncookies这个内核参数。如果全连接队列满了,且有young_ack(表示刚刚有SYN到达),那么同样也是直接丢弃

服务端响应第三次握手的时候,还会再次判断全连接队列是否满。如果满了,同样丢弃握手请求

无论是哪种丢弃发生,肯定是会影响线上服务的。当收不到预期的握手或者响应包的时候,重传定时器会在最短1秒后发起重试。这样接口响应的耗时最少就得1秒起步了。如果重试也没握手成功,很有可能就会报超时了

6)新连接的socket内核对象是什么时候建立的?

socket内核对象最核心的部分是struct sock

// include/linux/net.h
struct socket {
	...
	struct sock		*sk;
	...
};

内核其实在第三次握手完毕的时候就把socket对象创建好了。在用户进程调用accept的时候,直接把该对象取出来,再包装一个socket对象就返回了

7)建议一条TCP连接需要消耗多长时间?

一般网络的RTT值根据服务器物理距离的不同大约是在零点几秒、几十毫秒之间。这个时间要比CPU本地的系统调用耗时短得多。所以正常情况下,在客户端或者是服务端看来,都基本上约等于一个RTT。但是如果一旦出现了丢包,无论是哪种原因,需要重传定时器来介入的话,耗时就最少要1秒了

8)把服务器部署在北京,给纽约的用户访问可行吗?

正常情况下建立一条TCP连接耗时是双端网络一次RTT时间。那么如果服务器在北京,用户在美国,这个RTT是多少呢?

美国和中国物理距离跨越了半个地球,北京到纽约的球面距离大概是15000千米。那么抛开设备转发延迟,仅仅光速传播一个来回,需要时间=15000*2/光速=100毫秒。实际的延迟比这个还要大一些,一般都要200毫秒以上。建议在这个延迟上,想要提供用户能访问的秒级服务就很困难了。所以对于海外用户,最好在当地建机房或者购买海外的服务器

9)服务器负载很正常,但是CPU被打到底了是怎么回事?

如果在端口极其不充足的情况下,connect系统调用的内部循环需要全部执行完毕才能判断出来没有端口可用。如果要发出的连接请求特别频繁,connect就会消耗掉大量的CPU。当时服务器上进程并不是很多,但是每个进程都在疯狂地消耗CPU,这时候就会出现CPU被消耗光,但是服务器负载却不高的情况

推荐阅读:

4.1 TCP 三次握手与四次挥手面试题

4.4 TCP 半连接队列和全连接队列

内核参数 tcp_syncookies-- 默认开启tcp_syncookies

4.14 tcp_tw_reuse 为什么默认是关闭的?

从一次线上问题说起,详解 TCP 半连接队列、全连接队列

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1223248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

顺序表(数据结构与算法)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

【Spring】AOP进阶-JoinPoint和ProceedingJoinPoint详解

文章目录 1. 前言2. JoinPoint简介3. 获取被增强方法的相关信息4. ProceedingJoinPoint简介5. 获取环绕通知方法的相关信息6. 总结 1. 前言 在Spring AOP中&#xff0c;JoinPoint和ProceedingJoinPoint都是关键的接口&#xff0c;用于在切面中获取方法的相关信息以及控制方法的…

国科大数据挖掘期末复习——聚类分析

聚类分析 将物理或抽象对象的集合分组成为由类似的对象组成的多个类的过程被称为聚类。由聚类所生 成的簇是一组数据对象的集合&#xff0c;这些对象与同一个簇中的对象彼此相似&#xff0c;与其他簇中的对象相异。 聚类属于无监督学习&#xff08;unsupervised learning&…

整形数据和浮点型数据在内存中的存储差别

愿所有美好如期而遇 我们先来看代码&#xff0c;猜猜结果是什么呢&#xff1f; int main() {//以整型数据的方式存储int n 10;float* m (float*)&n;//以整型数据的方式读取printf("%d\n", n);//以浮点型数据的方式2读取printf("%f\n", *m);printf(&…

揭秘“ChatGPT之父”突遭罢免内幕:从开发者大会起,几件事已有征兆

腾讯新闻《潜望》 纪振宇 发自硅谷 美国时间11月17日午间&#xff0c;OpenAI首席执行官&#xff0c;被称为“ChatGPT之父”的山姆奥特曼突遭董事会罢免。 OpenAI在当天发布的官方声明称&#xff0c;董事会启动了一项特别的调查&#xff0c;结论是奥特曼在与董事会沟通过程中没…

基于JAYA算法优化概率神经网络PNN的分类预测 - 附代码

基于JAYA算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于JAYA算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于JAYA优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

深度学习乳腺癌分类 计算机竞赛

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

嵌入式中一篇搞定Cmake使用教程

今天分享一篇关于 cmake 的相关文章&#xff0c;通过这个工具可以生成本地的Makefile。让我们不用去编写复杂的Makefile。 引言 CMake是开源、跨平台的构建工具&#xff0c;可以让我们通过编写简单的配置文件去生成本地的Makefile&#xff0c;这个配置文件是独立于运行平台和…

数学才是顶级码农的核心修养,码农怎样搞好数学?来看看这些网友强推的数学神作!文末评论区进行评论参与送书哟

文章目录 导读 一&#xff1a;基础篇 1&#xff1a;优美的数学思维&#xff1a;问题求解与证明 2&#xff1a;数学分析 3&#xff1a;线性代数 4&#xff1a;线性代数及其应用 5&#xff1a;代数 二&#xff1a;进阶篇 1&#xff1a;初等数论及其应用 2&#xff1a;数…

【Linux网络】从原理到实操,感受PXE无人值守自动化高效批量网络安装系统

一、PXE网络批量装机的介绍 1、常见的三种系统安装方式 2、回顾系统安装的过程&#xff0c;了解系统安装的必要条件 3、什么是pxe 4、搭建pxe的原理 5、Linux的光盘镜像中的isolinux中的相关文件学习 二、关于实现PXE无人值守装机的四大文件与五个软件的对应关系详解 5个…

基于Python实现大型家用电器和电子产品在线商店购买数据分析【500010098】

导入模块 import pandas as pd import numpy as np import matplotlib.pyplot as plt获取数据 df pd.read_csv( r"./data/kz.csv",sep,)数据描述 该数据包含2020年4月至2020年11月从大型家用电器和电子产品在线商店购买的数据。 数据说明 event_time&#xff1a…

嵌入式中一文搞懂ARM处理器架构

1、嵌入式处理器基础 典型的微处理器由控制单元、程序计数器&#xff08;PC&#xff09;、指令寄存器&#xff08;IR&#xff09;、数据通道、存储器等组成 。 指令执行过程一般分为&#xff1a; 取指&#xff1a; 从存储器中获得下一条执行的指令读入指令寄存器&#xff1b;…

Redis篇---第六篇

系列文章目录 文章目录 系列文章目录前言一、Redis 为什么设计成单线程的?二、什么是 bigkey?会存在什么影响?三、熟悉哪些 Redis 集群模式?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,…

从0开始学习JavaScript--JavaScript 流程控制

JavaScript中的流程控制结构是编写结构化、可读性强的代码的关键。本文将深入研究JavaScript中的流程控制&#xff0c;包括条件语句、循环结构、跳转语句等&#xff0c;并通过丰富的示例代码来更全面地了解和运用这些概念。 条件语句 条件语句用于基于不同的条件执行不同的代…

基于PLC的污水厌氧处理控制系统(论文+源码)

1. 系统设计 污水厌氧由进水系统通过粗格栅和清污机进行初步排除大块杂质物体以及漂浮物等&#xff0c;到达除砂池中。在除砂池系统中细格栅进一步净化污水厌氧中的细小颗粒物体&#xff0c;将污水厌氧中的细小沙粒滤除后进入氧化沟反应池。在该氧化沟系统中进行生化处理&…

抖音直播间涨粉助手,其开发流程与需要的技术和代码分享

先来看实操成果&#xff0c;↑↑需要的同学可看我名字↖↖↖↖↖&#xff0c;或评论888无偿分享 一、直播间涨人气的15种方法 直播间的人气就像水池中的水&#xff0c;想要有源源不断的流量&#xff0c;就要想办法把水龙头的水流开到最大&#xff0c;也就是要增加直播间曝光率&…

使用maven命令打包依赖

1、maven仓库地址 阿里云地址&#xff1a;https://developer.aliyun.com/mvn/search 中央仓库地址&#xff1a;https://mvnrepository.com/ 2、下载方式 在地址栏中输入要搜索的依赖 选择需要的版本 &#xff08;1&#xff09;直接复制 &#xff08;2&#xff09;pom下载 …

如何使用Gitlab搭建属于自己的代码管理平台

大家好&#xff0c;我是Mandy。今天分享的主题内容是如何使用GitLab搭建属于自己的代码管理平台。 为什么会单独分享这篇文章呢&#xff0c;相信在很多的开发同学任职的公司中&#xff0c;都用到了gitlab来做代码管理平台&#xff0c;同时结合GitLab的一些自动化功能&#xff…

均匀光源积分球的应用领域有哪些

均匀光源积分球的主要作用是收集光线&#xff0c;并将其用作一个散射光源或用于测量。它可以将光线经过积分球内部的均匀分布后射出&#xff0c;因此积分球也可以当作一个光强衰减器。同时&#xff0c;积分球可以实现均匀的朗伯体漫散射光源输出&#xff0c;整个输出口表面的亮…

基于算术优化算法优化概率神经网络PNN的分类预测 - 附代码

基于算术优化算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于算术优化算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于算术优化优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…