《计算机网络-自顶向下的方法》读书笔记

大概是几个月前,曾被赶鸭子上架似的学习了一阵子socket,作为没有一丁点网络基础的菜鸟,完全无从下手,靠死记硬背写了几端代码,最后还是将它搁在哪里了。在现在的前端工作中,仍免不了要跟网络请求打交道,然而只明白几个http状态码,调用个Ajax接口是远远不够的,带着弄明白“从浏览器地址栏输入网址到看见整个网页发生了什么”这个经典面试题的目的,打开了《计算机网络》这本书。

<!--more-->

1. 计算机网络和因特网

因特网是一个世界范围内的计算机网络。

1.1. 网络边缘

数以亿记的设备与因特网相连接,这些设备被称为“主机”或“端系统”:

  • 因为这些设备处于因特网的边缘,所以称为端系统
  • 因为这些设备容纳(即运行)应用程序,所以也被称为主机。实际上主机又可以分为“服务器”和“客户机”。 主机间通过通信链路和分组交换机连接在一起,并向彼此交换报文。 主机,分组交换机和其他因特网部件都要运行一系列协议,协议定义了在两个或多个通信实体之间交换的报文格式和次序,以及报文发送/或接受到一条报文或其他事件所采取的动作。 这些协议控制着因特网中信息的接受和发送。最主要的两个协议是TCP(传输层控制协议)和IP(网际协议)。

1.2. 网络核心

网络核心指因特网网端系统的分组交换机和链路构成的网状系统。

当一台主机向另一台主机发送报文时,发送端主机会将数据分段,然后再每段数据加上首部字节,由此形成的信息包称为“分组”:

  • 通信链路由不同的物理媒体组成,比如电缆,铜线,光纤,无线电等,传输的速率按bit/s(通常说的宽带20M,指的就是M节而不是M字节...)
  • 分组交换机从他的一条“入通信链路”接收到到达的分组,并从他的一条出通信链路转发该分组。最常见的分组交换机是路由器(用于网络核心)和链路层交换机(用于接入网)

存储转发传输:多数分组交换机在入通信链路使用“存储转发传输”:在交换机能够开始向出通信链路传输该分组的第一个比特之前,必须接收到整个分组(需要耽搁一定时间)。 排队时延和丢包:每个分组交换机与多条链路相连,对于每条链路,交换机都有一条输出队列,该队列存储着发往这条链路的分组,这些分组按照先进先出的规定,排队等待被传输(需要耽搁一定时间)。如果网络十分拥塞,则可能发生输出队列被塞满的情况,这时候某些分组就可能被丢失(丢包)。 路由协议:前面提到每个分组交换机与多条链路相连,则需要按照一定规定选择发送分组的链路。每个主机都有一个IP地址,而每个路由器都有一个转发表,用于将分组的目的地址映射为出通信链路。

1.3. 协议分层

网络设计者以分层的方式组织协议以及实现这些协议的硬件和软件,某层向他上一层提供的服务,则成为服务模型。 每层有很多协议,该层的全部协议被称为协议栈。因特网的协议栈由物理层,链路层,网络层,运输层和应用层组成(这也是这本书的目录形式)。

  • 应用层是网络应用程序以及他们的应用层协议存留的地方(比如现在从事的web应用程序),通常把处于应用层的信息分组称为报文。
  • 运输层在应用程序端点之间传送应用层报文,主要存在两个重要的协议:TCP和UDP,通常把处于运输层的信息分组称为报文段。
  • 网络层负责将网络层的数据分组(通常称为数据报)从一台主机移动到另一台主机,该层最著名的协议为IP协议,该协议定义了在数据报中的各个字段以及端系统如何作用于这些字段。
  • 链路层将从网络层获取的数据包沿着路径传递给下一个结点,在下一个结点将数据报上传给网络层,通常包链路层的数据分组称为帧。
  • 物理层是将链路层每个帧中的一个一个比特从一个结点移动到下一个结点,根据实际传输媒体的不同,物理层有多种协议。

2. 应用层

