关于TCP

Posted by chunpat on November 29, 2020

TCP 简介

传输控制协议(TCP,Transmission Control Protocol),是一种面向连接的,可靠的,基于字节流的传输层通讯协议。

FpwQJ6gLG0ZnS8oh83PpPVDyEPxN

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 长连接的常见问题

举例图:

FunbR-4EnUkQ12hoYtV2KXFfgxbF

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 初稿

参考:


Creative Commons License
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 chunpat 及本文源链接。