二:用电信号传输TCP/IP数据-2.2-连接

一、连接是什么意思

话说网线一直插着,网络一直连着,网线中随时都有信号流过,那这个“连接”是连接什么呢?
可以类比人与人之间的联系,满大街都是人,身边随时有人走过,我们算是跟任何人有联系吗?当然没有!
怎么才算有联系?先要双方有交往意愿,然后互换个名片,这才算联系上了。
哪天一方找到另一方说吃个饭,另一方一看这人我认识,行答应吧,这是交往。
整个过程是有意愿-互换名片(有联系)-交往。
可以看出,互换过名片才算有联系,有联系是交往的前提。
放到客户端和服务器的通信上,“互换名片”就是连接,连接是通信的前提。
那它们的名片是什么?就是通信控制信息。
所以,这里的“连接”实质上就是通信双方交换控制信息

回顾上一章的内容:浏览器先是解析URL并生成了HTTP消息,然后通过DNS解析器查到了服务器的IP地址,按照规则,也知道了该使用哪个端口,然后创建了套接字。
服务器那边当然也是,上一章也讲过,服务器一启动就会创建套接字等待客户端连接。
到这里,客户端这边的套接字里已经有了通信对象的信息,但是还没有传递给协议栈;服务器那边的协议栈创建了套接字,同样不知道要和谁通信,连应用程序(服务器程序)也不知道通信对象是谁。
这样下去,永远也没办法开始通信。
就像两人交往,都不主动,那永远都不会开始。
于是,就需要有一方主动,主动的一方理所应当的是客户端。
不像人类那么矜持,客户端不要客套,直接告知服务器自己的交往意愿并递上名片就可以了,比如:“我想和你开始通信,我的IP地址是XXXXXX,我的端口号是XX”。
由此可见,客户端向服务器传达开始通信的请求也是连接操作的目的之一。

总结:
连接实际上是通信双方交换控制信息,在套接字中记录这些必要信息并准备数据收发的一连串操作,像上面提到的客户端将IP地址和端口号告知服务器的过程就是交换控制信息的一个例子。
所谓控制信息,就是用来控制数据收发操作的信息,除了IP地址和端口号,还有其他一些控制信息。
连接操作中所交换的信息是根据通信规则来确定的,只要根据规则进行连接操作,双方就可以得到必要的信息从而完成数据收发准备。
当执行数据收发操作时,还需要一块用来临时存放要收发的数据的内存空间,这块内存空间成为缓冲区,它也是在连接操作的过程中分配的。

连接这一节就为解答一个问题:“当应用程序调用connect时发生了什么?”

为什么要使用“连接”这个词?
通信技术的历史已经有100多年,从通信技术诞生之初到不远的过去,电话技术一直都是主流。
而电话的操作过程分为三个阶段:1、拨号与对方连接。2、通话。3、挂断。
人们将电话的思路套用在现在的计算机网络中,所以也就自然而然地将通信开始之前的准备操作称为“连接”。
如果“连接”这个词顾名思义起来不通顺,用“准备”这个词来替换也可以。

二、负责保存控制信息的头部

前面说的控制信息可以分为两类。
第一类是客户端和服务器相互联络时交换的控制信息。
第二类是保存在套接字中用来控制协议栈操作的信息。

第一类:客户端和服务器通信时交换的控制信息


这些字段是固定的,在连接、收发、断开等各个阶段,每次客户端和服务器进行通信都需要提供这些控制信息。
这些内容在TCP协议的规格中进行了定义,如上图所示。表中仅列出了必需字段,此外还有一些可选字段。

如上图 a 所示 ,这些信息会被添加在客户端与服务器之间传递的网络包的开头。
在连接阶段,由于数据收发还没有开始,所以如图 b 所示,网络包中没有实际的数据,只有控制信息。
所有的控制信息都位于网络包的开头,因此被称为头部。

以太网和IP也有自己的控制信息,为避免混淆,一般记作TCP头部、以太网头部(又称MAC头部)、IP头部。

客户端和服务器在通信中会将必要的信息记录在头部并相互确认,如下:
发送方:开始发送数据
接收方:请继续
发送方:现在开始发送的是xxx号数据
接收方:xxx号数据已收到
……

正是有了这样的交互过程,双方才能够进行通信。
头部的信息非常重要,了解了头部各个字段的含义,就等于理解了整个通信的过程。

