linux下tcp网络协议栈

网络编程的API:

server端:socket、bind、listen、accept、recv、send、close

client端:socket、bind(optional)、connect、send、recv、close

设置连接属性:setsockopt、getsockopt

socket:创建文件描述符fd、从3开始计算。0、1、2分别由系统的标准输入、标准输出和标准错误占用,分配一个tcb的块,此时tcp的状态为closed

bind:绑定一个ip和port,数据接收或发送时填充本机的ip和port

五元组:远程ip、远程端口、本机ip、本地端口、协议

tcp状态转换图

 

三次握手

第一次握手:首先服务端先调用listen,监听bind指定端口,以接收客户端连接,客户端端调用connect,由内核协议栈发送syn包到服务端。服务器接收后,从包里解析出五元组,构建出一个tcb,并加入半连接队列,客户端进入SYN_SEND状态。

第二次握手:接着服务端回发syn+ack包告知客户端,此时服务端进入SYN_RECV状态。此时客户端的connect已返回成功,tcp状态为ESTABLISHED。

第三次握手:收到服务端的syn+ack后,客户端在向服务端发送ack包,服务端收到后,以解析出来的五元组找到半连接队列里的tcb,把它放入全连接队列里,服务器端进入ESTABLISHED状态,三次握手完毕。若服务端没收到ack包,有一个超时重传机制,服务端继续发送syn+ack包,超过指定次数还未收到,从半连接队列里移除该tcb。

accpet:从全连接队列中取出一个tcb,为它分配一个fd,后续的操作就可以通过该fd对这个连接进行操作了

send:只是把用户空间的数据copy到内核空间的sendbuff中,具体发送由内核协议栈实现,这是一个异步的过程。

recv:具体接收由内核协议栈实现,recv只是把内核空间recvbuff中的数据,copy到用户空间上,这也是一个异步的过程。 

因为接收和发送都是一个异步的过程,所以当send不断的往sendbuff中copy数据,而sendbuff空间已满时,send会返回-1。recv端会告知send端,当前空间大小,让它在空间大于指定值时在发,所以在recvbuff空间不足时,send也会返回-1。

由于内核协议栈每次发送和接收包的大小不是由应用层定义,所以难免会造成粘包和分包的现象。假如你要发送一个大小为1500的数据,而协议栈每次只发送1024大小的包,那么该数据就会分为两个包发送。而第二个包里可能还会参杂这其他的数据,这样解析的时候就容易出错。我们可以在包头定义一个该数据包的大小,或者在结尾添加一些分隔符来解决。

由于网络环境复杂,无法保证先发的包先到,如何保证接收包的顺序性?可以通过延迟ack的方式,每接收到一个包,启动一个定时器,在规定时间内没有接收到下一个顺位的包,触发超时重传机制,让对端重发顺位包之后的所有包,如接收到了ack为1、2、5、6的包,定时器超时之后,让对端重传ack为2之后的所有包。这一层是由tcp协议栈去实现的。

四次挥手close:

第一次挥手:主机1调用close,向主机2发送FIN报文,请求关闭连接,此时主机1进入FIN_WAIT_1状态。

第二次挥手:主机2接收到FIN报文后,给主机1回ack报文,主机1接收到后进入FIN_WAIT_2状态,此时主机2进入CLOSE_WAIT状态,此时主机2还可以往主机1发送数据。

第三次挥手:主机2调用close,往主机1发送FIN报文,请求关闭连接,此时主机2进入LAST_ACK状态。

第四次挥手:主机1接收到FIN报文后,向主机2发送ACK报文,进入TIME_WAIT状态,主机2收到ACK后,立即关闭连接,主机1等待一段时间后(TIME_WAIT设置的时长)没有收到回复,主机1关闭连接。

还有一种场景,主机1和主机2同时发送FIN报文,那么双方都会进入CLOSING状态和TIME_WAIT状态。

服务器出现大量close_wait?可能是服务端在关闭连接时做了比较耗时的操作,没有及时调用close。

TIME_WAIT的作用?如果主动方的ACK丢失,被动方会重新发送FIN,主动方需要维护TIME_WAIT这个状态去处理被动方发送的FIN。如果没有这个状态,这个FIN的处理节点会出错。


版权声明:本文为qq_39239174原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。