名词解释
套接字:即socket,为通信的端点。每个套接字由一个 IP 地址和一个端口号组成。通过网络通信的每对进程需要使用一对套接字,即每个进程各有一个。
套接字与socket可以划等号。
TCP/IP:TCP/IP 指传输控制协议/网际协议 (Transmission Control Protocol / Internet Protocol)。它定义了电子设备(比如计算机)如何连入因特网,以及数据如何在它们之间传输的标准。 **TCP **用于应用程序之间的通信。当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信。这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止。**IP **用于计算机之间的通信。IP 是无连接的通信协议。它不会占用两个正在通信的计算机之间的通信线路。这样,IP 就降低了对网络线路的需求。每条线可以同时满足许多不同的计算机之间的通信需要。通过 IP,消息(或者其他数据)被分割为小的独立的包,并通过因特网在计算机之间传送。
TCP/IP协议栈:协议栈(Protocol stack),又称协议堆叠。是一系列网络协议的总和。TCP/IP协议栈,又称TCP/IP,是构成网络通信的核心骨架,它定义了电子设备如何连入因特网,以及数据如何在它们之间进行传输。TCP/IP 协议采用4层结构,分别是应用层、传输层、网络层和链路层,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
协议栈的内部如图 2.1 所示, 分为几个部分, 分别承担不同的功能。 这张图中的上下关系是有一定规则的, 上面的部分会向下面的部分委派工作, 下面的部分接受委派的工作并实际执行。
IP地址:ip(Internet Protocol)是指网络之间互联的协议。IP 地址(Internet Protocol Address)是互联网协议特有的一种地址,它是 IP 协议提供的一种统一的地址格式。
MAC地址:符合 IEEE 规格的局域网设备都使用同一格式的地址,这种地址被称为 MAC 地址。
IP地址和MAC地址:在单个局域网网段中,计算机与计算机之间可以使用网络访问层提供的 MAC 地址进行通信。如果在路由式网络中,计算机之间进行通信就不能利用 MAC 地址实现数据传输了:因为 MAC 地址不能跨路由接口运行;即使强行实现跨越,使用 MAC 地址传输数据也是非常麻烦的。这是由于内置在网卡里的固定 MAC 地址不能在地址空间上引入逻辑结构,使其无法具备真正的地址来表示国家、省、市、区、街道、路、号这类层次。因此,要进行数据传输,必须使用一种逻辑化、层次化的寻址方案对网络进行组织,这就是 IP 地址。
端口号:“端口”可以认为是计算机与外界通信交流的出口。一个IP地址可以有65 536(即256*256)个端口。端口是通过端口号来标记的,端口号只有整数,范围是0~65535)。
端口号比喻:一个主机是一栋楼,这栋楼的名字是这个主机的ip地址,这栋楼有65536个房间,每个房间供一个进程(程序)使用;假如说QQ使用10086号房间,那么从你好友那儿过来的信息是要去这栋楼10086号房间,意思就是好友发来的数据是由192.168.1.5这个主机的10086号端口接受,供给QQ这个程序使用。每个主机的端口总共有65536个,这个是固定的。假如说你写了一个程序,而且它需要通过网络去跟另一个主机上的一个程序通信,那么这时候你需要开一个房间给这个程序使用,这个是写在代码里的,可以指定一个房间(端口),也可以设置成让它自己随便找个房间(端口)使用。
ARP:Address Resolution Protocol,即地址解析协议。ARP 利用广播对所有设备,根据IP地址询问MAC地址,以此找到与IP地址对应的MAC地址。
前言
本章主要将讲解操作系统中的协议栈是如何处理数据发送请求的,并描述了通过套接字收发数据的整个过程。 所以,需要对协议栈,套接字两个名词有一个概念。
套接字: 即socket,为通信的端点,每个套接字由一个 IP 地址和一个端口号组成。通过网络通信的每对进程需要使用一对套接字,即每个进程各有一个。(反复理解套接字。。)
一个常见的问题:Socket为什么要翻译成套接字?
在网上看到把socket翻译成套接字比较赞的解释:
套接字的翻译逻辑我想应该是这样的: 两台计算机通过socket想插头与插座那样连接在一起,故称”套接“ 应用程序通过类似文件标识符的一个标识字对socket进行操作,故称”字“
下面开始本章内容。
创建套接字
- 协议栈与套接字
在协议栈内部有一块用于存放控制信息的内存空间, 这里记录了用于控制通信操作的控制信息, 例如通信对象的 IP 地址、 端口号、 通信操作的进行状态等。我们可以说这些控制信息就是套接字, 或者说存放控制信息的内存空间就是套接字。协议栈在执行操作时需要参阅这些控制信息。 总结下来就是:协议栈是根据套接字中记录的控制信息来工作的。
在 Windows 中可以用 netstat 命令显示套接字内容 :
- 调用套接字
应用程序通过 Socket 库调用 socket来申请创建套接字, 协议栈根据应用程序的申请执行创建套接字的操作。创建套接字时,首先分配一个套接字所需的内存空间,然后向其中写入初始状态。
连接服务器
创建套接字之后, 应用程序( 浏览器) 就会调用 connect, 随后协议栈
会将本地的套接字与服务器的套接字进行连接。
- 负责保存控制信息的头部
控制信息大体上可以分为两类。
(1)头部中记录的信息
(2)套接字(协议栈中的内存空间)中记录的信息
第一类是客户端和服务器相互联络时交换的控制信息,这些信息在 TCP 协议的规格中进行了定义。表 2.1 中的这些字段就是 TCP 规格中定义的控制信息 A。 这些字段是固定的, 在连接、 收发、断开等各个阶段中, 每次客户端和服务器之间进行通信时, 都需要提供这些控制信息。
这些控制信息位于网络包的开头, 因此被称为头部。此外, 以太网和 IP 协议也有自己的控制信息, 这些信息也叫头部,为了避免各种不同的头部发生混淆, 我们一般会记作 TCP 头部、 以太网头部、 IP 头部。
第二类就是保存在套接字中, 用来控制协议栈操作的信息。应用程序传递来的信息以及从通信对象接收到的信息都会保存在这里, 还有收发数据操作的执行状态等信息也会保存在这里, 协议栈会根据这些信息来执行每一步的操作。
- 连接操作的过程
连接操作的第一步是在 TCP 模块处创建表示连接控制信息的头部。 通过TCP 头部中的发送方和接收方端口号可以找到要连接的套接字。 客户端头部的控制信息有SYN(设为1时表示连接)、序号、窗口大小。
客户端TCP头部创建好后,将信息传递给IP模块,委托其进行发送。发送给服务器的IP模块负责接收,并传给TCP模块,之后 TCP 模块会返回响应, 这个过程和客户端一样, 需要在 TCP 头部中设置发送方和接收方端口号以及 SYN 比特, 此外, 在返回响应时还需要将 ACK 控制位设为1, 这表示已经接收到相应的网络包。
然后, 网络包就会返回到客户端, 通过 IP 模块到达 TCP 模块, 并通过 TCP 头部的信息确认连接服务器的操作是否成功。 如果 SYN 为 1 则表示连接成功, 这时会向套接字中写入服务器的 IP 地址、 端口号等信息, 同时还会将状态改为连接完毕。
收发数据
- 将 HTTP 请求消息交给协议栈
数据收发操作是从应用程序调用 write 将要发送的数据交给协议栈开始的。协议栈会根据一个叫作 MTUA 的参数来进行判断。 MTU 表示一个网络包的最大长度, 在以太网中一般是 1500 字节( 图 2.5)。
为了避免发送大量小包的问题,设置从应用程序收到的数据长度超过或者接近 MSS 时再发送出去 。
2.对较大数据进行拆分
HTTP 请求消息一般不会很长, 一个网络包就能装得下, 但如果其中要提交表单数据, 长度就可能超过一个网络包所能容纳的数据量。 这种情况下, 发送缓冲区中的数据就会超过 MSS 的长度,发送缓冲区中的数据会被以 MSS 长度为单位进行拆分, 拆分出来的每块数据前面都会加上 TCP 头部,并根据套接字中记录的控制信息标记发送方和接收方的端口号。
- 使用 ACK 号确认网络包已收到
网络包装好数据并发往服务器之后, 数据发送操作并没有结束。 TCP 具备确认对方是否成功收到网络包, 以及当对方没收到时进行重发的功能。这部分功能通过序号和ACK号来实现。
TCP 采用这样的方式确认对方是否收到了数据, 在得到对方确认之前, 发送过的包都会保存在发送缓冲区中。 如果对方没有返回某些包对应的 ACK 号, 那么就重新发送这些包。
4.接收 HTTP 响应消息
发送 HTTP 请求消息后, 接下来还需要等待 Web 服务器返回响应消息。浏览器在委托协议栈发送请求消息之后, 会调用 read 程序来获取响应消息, 然后控制流程会通过 read 转移到协议栈。 协议栈会检查收到的数据块和 TCP 头部的内容, 判断是否有数据丢失, 如果没有问题则返回 ACK 号。 然后,协议栈将数据块暂存到接收缓冲区中, 并将数据块按顺序连接起来还原出原始的数据, 最后将数据交给应用程序。
从服务器断开并删除套接字
- 断开连接
收发数据结束后,服务器一方的应用程序会调用 Socket 库的 close 程序。然后, 服务器的协议栈会生成包含断开信息的 TCP 头部(将控制位中的 FIN 比特设为 1)。接下来, 协议栈会委托 IP 模块向客户端发送数据,客户端收到服务器发来的 FIN 为 1 的 TCP 头部时,协议栈会将自己的套接字标记为进入断开操作状态。 然后客户端会向服务器返回一个 ACK 号。
收到服务器返回的数据后, 客户端的操作也就随之结束了。 因此, 客户端应用程序会调用 close 来结束数据收发操作, 这时客户端的协议栈也会和服务器一样, 生成一个 FIN 比特为 1 的 TCP 包, 然后委托 IP 模块发送给服务器。 一段时间之后, 服务器就会返回ACK 号。 到这里, 客户端和服务器的通信就全部结束了。
- 删除套接字
服务器的通信结束之后, 用来通信的套接字也就不会再使用了, 这时就可以删除这个套接字了。 不过, 套接字并不会立即被删除, 而是会等待一段时间之后再被删除(等待这段时间是为了防止误操作 )。 断开的操作顺序如下:
( 1) 客户端发送 FIN
( 2) 服务器返回 ACK 号
( 3) 服务器发送 FIN
( 4) 客户端返回 ACK 号
IP 与以太网的包收发操作
- 包的基本知识
包是由头部和数据两部分构成的,头部包含目的地址等控制信息, 大家可以把它理解为快递包裹的面单; 头部后面就是委托方要发送给对方的数据, 也就相当于快递包裹里的货物。
- 包的传输
TCP 模块在执行连接、 收发、 断开等各阶段操作时, 都需要委托 IP 模块将数据封装成包发送给通信对象。
发送方的网络设备会负责创建包, 创建包的过程就是生成含有正确控制信息的头部, 然后再附加上要发送的数据。 接下来, 包会被发往 最近的网络转发设备。 当到达最近的转发设备之后, 转发设备会根据头部中的信息判断接下来应该发往哪里。 这个过程需要用到一张表, 这张表里面记录了每一个地址对应的发送方向, 也就是按照头部里记录的目的地址在表里进行查询, 并根据查到的信息判断接下来应该发往哪个方向。
网络中有路由器和集线器两种不同的转发设备, 它们在传输网络包时有
着各自的分工:
(1) 路由器根据目标地址判断下一个路由器的位置
(2) 集线器在子网中将网络包传输到下一个路由
为了判断包接下来应该向什么地方传输, 集线器里有一张表( 用于以太网协议的表), 可根据以太网头部中记录的目的地信息查出相应的传输方向。实际上, 集线器是按照以太网规则传输包的设备, 而路由器是按照 IP规则传输包的设备, 因此我们也可以作如下理解。
(1) IP 协议根据目标地址判断下一个 IP 转发设备的位置(IP头部)
(2) 子网中的以太网协议将包传输到下一个转发设备(MAC头部)
包收发操作的起点是 TCP 模块委托 IP 模块发送包的操作,与此同时,TCP 模块还需要指定通信对象的 IP 地址, 也就是需要写清楚“将什么内容发给谁”。IP 模块会将包的内容当作一整块数据, 在前面加上包含控制信息的头部,即包含IP地址的IP头部和包含MAC地址的MAC头部。
封装好的包会被交给网络硬件(网卡)。 传递给网卡的网络包是由一连串 0 和 1 组成的数字信息, 网卡会将这些数字信息转换为电信号或光信号, 并通过网线( 或光纤) 发送出去, 然后这些信号就会到达集线器、 路由器等转发设备, 再由转发设备一步一步地送达接收方。
- IP 头部
IP 头部包含的内容如表 2.2 所示, 其中最重要的内容就是 IP 地址, 它表示这个包应该发到哪里去。 这个地址是由 TCP 模块告知的, 而 TCP 又是在执行连接操作时从应用程序那里获得这个地址的, 因此这个地址的最初来源就是应用程序。
- MAC 头部
生成了 IP 头部之后, 接下来 IP 模块还需要在 IP 头部的前面加上MAC 头部( 表 2.3)。 IP 头部中的接收方 IP 地址表示网络包的目的地, 通过这个地址我们就可以判断要将包发到哪里, 但在以太网的世界中, TCP/IP 的这个思路是行不通的。 以太网在判断网络包目的地时和 TCP/IP 的方式不同, 因此必须采用相匹配的方式才能在以太网中将包发往目的地, 而MAC 头部就是干这个用的。
查询 MAC 地址需要使用 ARP(Address Resolution Protocol),即地址解析协议。在以太网中, 有一种叫作广播的方法, 可以把包发给连接在同一以太网中的所有设备。 ARP 就是利用广播对所有设备提问:“×× 这个 IP 地址是谁的? 请把你的 MAC 地址告诉我。” 然后就会有人回答:“这个 IP 地址是我的, 我的 MAC 地址是××××。” 即,与接收者地址匹配的设备接收这个包, 其他的设备则丢弃这个包。
如果对方和自己处于同一个子网中, 那么通过上面的操作就可以得到对方的 MAC 地址。 然后将这个 MAC 地址写入 MAC 头部。
- 给网络包再加 3 个控制数据
IP 生成的网络包只是存放在内存中的一串数字信息, 没有办法直接发送给对方。 因此, 我们需要将数字信息转换为电或光信号, 才能在网线上传输。负责执行这一操作的是网卡,网卡的内部结构如图 2.23 所示。 网卡的 ROM 中保存着全世界唯一的 MAC 地址, 这是在生产网卡时写入的, 将这个值读出之后就可以对 MAC 模块进行设置, MAC 模块就知道自己对应的 MAC 地址了。
但网卡也无法单独工作, 要控制网卡还需要网卡驱动程序。网卡驱动从 IP 模块获取包之后, 会将其复制到网卡内的缓冲区中, 然后向MAC 模块发送发送包的命令。 MAC 模块会将包从缓冲区中取出, 并在开头加上报头和起始帧分界符, 在末尾加上用于检测错误的帧校验序列( 图 2.24)。报头是一串像 10101010…这样 1 和 0 交替出现的比特序列, 长度为 56比特, 它的作用是确定包的读取时机,起始帧分界符用来表示包起始位置的标记。
网络包末尾的 FCS( 帧校验序列)用来检查包传输过程中因噪声导致的波形紊乱、 数据错误, 它是一串 32 比特的序列。在包传输过程中, 如果受到噪声的干扰而导致其中的数据发生了变化, 那么接收方计算出的 FCS 和发送方计算出的 FCS 就会不同, 这样我们就可以判断出数据有没有错误。
加上报头、 起始帧分界符和 FCS 之后, 我们就可以将包通过网线发送出去了。发送信号的操作分为两种, 一种是使用集线器的半双工模式, 另一种是使用交换机的全双工模式。发送和接收同时并行的方式叫作“全双工”,相对地,某一时刻只能进行发送或接收其中一种操作的叫作“半双工”。
6.接收返回包
假设 Web 服务器返回了一个网络包, 网卡驱动会将其交给 TCP/IP 协议栈来进行处理。 接下来就轮到 IP 模块先开始工作了, 第一步是检查 IP 头部, 确认格式是否正确。 如果格式没有问题, 下一步就是查看接收方 IP 地址。 如果接收网络包的设备是一台 Windows 客户端计算机, 那么服务器返回的包的接收方 IP 地址应该与客户端网卡的地址一致,检查确认之后我们就可以接收这个包了。
网线和局域网中只能传输小包, 因此需要将大的包切分成多个小包。 如果接收到的包是经过分片的, 那么 IP 模块会将它们还原成原始的包。 分片的包会在 IP 头部的标志字段中进行标记,当收到分片的包时, IP 模块会将其暂存在内部的内存空间中, 然后等待 IP头部中具有相同 ID 的包全部到达, 这是因为同一个包的所有分片都具有相同的 ID。 此外, IP 头部还有一个分片偏移量( fragment offset) 字段, 它表示当前分片在整个包中所处的位置。 根据这些信息, 在所有分片全部收到之后, 就可以将它们还原成原始的包, 这个操作叫作分片重组。
到这里, IP 模块的工作就结束了, 接下来包会被交给 TCP 模块。 TCP模块会根据 IP 头部中的接收方和发送方 IP 地址, 以及 TCP 头部中的接收方和发送方端口号来查找对应的套接字。 找到对应的套接字之后, 就可以根据套接字中记录的通信状态, 执行相应的操作了。
UDP 协议的收发操作
不需要重发的数据用 UDP 发送更高效
大多数的应用程序都像之前介绍的一样使用 TCP 协议来收发数据, 但当然也有例外。 有些应用程序不使用 TCP 协议, 而是使用 UDP 协议来收发数据。 向 DNS 服务器查询 IP 地址的时候我们用的也是 UDP 协议。 像 DNS 查询等交换控制信息的操作基本上都可以在一个包的大小范围内解决, 这种场景中就可以用 UDP 来代替TCP 。
接下来, 网络包会从计算机出来跑向集线