百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

TCP连接的建立和终止

bigegpt 2024-08-28 12:24 8 浏览

TCP连接建立过程

三次握手

TCP连接建立过程需要经过三次握手,如图所示,三次握手的具体过程如下:

  • 客户端发送SYN包,指明打算连接的服务器端口,以及初始序号ISN(SYN包占用一个序号,seq=X,SYN)

  • 服务端收到客户端SYN包,返回包含ISN的SYN包进行确认,确认序号为客户端的ISN+1(seq=Y,SYN,ACK=X+1)

  • 客户端接收到服务端的确认SYN包,返回确认包,确认序号为服务端的ISN+1(ACK=Y+1)

为什么建立连接需要经过三次握手,两次握手行不行?如果采用两次握手即服务器返回SYN包之后就表示连接已经建立成功,正常情况下没有问题,但异常情况下就会存在问题。但是如果服务端的确认SYN包丢失,此时服务端认为TCP连接已经建立成功可以进行数据交互,客户端等待超时会进行重传SYN包,此时服务端会将此包当成新建连接的SYN包而非重传的SYN包。

连接过程中异常处理

  • SYN/SYN-ACK重传:在建立连接过程中,如果客户端发送SYN包由于丢失或服务器主动丢弃一直未收到服务端的SYN-ACK回复,等待超时后客户端会进行重传,重传的次数由参数内核参数tcp_syn_retries决定(/proc/sys/net/ipv4/tcp_retries,默认为6);同样,服务端在发送SYN-ACK包后在等待客户端回复ACK超时后也会进行重传,重传次数由内核参数tcp_synack_retries决定(/proc/sys/net/ipv4/tcp_synack_retries,默认为5)。两者每次重传的时间间隔都是指数增长的(eg: 1s、2s、4s...),如果超过重传次数后还未收到回复,则客服端放弃连接,服务端断开处于中间状态连接。

  • SYN FLOOD攻击:服务端在内核中维护着一个SYN队列,队列长度由内核参数tcp_max_syn_backlog决定,当这个队列满了之后,服务端会直接丢弃客户端请求的SYN包。基于此产生了SYN FLOOD攻击,给服务端发完SYN包后就下线,服务端要重传SYN-ACK一定次数后才会断开此连接,这样会将SYN队列耗尽,让正常的请求不能得到处理。为了应对此种情况,TCP设计了syncookie功能,该功能有内核参数tcp_syncookies开关控制(默认为1,打开),SYN队列满了后,服务端会通过<src_port, dst_port,timestamp>产生一个特殊ISN回复客户端,正常连接将此SYN cookie发回来之后建立连接。这种方式不建议用,在负载大情况下可以通过调大tcp_max_syn_backlog、调小tcp_synack_retries、打开tcp_abort_on_overflow。

建链中SOCKET系统调用处理过程

内核中处理TCP连接时维护着两个队列:SYN队列和ACCEPT队列,如上图所示,服务端在建立连接过程中内核的处理过程如下:

  • 客户端使用connect调用向服务端发起TCP连接,内核将此连接信息放入SYN队列,返回SYN-ACK

  • 服务端收到客户端的ACK后,将此连接从SYN队列中取出,放入ACCEPT队列

  • 服务端使用accept调用将连接从ACCEPT队列中取出

队列长度是有限制的,SYN队列长度由内核参数tcp_max_syn_backlog决定,ACCEPT队列长度可以在调用listen(backlog)通过backlog,但总最大值受到内核参数somaxconn(/proc/sys/net/core/somaxconn)限制。

若SYN队列满了,新的SYN包会被直接丢弃。若ACCEPT队列满了,建立成功的连接不会从SYN队列中移除,同时也不会拒绝新的连接,这会加剧SYN队列的增长,最终会导致SYN队列的溢出。当ACCEPT队列溢出之后,只要打开tcp_abort_on_flow内核参数(默认为0,关闭),建立连接后直接回RST,拒绝连接(可以通过/proc/net/netstat中ListenOverflows和ListenDrops查看拒绝的数目)。

抓包示例

net.ipv4.tcp_abort_on_overflow = 0

net.ipv4.tcp_max_syn_backlog = 128

net.ipv4.tcp_syn_retries = 6

net.ipv4.tcp_synack_retries = 5

net.ipv4.tcp_syncookies = 1

listen.backlog = 1 /* listen参数 */

ACCEPT队列溢出,SYN队列正常

  • 服务端一直不调用accept导致ACCEPT队列溢出,此时客户端发出SYN包请求连接,三次握手成功,此时客户端此连接状态为ESTABLISHED,而服务端连接状态为SYN_SEND

  • 此时服务端打算将连接从SYN队列移到ACCEPT队列,由于ACCEPT队列溢出移动失败,为连接建立定时器,超时后重新发送SYN-ACK,如包所示,在49/51/55/03/19时刻都重传了SYN-ACK,达到tcp_synack_retries次数,服务端断开此连接

  • 服务端上此连接已经被断开,但在客户端上此连接状态为ESTABLISHED,此时客户端给服务端发包,将会导致服务端回RST

