TCP 简介
传输控制协议(TCP,Transmission Control Protocol),是一种面向连接的,可靠的,基于字节流的传输层通讯协议。
TCP 三次握手常见的问题
1、连接拒绝 Operation now in progress (前提tcp_abort_on_overflow=0)
- 丢包: 路由器丢包
- 错误ip:ip错误、port服务端应用没用
- backlog满了: 第三次握手的时候把建立好的连接放到全连接backlog队列里面,backlog长度有限,此参数将决定最多同时有多少个等待 accept 的连接。
2、连接被重置 Connection reset by peer (前提tcp_abort_on_overflow=1)
- backlog满了:客户端发送SYN,服务端返回Rst让客户端重连
3、SYN FLOOD
客户端第三次ACK不发送,半连接backlog满了
查看backlog满,服务端阻塞
netstat -s |grep ‘times the listen queue of a socket overflowed’
TCP 四次挥手常见的问题
主要是客户端的TIME WAIT和服务端的CLOSE WAIT引起
四元组,(客户端ip + 客户端port + 服务端ip + 服务端port) 确认一个链接
1、持续一分钟,包的存活时间
挥手第四个步骤,服务端没有收到客户端的ACK,过一段时间会重传FIN,客户端的TIME WAIT阶段会存留一分钟。
2、Cannot assign requested address
客户端端口用光了,主要是客户端一分钟TIME WAIT占用端口
php-fpm经常会遇到,因为是用完即释放
3、Address already in use
重启服务,客户端TIME WAIT占用端口。解决:设置so REUSEADDR (swoole 默认开启)
其他解决办法
net.ipv4.tcp_timestamps=1
net.ipv4.tcp_tw_reuse=1
net.ipv4.ip_local_port_range调大
不要开启net.ipv4.tcp_tw_recycle=1
4、大量 CLOSE WAIT 链接占用服务端资源
服务端应用close方法出现异常,没有调用close方法。
扩展
swoole server SWOOLE_PROCESS模式close方法决定是否释放close swoole server SWOOLE_BASE模式 会自动调用底层FIN close释放,不会存在大量close wait
$serv = new Swoole\Server("0.0.0.0", 9501, SWOOLE_BASE); //SWOOLE_PROCESS
TCP 短连接问题
短连接顾名思义:用完即释放
1、多余传输
需要握手,挥手
2、TCP慢启动
TCP建立连接需要网络评估
3、握手阶段丢包
网络抖动,如发生在握手阶段重传耗秒级别,而在连接阶段重传在毫秒级别。
4、对连接的占用约等于长连接
占用资源和长连接差不多
优点
- 1、简单
- 2、理论上连接数相对少
- 3、无状态对LB负载均衡友好,效果好(长连接不释放连接一直占用,分发负载不了)
TCP 长连接的常见问题
举例图:
1、链接失效
长连接应用层表现:
- Redis :timeout(Error while reading line from the server)
- Mysql: wait timeout & interactive_timeout(has gone away)
解决办法:
1、用的时候重试,但是存在以下问题
- 1、server 断开后占用连接资源(内存、端口、句柄),被动断开close_wait永远不会消失
举例:以上图为例,服务端超时没收到东西,发送FIN到php客户端,php 底层tcp回ACK确认收到。但是长连接处于被动状态不发FIN到服务端主动关闭链接。 这样服务端就处于在FIN_WAIT2、客户端在CLOSE_WAIT状态。 客户端就一直处于CLOSE_WAIT,相反服务端由于有系统设置服务端自动断开连接(os系统超时时间)
net.ipv4.tcp_fin_timesout = 30
所以服务端不会保持在FIN_WAIT2状态。
-
2、面临短连接的问题
-
3、优点简单
2、定时发心跳维持连接 tcp_keepalive
需要解决的问题
- 1、心跳检测时间不够灵活
- 2、设置定时时间段检测 (早上八点检测)
- 3、无法检测假死,两个连接,依赖第三方redis服务
- 4、依赖网络环境,需要稳定
- 5、不是直连,通过代理链接,如socks5只转发应用层的包,不转发探测包
1、tcp_keepalive
2、swoole tcp_keepalive
$serv = new Swoole\Server("192.168.2.194", 6666, SWOOLE_PROCESS);
$serv->set(array(
'worker_num' => 1,
'open_tcp_keepalive' => true,
'tcp_keepidle' => 4, //4s没有数据传输就进行检测
'tcp_keepinterval' => 1, //1s探测一次
'tcp_keepcount' => 5, //探测的次数,超过5次后还没回包close此连接
));
3、swoole heartbeat_idle_time(连接最大允许空闲的时间)
上面两个keepalive的缺点就是无法检测死链接,死链接会自动回ping检测。
array(
'heartbeat_idle_time' => 600, // 表示一个连接如果600秒内未向服务器发送任何数据,此连接将被强制关闭
'heartbeat_check_interval' => 60, // 表示每60秒遍历一次
);
结合应用层设置定时心跳发送
- 1、指定ping/pong协议(mysql等自带ping协议)
- 2、客户端灵活的发送ping心跳包
- 3、服务端OnRecive检测可用性回复Pong
和检测转发的长连接服务,这相应的可以处理问题3、4、5,对于1、2无法解决。
面试常问
1、挥手一定是四次吗?
2、握手为什么是三次?
3、长连接有哪些问题?
迭代
- 2020年11月29日 22:28:12 初稿
参考:
- 1、几种TCP连接中出现RST的情况 - costaxu的个人页面 - OSCHINA - 中文开源技术交流社区
- 2、TCP/IP协议中backlog参数 - Orgliny - 博客园
- 3、Swoole微课程
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 chunpat 及本文源链接。