网络基础:传输层(tcp协议:连接管理机制)

tcp协议(传输控制协议):对数据的传输进行一个详细的控制。

tcp协议段格式
这里写图片描述

源/目的端口号:表示数据来自于哪个进程,要去到哪个进程。
32位序号/32位确认序号:后面详细讲解
4位tcp报头长度:表示该TCP头部有多少个32位bit(有多少个4字节),所以tcp头部最大长度是15*4 = 60BYTE。
16位窗口大小:后面详细讲解
16位校验和:发送端填充,crc校验,接收端校验不通过则认为数据有问题,此处的校验和不光包含TCP首部,也包含TCP数据部分。
16位紧急指针:表示哪部分数据是紧急数据

6位标志位:

字段 说明
URG 紧急指针是否有效
ACK 确认号是否有效
PSH 提示接收端应用程序立刻从tcp缓冲区把数据读走
RST 对方要求重新建立连接;我们把携带RST表示的称为复位报文段
SYN 请求建立连接;我们把携带SYN标识的称为同步报文段
FIN 通知对方本端要关闭连接我们称携带FIN标识的为结束报文段

下面我们将从可靠性和性能两方面来深入的理解TCP协议,来看一看TCP是如何来保证可靠性并且提高性能的。

可靠性方面

一:连接管理机制
正常的情况下,TCP要经过3次握手建立连接,经过4次挥手断开链接。
下面我们就来看一看到底是如何3次握手4次挥手的。
3次握手:
这里写图片描述

1、 当客户端有需求时,会首先发起连接请求(希望建立连接,携带SYN(请求建立连接)字段)。请求发送出去以后就等待对方(服务器)的应答(看对方是否同意建立连接)。第一次握手
2、服务器端是一直处于监听状态,等待是否有需要建立连接的人,如果收到一个希望建立连接的请求,此时服务器端就会进行应答(同意连接还是不同意)。(携带ACK字段(确认)和SYN字段(表示我也想建立连接))。第二次握手
3、客户端收到服务器端的应答,发现服务器端同意建立连接,此时就给服务器端应答(携带ACK字段),表示我知道了那么连接就建立好了。第三次握手
我们可以想的更加生活化一点帮助我们理解这样一个3次握手的过程。就像我们有时候约人出去吃饭,首先我们给他打电话问他:
我们一起去吃饭吧?(SYN)第一次握手
他说:好啊(ACK),什么时候(SYN)第二次握手
此时你当然会继续给对方回应:现在就去吧(ACK)第三次握手
这样一看,3次握手的过程就比较简单易懂了。

下面我们来讨论一下,为什么偏偏就要进行3次握手呢???
可以假设一下如果进行一次握手的后果是怎样的?
如果进行一次握手,也就是说,只有客户端发起连接。如果是这样那么请想象一下当你找人吃饭时你就跟他说我们去吃饭吧,然后你们之间就没有下文了,那么你怎么知道他到底是同意还是不同意,你们之间吃饭的时间和地点统统都没有协商好,这个事情又如何能进行下去呢?所以说,如果我们只进行一次握手,我们是不知道服务器端是否同意跟我们建立连接的,如果连接根本就没有建立,那么一切都没有任何意义啊。
好了,既然一次握手行不通,就在加一次,2次握手到底是否可以呢?我们可以来分析一下。如果进行两次握手,也就是说,客户端发送连接请求以后是可以收到服务器端的回信的,就像你约人吃饭你能够知道对方是同意还是不同意跟你去吃饭,然后2次握手完毕没有下文了,想一想,对方还问你了什么时候去哪儿吃,但是你却并没有回答人家,此时对方就会疑惑,他到底知道不知道我已经同意跟他去吃饭了或者会想这货到底是不是要跟我去吃饭啊,既然没有回信可能对方就不管你又去干别的事情了。另外一种情况就是,你昨天刚好有时间所以你约人出去吃饭,但是呢由于一些原因对方就没有看到你的消息,因此一直没有给你回信,但是今天他看到消息了并且回复了你说一起去吃饭,但是你今天就没有时间,如果此时采用2次握手(你不给他回消息),他就以为你们已经约好了一起去吃饭,然后就精心打扮等待着你……所以说,如果客户端在收到服务器端的应答以后如果不给对方回信,那么服务器端就以为连接建立好了,然后就等着客户端给他传送数据,这无疑就浪费了很多的资源。所以说就像你今天收到对方的回信说一起去吃饭时应该给对方回复我今天没有时间。对方就会知道了。对应于3次握手,如果你最终应答一个确认就说明连接建立好,将要使用这个连接发送数据,如果最终没有回复一个应答就说明该连接已经不需要了

