三次握手流程:
客户端给服务端发送数据时,数据包中带有一个头,这个头就是前几十个字节,就是下面这张图。从源端口号,目的端口号,一直到序列号,直到Options。第一个包会将这前十几个字节中的SYN置1然后在Sequence Number中填充了一个值。而服务器回ACK的时候则是把ACK置1,把SYN置1,然后把32bits的Sequence Number填充,再将32bits的Acknowledgment Number填充,再发回给客户端。最后再回一个ACK,其实就是将ACK位置1,填充32bits的ACK,再发回给服务端,至此三次握手结束。
客户端连接服务器后,服务器处于一个listen的状态,此时服务端允许三次握手。
三次握手
1、客户端是在哪个函数?
connect
2、服务端是在哪个函数?
int clientfd = accept(fd,);
accept的流程
1、从accept的队列里面取出一个节点
2、为这个节点分配一个fd
所以我们再次思考 三次握手在服务端的哪个函数,你必须排除调accept,因为在accept时,三次握手早已结束,同时你又要排除掉listen,因为在三次握手的时候,listen早就返回了。因此三次握手,在哪个函数都没有发生,他是一个被动完成的过程。
注意这个过程:
服务器在接收到一个客户端的连接请求的时候,为他专门分配了一个节点。这个节点与socket一点关系都没有,他只是一个半连接。
再往后,客户端接收到服务端的ack之后,再给服务端回一个ack的时候,服务端需要将半连接的节点,放到accept中全链接的队列,那么这里就有一个问题?:
服务端怎么知道要从半连接队列中选择哪个节点(这个所谓的节点,其实就是一个连接,TCP的每一个连接都有一个这样的节点,叫做TCP的控制块TCB,伴随这个整个TCP的连接生命周期,TCP的11个状态,就保存在这个控制块中),放到全链接队列中。
如何在半连接队列里面,找到对应节点?
端口65535个,为什么大台服务器能够做到几百万的并发连接?
其实就是通过最后ACK的数据包中的五元组,通过这五个元素。
五元组(srcip源ip,srcport源端口,dstip目的ip,dstport目的端口,proto协议) 前面两个问题都可以用五元组回答,只要连接里,有任何一个不一样,就可以建立一个连接。
proto的作用:
tcp可以监听8080的端口,同时udp也可以在8080的端口进行receive。
说白了就是 tcpfd–>tcp:8080
udpfd–>udp:8080 可以同时工作
所以上面说的:
accept的流程
1、从accept的队列里面取出一个节点
2、为这个节点分配一个fd
从一开始创建这个fd开始 就生成了一个tcb
fd–>tcb
我们思考两个函数
send(fd,buffer,length,0)
rec(fd,buffer,length,0)
send是怎么发出去的,而rec又是如何接收到数据的?
其实原因就在于,这个fd映射了tcb.
send返回正数,是不是表示发送成功了?答案是不是。
尽管tcp是一个可靠协议,但send其实就是写入发送缓冲区而已,真正发还是要内核空间发,都是协议栈决定的,两个步骤是异步的。
接着讲一下四次挥手
四次挥手 不分客户端服务器 只分主动或者是被动
主动放发送fin(调用close函数) 被动方发送ack
接着被动方发送fin 主动方发送ack
那么有一个问题,主动方调用了close,被动方是如何知道的?其实就是recv返回0
返回0之后 我们再调用close函数 再回fin 对方收到返回ack后 四次挥手才结束。
以上图为例,讲述四次挥手的过程
主动方,主动调用close函数,发送fin。主动方在收到ACK之前,TCP状态均从原本的establised,变更为FIN_WAIT_1(从close到fin_wait_1)
主动方收到被动方的ACK后,TCP状态变为FIN_WAIT_2,直到收到被动方发送的fin之前。
在主动方收到被动方的fin之后,立即返回ack后,主动方Tcp的状态为time_wait
我们再看被动方,在接收了FIN之后,立即返回ACK,被动方的TCP状态从原本的established变为close_wait.
接着被动方再发送完fin之后,进入last_ack状态。
那么现在有一个问题,双方同时调用close,会出现什么状态。
其实是会出现一个closing的状态。closing的状态还会在主动方发送fin后,被动方的ack包丢失的情况。
如果出现大量的time_wait是什么原因?
比如服务器出现大量的time_wait是什么原因,有什么解决方案?
前提你要明白,服务器之所以出现大量time_wait的前提是主动方主动调用close,才会出现。服务器主动关闭了大量的socket,其实现象是并不多的,作为服务器,一般是由客户端主动去处理的。
如果服务器出现大量time_wait?
1、代码逻辑上面出现了问题
。
如果客户端出现大量fin_wait_2?
发送这个问题说明:
在receive返回0,一直到调用close的中间这段时间,太长了!很有可能服务端的代码,根本没有调用close_wait,需要由服务端去查,很有可能没有调用close。
fin_wait_2如何终止?
答案是终止不了,通过看图就可以发现fin_wait_2,没有进入closing的路径。但凡没有的,你只能Kill进程结束
备注:(正常情况下,主动关闭连接的一端(客户端)在 FIN_WAIT_2 状态等待一段时间后,会收到对端(服务器)的FIN报文,从而进入TIME_WAIT状态等待连接的真正关闭。(服务器何时发送FIN取决于服务器应用程序的处理,一般会在read返回0发现客户端已经关闭连接后,也调用close关闭连接)
然而有在某些异常情况下,可能处于 FIN_WAIT_2 状态的一端一直等不到对端的FIN。如果没有外力的作用,连接两端会一直分别处于 FIN_WAIT_2 和 CLOSE_WAIT 状态。这会造成系统资源的浪费,需要对其进行处理。(注意内核协议栈就有参数提供了对这种异常情况的处理,无需应用程序操作)
目前的做法是:如果应用程序调用的是完全关闭(而不是半关闭),那么内核将会起一个定时器,设置最晚收到对端FIN报文的时间。如果定时器超时后仍未收到FIN,且此时TCP连接处于空闲状态,则TCP连接就会从 FIN_WAIT_2 状态直接转入 CLOSED 状态,关闭连接。
在Linux系统中可以通过参数 net.ipv4.tcp_fin_timeout 设置定时器的超时时间,默认为 60 s。)
滑动窗口 逐步递增的输送数据包
慢启动
拥塞控制
netstat -anop | grep 9096查看端口状态 以及是否被占用
会出现可以传输数据,但在用netstat无法查看到,说明走的不是操作系统的协议栈。用户态的协议栈。