- 今天腾讯一面,问了三次握手。为什么要三次,两次或者四次可以吗?四次挥手,为什么握手只需要三次,挥手要四次,三次可以吗?
这里就针对最后一个我没回答出来的问题进行总结。为什么要四次挥手?
TCP是全双工通信,为了确保数据能够完成传输。
关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了,但未必你所有的数据都全部发送给对方了。所以你可能未必会马上会关闭,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了。所以这里的ACK报文和FIN报文多数情况下都是分开发送的。先发ACK报文是因为,如果不发送,那么客户端可能因为没有收到应答而继续发送断开连接的请求。
可能有人会有疑问,tcp我握手的时候为何ACK(确认)和SYN(建立连接)是一起发送。挥手的时候为什么是分开的时候发送呢.
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。
但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步挥手。
- TCP释放连接后,最开始发送Fin报文的那一方会有一个time-wait的时间段,如果服务器上大量的timewait,会怎么样?怎么处理?
当大量的连接处于 time_wait 时,新建立 TCP 连接会出错,address already in use : connect 异常
网上还有说,“在高并发(每秒几万qps)并且采用短连接方式进行交互的系统中运行一段时间后,系统中就会存在大量的time_wait状态,如果time_wait状态把系统所有可用端口都占完了且尚未被系统回收时,就会出现无法向服务端创建新的socket连接的情况。此时系统几乎停转,任何链接都不能建立。”我好像有点不能理解,就比如说,http请求,服务器每次都监听80端口,建立的所有socket都是80端口,那么端口号只用了80端口呀,并没有占用其他的端口号呀.关于为什么一直是80端口,参考了这篇博客
(其他的等我哪天搞明白了再来补充吧)。
没错,我来补充了。
上面我说的80端口的情况,确实不会讲服务器说有的端口号用尽。但是,如果每次连接都是用的不同的端口号,就有可能将所有的端口号用尽。
处理方式一:客户端,按照HTTP协议的头,在发出的HTTP协议头里面加上connection:keep-alive,就相当于客户端告诉服务端,服务端你执行完成请求之后还是保持连接。保持连接就意味着在保持连接期间,只能由客户端主动断开连接,还有一个keep-alive的头,设置的值就代表了服务端保持连接保持多久,这样timewait就发生在客户端。
处理方式二:服务器方修改参数。
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout = 修改系统默认的 TIMEOUT 时间
net.ipv4.tcp_max_tw_buckets = 5000 表示系统同时保持TIME_WAIT套接字的最大数量,(默认是18000). 当TIME_WAIT连接数量达到给定的值时,所有的TIME_WAIT连接会被立刻清除,并打印警告信息。但这种粗暴的清理掉所有的连接,意味着有些连接并没有成功等待2MSL,就会造成通讯异常。一般不建议调整
net.ipv4.tcp_timestamps = 1(默认即为1)60s内同一源ip主机的socket connect请求中的timestamp必须是递增的。也就是说服务器打开了 tcp_tw_reccycle了,就会检查时间戳,如果对方发来的包的时间戳是乱跳的或者说时间戳是滞后的,那么服务器就会丢掉不回包,现在很多公司都用LVS做负载均衡,通常是前面一台LVS,后面多台后端服务器,这其实就是NAT,当请求到达LVS后,它修改地址数据后便转发给后端服务器,但不会修改时间戳数据,对于后端服务器来说,请求的源地址就是LVS的地址,加上端口会复用,所以从后端服务器的角度看,原本不同客户端的请求经过LVS的转发,就可能会被认为是同一个连接,加之不同客户端的时间可能不一致,所以就会出现时间戳错乱的现象,于是后面的数据包就被丢弃了,具体的表现通常是是客户端明明发送的SYN,但服务端就是不响应ACK,还可以通过下面命令来确认数据包不断被丢弃的现象,所以根据情况使用
其他优化:
net.ipv4.ip_local_port_range = 1024 65535 增加可用端口范围,让系统拥有的更多的端口来建立链接,这里有个问题需要注意,对于这个设置系统就会从1025~65535这个范围内随机分配端口来用于连接,如果我们服务的使用端口比如8080刚好在这个范围之内,在升级服务期间,可能会出现8080端口被其他随机分配的链接给占用掉,这个原因也是文章开头提到的端口被占用的另一个原因
net.ipv4.ip_local_reserved_ports = 7005,8001-8100 针对上面的问题,我们可以设置这个参数来告诉系统给我们预留哪些端口,不可以用于自动分配。