网络是怎样连接的学习笔记-用电信号传输TCP/IP数据(上)
l TCP、IP协议
l 协议栈、套接字
l ACK、滑动窗口
1、 创建套接字
整体上看,TCP/IP软件分层情况:
图中,Socket库中的解析器用来向DNS服务器发送查询。
ICMP协议用于告知网络包传送过程中产生的错误以及各种控制消息。
ARP协议用于根据IP地址查询相应的以太网MAC地址。
连接实际上是通信双方交换控制信息,在套接字中记录这些必要信息并准备数据收发的一连串操作。
1. 把服务器的IP地址和端口号等信息告知协议栈(实际上就是写入套接字对应的内存中);
2. 客户端向服务器传达开始通信的请求(与服务器互相交换套接字中的控制信息);
3. 分配缓冲区(一块用来临时存放要收发的数据的内存空间)。
注:服务器程序一般会在系统启动时就创建套接字并等待客户端连接。
控制信息大体上分为两类:
1. 客户端先创建一个包含表示开始数据收发操作的控制信息的头部(设置SYN为1),客户端(发送方)的套接字根据头部中的服务器(接收方)IP地址和端口号确定了应该连接哪个套接字;
2. 当TCP头部创建好之后,接下来TCP模块会将信息传递给IP模块并委托它进行发送;
3. IP模块执行网络包发送操作后,网络包就会通过网络到达服务器,然后服务器上的IP模块会将接收到的数据传递给TCP模块,服务器的TCP模块根据TCP头部中的信息找到端口号对应的套接字(从处于等待连接状态的套接字中找到与TCP头部中记录的端口号相同的套接字);
4. 服务器的TCP模块生成响应TCP头部数据包(设置SYN为1,设置ACK为1表示已经收到请求包),IP模块接到委托后向客户端返回响应信息;
5. 客户端收到响应信息后,查看SYN为1表示连接成功,向套接字中写入服务器IP地址和端口号等信息;
6. 客户端将ACK置1,生成响应数据包,发送给服务器,向服务器确认收到响应包;
3、 收发数据
数据收发操作是从应用程序调用write将要发送的数据交给协议栈开始的,协议栈收到数据后执行发送操作。
1. 协议栈不关心数据内容,数据对于协议栈来说就是一段一定长度的二进制序列;
客户端和服务器双方都需要各自计算序号,因此双方需要在连接过程中互相告知自己计算的序号初始值。
5. 根据网络包平均往返时间调整ACK号等待时间(超时时间)。超过ACK等待时间认为数据发送超时,发送方会重新发送数据,当网络拥堵时,ACK返回等待时间会变长,如果不动态调整超时时间,会导致多余的数据重发,加重网络拥堵。TCP会在发送数据的过程中持续测量ACK号的返回时间,如果ACK号返回变慢,则相应延长等待时间;相对地,如果ACK号马上就能返回,则相应缩短等待时间。
7. ACK与窗口的合并。接收方在发送ACK号和窗口更新(只有应用程序从缓存中拿走数据时才需要更新)时,并不会马上把包发送出去,而是会等待一段时间,在这个过程中很有可能会出现其他的通知操作,这样就可以把两种通知合并在一个包里面发送了。比如将ACK号和窗口更新信息合并为一个包,比如将多个联系的ACK号精简为只发送最后一个ACK号(因为ACK号只是用来确认已接受数据包的最后位置的)。
8. 接收服务器的响应消息。客户端发送请求消息后,会调用read程序委托协议栈读取本地接收缓存,如果缓存为空,协议栈将此委托暂时挂起(协议栈会在此时处理来自其他程序的委托),直到服务器返回的响应消息到达之后再继续执行。
9. 协议栈收到数据后首先检查TCP头部内容,判断数据是否有丢失,如果没有,则将数据块暂存到接收缓冲区,并按照顺序拼接成原始数据,最后交给应用程序。
4、 从服务器断开并删除套接字
完成数据发送的一方会发起断开过程。以服务器发起断开过程为例:
1. 服务器一方的应用程序会调用Socket库的close函数;
2. 服务器的协议栈会生成包含断开信息的TCP头部,具体来说就是将控制位中的FIN比特设为1;
3. 协议栈会委托IP模块向客户端发送数据。同时,服务器的套接字中也会记录下断开操作的相关信息;
4. 当客户端收到服务器发来的FIN为1的TCP头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态;
5. 为了告知服务器已收到FIN为1的包,客户端会向服务器返回一个ACK号;
6. 客户端应用程序调用read函数委托协议栈读取服务器发来的FIN为1的数据包,这时协议栈不会向应用程序传递数据,而是会告知应用程序来自服务器的数据已经全部收到了。客户端应用程序调用close函数;
7. 客户端协议栈叶生成一个FIN比特为1的TCP包,委托IP模块发给服务器,一段时间后,服务器也会返回ACK号。
8. 通信结束。
通信结束后,套接字不会马上被删除,防止误操作。(举一个误操作的例子:如果服务器最后返回的ACK客户端没有收到,可能客户端会重发一次FIN,而此时如果服务器的套接字已经删除且该端口分配给了另一个应用程序,客户端重发的FIN会导致刚新建的套接字执行关闭操作。)