本文介绍linux中与tcp网络通信相关的POSIX API,在每次调用的时候,网络协议栈会进行的操作与记录。
Posix API,提供了统一的接口,使程序能得以在不同的系统上运行。简单来说不同的操作系统进行同一个活动,比如打开文件,所调用的函数是不同的。系统1调用的是open1函数,系统2调用的是open2函数,在Posix API中统一规定把这个功能的函数包装成open,那么当应用程序在不同的系统上调用open函数的时候就都能打开文件了。
应用程序可能会有自己的打开文件函数,比如popen,fopen等等,都是在直接或间接的调用open。
网络协议栈是内核的一部分,是一系列网络协议的集合。负责网络通信和网络数据传输。
socket通俗的翻译是插座,插座分为插头和座,socket函数实际上是实现了网络通信中建立连接所需要的“插头”和“座”。在网络通信中,“插头”对应着socketfd,“座”对应着tcp控制块。
socketfd存在于用户空间中,Tcp协议是网络协议栈的一部分,tcp控制块又是tcp协议的一部分。
Tcp控制块包含了以下内容:源IP地址和源端口、目标IP地址和目标端口、连接状态、序列号和确认号、窗口大小、拥塞控制参数、超时和重传信息、接收和发送缓冲区、计时器、选项和标志。
给socetkfd的tcp控制块的目的IP地址和目的端口赋值。有些客户端不绑定一个特定的端口,那么系统就会随机分配。
Listen函数的第二个参数是在设置syn_queue与accept_queue的总长度。listen函数主要在做两件事:
a.修改tcp控制块的连接状态,发生连接前后分别是LISTEN状态和ESTABLISH状态。
b.建立syn_queue(同步队列)和accept_queue(接受队列)。syn_queue用于存储未完成的三次握手的连接请求控制块,accept_queue用于存储刚刚完成了3次握手的连接请求控制块,等待移交给accept函数进行处理。
客户端连接到服务器需要首先进行三次握手,这三次握手是在底层进行的。服务器在listen状态,客户端调用connect之后,就在进行三次握手了,整个过程对于用户是黑箱的。作为高水平程序员,我们还是需要来了解下底层到底是怎么样的运作机制。
首先了解tcp头,需要重点了解的是控制位(Flags),包括了8块内容:CWR、ECE、URG、ACK、PSH、RST、SYN、FIN,这些内容位要么填1,要么填0,用于控制和标识TCP连接的状态和行为。比如客户端发起了第一次握手,会往服务器发送一个tcp包,这个包会包含包头和没有数据的包体(包括后面的两次握手也是),这时候SYN被设置为1,说明是第一次握手。
三次握手的流程如图所示,以下的一根蓝线代表一次握手:
第一次握手,发送一个tcp包,这个包包头的SYN位置被设为1,seqnum被随机生成,比如是12345,由客户端发送给服务器。请求进入到了服务器的syn队列中,随即建立相应的请求控制块。
第二次握手,服务器返回一个包,这个包包头的SYN和ACK被设为1,客户端发送的12345的seqnum,收到12345的acknum。服务器也随机生成了一个seqnum,比如34531。
第三次握手,客户端发送了一个tcp包,于是服务器发送的是34531的seqnum,收到的34532的acknum。收到多大的acknum,表示seqnum小于这个acknum的tcp包都已经全部被对端接收过了。相应的请求控制块从syn队列移动到accept队列中。
通过seqnum和acknum的数值排序,可以实现tcp通信的不重复、不乱序、不缺失。
Accept函数做了两件事:
a.???? 根据取出的连接请求控制块,建立新的fd,也就是通常新的clientfd。
b.???? 为这个新的fd建立新的tcp控制块。
tcp通信的这两个函数其实只做一件事,就是把数据从应用层appliacation,拷贝进入内核kenerl的发送缓冲区中。至于内核什么时候把数据发送到对端,就不一定了,有可能是先凑满一定的字符数再一起发送,有可能是看到一定时间内都没有数据再拷贝进入就发送,不同的系统会有不同的操作。我们可以通过ifconfig命令查看mtu,也就是单个数据包中可以传输的最大字节数。
tcp通信的这两个函数其实也只做了一件事,就是把数据从内核的接收缓冲区拷贝到应用层。
该函数是负责断开客户端与服务器之间的连接。底层的流程通常称为“四次挥手”。不同于发起连接只能由客户端发起,断开连接可以由任意一方启动,只分为主动方和被动方。
Close函数做了两部分工作:a.回收fd。b.发送一个控制位FIN为1的包(第一次挥手),也叫fin包或final包,这个包的包体没有任何内容,于是对端的recv会返回0。
当对端接收到FIN包之后,通常会返回一个ack包,再调用close函数返回一个fin包(第二、三次挥手)。本端收到fin包之后再返回一个ack包(第四次挥手)。
下图是close主动方与被动方的挥手关系与状态变化:
红圈对应着主动方与被动方的状态变化。