传输TCP/IP数据

发布时间 2023-10-22 14:15:05作者: yunChuans

前言

上一章讲到了委托协议栈发送消息我们知道IP地址之后,就可以委托操作系统内部的协栈上向这个目标IP地址,也就是我们要访问的web服务器发送消息了。

收发数据的操作大致为以下几点

  1. 创建套接字(创建套接字阶段)
  2. 将管道连接到服务器端的套接字上(连接阶段)
  3. 收发数据(通信阶段)
  4. 断开管道并删除套接字(断开阶段)

接下来,将会详细的讲解这几个阶段

创建套接字

协议栈内部结构

套接字表示管道两边的接口

这张图中的上下关系是有一定规则的,上面的部分会向下面的部分委派工作,下面的部分接受委派的工作并实际执行。

image

下面一半是由IP协议控制网络包收包操作的部分。在互联网上传输数据时,数据会被分成一个一个的网络包,而将网络包发送给通信对象的操作就是由IP来负责的。

浏览器、邮件等一般应用程序收发数据使用TCP。
DNS查询等收发较短的控制数据时用udp。

套接字实体

套接字的实体就是通信控制信息

协议栈内部有一块用于存放控制信息的内存空间,记录了通信操作的控制信息,比如通信对象的IP地址、端口号、通信操作的进行状态等。

我们要清楚,套接字只是我们提出的一个概念,并非实体,他其实就是这些控制信息。我们需要委托协议栈执行操作,那么协议栈需要知道对象的IP地址和端口,发送数据后协议栈需要等待返回消息,但有可能数据丢失,等待不到响应,这样的情况下我们不能一直等下去。或者说,需要有一个定时器一样的东西,那么协议栈必须要知道发送数据过了多久。为此,套接字必须记录是否已经收到响应,以及发送数据后过去的时间。

协议栈首先会分配用于存放一个套接字所需的内存空间。在套接字的内存空间中写入表示初始状态的控制信息。到这里,创建套接字的操作就完成了。

连接服务器

连接的含义

套接字刚刚创建完成的时候,里面并没有存放任何数据,也不知道通信的对象是谁。在这个状态下,即使应用程序要发送数据,协议站也不知道数据应该发送给谁。因此,我们需要把服务器的IP地址和端口号等信息告知协议站。

那么服务器这边又是什么样的情况呢?服务器上也会创建套节字,但服务器上的协议站和客户端一样,只创建套节字是不知道应该和谁进行沟通的。于是,我们需要让客户端向服务器告知必要的信息,比如我想和你开始通信,我的IP地址是XXX,端口号是XXX。可见,客户端向服务器传达开始通信的请求也是连接操作的目的之一。

负责保存控制信息的头部

通信操作中使用的控制信息分为两类。

  • 客户端和服务器相互联络时交换的控制信息(TCP头部)。
  • 以太网和ip协议的控制信息。

链接的实际过程(3次握手)

服务器的IP地址和端口号,这些信息会传递给协议站中的TCP模块,然后TCP模块会与该IP地址对应的对象,也就是与服务器的TCP模块交换控制信息。这一交换过程包括下面几个步骤。

客户端先创建一个包,表示开始数据搜发操作的控制信息的头部。头部包含很多字段,这里要关注的重点是发送方和接收方的端口号。到这里,客户端的套接字就准确找到了服务器的套接字,也就是搞清楚了我应该连接哪个套接字。然后我们将头部中的控制位的syn比特设置为一,大家可以认为它表示连接。

当TCP头部创建好以后,接下来TCP模块会将信息传递给IP模块,并委托他进行发送。IP模块执行网络包发送操作后,网络包就会通过网络到达服务器,然后服务器上的IP模块会接收到的数据传递给TCP模块。服务器的TCP模块根据TCP头部中的信息找到端口号对应的套接字,也就是说,处于等待连接状态的套接字中找到与TCP头部中记录的端口号相同的接字就可以了。当找到对应的套接字之后,套接词中会写入相应的信息,并将状态改为正在连接。上述操作完成后,服务器的TCP模块会返回响应,这个过程和客户端一样。此外,在返回响应时还需要将a控制位设置为一,这表示已经接收到相应的网络包。接下来,服务器TCP模块会将TCP头部传递给IP模块,并委托IP模块向客户端返回响应。

然后网络包就会返回到客户端,通过IP模块到达TCP模块,并通过TCP头部的信息确认连接服务器的操作是否成功。如果syn为一,则表示连接成功。这时会向套接字之中写入服务器的IP地址、端口号等信息,同时会将状态改为连接完毕。到这里,客户端的操作就已经完成。

但其实还剩最后一个步骤,刚才服务器返回响应时,将ACK比特设为一,相应的客户端也需要将ACK比特设置为一,并发回服务器,告诉服务器刚才的响应包已经收到,当这个服务器收到这个返回包之后,连接操作才算全部完成。

收发数据

从服务器断开并删除套接字(四次挥手)

这里我们以服务器一方发起断开过程为例来进行讲解。首先,服务器一般的应用程序会调用socket库中的close程序,然后服务器的协议栈会生成包含断开信息的TCP头部,具体来说就是将控制位中的fin比特设置为一。接下来,协议站会委托IP模块向客户端发送数据,同时服务器的套接字中也会记录下断开操作的相关信息。

接下来轮到客户端了,当收到服务器发来的fin为一的TCP头部时,客户端的协议上会将自己的套接字标记为进入断开操作状态。

然后,为了告知服务器已收到fin为1的包,客户端会箱会向服务器发返回一个ack号。这些操作完成后,协议站就可以等待应用程序来取数据了。

过了一会儿,应用程序就会调用read来读取数据,这时协议栈不会向应用程序传递数据,而是会告知应用程序来自服务器的数据已经全部收到了,客户端应用程序会调用close来结束数据收发操作,这时客户端的协议栈也会和服务器一样生成一个fin比特为1的TCP,然后委托IP模块发给服务器。一段时间之后,服务器就会返回ACK号,到这里,客户端和服务器的通信就全部结束了。

删除套接字

和服务器的通信结束之后,用来通信的套接字就不会在使用了,这时候我们就可以删除这个套接字了。不过套接字并不会立即删除,而是会等待一段时间之后再被删除,等待这段时间是为了防止误操作。