收发数据的原理(上)

1,159 阅读14分钟

前言:网络知识非常的重要,如果你不是做程序的,那么一些网络常识还是得知道的;而做程序的,就更不用说了,不仅需要了解一些网络知识,还是知道其原理,如果不了解原理,不敢说他不是程序员,但是总缺了点意思,就像去北京没去过长城一样。

网络原理系列文章:

一、五分钟了解网络连接(已完成)

二、收发数据的原理(上)(已完成)

三、收发数据的原理(下)(已完成)

四、收发数据的番外篇(未完成)

上一篇五分钟了解网络连接讲了网络连接的大概流程,并且文末讲到客户端委托协议栈收发数据可以总结为四步:

1、创建套接字(创建套接字阶段)

2、将管道连接到服务器端的套接字上(连接阶段)

3、收发数据(通信阶段)

4、断开管道并删除套接字(断开阶段)

本文会对前两个步骤展开描述,后面两个步骤,下一篇文章介绍,敬请关注!由于网络知识点非常繁杂,读者请沉住气阅读此文,我尽可能详细介绍,尽可能的不那么枯燥,所以本文介绍也有侧重点。读完本文,你可能会对一些知识恍然大悟,哦,原来是这样啊!好了,废话不多说,直接进入主题。

创造套接字

协议栈的内部结构

image.png

上面是协议栈的内部结构。分为接个部分。上面部分会向下面的部分委派工作。

应用程序的下面是Socket库,其中包括解析器,解析器向DNS服务器发出查询,它的工作过程我们在上一篇已经介绍过了。

再下面就是操作系统内部了,其中包括协议栈。协议栈的上半部分有两块,分别是负责用TCP协议收发数据的部分和负责用UDP协议收发数据的部分,它们会接受应用程序的委托执行收发数据的操作。关于TCP和UDP,会在后面文章讲解,目前只要知道像浏览器、邮件等一般的应用程序、QQ文件传输都是使用TCP收发数据的,而像DNS查询、QQ语音 、QQ视频等收发较短的控制数据的时候则使用UDP。

协议栈的下半部分是利用IP协议控制网络包收发数据的部分,在互联网中发送数据,需要将数据分成一个个小的网络包,然后将网络包发送给通信对象就是由IP负责的。另外,ICMP用于错误提示以及各种控制消息,ARP则是用于查询IP相应的以太网MAC地址。现在只需要大概知道这个名词,后面才会细讲。

IP下面是驱动程序负责控制网卡硬件,最下面的网卡则是负责完成实际的收发操作——对网线中的信号执行发送和接受操作。

套接字的实体

实际上套接字并没存在实体,只是一个概念。在协议栈内部有一块存放控制信息的内存空间,用于记录通信操作的控制信息。比如通信对象的IP地址、端口号、通信操作的状态等。所以硬要说套接字是个实体,那就是这些控制信息或者是保存这些控制信息的内存空间。

协议栈执行操作时需要参阅这些控制信息。来决定下一步该做什么。比如:发送数据时,看看IP地址和端口号;发送数据后,协议栈需要等待对方返回数据的响应信息,但是数据可能会半途丢失。我们不可能一直等待,所以套接字中需要记录是否已经收到或者发送数据了多久,才方便知道是否要重发数据。套接字的控制信息还有很多作用,在此不一一列举了。

协议栈是根据套接字中记录的控制信息工作的。

调用Socket库中的组件

创建套接字时,需要调用Socket库中的socket组件,注意这里,大写的是Socket,小写是库中的一个程序组件。

应用程序调用socket程序申请创建套接字,而协议栈则根据应用程序的申请执行创建套接字的操作。

在创建过程中,协议栈会分配一个用于存放套接字所需的内存空间,用于存放记录套接字操作的控制信息。创建套接字时,数据收发操作还没开始,所以把这初始状态的信息存入内存空间中。到此,创建套接字操作完成。

创建套接字,不仅要分配空间,而且需要初始化状态信息。

