posix api(TCP)与网络协议栈的联系
linux 下 posix api有哪些
服务端
1、socket
2、bind
3、listen
4、accept
5、recv
6、send
7、close
客户端
1、socket
2、bind(可有可无)
3、connect
4、send
5、recv
6、close
设置socket参数
setsocketopt
getsocketopt
socket是什么
直译过来是插座,fd(文件描述符)是我们可以操作的,与之对应的有tcb(tcp control block),fd与tcb的生命周期相同,fd与tcb是一一对应的。创建出来的fd是从3开始的,0是stdin,1是stdout,2是stderr.
socket函数
创建listenfd,用于监听接收或者连接
bind函数
绑定本地的ip+prot,如果不确定ip是哪个的时候用0.0.0.0
五元组
remoteip/sourceip 远端(源)ip
remoteprot/sourceport 远端(源)端口
localip/destip 本地(目的)ip
localprot/destport 本地(目的)端口
proto 协议类型(TCP/UDP)
五元组与tcb与fd一一对应
一台服务器只有65525个端口,为什么能做到百万连接?
因为五元组与端口复用,一个五元组对应一个tcb对应一个fd,五元组的不同可以区分出不同的tcb和fd,并且端口复用,让同一个端口可以被多个tcb/fd使用
五元组何时建立
客户端:
调用connect时
服务端:
收到第一次握手时
三次握手
是在内核协议栈中发生的,posix api感受不到三次握手
图解
1、将服务端置于listen状态
2、客户端调用connect
3、客户端的内核协议栈发送第一次握手(syn和seqnum)
服务端的内核协议栈接收到第一次握手,建立五元组与tcb,将tcb加入半连接队列,此时处于syn_recv状态
4、服务端的内核协议栈发送第二次握手(syn和acknum)
客户端的内核协议栈收到后让connect返回(若fd创建为阻塞的话,如果是非阻塞的就在2中返回了)
5、客户端的内核协议栈发送第三次握手(acknum)
服务端的内核协议栈接收到后,根据五元组去半连接队列中查找是否有对应的tcb,找到的话就放入到全连接队列,然后停止listen,accept取出全连接队列中的tcb并返回一个clientfd
listen函数
将服务端置于监听状态,当服务器接收到第一次握手时,clientfd对应的tcb的状态置为syn_recv(此时的clientfd用户没有获得也无法操作),并将对应的五元组加入到半连接队列
listen第二个参数是半连接队列和全连接队列之和(unix)或全连接队列(linux)的最大长度
accept函数
三次握手结束后,从全连接队列中取出并返回一个clientfd,只有accept返回之后,我们才能得到了clientfd,才能操作对应的tcb。
数据传输
图解
1、本端调用send命令,将send的buffer拷贝到内核sendbuffer中,若sendbuffer满了,send返回-1。
2、本端内核协议栈将sendbuffer中的数据发送给对端内核中的recvbuff。
3、对端调用recv取出recvbuffer中的数据。
sendbuffer为什么会满
当对端recv不及时,导致recvbuffer满了,再导致sendbuffer发不出去,所以sendbuffer就满了
粘包
上述1、 2、 3阶段都为异步的,因此就可能出现一下情况:
本端调用n次send,但是内核协议栈发送了m次sendbuffer(m<n),然后对端recv到m次,此时就会出现所谓的粘包的问题。因此需要分包(将接收到m次数据分成n份)
分包
分包的方式:
1、每次send的时候都加入一个唯一标志(如http的/r/n/r/n,可参考之前的文章)
2、每次发送数据的时候,都在最前面定义一个长度,即数据格式为:buffer_size+buffer
TCP如何实现顺序发送
之前提到的粘包问题和分包处理都是基于TCP顺序的前提下,若TCP发送数据不顺序,接收到的数据就会错乱,也无法进行分包处理。
顺序发送和传输可靠性
图解
TCP协议栈中有一个延迟ACK(定时器,可关闭)。
对端每次接受到一个包(recvbuffer)定时器重置,当定时器超时时,会返回ACK给本端。如上图中对端接收到1、2、4号包(不管接受顺序是怎么样),返回ack=3给本端,本端会重新发送3、4号包,当对端返回ack=5的时候说明对端接受到所有数据,再根据包号就能做到顺序接受。
udp使用场景
因为每次丢包都要重传,所以在网络较弱或者带宽小的情况下,使用udp会比较合适。
1、游戏,要求实时性
断开连接(四次挥手)
图解
1、本端调用close,此时本端将不能在send和recv(调用close的时候,会先将之前sendbuffer中的数据发完,再单独发一个空包),若本端与对端都调用close,则会进入closeing状态
2、本端协议栈将fin位置1,sendbuffer为空,大小为0
3、对端调用recv,返回0,对端用户处理相关业务
4、对端处理完业务之后,调用close
5、对端协议栈将fin位置1,sendbuffer为空,大小为0
4、本端协议栈接收到之后,返回ack,断开成功
time_wait存在的意义
若对端没有接收到第四次挥手的数据(即ack),则对端会进行重传,若本端发送ack就进入close状态的话,就没有办法接收到对端的重传要求和发送ack了
断开连接出现的问题
服务端有大量的close_wait状态怎么解决?
出现原因:
从recv返回0到close中有大量耗时的操作。
解决方法:
1、close放在耗时操作前面
2、将耗时的业务放到任务队列中或者开一个线程池去处理