网络那些事儿2-数据途经网卡发生的事儿1

上一节我们从整体上了解到了从浏览器中输入一个网址开始,经过服务端的响应,在浏览器上显示出服务端的响应内容。这一节我们来说一下浏览器生成的HTTP数据是怎么发送出去的,计算机网卡和协议栈在这里面起到了什么作用,数据是怎么通过网卡传递出去的以及网卡是怎么从网线(有线或者无线)中接收数据的。  

协议栈是什么?

数据在网络中传输,肯定是离不开那些网络硬件设备的,比如计算机的网卡、路由器、交换机、集线器等,但是只有这些硬件设备,我们还不能高效地幼龟鹿地把数据传递出去,我们还需要制定一些规则来让数据传递的方式更加自动化、让数据传递的成功率更加有保证,这就是协议栈产生的原因以及它的作用。网络协议栈内部包含很多子集,每种子协议栈各自分工,负责不同的功能,保证网络中数据高效地进行传输。

协议栈内部结构可以用下面的图简单概括一下,这里只是一个示意图,更多的体现的是协议栈的组成结构。

这张图

网络中数据的传递是分不同的过程的,从数据的产生,到数据的转化,再到数据的传递这中间要经理一系列的处理,上图中应用程序、操作系统、驱动程序、硬件分别在不同数据传输阶段承担不同的传输任务。数据的发送就是由图中由上而下,逐渐传递的,上层会向下层委派工作,下层接收到委派的工作并执行,执行完成后再委派给自己的下层,以此类推,直到数据发送出去。这里上下层的概念是人为划分的,目的首先是便于理解网络在传递数据时所做的工作,其次分层的概念也能够清晰编程时的边界,防止紧耦合,方便维护和调试相关代码。所以这里我们用这个可以说是分了四层的结构图来表示一下协议栈所处的位置以及它在网络数据传输整体中所起的作用。

最上面的是网络应用程序,比如浏览器、电子邮件客户端、web服务器、电子邮件服务器等程序,他们会组织好数据然后将收发工作交给下层部分来完成。应用程序下面是Socket库,其中包括解析器,用来向DNS服务器发出查询请求,这在前面已经介绍过。

再往下走就是操作系统了,协议栈就在这里。协议栈上半部分有两块,分别是负责用TCP协议收发数据的部分和负责用UDP协议收发数据的部分他们接受应用程序的委执行收发数据的操作。协议栈下半部分是IP协议控制网络包收发操作的部分,数据较大的包在互联网中都会被分割成一个个小包进行传输,IP就是负责将这些小包正确传递出去。IP中包括有其他多种协议,比如ICMP和ARP。

IP再往下就是网卡的驱动程序以及它负责控制的硬件部分,数据最终发送出去就是通过网卡这个硬件设备传递到网线(如果是无线网卡就是传递到无线介质中)。

套接字是什么?

协议栈内部有一块用于存放控制信息的内存空间,这里面记录了用于控制通信操作的控制信息,比如通信对象的IP地址、端口号、通信操作进行状态等。套接字就是一个概念,用来统称这些控制信息的集合,我们可以说这些控制信息就是套接字的实体,或者说存放这些控制信息的内存就是套接字的实体。

协议栈根据这些控制信息进行通信操作,比如在发送数据时通过套接字查看一下通信对象的IP地址和端口号,以便向其发送数据;发送完数据后协议栈需要等待对方的回应,如果这时候对方回应的数据在中途丢失,那我们就永远也接收不到了,我们不能一直等待这时候我们也需要通过套接字来查看是否收到了响应,或者是数据发送后经过了多长的时间,这样我们才能决定是否要重发请求。

理解了套接字我们才能够理解socket,因为socket是基于套接字进行网络操作的。之前我们讲过数据的收发流程,其实就是socket通信的操作流程,操作系统中网络的通信是基于socket的。创建套接字阶段中,应用程序调用socket库申请套接字,协议栈根据应用程序的申请执行创建套接字的操作,协议栈先分配一块内存出来给套接字,并根据创建时指定的控制信息来初始化套接字。

连接服务端

创建套接字后,下一步就是调用socket库的connect函数进行连接操作,协议栈会将本地套接字与服务端套接字进行连接,我们直到套接字就是一系列控制信息的集合,这里“连接“的意思就是控制信息的交换。套接字刚创建出来的时候里面没有任何数据,也不知道通信对象是谁,在这种情况下就需要进行下一步操作:往套接字中填入相应必要信息,比如把服务端的IP地址和端口号等信息填入套接字。

同样在服务端也需要有相应的套接字来接收客户端的连接,而且服务端的套接字刚创建时和客户端的一样是没有任何数据信息的,但是与客户端不同的是服务端是一对多的,它不应设置为固定监听某一个客户端的连接,而是应该监听所有连接请求。所以服务端要想跟客户端通信,需要客户端发来请求信息(其实就是套接字)来进行连接操作。

连接操作中控制信息的交换是根据一定的通信规则进行的,双方获得到必要的信息后才能为数据的收发做好准备。进行数据收发时,协议栈会开辟一块儿内存空间来临时存放要收发的数据,这块儿内存空间称为缓冲区,缓冲区实在连接的过程中分配的。至此连接操作就完成了。

connect(< 描述符 >, < 服务器 IP 地址和端口号 >, … )

应用程序调用connect之后将服务端的IP和端口号信息填入套接字,这些信息会传递给协议栈的TCP模块,然后TCP模块与服务端交换控制信息,交换信息的过程是这样的:

首先,客户端创建一个包含表示数据收发操作的控制信息的TCP头部,头部包含很多字段,这些都要严格规定,这里面重点是发送方和接收方的端口号,根据这些信息客户端就准确找到了服务端的套接字,下一步就是发送连接请求,将头部中的控制位SYN位设置为1,表示连接,另外还要设置一些适当的序号和窗口大小,这里先不详细展开。

TCP头部创建好后,TCP模块会将信息传递给IP模块委托其进行发送,IP模块执行完发送操作后,网络包到达服务端,服务端的IP模块接收到该网络包进行相应处理后传递给TCP模块,服务端的TCP模块根据传过来的数据中的TCP头部信息找到与头部中记录的端口号匹配的套接字,往套接字中写入相应的信息,并将其状态改为正在连接。

这些操作完成后,服务端TCP模块返回响应,这个过程和客户端类似,先在TCP头部中设置发送方和接收方的端口号和SYN位,另外返回响应消息时需要将ACK控制位设置为1表示收到了相应的网络包。网络中丢包的情况经常发生,所以通信双方必须要确认对方是否收到了网络包,设置ACK位就是为了进行这种确认。然后服务端TCP模块将TCP头部传递给IP模块,委托IP模块向客户端返回响应。

响应消息到达客户端后,客户端IP模块转交给TCP模块,通过TCP头部信息确认服务端操作是否成功,如果SYN为1表示连接成功,向客户端的套接字中写入服务端IP地址和端口号等信息。然后客户端再发送一个响应消息,并将TCP头部中的ACK位设置为1,告诉服务端响应包已经收到,这个包被服务端接收后,连接操作正式结束,双方进入随时可以收发数据的状态,可以认为有一个管子把两个套接字连接了起来,数据传输过程只要在进行,这个连接就一直存在。连接成功后控制流程交给应用程序。

下一次我们讲一下数据的收发和连接的断开,里面涉及到一些网络协议栈数据收发协议内容,数据的分片,以及协议栈为保证数据传递的效率和正确性做的一些机制。