然后,套接字需要将它的描述符告诉应用程序。描述符相当于车库号,告诉我车库号,我才知道哪个才是我要的车库。同样,描述符是用在应用程序委托协议栈收发数据的时候。套接字包含了通信对象的信息,比如已经说过的IP地址、端口号,所以应用程序收到套接字的描述符,应用程序再提供给协议栈,协议栈就知道了套接字中所包含的通信对象信息,就可以准备连接通信对象了。

连接服务器

关于连接

因为历史原因,其实这里的“连接”就更像是通信前的准备,叫“准备”其实更适合

调用socket程序申请创建完套接字,然后应用程序会调用Socket库的另外一个程序组件connect,这样协议栈就会将本地的套接字与服务器的套接字进行连接。这里的连接是指通信双方交换控制信息,在套接字记录一些必要信息并准备数据收发的一连串操作。

我们说的连接不是指网线一直插着的连接,不是指通信过程中将数据转换成电信号。而是当应用程序委托发送数据时,协议栈通过描述符找到的套接字取得通信对象的IP地址和端口号等信息。这属于连接操作的目的之一。

说完应用程序,再说下服务器那边,服务器也会创建套接字,但是服务器的协议栈和客户端这边一样,没有类似一个描述符的东西就没办法知道通信对象,没法开始通信。所以得有客户端先开始请求,告诉服务器必要信息。比如“我要和你请求,我的IP地址是10.10.1.118,端口号是8900”。所以,应用程序向服务器发送请求,也是连接操作的目的之一。

连接实际上通信双方交换控制信息,在套接字中记录必要信息并准备数据收发的一连串操作。

控制信息,是控制数据收发操作的一些信息。IP地址、端口号就属于其中的信息。其余的控制信息,我们后面再介绍。双方通过通信规则进行信息交换从而完成数据收发准备。收发操作,需要一块临时存放要收发的数据的内存空间,这块内存空间叫做缓冲区,它是在连接操作过程中分配的。

关于控制信息头部

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

第一类:客户端和服务器交换的控制信息,不仅是在连接时需要,包括数据收发和断开连接操作在内,整个通信过程都需要。我们前面说过,数据会被分成一个个网络包发送,而这些信息就是被添加在客户端与服务器传递的网络包的开头。在连接阶段,由于数据收发还没有开始,网络包中没有实际数据,只有控制信息。这些控制信息位于网络包的开头,因此成为头部。当然,以太网和IP协议也有自己的头部(包含着控制信息),为了避免不同头部混淆,我们一般记作TCP头部,以太网头部、IP头部。本文主要讲解TCP头部,其余知识后面再讲,读者有兴趣可先自行查阅。

客户端和服务器在通信中会将必要的信息存放在头部并相互确认。大家现在要知道的就是头部是用来记录和交换控制信息的。

第二类:套接字中的控制信息。这里会保存应用程序传递来的信息以及从通信对象接收到的信息,还有收发数据操作的执行状态等信息也会保存于此,协议栈根据这些信息来执行每一步操作。

通信操作中使用的控制信息分为两类: (1)头部中的信息 (2)套接字(协议栈的内存空间)中记录的信息

连接操作的实际过程

上面我们已经介绍了连接(准备)操作的含义,接下来看一下具体操作过程。首先是应用程序调用Socket库中的connect,类似下面👇

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

上面的调用提供了服务器的IP地址和端口号,这些信息会传递给协议栈中的TCP模块。TCP模块就会与该IP地址对应的对象,也就是与服务器的TCP模块交换控制信息。

tcp-header

seq:(Sequence Number):本报文段数据的第一个字节的序号 ack:(Acknowledgment Number):确认号——期望收到对方下个报文段的第一个数据字节的序号

控制位包含以下几部分 URG:紧急指针有效位。 SYN:建立连接,当需要建立连接时,他的值为1.即SYN=1 ACK:确认连接,当ACK=1是才有效,ACK=0是此控制位无效。 FIN:断开连接,提出断开连接这一方的值为1. RST:重新建立连接,值为1时代表重新建立连接。 PSH:要求接收方将数据尽快将数据段送达应用层