研发网络应用程序的核心是写出能够在运行在不同的端系统通过网络彼此通信的程序。根据应用程序在各种端系统上的组织形式,可分为“客户-服务器体系”结构和“对等(P2P)”体系。

  • 客户-服务端体系中,客户相互之间不直接通信,而是与运行的服务器通信,该服务器拥有固定且周知的地址(即IP地址)
  • P2P体系中,应用程序在简短链接的主机对之间使用直接通信

从操作系统的角度看,应用程序之间的通信实际上是进程(处于激活状态的程序)通信,进程之间通过网络传递报文进行通信。而进程是通过一个被称为套接字(socket)的软件接口从网络发送或接收报文。 进程之间通信,不仅需要他们所处主机的地址(IP地址),也需要知道进程的标识符(一台主机上可能会同时运行多个进程),这个标识符被称为"端口号"。

常见的应用层协议有HTTP,FTP...

2.1. HTTP协议

HTTP定义了Web客户向Web服务器请求Web页面的方法,以及服务器向客户发送Web页面的方式。

  • HTTP使用TCP作为运输层协议,因此不必担心数据的丢失。
  • HTTP服务器不保存关于客户的任何信息,因此HTTP是一个无状态协议。
  • HTTP可以使用“非持续连接”和“持续连接”两种方式处理多对“请求-响应对”。

2.1.1. 请求报文

下面是一个常见的HTTP请求报文。

// 请求行
GET /somedir/page.html HTTP/1.1 
// 首部行
Hosr: www.xxx.com
Connection: close
User-agent: Mozilla/5.0
Accept-language: fr
// 请求实体,也就是常说的参数
  • 请求行包括方法字段,URL字段和HTTP版本
  • 首部行提供的信息可能是Web代理告诉缓存所要求的
  • 请求实体指请求报文中所需要传递给服务器的“真正的数据”

2.1.2. 响应报文

// 状态行
HTTP/1.1 200 OK
// 首部行
Connection: close
Date: Tue, 09 Aug 2011 15:44:04 GMT
Server: Apache/2.2.3(CentOS)
Last-Modified: Tue, 09 Aug 2011 15:24:04 GMT // 影响缓存
Content-length: 6000
Content-type: text/html
// 响应实体
  • 状态行包括协议版本,状态码和响应状态信息,常见的状态码有(200,301,400,404,505)
  • 每个首部行提供相应的信息用以达到不同目的,浏览器产生的首部行与很多因素有关
  • 响应实体包含实际的响应返回结果

HTTP是一个无状态的协议,不保存客户的任何信息,但是很多情况下服务器需要识别客户或者做出某些限制,这里可以使用cookie来标识一个用户。用户首次访问时,服务器在HTTP响应报文中返回一个Set-Cookie,在后续会话中,浏览器的请求报文包含了这个cookie首部,从而向服务器标识该用户。 因此cookie可以在无状态的HTTP协议之上建立一个用户会话层。

2.1.4. Web缓存

Web缓存器也叫代理服务器。经过配置,浏览器建立与Web缓存器之间的连接,发送请求报文,如果缓存器上存在请求对象则直接返回;如果不存在,则缓存器向初始服务器发送请求报文,并在接受到初始服务器的响应报文之后将请求对象在该缓存器上备份(这里就发生了“缓存”),然后返还给浏览器。

  • Web缓存器大大减少了浏览器请求的响应时间
  • 减少一个机构的接入链路到因特网的通信量,减少因特上的Web流量,从而改善所有应用的性能。

Web缓存还有一个十分重要的应用:内容分发网络(CDN),安装许多在地理上分散的缓存器,实现大量流量的本地化,后面将会提到。

2.1.5. 条件GET

Web缓存器向返回浏览器的请求对象,但是,存放在缓存器中的文件副本可能已经过时了。幸好HTTP有一种“条件GET”方法:

  • 请求报文使用GET方式
  • 请求报文包含一个If-Modifid-Since首部行

同时具备上述两个条件的请求报文就构成了一个条件GET请求报文。当Web缓存器结构到着这种条件GET。

