目录
TCP——传输控制协议(Transmission Control Protocol)。是一种面向连接的传输层通信协议
什么是连接
TCP连接是指通过TCP协议在网络中建立的一种可靠的通信链路。TCP连接在应用层之间提供可靠,高效的通信方式,广泛应用于互联网上的各种应用,如网页浏览,电子邮件,文件传输等。
建立连接在操作系统中是需要有对应的数据结构管理,维护的。所以连接的建立,维护都是有成本的
为什么要建立连接
MSS和SACK可参看【计算机网络】TCP协议报头详解的选项部分
TCP建立连接,通信,断开连接的流程总图如下:
【1. 服务器初始化状态】
服务器端进程先创建传输控制块(TCB),socket()创建套接字listenfd,bind()将套接字和端口号绑定,服务器进程准备接收客户端的连接请求。然后服务器进程调用listen()函数,使listenfd成为监听套接字,后续连接都从监听套接字获取。此时服务器端进程处于监听(listen)状态,紧接着调用accept()函数,在listenfd套接字中等待客户的连接到来
服务器端进程调用函数顺序:socket => bind => listen => accept。当执行到accept函数时,服务器进程会一直处于阻塞状态,直到有客户连接请求到达才返回
?【2. 客户端发起连接请求,发送SYN同步报文段,第一次握手】
客户端进程也创建传输控制块(TCB),socket()创建套接字,然后向服务器端发送连接请求报文段,这时请求报文段的首部标志位SYN=1,同时选择一个初始序号seq=x,这个初始序号x是随机产生的整数ISN。TCP规定,SYN报文段(即SYN=1的TCP报文段)不能携带数据,但要消耗一个序号。此时,客户端进程进入SYN-SENT(同步报文已发送)状态
客户端进程函数调用顺序:socket => connect。当客户端调用connect函数时,操作系统会自动bind(),客户端进程就会向服务器进程发送连接请求的SYN同步报文段
SYN=1的报文段称为同步报文段
ISN(Initial Sequence Number)初始序列号
seq序号,即当前发送的报文首字节的编号
?【3. 服务器同意建立连接,回复确认信息,第二次握手】
服务器端进程收到连接请求报文,若同意建立连接,从listenfd中获取客户端信息,并且由服务器端操作系统向客户端进程发送SYN报文段给出确认。在确认报文的首部中,SYN=1,ACK=1,确认号ack=x+1 。同时也为自己选择一个初始序号seq = y。这个确认报文也不能携带数据,但同样要消耗一个序号。这时,TCP服务器进入SYN-RCVD(同步报文已收到)状态
TCP协议规定,只要接收方接收到数据,必须给发送方发送确认报文——报文首部ACK标志位为1
ack确认号,期望对方发送的下一个报文首字节的编号
【4. 客户端确认连接,发送确认连接信息,第三次握手】
客户端进程收到服务器进程的确认报文后,还要向服务器进程的SYN报文给出确认。在确认报文首部,ACK=1,确认号ack=y+1,序号seq=x+1。TCP标准规定,ACK报文可以携带数据,但如果不携带数据,则不消耗序号。在这种情况,客户端进程的下一个数据报文序号仍为seq=x+1。客户端在发送确认报文后,认为连接成功建立,先进入ESTABLISHED(已建立连接)状态。
当服务器端进程收到客户端发送的确认报文后,认为连接成功建立,进入ESTABLISHED(已建立连接)状态
上面给出的TCP连接建立过程叫做“三次握手”。注意,上图服务器发送给客户端的报文,也可以拆分为两个报文,即先发送ACK=1(ack=x+1)的确认报文,再发送SYN=1(seq=y)的同步报文。客户端收到服务器端的同步报文段,发送确认报文,过程会变成“四次握手”,但效果一样
问题1:TCP建立连接可以只有2次握手吗?
回答:肯定不行。理由:在握手过程中,我们看到客户服务分别在最后一次握手建立连接。如果是两次握手,那么最后的报文由服务器发送,客户端接收
如此,服务器就会先于客户端建立连接。这是万万不可的。
“已失效的连接请求报文”是如何产生的呢?
假定一种异常情况:客户端A发送的第一个连接请求报文在某个网络结点滞留了,延误到客户端A认为该报文丢失失效,本次连接失败时,请求报文到达了服务器B。此时该请求报文已经失效,但B并不知道,所以给客户端A发送确认报文,如果只有两次握手,那么此时服务器B发出确认报文,建立连接,进入ESTABLISHED状态
由于A没有再发出建立连接的请求,因此不会处理B的确认报文,也不会向B发送数据。但B却认为连接成功建立,并一直等待A发送数据,浪费资源
所以握手次数绝不能是偶数次,因为这样会使得服务器先建立连接,将维护连接的成本嫁接给服务器
问题2:TCP连接建立为什么需要3次握手?
理由一:奇数次握手,客户端优先建立连接。
理由二:防止已失效的连接请求报文突然又传送到了服务器端,因而产生错误。
理由三:三次握手是客户服务器验证双方信道通畅,发送接收能力无误的最小成本
问题3:在TCP连接建立过程中,如果服务器一直收不到客户端的ACK确认报文,会发生什么?
操作系统会给每个处于SYN-RCVD状态的服务器进程设定一个计时器,如果超过一定时间还没有收到客户端第三次握手的ACK确认报文,将会重新发送第二次握手的确认报文,直到重发达到一定次数才会放弃
问题4:初始序列号ISN为什么要随机初始化?
seq序号表示的是发送的TCP报文数据部分的起始字节位置,服务器/客户端可以通过序号正确读取数据。如果不是随机分配起始序列号,那么黑客就会很容易获取客户端与服务器之间TCP通信的初始序列号,然后通过伪造序列号让通信主机读取到携带病毒的TCP报文,发起网络攻击
问题5:SYN洪水攻击如何解决
SYN洪水攻击:攻击者在短时间内伪造大量不存在的IP地址,向服务器不断地发送连接请求的SYN同步报文。服务器需要为每个请求发送SYN-ACK确认报文,并等待客户端的确认,但因为是不存在IP地址,所以要像问题三中那样,不断发送SYN-ACK确认报文,直到重发到一定次数才会放弃,但这样同样消耗资源。
解决方法:
TCP连接的释放可以用“四次挥手”的过程来描述。数据传输结束后,通信双方都可以释放连接。现在客户端和服务器都处于ESTABLISHED状态。释放连接的过程如下图所示:
【1. 客户端主动断开连接,调用close(fd)发送释放连接的FIN报文,第一次挥手】
客户端进程调用close(fd)关闭套接字,操作系统发送释放连接的FIN结束报文,并停止发送数据,主动关闭TCP连接。在结束报文的首部,标志位FIN=1,序号字段seq=u,等于前面已发送的数据的最后一个字节的序号+1.此时客户端进入FIN_WAIT_1(终止等待)状态,等待服务器发送确认报文。
TCP规定,FIN报文即使不携带数据,也要消耗一个序号。与SYN报文一样
【2. 服务器收到客户端发送的结束报文,发出确认报文段,第二次挥手】
服务器收到客户端发送的释放连接的FIN结束报文,立即发送确认报文,确认号ack=u+1,序号seq=v,等于服务器前面已发送的数据的最后一个字节的序号+1。服务器进入CLOSE_WAIT(关闭等待)状态。TCP服务器进程此时通知上层应用程序,客户端不向服务器发送数据了,但服务器若有数据发送,客户端仍要接收,此时TCP连接处于半关闭(half-close)状态。
客户端收到服务器的确认报文后,进入FIN_WAIT2(终止等待2)状态,等待服务器发送的FIN结束报文
【3. 服务器调用close(connfd)关闭套接字,释放连接,发送FIN结束报文,第三次挥手】
当服务器没有要向客户端发送的数据时,其应用进程调用close(connfd)通知TCP释放连接,向客户端发送FIN结束报文。结束报文中,FIN=1,假定序号seq=w(半关闭状态,服务器可能还发送了一些数据),同时还必须重复上次已发送过的确认号ack=u+1。此时,服务器进程进入LAST_ACK(最后确认)状态,等待客户端确认。
【4. 客户端收到服务器的FIN结束报文,发送确认报文,第四次挥手】
客户端在收到服务器的FIN结束报文后,向服务器发送ACK确认报文。在报文首部中,ACK=1,确认序号ack=w+1,序号seq=u+1(第一次挥手的FIN报文消耗一个序号)。然后客户端进入TIME_WAIT(时间等待)状态
此时TCP连接还没有释放掉,必须经过时间等待计时器(TIME_WAIT timer)设置的时间2MSL后,A才进入CLOSED状态,时间MSL(Maximum Segment Lifetime,最长报文寿命)即一个TCP报文存活的最长时间。RFC793建议2分钟,现在可以根据情况使用更小的MSL值。因此客户端进入TIME_WAIT状态,要经过4分钟才进入CLOSED状态,才可以建立下一个连接,当客户端撤销相应的传输控制块TCB,才结束这次TCP连接
服务器只要收到客户端的确认报文,就进入CLOSED状态。同样,服务器在撤销相应的传输控制块TCB后,就结束此次TCP连接
可以发现,先发起释放连接请求的一方,后结束TCP连接
问题1:为什么建立连接是三次握手,关闭连接是四次挥手
首先,建立连接也可以是四次握手,中间的SYN和ACK可以分成两次报文。其次关闭连接,中间服务器给客户端发送的确认报文和FIN结束报文也可以合并,但可能服务器方还有数据需要传输,FIN结束报文在上层调用close()时发送,此时服务器已没有数据传输。
发送FIN报文,只是表示本端不再继续发送数据,但还可以接收数据。TCP通信时全双工的,收到FIN报文,只是关闭一个方向的连接,此时TCP处于半关闭状态
问题2:为什么客户端在TIME_WAIT需要等待2MSL才进入CLOSED状态
理由一:保证客户端发送的最后一个ACK报文能到达对端,保证可靠的终止TCP连接。因为如果出现网络拥塞,该报文可能丢失,因而会使LAST_ACK状态的服务器收不到确认报文,而超时重传FIN报文,而客户端能在2MSL时间内收到重传的FIN报文。接着客户端重传一次确认,重新启动2MSL计时器。最后双方正常进入CLOSED状态
理由二:防止已失效的连接请求报文出现在本次TCP连接。客户端在发送完最后一个ACK报文后,再经过2MSL后,就可以使本次TCP连接持续时间内所产生的所有报文都从网络上消散。这样可以使下一个新的TCP连接不会出现之前旧的请求报文
问题3:TIME_WAIT状态何时出现?TIME_WAIT会带来哪些问题?
TIME_WAIT状态是主动发起关闭连接的一方在收到对方发送的FIN结束报文,并本端发送ACK报文后的状态。
TIME_WAIT的引入是为了让TCP报文得以自然消散,同时为了让被动关闭的一方能够正常关闭连接
问题4:解决TIME_WAIT状态引起的bind()函数执行失败
问题场景:因为主动关闭连接最终会进入TIME_WAIT状态,此时bind的IP地址,端口号都没有释放,重新启动服务会导致bind端口号失败。
解决方法:可以使用setsockopt()函数,设置socket描述符的SO_REUSEADDR选项,该选项可以让端口号被释放后立即再次使用,表示允许创建端口号相同但IP地址不同的多个socket描述符
问题5:半连接,半打开,半关闭的区别
半连接:在TCP连接建立的三次握手中,主动发起连接请求的一方不发最后一次的ACK确认报文,使得服务器阻塞在SYN_RCVD(同步收到)状态
半打开:如果TCP通信一方异常关闭(如断网,断电,进程被kill掉),而通信对端并不知情,此时TCP连接处于半打开状态,如果双方不进行数据通信,是无法发现问题的。解决方法是引入心跳机制,设置一个保活计时器(keepalive timer),以检测半打开状态,检测到就发送RST复位报文,重新建立连接
半关闭:主动发起连接关闭请求的一方A发送了FIN结束报文段,对端B回复了ACK确认报文段后,B并没有立即发送本端的FIN结束报文段给A。此时A端处于FIN_WAIT-2(结束等待2)状态,A仍然可以接收B发送过来的数据,但是A已经不能再向B发送数据了。这时的TCP连接为半关闭状态。
补充:
?在Socket编程中,服务器Listen的第二个参数
三次握手是操作系统自助完成的,但服务器需要accept返回,才会拿到客户端的连接信息。如果服务器没有accept,这个连接就是全连接。backlog是全连接队列的容量-1。
Linux内核协议栈为TCP连接管理使用两个队列:
为了更清晰地看出TCP连接的各种状态之间的关系,下图给出了TCP的状态转换示意图。
说明:紫色框框是TCP状态,红色是服务器进程的正常状态转换,蓝色是客户端进程的正常状态转换,黑色是异常变迁,即出现问题时的状态转换。
服务端状态转化:
客户端状态转化:
本篇博客到此结束,感谢看到此处。
欢迎大家纠错和补充
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。