上图主要介绍了TCP头部。其中TCP头部目前要认知的有发送方和接收方的端口号,序号(seq)是发送方告诉接收方本报文段数据的第一个字节的序号,ack号是接收方告诉发送方已收到了所有数据的第几个字节。其中ack是Acknowledgment Number的缩写。可能读者纳闷IP地址在哪,IP地址其实在IP头部才有。由于本文重点是介绍TCP,所以下方只给出IP头部图,读者自行阅读

ip-header

应用程序与服务器的TCP模块交换控制信息这一过程包含几个步骤:首先,客户端先创建一个包含表示开始数据收发操作的控制信息的头部(上面所说网络包中开头的控制信息),头部包含很多字段,其中要关注的重点是客户端和服务器的端口号,也就是说,客户端的套接字知道了连接服务器的哪个套接字。然后,我们把头部中的控制位的SYN设置为1,大家可以认为它表示连接。此外,还需设置适当的序号和窗口大小。

连接操作的第一步是在TCP模块处创建表示连接控制信息的头部

通过TCP头部中的发送方和接收方的端口号可以找到要连接的套接字

当TCP头部创建好之后,接下来TCP模块会将信息传给IP模块并委托它进行发送。IP模块执行网络包发送操作后,网络包就会通过网络发送到服务器的IP模块,再由服务器的IP模块把接收到的数据传给服务器自身的TCP模块,这时,服务器的TCP模块会根据TCP头部的信息找到端口号对应的套接字,然后套接字就会写入相应的信息,并把状态改成正在连接。

TCP模块、IP模块分别属于网络原理中OSI模型7层结构的传输层、网络层,而传输层处于网络层的上一层,也就是高一层,要完成传送数据,必须从通信一方的高层传到低层,再通过网络传给通信另外一方的低层,再到那一方的高层完成接收。所以发送数据得从高一层的TCP到低层的IP模块逐一传递。

上述操作完成后,服务器的TCP模块会返回响应,这个过程跟客户端发送数据给服务端一样,需要在TCP头部中设置发送方和接收方端口以及SYN比特。另外,客户端向服务器发送第一个网络包时,由于服务器还没有接受过网络包,所以ACK比特设为0,那么在返回响应就需要将ACK控制位设为1,表示已经收到相应的网络包。网络中经常发生错误,网络包也会丢失。因此通信双方必须相互确认网络包是否已经送达,通信双方如何确认?其实就是通过设置ACK。接下来,服务器TCP模块会讲TCP头部传给IP模块,并委托IP模块向客户端返回响应。

然后,网络包就会返回客户端,通过IP模块到达TCP模块,并通过TCP头部信息确认连接服务器的操作是否成功。如果SYN为1,则表示连接成功,这时会向套接字中写入服务器的IP地址、端口号等信息,同时还会将状态改成连接完毕。到这里,客户端的操作就已经完成。但其实还剩下一个步骤,客户端收到数据后,也要像服务器那样把把ACK设置为1,并发回给服务器,告诉服务器,我已经收到服务器发来的响应包,当服务器收到这个返回包后,连接操作才算全部完成。

有没有觉得上面的双方相互确认网络包操作,似乎很熟悉,没错!它其实就是tcp的三次握手。

TCP三次握手 1.A向B发起建立连接请求: 2.B收到A的发送信号,并且向A发送确认信息 3.A收到B的确认信号,并且向B发送确认信号

连接(准备)操作完成后,套接字可以随时进行收发数据了,这个时候我们可以理解通信双方已经有一条相连的管道,这条管道连接着双方的套接字。当然这条管道并不真正存在,只是业界为了方便理解,比喻而已。

建立连接后,协议栈的连接操作就结束了。也就是说,当初应用程序调用Socket库中connect程序组件操作已经执行完毕,控制流程又重新交回到客户端。等到后面的收发数据操作。

在此,收发数据的创建套接字阶段、连接阶段已经讲完,剩下的通信阶段、断开阶段留到下次再讲。网络的东西很枯燥,并且并不是那么可视化,学会的要诀是沉住气,看到这里,如果你现在还不能讲到收发数据的前两个步骤,请回到文章顶部,再看一遍。Over and over again,你就会学有所成。




欢迎关注技术公众号「程序员大咖秀」