由此看来3次握手是必不可少的,但是我们也应该清楚的知道3次握手不保证一定成功,也是有可能会失败的,但失败对服务器端并没有影响,对客户端是有影响的,但是可以弥补,就是在我们的客户端通过连接发送数据时,发现连接并没有建立好,此时就会进行RST(reset,复位报文段),重新建立连接。

4次挥手
这里写图片描述
服务器端一旦接收到客户端的确认报文就会进入established状态,可以进行数据的读写。

1、当客户端调用close主动要求关闭连接时,服务器就会接收到结束报文段(FIN)。客户端进入fin_wait_1状态。第一次挥手
2、服务器返回一个确认报文(ACK)并进入close_wait状态。第二次挥手
3、客户端收到确认报文以后,说明服务器端已经准备要关闭连接了(还需要处理完之前的数据),客户端就进入fin_wait_2状态,等待服务器端处理完数据。服务器端处理完数据以后向客户端发送结束报文段(FIN),此时进入last_ack状态,等待最后一个客户端应答的ACK。第三次挥手
4、客户端收到服务器端的结束报文段(FIN),就明白服务器已经将之前的数据处理完毕可以关闭连接了,于是客户端就向服务器端回应一个确认报文(ACK)确认自己收到了FIN。第四次挥手

TIME_WAIT状态
TCP协议规定,主动请求想要断开链接的一方最终要进入一个time_wait状态。等待两个MSL(报文最大生存时间)的时间之后才能回到closed状态。这个time_wait的具体值在不同的操作系统中也有不同的规定,比如说在RFC1122中规定为2分钟,而在Centos7上规定的值又是1分钟。下面我们来了解一下这个time_wait状态。
顾名思义,time_wait,就是要等的意思,那么为什么要等呢?之所以要等,是因为我们必须保证连接已经完全断开。那为什么又等的是2MSL呢?因为这样一来就能保证在两个传输方向上的上未被接收或迟到的报文段都已经消失,在这个等的期间,我们就可以让更多的报文消散(有可能某些报文在连接关闭以后才到达,此时就有可能已经建立了一个新的连接了)。因此等待的久一点让更多的报文得以消散。同时也保证了最后一个报文的可靠到达(假设最后一个ACK报文丢失,那么服务器会在重发一个FIN,这时虽然客户端的进程不在了,但是TCP连接依然存在,所以可以重发last_ack)。

解决TIME_WAIT状态引起的bind失败的方法
我们现在知道主动请求断开连接的一方会进入一个time_wait状态,但是这并不是对所有的情况来说都是合理的。假设说我们现在的服务器处理大量的客户端的连接,但是某些客户端不活跃,此时服务器就会主动的去断开连接从而清理掉这些不活跃的客户端,一旦清理的不活跃的客户端多了,服务器端就会产生大量的time_wait状态,那么最终就会导致我们的服务端口不够用就没有办法继续去处理那些新的客户端请求。
解决办法:使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。

//在服务器端的代码中的socket()和bind()之间加入以下代码:
int opt = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

后续的TCP协议的数据传输的可靠性保证请移步下一篇文章:TCP协议数据传输的可靠性保证(续)

发布了110 篇原创文章 · 获赞 47 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章