当Web缓存器从服务器第一次获取请求对象时,服务器的响应报文中会包含一个Last-Modifyied首部行。 当一段时间过去后,Web缓存器接受到浏览器的条件GET请求报文时,由于不确定初始服务器是否更新了文件,因此Web缓存器也会向初始服务器发送一个包含If-Modifid-Since的条件请求报文。 初始服务器会根据文件对象的Last-Modifyied的值是否在If-Modifid-Since之后,只有当在指定日期之后对该文件对象修改过,才会返回修改后的新对象,否则,返回304 Not Modified(返回新的文件对象会浪费大量的带宽,而返回304表示告知Web缓存器可以使用它自己的缓存对象副本)。

PS:这里突然想到之前项目里面,由于微信开发者工具的缓存十分严重,在服务器上修改了CSS文件一直得不到刷新,后面写了段JS,每次在请求后面带上了一个随机的GET参数,然后就成功解决了,大概就是这里的条件GET原理吧。

2.2. DNS协议

DNS(Domain Name System)是因特网上的目录服务。主机都可以通过多种方式进行标识。主机名更容易被人们接受(www.foo.com),但是却没有提供多少该主机在因特网中的位置。实际上,主机通过使用IP地址来表明他自身的位置(IP地址后面提)。 也就是说主机既可以使用主机名来标识(人们更喜欢好记的主机名),也可以使用IP地址来标识(路由器更喜欢定长的有层次结构的IP)。因此需要一种进行主机名到IP地址转换的目录服务:DNS。

下面是简单的DNS工作流程:

  • 浏览器从URL中抽取出主机名,并将主机名传递给DNS客户端(就运行在该主机上)
  • DNS客户端向DNS服务器发送解析主机名的请求报文
  • DNS服务器返回包含对应主机名的IP的响应报文,并被DNS客户端接受。
  • DNS向浏览器提供相应主机名的IP地址。浏览器向位于该IP地址的服务器发送请求报文并建立TCP连接。

为了处理扩展性问题,DNS使用了大量的DNS服务器,并以层次方式组织这些服务器,通常包括:

  • 根DNS服务器
  • 顶级域服务器
  • 权威DNS服务器
  • 本地DNS服务器

没有任何一台DNS服务器保存了因特网上全部主机的IP映射。通过递归和迭代查询主机名对应的IP地址并返回给浏览器。 为了改善时延性能并减少DNS报文数量,DNS广发使用缓存技术。每当本地DNS服务器从某个DNS服务器接收到响应报文后,就将相应的主机名和IP地址保存起来,接下来的一段时间内接受到相应的解析请求就直接返回该IP地址,而不必再查询其他DNS服务器

2.3. 套接字编程

学习C++的时候曾经翻过这小节,当时完全看不明白,全靠死记硬背的。现在再看,实际上套接字跟DOM一样,也是提供了一套很方便接口,让我们不用去关心具体的网络实现细节,而专注于应用层的逻辑处理。(当时我压根不明白“接口”到底是一个什么样的概念...)

Socket套接字提供给的接口是用于连接应用层和传输层,而实际上,开发者在应用层的一侧可以控制所有东西(业务逻辑),而对传输层的一侧基本上没有控制权。前面提到,传输层两个最重要的协议是TCP和UDP,这两者的传输方式完全不相同,因此,在数据分组从应用层推送到传输层之前,在套接字的应用层这一侧,我们必须根据选择的传输层协议做一些准备工作。

2.3.1. UDP

使用UDP时,每个分组上都必须包含目的主机的IP地址(这样路由器才能将报文路由到正确的主机上)和接收端口号(一台主机上可能运行着多个进程,这些进程又可能包含一个或多个端口号,必须明确指出是哪一个端口号的进程处理这个分组)。

# 直接把书上的示例代码敲了一遍,啊很久没写python了,当初真是靠背下来的

# client
from socket import *
serverName = '127.0.0.1'
serverPort = 12000
clientSocket = socket(AF_INET,SOCK_DGRAM)
message = raw_input('input a message:')
clientSocket.sendto(message,(serverName,serverPort))

msg, serverAddress = clientSocket.recvfrom(2048);
print msg
clientSocket.close()

# server
from socket import *
serverPort = 12000
serverSocket = socket(AF_INET,SOCK_DGRAM)
serverSocket.bind(('', serverPort)) # 任意请求IP
print 'server start..'
while True:
    msg, clientAddress = serverSocket.recvfrom(2048)
    msgModify = msg.upper()
    serverSocket.sendto(msgModify,(clientAddress))

