1 POSIX 网络API
网络编程常用的API:
2 IO函数的内部过程分析
2.1 Socket
socket作为网络编程的第一个函数,主要作用是用于创建句柄和对应的TCB控制块;建立起文件描述符和内部控制块的对应关系,类似与插座和槽的关系。TCB主要包括关系信息有网络的五元组(sourceip, sourceport, dstip, dstport, proto)确定一个具体的网络连接。
2.2 BInd
bind函数:在接收端或者发送端主要作用是用来填充本地的ip和端口号。这里需要注意的是:0.0.0.0表示本机的任意网卡ip。所以对于单网卡或者不需要关注绑定到固定网卡,也可用0.0.0.0:port方式。
bind:接收或者发送数据,用来填充本机的ip和端口。
2.3 listen/connect/accept
listen/connect/accept三个函数和三次握手有关,这里放到一起进行描述。当客户端调用connet函数时表示开始进入三次握手,当服务端收到syn包后,listen函数会在内核协议栈为新的客户端创建一个TCB块同时将其加入到半连接队列。客户端收到对方的ACK和Syn包后会回复一个ACK这时候客户端就会认为整个连接已经建立;服务端收到ACK后会将半连接队列的TCB数据移动到全连接队列;当服务端调用accept会在全连接队列有数据时被触发,同时返回客户端的文件句柄和对方的ip端口;这时候TCB一个完整的五元组被构建好,服务端和客户端连接成功。具体如下图所示:
注意点:
1. 三次握手为啥是三次?
一个完整的TCP连接需要双方都得到确认,客户端发送请求和收到确认需要两次;服务端发送请求和收到确认需要两次,当中服务回复确认和发送请求合并为一次总共需要3次;才能保证双向通道是通的。
2. 百万连接是如何做到的?
一个服务器的端口数是65535,为何能做到一百万的连接,主要是因为一条连接是由五元组所组成,所以一个服务器的连接数是五个成员数的乘积。例如:客户端ip:100,客户端port:100,服务端ip:10,服务端port:100,协议:tcp。总的连接数可以达到:10000000连接数。
3. DOS攻击怎么解决?
DOS攻击就是利用三次握手的原理,模拟客户端只向服务器发送syn包,然后耗尽被攻击对象的资源。比较多的做法是利用防火墙,做一些过滤规则。
2.4 send/recv
send和recv在连接生命周期中占用的时常最长,主要负责数据的收发。send函数负责将数据拷贝到内核,内核协议栈主要是利用TCB中的发送缓冲区进行数据缓存,然后根据内核自己的策略决定何时将数据发送。接收端数据也是先到达TCB的接收缓冲区,然后才是通过recv拷贝到用户空间。
注意点:
1. 接收数据的黏包问题如何解决?
一种是利用包头上添加一个数据包长度的字段,用于数据的划分;
另一种是在包的尾部添加分隔符,用于数据的划分;
2. 如何保证接收数据的顺序到达?
顺序到达是由于TCP的延迟ACK的机制来保证的,TCP接收到数据并不是立即回复而是经过一个延迟时间,回复接收到连续包的最大序列号加1。如果丢包之后的包都需要重传。在弱网情况下这里就会有实时性问题和带宽占用的问题;
3UDP优势?
UDP在实时性要求高的场景和弱网情况下较TCP更具有优势。目前竞技类游戏实时性要求高的行业,音视频通话及小数据量交互等场景下UDP使用得比较多。
2.5 Close
close函数是最简单的一个函数,但是断开连接也是状态机也是最为复杂的。close过程涉及到四次挥手的全过程。首先是客户端调用close,内核开始进入四次挥手阶段,首先是发送fin包,自己先进入fin_wait_1状态,然后对方回复ack,进入close_wait状态;这时主动方进入fin_wait_2阶段,等待对方发送fin信息,被动方发送fin后自己进入last_ack状态,主动方发送ack进入time_wait状态。
整个过程比较复杂。
1. 正常情况下一方调用close情况如下图:
2. 当双方同时调用close,如下图:
注意点:
1. Fin_wait_1作用?
等待对方回复,超时自动重发fin。
2 Fin_wait_2作用?
等待对方业务逻辑处理后,发送fin包。这里有可能出现死等待的情况服务器如果出现大量的Fin_wait_2可能需要考虑是不是没有close,或者close之前做了耗时操作。
3 time_wait 作用?
防止最后一个ACK没有顺利到达对方,超时重新发送ack。time_wait时常一般是120s可以修改。
4 服务器掉线重启出现端口被占用怎么办?
其实主要是由于还处于time_wait状态,端口并没有真正释放。这时候可以设置SO_REUSEADDR属性,保证掉线能马上重连。
最后附上网络状态迁移图镇场