net.ipv4.tcp_abort_on_overflow = 1

在tcp_abort_on_overflow=1情况下,当ACCEPT队列溢出,经过三次握手建立连接之后,服务端立马发送RST包断链

net.ipv4.tcp_abort_on_overflow = 0

net.ipv4.tcp_max_syn_backlog = 1

net.ipv4.tcp_syn_retries = 6

net.ipv4.tcp_synack_retries = 5

net.ipv4.tcp_syncookies = 0/1

listen.backlog = 1 /* listen参数 */

SYN队列溢出

  • 服务端一直不调用accept导致ACCEPT队列溢出,最终导致了SYN队列溢出

  • SYN队列溢出,客户端发起SYN连接请求,如果服务端开启syncookies,正常建立连接

  • 若服务端未开启syncookies,则直接丢弃客户端SYN,客户端超时重发SYN包tcp_syn_retries次后还未收到SYN-ACK包,放弃连接请求

TCP断链过程

断链的四次握手

TCP断开连接需要经过四次握手,如图所示,四次握手交互过程如下:

  • 客户端关闭连接,发送FIN包(FIN包和SYN包一样,占用一个序号,FIN seq=X+2 ACK=Y+1)

  • 服务端收到FIN包,回复ACK(ACK=X+3)

  • 服务端完成清理工作,发送FIN包关闭连接(FIN seq=Y+1 ACK=X+3)

  • 客户端对FIN包进行确认,发送ACK(ACK=Y+2),至此连接断开

TIME_WAIT状态

TCP经过四次握手断开连接后,主动关闭方进入的是TIME_WAIT状态,经过2MSL(60s)时间后再进入CLOSED状态。TIME_WAIT状态设置的原因主要有两个: 1) 保证有足够的事件确认对端收到ACK,如果ACK丢失就会触发对方重发FIN包,一个RTT正好是2MSL。 2) 保证连接上旧的数据包在网络中消逝。旧连接上的数据包由于路由器缓存等原因一直未到达对端,旧的连接关闭,然后在相同的地址和端口上又建立新连接,这时旧连接分包到达,新连接会把它当做新的数据包。而在TIME_WAIT状态停留2MSL能保证旧的数据包消逝。

处于TIME_WAIT状态连接的端口是不能被使用的,如果服务端程序主动关闭需要立马重启的,可以使用SO_REUSEADDR选项,允许处于TIME_WAIT状态连接的端口的重复绑定,但这可能有非期望数据到达,引起服务器程序混乱。

在大并发短连接的情景下,TIME_WAIT就会太多,会占用大量的系统资源,可以通过以下方法控制TIME_WAIT数量:

  • 最有效的是服务端不主动关闭连接,尽量让客户端主动关闭连接,只有主动关闭方才会进入TIME_WAIT状态

  • 设置SO_LINGER选项,在关闭时,直接发送RST,不经过TIME_WAIT直接进入CLOSED状态

  • 开启内核参数tcp_tw_recycle不用等待2MSL等待1个重传事件就释放TIME_WAIT连接,旧的数据包通过IP和时间戳去区分。但对端如果是NAT网络使用同一个IP情况下会导致连接失败

  • 开启内核参数tcp_tw_reuse重用TIME_WAIT连接

  • 调整内核参数tcp_tw_max_buckets,控制TIME_WAIT连接数量,超过时将多余的TIME_WAIT连接删除掉并记录日志,能够预防简单的DOS攻击

close/shutdown系统调用

关闭socket连接有两个系统调用,分别是close和shutdown,函数原型为

int close(int fd);

int shutdown(int sockfd, int how);

close调用只是将socket描述符的引用计数减一,在多进程共享一个socket情况下,直至引用计数变成0时,才会关闭socket连接,close进行的是全关闭,即关闭后既不能进行读也不能进行写,若对端在关闭后还发送数据,直接返回RST包,如果继续发送数据,系统会返回SIGPIPE信号给进程。

shutdown调用直接关闭socket,如果该socket是多进程共享,那么其它进程在关闭后就无法使用。shutdown通过how参数控制其关闭的方向,可以关闭任一方向(读或写或读写)。

调用close时,选项SO_LINGER将决定如何处理残留在发送缓冲区中的数据。选项参数结构体如下:

typedef struct linger {

u_short l_onoff; //开关,零或者非零

u_short l_linger; //优雅关闭最长时限

} linger;

下表为close的在不同参数下的行为

l_onoffl_lingerclose 行为
0任意默认处理是close立即返回,系统将发送缓冲区数据
非零0丢弃发送缓冲区中数据,立即发送RST包,不经过TIME_WAIT状态直接进入CLOSED状态
非零非零阻塞到超时或数据全部发送完成,超时close返回EWOULDBLOCK错误

close和shutdown在不同情况下的行为如下表所示:

