Linux 操作系统为 网络编程提供了很多系统级别的接口,这篇文章主要讲述 这些接口以及相应的原来,希望能写清楚。
系统接口
服务端
socket() 本质是系统分配一个fd 并配上 tcb
bind()
listen() 把tcb 置为 listen 的状态
accept() 为全连接队列分配一个fd
recv()
send()
close() fin 的包 放到 包里面,并回收fd
客户端
socket()
bind(optional)
connect() 向对方发送sync 包,connect 是阻塞的么?有等待,连接被拒绝。
send()
recv()
close()
那么这些API函数对应了 TCP 协议的 三次握手,数据传输, 四次挥手 的哪些过程呢?
三次握手
半连接队列和全连接队列
backlog 有其历史原因,可以是 半连接队列的长度,也可能是全连接队列的长度。需要在系统钟确定
man listen 查看 backlog 的解释
accept() 怎么在三次握手之后知道 有连接?
阻塞
全连接队列为空的时候,会一直在等待
非阻塞
全连接队列为空的时候,返回-1
什么是SYN 呢?
SYN 如图所示是TCP 包头的一位数据。
序号(seq)
TCP是面向字节流的。在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。整个要传送的字节流的起始序号必须在连接建立时设置。首部中的序号字段值则是指的是本报文段所发送的数据的第一个字节的序号。长度为4字节,序号是32bit的无符号数,序号到达2^32 - 1后又从0开始。
确认号(ack)
ack:确认序号,更确切地说,是接收端所期望收到的下一个序号。
确认序号为上次接收的最后一个字节序号加1.只有确认标志位(ACK)为1的时候,确认序号才有效。
三次握手 之后 Client 端与 Server 端状态变化
note: SYN_SENT 与 SYN_RCVD 状态可以转换。
Server 端由 Close 状态 ,通过 系统函数 (socket()) 创建 socket , bind() 绑定端口,listen() 监听,进入 LISTEN 状态,等待连接。
Client 端 也 创建socket, bind() 可选, Connect() 发送SYN ,第一次握手。进入到 SYN_SENT状态。
Server 端 收到 Client 端 发送的SYN,返回 ACK 并再次发送SYNC 后,进入到 SYN_RCVD 状态
Client 端 收到 Server 端 发来的确认信息和 SYNC 信息后 回复 后进入 ESTABLISHED 状态
Server 端 收到 Client端的回复 也进入ESTABLISHED 状态
这样 TCP 建立链接就完成了。。。下面就是收发信息了。
Note: 监听状态的socket 也可以连接接其他服务器的,这个做个实验观察一下。
数据传输
send()的工作原理
send()函数只负责将数据提交给协议层。当调用该函数时,send()先比较待发送数据的长度和套接字的发送缓冲区的长度:
·当待拷贝数据的长度大于发送缓冲区的长度时,该函数返回SOCKET_ERROR;
·当待拷贝数据的长度小于或等于发送缓冲区的长度时,那么send先检查协议是否正在发送发送套接字的发送缓冲区中的数据:
如果是就等待协议把数据发送完,再进行拷贝;
如果协议还没有开始发送套接字的发送缓冲区中的数据或者该发送缓冲区中没有数据,那么send就比较该发送缓冲区中的剩余空间和待拷贝数据的长度:
如果待拷贝数据的长度大于剩余空间的大小,send就一直等待协议把该发送缓冲区中的数据发完;
如果待拷贝数据的长度小于剩余空间大小,send就仅仅把buf中的数据拷贝到剩余空间中。(注意:并不是send把该套接字的发送缓冲区中数据传到连接的另一端,而是协议传的,send仅仅是把数据拷贝到该发送缓冲区的剩余空间里面。)
如果send函数拷贝成功,就返回实际拷贝的字节数;如果拷贝的过程中出现错误,send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
要注意,send函数把buffer中的数据成功拷贝到套接字的发送缓冲区中的剩余空间里面后,它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传输过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。(每一个除send外的socket函
数在执行的最开始总要先等待套接字的发送缓冲区的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该socket函数就返回SOCKET_ERROR。)
** recv()的工作原理**
recv先检查套接字的接收缓冲区,如果该接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把套接字的接收缓冲区中的数据拷贝到用户层的buffer中,(注意:协议接收到的数据可能大于buffer的长度,所以在这种情况下,要调用几次recv函数才能把套接字接收缓冲区中的数据拷贝完。)recv函数仅仅是拷贝数据,真正的接收数据是协议来完成的。
recv函数返回其实际拷贝的字节数。如果recv在拷贝时出错,那么就返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。对方优雅的关闭socket并不影响本地recv的正常接收数据,如果协议缓冲区内没有数据,recv返回0,指示对方关闭;如果协议缓冲区有数据,则返回对应数据(可能需要多次recv),在最后一次recv时,返回0,指示对方关闭。
为什么会出现发送不成功
公网情况复杂会出现丢包的现象。
send 是把 消息放到内核态的buffer 里面就不用去管了。
五元组( srcip,dstip,srcport,desport,protocol)
TCB (tcp control block)
send 发送环节
send 发送成功不代表成功。
- 单个 send
- 多个send
- 循环 send
TCP 的分包 与 粘包解决
- 在应用层协议头前加上 pktlen 包长度
read(tcphdr,2); // 读取包长度
read(tcphdr->length); // 读取剩余的长度
while(count < tcphdr->length){
size = read(tcphdr->length -count);
count + = size;
} - 为每一个包加上分割符
读取固定长度
read(buffer,1024);
buffer[idx] =“\r\n”;
保留 pkt = &buffer[idx+2]
需要用到 ringbuffer 的东西。
四次挥手
客户端宕机只能由 应用层的协议去检测。
close() 函数本质也是一个 send(),对方收到 recv 返回为0.
此时 server 端程序再调用close() 给客户端 发送 Fin
注意同时关闭
出现大量close_wait 是什么原因,怎么解决
Server 收到了 大量 client 主动 发送过来的 关闭请求。
解决方法: recv = 0 时使用
recv=0 与close 做成异步
双方同时调用 close()
Closing 状态 与 TIME_WAIT
TIME_WAIT 系统默认可改
为什么会有time_wait
防止 last_ack 丢了。丢了之后 TCP 会重发。
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:
服务器高级架构体系:
https://ke.qq.com/course/417774?flowToken=1010783