11.3.4确认应答机制
1.双方通信时要返回确认应答报文,保证对方发送的报文是有效的;尽管整个通信过程中无法保证数据全部可靠,但是可以保证单个方向发送的数据是可靠的;
发送的报文要设置序号,如果是应答报文要设置确认序号;
11.3.5超时重传机制
当报文超时或者丢包就要进行重传;丢包分为消息报文丢失和应答报文丢失;
当一个报文发送出去且没有收到应答之间,这段时间是不知道报文是丢了还是还在发送的过程当中;所以必须要设置一段特殊的时间间隔,在时间间隔之内没有收到应答就认为丢包了,就进行报文重传,这种策略叫做超时重传;
如果补发多次没有成功,主机就会判定网络出现了问题,连接自动断开;
由于超时重传,所以接收方一定会收到多个相同的报文,导致数据不可靠,所以需要进行去重;使用序号就可以实现去重;
超时时间设置:
1.对于网络情况好就需要时间间隔要短;时间过长就需要等待很长的时间才重传,效率太慢;
2.网络情况较差,如果设置的时间间隔较长,就会导致发一个报文就需要进行重传,产生大量的重复报文,需要进行大量的去重,使得重传和去重变成了一种负担;
为了保证TCP通信的高性能,时间间隔的设置是动态的,与网络状况是有关的;Linux中超时以500ms为一个单位进行控制,每次超时重传的时间间隔都是500ms的整数倍;默认是500ms,如果重传一次之后仍然得不到应当,就等待2*500ms后再进行重传,还是得不到应答就等待4*50ms,以此类推,当达到某一重传次数时,本机的TCP就会认为网络或者对端主机出现了异常,然后断开连接;
11.3.6连接管理机制
TCP通信是基于连接的,在连接建立和断开的过程中会进行三次握手和四次挥手;
三次握手和四次挥手的原因是,保证双方互相给对方发送消息的可靠性;其实本质上是每次发送过去的报文要保证可靠性,都要有一个应答;如果数据报文不完整,则会导致功能不完整,应答不完整就会导致数据不可靠,甚至导致报文重传还需要进行去重;
是三次握手而不是四次握手是因为,通信的前提是双方都建立连接,一方建立连接另一方不建立是无意义的,所以双方都要同步的发起建立连接请求,使用捎带应答的方式提高了通信效率;而四次挥手不是三次挥手是因为,双方断开连接是需要协商的,是有时间差的;即一方完成了通信的需求,但是另一方还未完成,需要继续通信,就不可以断开连接,只有双方都完成了通信才可以断开连接,才可以使用捎带应答的方式实现"类似的三次握手",但是这种情况占少数,不采用这种方式;
三次握手的原因
1.三次握手有效的保证了客户端和服务器各自至少进行了一次收和发并且是可靠的,测试了连接是否通畅;
2.没有应答机制,会导致服务端直接建立连接并且承担风险,影响其他客户端;
如果是一次握手并且一次握手就使得双方建立连接,客户端就会存在先建立连接,然后一直向服务端发送建立连接请求的(SYN洪水)情况,这时服务端就会建立一大批连接的结构体并管理起来,但是实际上只是用一个连接资源管理就可以了,这样就会导致资源的闲置和浪费;
3.奇数次握手保证了,握手失败的连接成本嫁接到了客户端,而不是服务端接收风险,降低了对其他客户端的影响;使用三次握手使得保证可靠性的同时建立连接的成本最小;
如果是两次握手,会存在服务端返回确认应答并建立连接,客户端收到应答建立连接;服务端先建立了连接,不管客户端是否异常都会默认维护连接一段时间;如果客户端异常断开了,那么这些连接实际上是无效的,无法进行通信的;因为服务器是一对多的,服务器出现了问题,所有的客户端就都不可以使用了,不应该让服务器来承担风险;三次握手保证了是让客户端先建立了连接,这样就让风险由客户端来承担,影响就减少了;
其实TCP服务端在第一次握手的时候是会建立连接的,只不过叫做半连接;大量的客户端与服务端进行第一次握手时,就会将服务端的连接资源消耗殆尽;黑客可以使用恶意程序控制多台机器,定期领取任务向服务端发起第一次握手,这些机器就叫做肉机;所以一些企业实际上是会设计一些策略,对大流量的访问进行限流,防止服务器挂掉;
四次挥手的原因
四次挥手保证双方都可以得知对方不发数据的意愿;如果只有一方关闭了连接而另一方并没有,其实连接是没有被关闭的,这时候另一方就可以继续发送完数据再进行关闭;此时对方就会收到数据,最后整个连接才是真正地关闭了;换句话说,关闭连接其实是不想发送数据了,就会将发送缓冲区关闭,但是接收缓冲区还在,所以还具有读的功能;
四次挥手其实就是,双方互相协商,主动断开连接的一方会发送FIN,状态设为FIN_WAIT_1,服务端收到报文后,将状态设为CLOSE_WAIT并且发送ACK,客户端收到应答报文后,就会将发送缓冲区关闭,将状态置为FIN_WAIT_2不会发送数据只是发送应答报文,这时候服务端可以继续发送数据,如果服务端要关闭连接就会发送FIN并将状态置为LAST_ACK,客户端收到后将自身设置为TIME_WAIT状态(一段时间后自动设为CLOSED状态),返回应答报文,服务端接收到应答就会将自身设置为CLOSED状态;
11.3.7验证客户端和服务端三次握手和四次挥手时的状态
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
netstat ntp
//查看连接的状态
将TCP服务端套接字设置为listen状态之后,此时服务端是处于LISTEN状态的;服务端没有使用accept接口时,在收到客户端的连接请求时双方会经历3次握手,最终都处于ESTABLISHED状态;即连接的建立和accept没有关系,三次握手是双方操作系统自动完成的;
当listen的第二个参数设为一时,能建立连接的连接数是2;操作系统会将没有被上层accept的连接管理起来,对它们先描述再组织,并且以队列的方式管理这些连接结构;三次握手时每次形成的连接本质上就是创建一个连接结构体对象并将其链入到队列当中,而accept就是从队列中将连接取走和特定的文件关联起来,返回特定的文件描述符;listen的第二个参数+1表示已经建立好的连接队列的最大长度,这个队列叫做全连接队列;accept和连接入队还有全连接队列构成了CP模型;服务端三次握手完成或者建立连接成功就将连接入队列,如果队列满了就无法入队列了,就会将连接状态设置为SYN_RECV状态,换句话说就是因为全连接队列满了,服务端将客户端发送过来的第三次握手应答报文直接丢弃了;
如果服务端长时间无法得到应答就会释放掉SYN_RECV状态的连接,这种连接叫做半连接;半连接也需要进行管理,所以也会存在半连接队列,节点并不会长时间维持;
这样就出现了客户端和服务器连接不一致的问题;服务端直接将应答丢弃,但是确实是收到了应答,所以第二次握手时可靠的,知道客户端建立连接成功了,并不会发送RST标志位;对于客户端建立连接成功了,但是发送数据不成功,就转而继续开始进行三次握手;
大量建立半连接会导致真正地SYN洪水;服务器资源有限制不会挂掉,但是其他客户端就无法正常的访问了;
全连接队列长度不可以太长,因为上层处理繁忙时,就无法保证将全连接队列获取完,而队列长度过长就会导致资源闲置,维护还要有成本,而且变相地减少了上层空间,降低了处理的效率;而队列的存在可以保证,半连接变成全连接和全连接被上层处理可以并发运行;所以一般全连接队列的长度一般要设置为10左右;