2.3.2. TCP

TCP是一个面向连接的协议,因此客户机和服务机在发送数据之前,必须通过“三次握手”建立连接:客户端套接字地址(客户端IP和端口号)与服务端套接字地址之间的链接。连接建立之后,只需要将数据推送给TCP连接就可以了(不需要再额外想分组中添加目标主机的IP和端口号)。“三次握手”和“四次挥手”是TCP连接中十分重要的过程,后面也会提到。

// client
from socket import *
serverName = '127.0.0.1'
serverPort = 12000
clientSocket = socket(AF_INET,SOCK_STREAM)
clientSocket.connect((serverName,serverPort))

message = raw_input('input a message:')
clientSocket.send(message)

msg= clientSocket.recvfrom(2048)
print msg
clientSocket.close()

// server
from socket import *
serverPort = 12000
serverSocket = socket(AF_INET,SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(1) // 欢迎套接字
print 'server start..'
while True:
    connectionSocket,addr = serverSocket.accept() // 连接套接字
    msg = connectionSocket.recvfrom(2048)
    msgModify = msg.upper()
    print msgModify
    connectionSocket.send(msgModify)
    connectionSocket.close()

2.4. 其他

书中还介绍了诸如FTP,SMTP等应用层协议以及P2P体系,但这些我只是大致浏览了一下,毕竟现在的重点是弄明白浏览器发送请求到展示网页的过程,因此其他的部分待日后再回头看看吧。

3. 传输层

传输层协议为运行在不同主机上的应用进程提供了逻辑通信:

  • 在发送端,运输层从应用程序进程接收到的报文(包含数据分组)转换成运输层报文段(报文段尺寸比较小,且每段报文段之前都包含了运输层首部信息)。
  • 在接收端,网络层从数据报(即网络层分组)中提取运输层报文段,并将该报文段向上传递给运输层

将主机交互扩展到进程间的交互被称为运输层的多路复用和多路分解:

  • 在接受端,运输层检查报文段,并标识出套接字,从而将报文段定向到该套接字将运输层报文段中的数据交付到正确的套接字的工作称为多路分解
  • 在源主机,从不同的套接字收集数据块,并为每个数据块封装首部信息从而生成报文段,然后将白报文段发送到网络层,这个工作称为多路复用

一个UDP套接字由一个二元组来标识(目的IP地址和目的端口号),因此,即使两个UDP报文具有不同的源IP地址和(或)源端口号,只要他们的目的IP地址和目的端口号相同,这两个报文段都能被定向到目的主机同一个目的进程上。而在UDP的报文段中,源端口号只是为了目的主机响应报文的返回地址而已(完整的返回地址包括源IP地址和源端口号)。

一个TCP套接字由一个四元组标识(目的IP地址和目的端口号,源IP地址和源端口号)。考虑上面UDP中例子,两个具有不同源IP地址和(或)源端口号的TCP报文,访问相同的目的IP和目的端口号,他们也会被定向到同一个目的主机不同的端口号上(这里就能理解为什么说TCP报文中的目的端口号只是一个欢迎端口号了)。后续到达的报文,如果他们的四个值与目的主机上某个套接字的四个标识符都相同,就会定位到相应的套接字上。 拿Web服务器来说,服务器会为每一个链接生成一个新进程(独一无二的套接字),通过这些套接字,每个连接都可以与服务器请求和发送HTTP报文。实际上,连接和进程并非是一一对应的,某些高性能Web服务器为每个连接生成一个线程而非进程。

3.1. UDP

UDP的传输速率十分高:

  • UDP是除了最基本的复用/分解和极少量的差错检测之外,几乎没有对IP增加其余的东西
  • 由于UDP套接字由一个二元组标识,数据报首部行开销小(仅需8字节)。
  • UDP没有拥塞控制机制,只要应用程序将数据传递给UDP套接字,套接字就会将此数据打包成UDP报文段并立即传递给网络层

因此,UDP常用于无需建立连接,无需可靠数据的网络请求中。最常见的例子就是DNS:路由选择表被周期性的更新,更新的丢失可以被下一次更新所替代,因此丢包,过期的更新是无效的。

3.1.1. UDP报文段

UDP报文段的首部行只有4个字段,每个字段由两个字节组成:源端口号,目的端口号,长度,校验和。

  • 源端口号和目的端口号用来将报文段定向到对应的进程,执行分组/复用功能
  • 接收方使用长度字段来检验和检查该报文段中的数据是否出现了差错
  • 由于不能保证源和目的主机之间的所有链路都提供了差错检测,因此,即使报文段经链路正确传输,也可能引入报文差,因此UDP就必须在端到端的基础上为运输层提供差错检测(只能是检测而对于差错的恢复无能为力)

3.2. 可靠数据传输原理

书中用了大量的篇幅,从简到深,介绍如何运输层是如何实现可靠数据传输的,然而!我!看!不!懂!这真是一个悲伤的故事。 可靠数据传输包括差错检测,重传,累积确认,定时器以及用于序号和确认号的首部字段等等。

3.3. TCP

TCP是一个面向连接的过程,这句话已经被重复了很多次了,在两台主机彼此发送数据之前,必须相互发送某些报文段内,以建立“确保数据传输”的参数:

  • 客户进程首先通知客户运输层并发送一个特殊的TCP报文段,服务器用另一个特殊的TCP报文段响应,然后客户再用第三个特殊的报文段响应,这种连接建立过程被称为三次握手
  • 建立连接之后,主机间通过套接字传递数据流:数据一但进入套接字,就由运输层控制了,TCP将这些数据引导至发送缓存,并不时从里面取出一块数据,配上TCP首部行,形成TCP报文段,然后向下传递给网络层;而当另一端的TCP接收到一个报文段后,就将数据存放在TCP的接收缓存中,应用程序从此接收缓存中读取数据流。(需要注意的是主机之间的网络元素没有为该TCP链接分配任何缓存和变量)。

3.3.1. TCP报文段

TCP的首部行一般是是20字节(UDP的首部行是8个字节)。与UDP一样,TCP报文端首部行包括源端口号,目的端口号以及检验和字段。此外还包括:

  • 32位的序号字段和32为的确认号字段,用来实现可靠数据传输服务
  • 16位的接收窗口字段,用于流量控制
  • 4位的首部长度,需要注意的是TCP首部的长度是可变的。
  • 可选与变长的选项字段
  • 6位的标志字段,用来标识TCP连接状态

一个TCP报文段的序号,是该报文段首字节的字节流编号(比如一段数据流有1000个字节,报文段每次传输200个字节,则第一个报文段的序号是0,第二个报文段的序号就是200,第三个400...); 一个TCP报文段的确认号就是发送该报文段的主机期望从另一台主机收到的下一字节的序号:

  • 如果主机A已经收到了编号为0-200的数据流,此时他向主机B发送的报文段确认号就是201
  • 如果A已经收到了0-200和400-600的数据流,但是没有收到200-400的数据流,则主机A为了重新构建主机B的数据流,则仍然会等待201及后面的数据(虽然已经收到了400-600),因此此时主机A发送的报文段确认号仍然是201。因为TCP只缺认该流中至第一个丢失字节为止的字节,所以TCP被称为提供累积确认
  • 上面第二条例子中,400-600的数据流可以看作是一个失序的报文段,然而TCP并没有显式规定如果处理这个失序的报文段,它由实现TCP的编程人员去处理,决定是否保留(节省带宽)还是直接丢弃(重新请求有序的报文段)。

3.3.2. 可靠数据传输

TCP的可靠数据传输确保一个进程从其接收缓存中读出的数据流是无损坏,无间隔,非冗余和按序的数据流,从TCP的发送方高度简化的角度来看,需要监控三个事件:运输层从上层应用接收数据,定时器超时和收到ACK

  • 第一个事件发生,TCP将接收到数据封装成报文段,每个报文段都包含一个序号。另外,如果此时定时器如果没有为其他某些报文段运行,则将数据传递给IP层时,TCP会启动该定时器(定时器与最早的未被确认的报文段相关联)。
  • 如果触发了超时事件,则TCP会重传引发超时事件的那个报文段,并重启定时器
  • TCP维护一个状态变量sendbase,是最早未被确认的字节的序号。当TCP接收到来自接收方的ACK响应后,会将ACK的值ysendbase进行比较,TCP采用的是累积确认,即y确认了字节编号在y之前的所有字节都已经接收到了。如果y > sendbase,则发送方更新sendbase

3.3.3. TCP连接管理

三次握手 客户中的TCP会以下面的方式与服务器中的TCP建立一条TCP连接:

  • 客户端TCP首先发送一个特殊的TCP报文段,该报文段首部行的一个标志位(即SYN比特)被置为1(因此这个特殊的报文段被称为SYN报文段),此外,客户会随机选择一个初始序号client_isn,并将此编号放在SYN报文段的序号字段中。
  • 服务端接收到这个TCP SYN报文段之后,为该TCP连接分配TCP缓存和变量,并向客户TCP发送允许连接的响应报文段(有时候被称为SYNACK报文段,告诉客户端服务器同意建立TCP连接)。这个响应报文段的首部包含了3个重要信息:
    • SYN 比特被置为1
    • 确认号字段被置为client_isn + 1
    • 序号字段是服务端自己选择的初始序号server_isn
  • 在收到SYNACK响应报文段之后,客户端也要为该TCP连接分配缓存和变量,然后向服务端再次发送一个报文段,这个报文段对服务器允许连接的SYNACK报文段进行确认:客户将server_isn+1放入该报文段首部行的确认字段中,并将SYN比特置为0(表示连接已经建立了)。此外,这个报文段实际上就可以携带数据到服务端了。

在之后的连接中,SYN比特都被置为0,为了创建这个链接,客户端和服务器发送了三个分组,因此被称为“三次握手”。

四次挥手 参与一条TCP连接的任意一方都能够终止该连接。以客户端发送关闭连接为例:

  • 客户TCP向服务器进程发送一个特殊的TCP报文段,这个报文段首部的FIN比特标志位被设置为1
  • 服务端接收到该报文段,就向发送端回送一个确认的报文段;然后,服务端会向客户端进程发送另一个特殊的报文段,该报文段的FIN比特标志位被设置为1;
  • 客户端接受到服务端的FIN报文段之后,也会再次向服务端发送一个确认报文段。最后,两台主机用于该连接的资源都被释放了。

由于关闭连接一共发送了四个分组,因此被称为“四次挥手”。

3.4. 拥塞控制和流量控制

书中也有相当的篇幅介绍这方面的知识,然而...

4. 网络层

网络层也是协议栈最复杂的层次之一。

4.1. 网络层的功能和服务

两台主机之间通过数台中间路由器相连,路由器具有截断的协议栈(即没有网络层之上的传输层和应用层)。网络层的目的看似非常简单:将分组从一台数据移动到另一台接收主机。为此,需要实现两项基本的功能:转发和路由选择

  • 转发:将一个分组在单一的路由器中从一条输入链路移动到适当的输出链路(微观上);
  • 路由选择:当分组从发送放流向接收方时,网络层必须决定这些分组所采用的路由或路径,计算这些路径的算法被称为路由选择算法(宏观上)

每一台路由器都具有一张转发表(转发表是根据路由选择算法所决定的),路由器根据到达该路由器的分组(报文段)首部字段的值,从转发表中查找该分组将被转发的输出链路接口,然后将该分组移动到对应的输出链路。

同运输层类似,网络层也能够在两台主机之间提供无连接服务或连接服务,但是仍有很大的区别:

  • 仅在网络层提供连接服务的计算机网络被称为虚电路网络,比如ATM,帧中继的体系结构
  • 仅在网络层提供无连接服务的计算机网络称为数据报网络,比如因特网

在数据报网络中,每当一个端系统要发送分组时,它就为该分组加上目的端系统的地址,然后将分组推进网络中,路由器无需维护任何虚电路。 当分组从源到目的地的传输过程中,中间路由器都使用分组的目的地址来转发该分组(通过将目标地址映射到转发表的输出链路出口)。

4.2. 路由器的工作原理

前面已经提到,网络层的转发功能,实际上是将分组从一台路由器的输入链路转移到这台路由器的输出链路。具体的细节是,路由器使用分组首部目的地址的前缀(目的地址的前缀是具有一定规则的,后面会提到)与这台路由器的转发表进行匹配:

  • 如果存在一个匹配项,则路由器想与该匹配结果相关联的链路进行转发;
  • 如果存在多个匹配项,则使用最长前缀匹配原则
  • 如果不存在匹配项,则想最后一条链路出口转发(?不太明白)

一台路由器包括基本的:输入端口,交换结构,输出端口和路由选择处理器。影响转发过程的效率包括:前缀与转发表的匹配速度,以及从输入链路到输出链路的数据堵塞情况。下面来看看路由器的各个部分。

输入端口 在输入端口中,最主要的操作便是通过IP地址查找转发表,确认分组的输出端口,除此之外,还必须检查分组的版本号,检验和以及寿命字段,并更新用于网络管理的计数器。

转换结构 交换结构是一台路由器的核心,常用的交换方式有:

  • 经内存交换,类似于传统操作系统中的IO设备一样,分组从输入端口被复制到内存,然后将该分组复制到输出端口的缓存中
  • 经总线交换,输入端口经一条共享主线直接传送到输出端口,不需要经过路由处理器的处理
  • 经互联网络交换

输出端口 输出端口取出其内存上的数据并发送到输出链路上。在输出端口上的分组调度程序控制排队分组的发送

4.3. 网际协议

因特网编码和转发是网际协议的重要组成部分。

4.3.1. 数据报

网络层分组被称为数据报,一个数据报具有20字节的首部行,其中,最重要的字段有:

  • 标识,标志,片位移,主要用于重新拼接组合完整的分组
  • 首部检验和,帮助路由器检测接收到的IP数据报中的比特错误
  • 源和目标地址,当某个源生成一个数据报时,它在源IP字段中插入他的IP地址,在目的IP地址字段中插入其最终的目的地址(通常源主机通过DNS解析获取目标地址)
  • 数据,主要承载需要交付给目的运输层的数据

4.3.2. IP编址

主机与物理链路的边界叫做接口(一般只有一个),路由器与它的任意一条链路之间的边界也叫做接口(一般会有两个或多个,因为它是路由器~),一个IP地址技术上是与一个接口相关联的。 每个IPV4地址展32比特,因此总共有2^32个可能的IP地址,而每一台路由器上的每一个接口和连接的每一台主机,都必须有全球唯一的IP地址。然而这些IP地址并不能随便选择,而是由其需要连接的子网决定的。

一个路由器接口与几台主机相连组成的网络称为子网(这里可以理解为一台路由器为数台主机服务,他们组成的小圈子称为子网),IP编址为这个子网分配一个类似于223.1.1.0/24的地址,其中/24的记法,被称为子网掩码,表明32位地址最左侧的24位定义了子网地址,然后,该子网的每台主机以及与这台路由器相连的那个接口,由最右边的8位区分(因为他们的子网掩码都是相同的)。

使用这种a.b.c.d/x的地址分配策略,将子网寻址的概念一般化了,该IP地址的最左侧x位构成了IP地址的网络部分(通过路由器与网络相连),而剩下的最右侧32-x位用来区分该子网内部不同的主机。此时,该子网外部的其他路由器(即不直接与子网内部相连的中转路由器),只需要识别前x位地址即可,则相当大地减少了网络IP地址在这些外部路由器中转发表的长度——当接受到数据报的时候,只需要使用最长前缀匹配原则进行转发即可,而不需要完全校32位全部地址。最后,只有子网的内部路由器使用剩下的32-x位决定将数据转发给这个子网内部的某台主机。

4.3.3. 分配IP

为了获取一块IP地址用于一个组织的子网,该组织的网络管理员需要向他的网络服务商申请,并从该服务商的地址块中获取到一部分IP地址,ICANN向区域性因特网注册机构分配地址。 然后,网络管理员就可以为本组织内的路由器和主机挨个分配IP地址了,一般地需要手动地设置路由器的IP地址,而使用DHCP(动态主机配置协议)配置主机IP地址(每次为主机分配一个临时的IP地址)。

此时又会出现另一个问题:如果子网内需要连接网络的主机超过分配的IP地址,需要怎么处理呢?有一种网络地址转换方法可以使用。

2018年五月面试发现的一些问题 BFC及其应用