xiaohanliang
Network
Network
  • hi
  • LOWER
    • 0. arp决定下一跳
    • 1. dns决定终点
    • 2. [WIP]dns是不是真的有这些层级
  • MIDDLE
    • 0. 如何理解tcp握手的设计
    • 1. 诡异的tcp拆包现象
    • 2. tcp是一种高效的协议吗
    • 3. 为什么说没有人可以裸用tcp
    • 4. 尝试理解tcp的设计
    • 5. 连接建立@tcp调优
    • 6. 连接断开@tcp调优
    • 7. [WIP]拥塞控制@tcp调优
    • 8. 不需要这些花里胡哨的东西
    • 9. 怎么又是socket又是tcp
  • UPPER
    • 0. 为什么大家都用http
    • 1. [WIP]为什么http也keep-alive
    • 2. 如何保证pipeline的顺序到达
    • 3. 如何保证http的安全性
    • 4. 只不过https基于tls连接
    • 5. 怎么理解get/post
    • 6. http2为什么更快
    • 7. [WIP]内置加速的http3
    • 8. 怎样制造出实时效果-ws
    • 9. kcp是如何榨干你的带宽的
  • DEVICES
    • [302] 跳转到Linux网络设备
  • KUBERNETES NETWORK
    • [302] 跳转到容器网络
Powered by GitBook
On this page
  • 参数 - 连接建立相关
  • [半连接] 队列溢出
  • [全连接] 队列溢出
  • 参数 - 拥塞控制相关
  • 场景 - 传输耗时
  • reference

Was this helpful?

  1. MIDDLE

5. 连接建立@tcp调优

Previous4. 尝试理解tcp的设计Next6. 连接断开@tcp调优

Last updated 4 years ago

Was this helpful?

[toc]

参数 - 连接建立相关

  • syn-cookies: 是否要使用syn-cookie

  • synack_retries: 决定重新发几次SYN,ACK包

  • max-syn-backlog: syn队列长度, 允许更多的等待连接

  • somaxconn: syn队列/accept队列的长度

如上图所示, 你的连接在建立之后, accept正式启用之前, 一直存在这两个队列里, 而这两个队列是有长度限制的, 很显然, 如果你的程序处理不过来(accept不过来), 很容易就会导致这两个队列溢出, 通过如下手段改善溢出问题

// 1. 判断 [半连接] 队列否产生溢出 -> 287次半连接队列溢出
$ netstat -s | grep listen
287 times the listen queue of a socket overflowed
// 1-1 判断 [全连接] 队列是否溢出
$ netstat -s | grep 'drop | Drop'

// 2. 查看现在相关队列大小参数
// 2-1 syn_backlog 决定半连接队列大小默认128
// 2-2 全连接队列长度由二者共同决定:min(backlog,somax)
$ cat /proc/sys/net/ipv4/max_syn_backlog // 128
$ cat /proc/sys/net/core/somaxconn       // 128

// 3. 修改一下, 并尝试持久化生效
$ echo "net.core.somaxconn=32768" >> /etc/sysctl.conf 
$ echo "net.ipv4.max_syn_backlog=32768" >> /etc/sysctl.conf
$ sysctl -p

[半连接] 队列溢出

正常情况下, 如果半连接队列溢出了, 服务端会直接忽略掉这个SYN包, 让客户端以为自己的SYN包没有发出, 但除此之外还有syn-cookies参数也会参与这个过程

我们之前提过存在一种情况, 就是有人会故意挤占你的syn队列, 在上面我们说服务器会忽略这个SYN包, 导致别的客户端无法访问. syn-cookies是专门防御这种攻击的办法, 在syn-cookies开启以后:

  • 如果现在SYN队列没有满: 按照正常方法处理

  • 如果现在SYN队列已满: 我们会把(源地址/端口+目的地址/端口)哈希成一个整数, 并用这个整数作为(SYN,ACK)包响应回去, 然而接下来这个连接并不会进入队列, 而是直接释放. 稍后客户端, 如果是真的他就会发出最后一个ACK包, 这时候我们再计算一下这个哈希值, 确认是之前链接的返回包, 就进入连接状态

[全连接] 队列溢出

溢出确实不好, 但溢出以后会发生什么呢? 查看net.ipv4.tcp_abort_on_overflow, 参数, 这个参数决定了全连接队列满了以后怎么处理:

  • 如果是1: 直接RST, 客户端那边收到reset by peer

  • 为0: 好, 现在第二个参数登场, 就是synack_retry参数发挥作用

如果是0, 也会回 (SYN,ACK) 包, 收到了这个包的客户端立刻回复了三次握手中的最后一个ACK包, 美滋滋以为连接已经建立了, 但是很不幸, 这个包会被服务器直接忽略, 你无法进入全连接队列了, 你只能在半连接队列里继续呆着了.

而服务器装傻, 他一边忽略这个包, 一边又在问: "我最后一个ACK包去哪了", 然后他不停的发 (SYN,ACK) 催客户端重发ACK, 第二个参数synack_retry 就决定了这句话要问几遍: 现在如果客户端真的回了ACK, 那么服务器会重新尝试看看他能不能挤进全连接队列. 默认5次, 每次间隔时长递增, 最终会消耗3分钟

设想一个场景, 如果客户端回完ACK, 兴奋的以为自己连接已经建立了, 于是开始发数据, 接下来会发生什么? (你压根就不在全连接队列里, 自然也没有人会来accept/read你), 实际上压根没人理你, 超时以后等你主动FIN

这种场景跟sync-flood有关吗? 其实有的, 如果有人专门消磨你这三分钟, 故意挤占你的半连接队列呢? 这减少重试次数, 比如最多重问一次, 一次不回复就算作sync-flood, 直接关闭连接

参数 - 拥塞控制相关

  • keepalive: 发送频率, 默认是两小时一发

  • fastopen:

场景 - 传输耗时

理论上的传输耗时 = 传输数据耗时 + TTL (其中TTL是指浪费在路由器跳跃上的时间, 这一项可以通过Ping得到) , 我们举例说明: 如果你的带宽是"千兆", 一秒钟能发送"100 MegaBits(Mb)"的数据, 那就是"100 MegaBytes(MB)"的数据, 这种情况下:

  • 如果你想发送1MB的数据: 需要 1/100 秒 + TTL

    • 如果是同机房发送, 不走路由器, TTL等于0, 消耗 10ms

    • 如果跨机房发送, TTL是4ms, 那就是10+4 = 14毫秒

  • 如果你想发送4MB的数据. 需要4/100秒+TTL = 40(同)/44(跨)毫秒

reference

开启更大的连接数
连接队列的实验
你的连接去了哪儿