第二类:套接字中用来控制协议栈操作的信息

这些信息保存在协议栈中的套接字内存空间中。

这个内存空间保存三种信息:
1、应用程序传递来的信息
2、从通信对象接收到的信息
3、收发数据操作的执行状态
协议栈会根据这些信息来执行每一步的操作。

可以说,套接字的控制信息和协议栈的程序本身是一体的。
协议栈具体需要哪些信息会因协议栈本身的实现方式不同而不同,但这并不会影响通信,因为协议栈中的控制信息是对内的,通信是对外的,互不干涉。只要通信时按照约定的规则将必要的信息写入头部,客户端和服务器之间的通信就能达成。
比如,Windows和Linux操作系统的内部结构不同,协议栈的实现也不同,必要的控制控制信息(第二类)也就不同,但这并不影响两种系统之间的通信。
同样地,计算机和手机之间能够通信也是这个道理。

三、连接操作的实际过程

连接操作的过程是从调用Socket库的connect开始的。
connect(<描述符>,<IP地址:端口号>...)
这个调用提供了服务器的IP地址和端口号,这些信息会传递给协议栈中的TCP模块。然后TCP模块会与该IP地址对应的对象,也就是服务器端的TCP模块交换控制信息。
步骤如下:

1、客户端创建一个TCP头部

该头部包含了表示开始数据收发操作的控制信息。
如图2.1所示,头部包含很多字段,这里关注的重点是发送方和接收方的端口号。
到这里,客户端(发送方)就准确找到了服务器(接收方)的套接字,也就是这里该连对面的哪个套接字。
同时,将控制位中的SYN设置为1,表示连接。
此外,还要设置适当的序号和窗口大小。

疑问:为什么TCP包还没有通过IP发送出去就说“找到了服务器的套接字”?
理解:这里指的是本地套接字的信息传递到了协议栈,套接字中有服务器IP地址和端口,协议栈因此可以准确找到服务器的套接字。
前面说过,协议栈才是通信过程中真正干活的“客户端”,本地套接字创建完成后有了通信对象的信息,但是还没有传递给协议栈,只有协议栈知道了服务器的套接字才能实现通信控制信息的互换。

2、委托IP模块发送

当TCP头部创建好之后,接下来TCP模块会将信息传递给IP模块并委托它进行发送。
IP模块执行完发送操作,网络包到达服务器。

3、服务器的IP模块收到发来的TCP头部

服务器上的IP模块把收到的数据传递给TCP模块。
服务器的TCP模块根据收到的TCP头部信息中的端口号找到对应的套接字。
对套接字中写入相应信息,并将状态由等待连接改为正在连接。

4、服务器TCP模块返回响应

过程和客户端一样,需要在TCP头部中设置发送方和接收方端口号以及SYN比特。
如果由于某些原因不接受连接,将不设置SYN,而是将RST设为1。
同时,将ACK控制位设为1,表示以及接收到相应的网络包。
网络中经常发生错误,网络包也经常发生丢失,因此双方通信时必须相互确认网络包是否以及送达,设置ACK控制位就是进行这个确认。
服务器TCP模块将数据传递给IP模块,向客户端返回响应。

5、客户端收到响应

客户端上的IP模块把收到的数据传递给TCP模块。
根据收到的TCP头部信息确认连接服务器的操作是否成功,如果SYN为1则表示成功,这时会向套接字中写入服务器的IP地址、端口号等信息。
同时,将状态改为连接完成。
到这里,客户端的操作全部完成。

6、客户端返回ACK

刚才服务器返回响应时将ACK设为1,相应地,客户端也要将ACK设为1并返回服务器,告诉服务器刚从的响应包已经收到。

7、连接完成

服务器收到这个返回包之后,连接操作全部完成。

前面说过,连接就是建立通信的管道。
建立管道的关键在于两端的出入口,两头要互相知道对方在哪,这样管道才能建立起来。
出入口就是通信双方的套接字,双方的套接字交换完成,连接也就建立了。
现在,就可以认为有一根管子把两端的出入口连接了起来,只要把信息从一端送入出入口,另一端就可以收到。
这个管子,也就是连接,在调用close之前,是一直存在的。

建立连接之后,connect执行完毕,控制流程返回应用程序。

posted @ 2023-05-07 10:46  GPL-技术沉思录  阅读(18)  评论(0编辑  收藏  举报