函数说明
shutdown SHUT_RD不再接收数据,可以继续发送数据丢弃接收缓冲区数据,再接收到数据由TCP层丢弃发送缓冲区不受影响可
shutdown SHUT_WR不再发送数据,可以继续接收数据接收缓冲区不受影响|发送缓冲数据被发送到对端后发送FIN
close l_onoff=0不再发送和接收数据丢弃接收缓冲区数据发送缓冲数据被发送到对端后发送FIN
close l_onoff=1 l_linger=0不再发送和接收数据丢弃接收缓冲区数据丢弃发送缓冲数据发送RST到对端
close l_onoff=1 l_linger!=0不再发送和接收数据丢弃接收缓冲区数据发送缓冲数据被发送到对端后发送FIN,若在发送完成前超时,close返回EWOULDBLOCK

TCP状态机

RST包

RST标识复位,用来异常的关闭连接。发送RST包,不必等缓冲区里面包都发送出去,直接丢弃缓冲区包,然后发送RST。接收到RST包,不用回复ACK来确认。

在以下几种情况下,会发送RST包

  • 当连接请求到达时,该端口没有进程正在监听会回复RST包

  • 异常终止一个连接,使用SO_LINGER选项在close时直接发送RST包,而不没有四次握手

  • 如果一方已经关闭或异常终止连接而对端不知道,此时对端发送数据返回RST包

相关推荐

Go语言泛型-泛型约束与实践(go1.7泛型)

来源:械说在Go语言中,Go泛型-泛型约束与实践部分主要探讨如何定义和使用泛型约束(Constraints),以及如何在实际开发中利用泛型进行更灵活的编程。以下是详细内容:一、什么是泛型约束?**泛型...

golang总结(golang实战教程)

基础部分Go语言有哪些优势?1简单易学:语法简洁,减少了代码的冗余。高效并发:内置强大的goroutine和channel,使并发编程更加高效且易于管理。内存管理:拥有自动垃圾回收机制,减少内...

Go 官宣:新版 Protobuf API(go pro版本)

原文作者:JoeTsai,DamienNeil和HerbieOng原文链接:https://blog.golang.org/a-new-go-api-for-protocol-buffer...

Golang开发的一些注意事项(一)(golang入门项目)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

golang 托盘菜单应用及打开系统默认浏览器

之前看到一个应用,用go语言编写,说是某某程序的windows图形化客户端,体验一下发现只是一个托盘,然后托盘菜单的控制面板功能直接打开本地浏览器访问程序启动的webserver网页完成gui相关功...

golang标准库每日一库之 io/ioutil

一、核心函数概览函数作用描述替代方案(Go1.16+)ioutil.ReadFile(filename)一次性读取整个文件内容(返回[]byte)os.ReadFileioutil.WriteFi...

文件类型更改器——GoLang 中的 CLI 工具

我是如何为一项琐碎的工作任务创建一个简单的工具的,你也可以上周我开始玩GoLang,它是一种由Google制作的类C编译语言,非常轻量和快速,事实上它经常在Techempower的基准测...

Go (Golang) 中的 Channels 简介(golang channel长度和容量)

这篇文章重点介绍Channels(通道)在Go中的工作方式,以及如何在代码中使用它们。在Go中,Channels是一种编程结构,它允许我们在代码的不同部分之间移动数据,通常来自不同的goro...

Golang引入泛型:Go将Interface「」替换为“Any”

现在Go将拥有泛型:Go将Interface{}替换为“Any”,这是一个类型别名:typeany=interface{}这会引入了泛型作好准备,实际上,带有泛型的Go1.18Beta...

一文带你看懂Golang最新特性(golang2.0特性)

作者:腾讯PCG代码委员会经过十余年的迭代,Go语言逐渐成为云计算时代主流的编程语言。下到云计算基础设施,上到微服务,越来越多的流行产品使用Go语言编写。可见其影响力已经非常强大。一、Go语言发展历史...

Go 每日一库之 java 转 go 遇到 Apollo?让 agollo 来平滑迁移

以下文章来源于GoOfficialBlog,作者GoOfficialBlogIntroductionagollo是Apollo的Golang客户端Apollo(阿波罗)是携程框架部门研...

Golang使用grpc详解(golang gcc)

gRPC是Google开源的一种高性能、跨语言的远程过程调用(RPC)框架,它使用ProtocolBuffers作为序列化工具,支持多种编程语言,如C++,Java,Python,Go等。gR...

Etcd服务注册与发现封装实现--golang

服务注册register.gopackageregisterimport("fmt""time"etcd3"github.com/cor...

Golang:将日志以Json格式输出到Kafka

在上一篇文章中我实现了一个支持Debug、Info、Error等多个级别的日志库,并将日志写到了磁盘文件中,代码比较简单,适合练手。有兴趣的可以通过这个链接前往:https://github.com/...

如何从 PHP 过渡到 Golang?(php转golang)

我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗旨,从零开始,开始学习。因为我司没有专门的Golang大牛,所以我也只能一步步自己去...