应用层概述

主要参考书籍 《计算机网络:自顶向下方法》的第二章关于网络应用层的介绍。对网络应用层的常用协议:HTTP,FTP,邮件协议,DNS 以及 P2P的两种协议 进行了简单学习。最后介绍了套接字编程,直观地展现了运输层协议TCP与UDP的区别。

1. 原理概述

应用程序体系结构(application architecture)主要分为两种:客户-服务器体系结构 和 对等体系结构(P2P)。

由于一个计算机上通常可以同时运行多个程序,为实现计算机网络应用程序进程间的通信,应用程序使用称为 socket 的程序接口向网络发送和接收报文(实际上一般就是 IP + 端口的形式)。

应用层程序需要依赖传输层服务,共有两种传输层服务:

  • TCP:是一种面向连接的服务(就是每次通讯之前客户机-服务器对应进程之间首先通过通信握手建立全双工的TCP连接);并且,TCP 服务还能够保证数据的可靠传输-顺序不变,数据不丢失、冗余;TCP服务还拥有拥塞控制机制。
  • UDP:是一种不提供不必要服务的轻量级传输协议,不需要建立连接,直接传输,但不保证传输的可靠性。

由于 TCP 和 UDP 协议都没有做安全上的处理-也就是二者在网络中都是明文传输的。为实现传输的安全性,出现了一种 TCP 协议的加强版- SSL(secure sockets layer - 安全套接字层)。具体地,首先应用层进程向 SSL 套接字发送明文数据,SSL 进行加密后传递给 TCP 套接字,该内容被接收方的 TCP 套接字接收,传给接收方的 SSL 套接字,进行解密后传输给接收方应用程序接口。

应用层协议对应用所交换的报文的格式、语法、含义进行了规定。本书将介绍5种重要的应用层协议:web(Http)、文件传输(FTP)、电子邮件(SMTP)、目录服务(DNS)、P2P。

2. Web 和 HTTP

2.1 概况

HTTP 协议即超文本传输协议(HyperText Transfer Protocol),它是 web 的核心协议。HTTP协议由两个程序实现:客户程序和服务器程序。

HTTP协议使用 TCP 协议作为它的传输层协议。

HTTP是一个无状态协议(atateless prototype),也就是服务器不存储任何关于用户的状态信息,两次连接之间相互独立,不受影响。

2.2 非持续连接和持续连接

根据一连串 HTTP 请求是分别使用单独的 TCP 连接还是全部使用相同的 TCP连接,我们将 HTTP应用程序分为非持续连接和持续连接。通常来说,持续连接可以提升效率,因为每一次重新 TCP 连接需要一次额外的握手时间。

书中称,HTTP使用流水线方式实现了 持续连接。但火狐的一个教程文档中称, 由于流水线方式在实际应用中难以推行, HTTP/1.1 中并没有很好地实现该功能(Ref)。

2.3 HTTP 报文格式

HTTP 的请求和相应格式如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 请求
GET/ HTTP/1.1
Host: developer:mozilla.org
Accept-Language: CN

// 响应
HTTP/1.1 200 OK
Date: Sat, 09 ...
Server: Apache
Last-Modified: Tue, 01 Dec,...
Etag: ...
Accept-Ranges: bytes
Content-Length: 29769
Content-Type: text/html

(data.... entity body)

请求时常用的除了 GET 方法还有 POST 方法。通常提交表单使用 POST 方法。当然,一些不敏感的 提交信息,也可以使用 GET 方法提交。

响应时,常见的状态码有:

  • 200 OK,请求成功。
  • 301 Moved Permanently. 请求对象已经被永久转移,新的 URL 定义在相应头中,浏览器可以直接自动获取新的 URL。
  • 400 Bad Request。通用差错代码。表示请求不能被服务器理解。
  • 404 Not Found。请求的文档不在服务器上。

由于 HTTP 服务是无状态的,为实现对用户的记录,可以使用 cookie 实现。一个典型的 cookie 使用流程如下图所示。

具体的,cookie 有4个组件:

  • 在 HTTP 响应报文的头中带有 cookie;
  • 在 HTTP 请求报文中也会带有一个 cookie 信息;
  • 用户端系统中有一个 cookie 文件,由浏览器进行管理保存;
  • Web站点后端数据库中存有用户对应的cookie。

服务器通过 cookie 可以获得对应用户的信息从而可以记录其行为。(当然,也正是由于其可以追踪用户行为,存在一些隐私方面的争议)

2.5 Web 缓存

Web 缓存(cache)也叫代理服务器(proxy server)。通常是由 ISP 提供的,用于保存网络终端系统最近请求过的响应对象副本,可以大大提升访问速率,降低网络带宽升级成本。在学校,公司等场景中,应用很广泛。

具体流程是浏览器建立一个到 Web 缓存器的 TCP 连接,并向 Web 缓存器发送一个 HTTP 请求; Web缓存器检查该请求,看在本地是否有该请求的响应副本,如果有,直接返回该副本给客户浏览器;如果Web 缓存器上没有该请求响应的缓存,则Web缓存器会建立一个通向最终请求服务器的 TCP连接,收到响应后,在本地保存一份副本后,再将其发送给客户浏览器。- 可以看出,Web 缓存器既是客户机,又可看做一种服务器。

Web 缓存器存在副本可能不是最新的问题,这时,可以使用条件 GET 方法,实现高效的副本更新。具体地:

1
2
3
4
5
6
7
8
9
<!--请求头添加一句 If-Modified-Since -->
GET/ ...(资源地址).. HTTP/1.1
Host: www. ... (网址)
If-Modified-Since: xxxx (日期_为上次缓存中:Last-Modified字段的值)

<!--如果没有变动,服务器返回 Not Modified-->
HTTP/1.1 304 Not Modified
Date: ....(当前时间)
Server: ...

此外,通过使用 内容分发网络(content distribution network: CDN), Web 缓存技术正得到更广泛的应用。

3. 文件传输协议-FTP

FTP协议用于文件传输。客户机可以通过一个在主机注册了的用户名和密码从主机读取和写入文件。与 HTTP 协议类似,传输层使用TCP 协议,以确保可靠的信息传输。

但不同于 HTTP 协议只使用一个 TCP 连接,FTP协议使用两个并行的 TCP连接来传输文件,一个是 控制连接(control connection),一个是数据连接(data connection)。控制连接用于两主机间的控制信息的传输,如用户标识、口令、读取请求等。而数据连接用于数据传输。

具体地,控制连接贯穿了整个用户会话期间,期间,每当用户通过控制连接发起文件读写请求,会建立一个数据连接,文件传输完毕后再关闭该数据连接,因此,数据连接是非连续的。

不同于 HTTP协议是无状态的,FTP 协议在整个会话期间内保留用户的状态(state)。

3.1 FTP命令与回答

常见的命令如下:

  • USER username: 向服务器传送用户名;
  • PASS password;
  • LIST: 请求服务器的文件列表,列表会通过数据连接传输;
  • RETR filename: get 远程主机中的文件;
  • STOR filename: put 本地文件到远程主机上。

常见的响应报文如下:

  • 331 Username OK, password required.
  • 125 Data connection already oepn, tranfer starting.
  • 425 Can’t open data connection.
  • 452 Error writing file.

4. 电子邮件协议

电子邮件的发送大致按照下述流程进行,发件人在邮箱中写完邮件,通过用户代理(邮箱软件or 浏览器)发送给邮件服务器(通常由 ISP 等提供),邮件服务器通过 SMTP 协议发送该邮件到收件人邮件服务器,收件人在方便的时候,通过用户代理从他的邮件服务器上取回邮件,进行阅读。参考下图:

整体来说,邮件协议主要分为收发两部分。

4.1 发送- SMTP 协议

SMTP(simple mail transfer protocol) 是邮件发送协议,由于邮件服务器既可以发送也可以接受邮件,邮件服务器既是 SMTP 协议的客户端,也是服务器端。

SMTP 协议基于 TCP 传输协议。

SMTP 协议一般不使用中间邮件服务器中转邮件。当目标邮件服务器不能正常接受邮件时,发送服务器可以将邮件暂存在邮件队列中,等待一段时间后再重新发送。

与 HTTP 协议不同,SMTP 是一个 推协议(push protocol)(HTTP 协议主要是一个 拉协议(pull protocol),从服务器上拉取资源)。

4.2 访问- POP3/IMAP

当邮件抵达收件方服务器后,收件人的用户代理(user-agent) 需要从服务器上取回邮件,这通常使用 POP3(post office protocol -version 3), 或者 IMAP(internet mail access protocol)协议。

POP3协议是一个非常简单的邮件访问协议,它主要进行三个阶段的工作:

  1. 认证- authorization;即发送用户名密码给邮件服务器,进行用户认证;
  2. 事务处理:认证完成后,用户可以取回报文,在本地进行阅读以及进行删除标记等操作;
  3. 更新阶段:客户发出 quit 命令后,POP3 会话终止,这时,邮件服务器会删除那些阶段2中被标记为删除的报文。

IMAP 协议相对 POP3 协议最大的进步是允许在邮件服务器中建立文件夹,从而可以在任何一个终端中获取带有文件夹信息的邮件内容。

最后,由于浏览器的广泛使用,人们也常常是由浏览器进行收发邮件,这时,浏览器作为邮件服务代理,通过 HTTP 协议与邮箱服务器进行通信。

5. 互联网的目录服务- DNS

DNS (Domain Name System)也是一种应用层协议(当然,与HTTP 等不同的是,它不直接面向终端用户),它用于处理域名到 IP 地址的映射。

所有的 DNS 请求和回答报文都使用 UDP协议进行传输(端口53)。

5.1 DNS 服务器结构

为防止单个 DNS 服务器故障引起网络服务故障,DNS 服务采用了分布式的设计方案,按照三层结构进行设计。如下图所示:

https://ithelp.ithome.com.tw/m/articles/10210481

具体地,root DNS 服务器用于提供顶级域服务器的地址,顶级域服务器(top level domain,TLD)提供权威 DNS 服务器的地址,authoritative DNS 服务器则提供具体的服务器的地址(可能还有子级系统,这部分由 authoritative DNS server 负责查询)。一个简单的网络访问流程如下图所示。

https://ithelp.ithome.com.tw/m/articles/10210481

5.2 本地 DNS 服务器

通常每个 ISP (例如大学,公司,居民区) 都有一个本地 DNS 服务器(local DNS server),他不属于上述的 DNS 层次服务结构。

本地DNS服务主要提供DNS代理查询和DNS缓存服务。通过DNS缓存,可以减少DNS查询次数,提升访问速度。

5.3 DNS 记录和报文

DNS 分布式数据库中存储的资源记录(resource record - RR)通常由4个字段组成:Name, Value, Type, TTL(TTL为对应资源记录项的生存时间,不深入讨论)。根据 Type 不同,记录可分为下面4种:

  • Type = A, 则要求 Name 是主机名,Value 是主机对应的 IP 地址,即name-value 为一个标准的主机-IP 映射。
  • Type = NS, name 为一个域(例如:foo.com), value 则是知道该域中主机 IP 地址的 authoritative DNS server 的主机名。这个记录用于沿链路查询 IP 地址。
  • Type = CNAME, value 为 别名,name 为主机对应的规范名。
  • Type = MX, value 为别名,name 为邮件服务器的规范主机名。

Note: 在申请个人域名的时候,需要填写上述的DNS 资源记录。

6. P2P

应用层协议中,除了使用 客户-服务器 的体系结构,还有一类使用 P2P 的结构 - 即不区分客户与服务器,每个参与者都是一个对等方,他们彼此间可以直接通信。

两种典型的基于 P2P 设计的应用为:1-文件分发、2-分布在对等方社区中的分布式数据库。

6.1 P2P 文件分发

最流行的 P2P 文件共享协议是 BitTorrent 协议。

在 BitTorrent 协议中,参与一个特定文件分发的所有对等方的集合被称为一个 torrent。这个文件会被分解为长度相等的文件块(chunk,典型长度为 256 KB),每个对等方可以从其他对等方下载块,也可以上传块给其他对等方。

BitTorrent 协议中,一个文件会伴有一个追踪器(track),新加入该文件共享的对等方向该追踪器注册自己(并定时通知该track自己仍然在 torrent 中),并从 track 中获取一些对等方建立并行的 TCP 连接来进行文件传输(称所有建立 TCP 连接的对等方为邻近对等方)。

BitTorrent 中,每个对等方会与4个流入速率最高的对等方连接,这4个对等方称为 unchoked。此外,每过 30s,每个对等方会随机选择一个邻近的对等方进行数据传输,如果传输的速率高于之前的4个稳定连接的对等方,则更新之前的4个连接(因此,任意时刻,每个对等方保持与5个其他对等方的连接)。

此外,在传输文件块的时候,会依据 稀缺优先(rarest first) 的原则,最优先传输最为稀缺的chunk。

6.2 分布式散列表

分布式散列表(Distributed Hash Table,DHT)是一种存储于 P2P 网络中的数据库,该数据库只存储键值对类型的数据。

DHT 的原理是这样的,我们为连接在 P2P 网络中的每个对等方分配一个标识符($0 到 2^n-1$),并且将键值通过散列函数进行数值化,并将 key-value 对存储到与键值的最近的对等方中(最邻近后继的对等方)。

https://www.slideshare.net/kundan10/peertopeer-internet-telephony

如果P2P 的网络很大,从一个对等方出发,我们难以直接寻找到对应 key 的存储单元,这时候,我们使用 环形DHT + 捷径的方式,在查找速度和连接量之间取得平衡。

DHT 一个重要的应用是 BitTorrent 中使用的分布式追踪器(Kademlia DHT), 在 BitTorrent 中,track 通常不存储在一个统一的服务器中,而是以分布式的形式存储在所有对等方中,其键是 torrent 的标识符,值为torrent 中所有参与对等方的 IP 地址。

7. 套接字编程

在之前的章节中,我们提到,网络应用程序是进程之间的通信,而应用程序直接接触的网络对象对各自计算机中的套接字,客户的套接字再连接目标服务器的套接字进行通信。

7.1 UDP 套接字编程

使用 Python 代码展示一个简单的套接字程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# UDPClient.py

# socket, python 中处理套接字相关的包
from socket import *

# 服务器名(也可以是 IP) 和端口号,用于连接服务器对应的套接字
serverName = 'hostname'
serverPort = 12000

# 新建客户端套接字,两个参数:AF_INET 表示使用 IPv4 地址, SOCK_DGRAM 表示使用 UDP 协议
clientSocket = socket(AF_INET, SOCK_DGRAM)

# 客户从键盘输入字符(设计的该程序是客户端输入小写字符,然后由服务器转为大写字符后返回)
message = raw_input('Input lowercase snetence: ')

# 发送数据到服务器对应套接字(使用IP + 端口号确定)
clientSocket.sendto(message, (serverName, serverPort))

# modifiedMessage 接收返回的信息,serverAddress 接收服务器的地址和端口号
# 缓存长度 2048
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)

print modifiedMessage

# 关闭服务器套接字
clientSocket.close()


# UDPServer.py

from socket import *
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)
# 将服务器套接字与对应端口绑定
serverSocket.bind(('', serverPort))
print "Ther server is ready to receive"

# 死循环,等到从客户端到来的数据
while true:
# 将接收的信息放到 message 中,clientAddress 保存客户端的地址和端口号
message, clientAddress = serverSocket.recvfrom(2048)
modifiedMessage = message.upper()
# 发送修改完毕的信息
serverSocket.sendto(modifiedMessage, clientAddress)

7.2 TCP 套接字编程

对于 TCP 套接字编程,需要注意的是,TCP 的服务器端需要提供两个套接字,一个称为 serverSocket,用于接收所有客户端的握手请求;另一个套接字为 connection socket,用于握手完毕后,与特定的客户端套接字进行数据传输。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# TCPClient.py

from socket import *
serverName = 'servername'
serverPort = 12000

# 新建客户端套接字,两个参数:AF_INET 表示使用 IPv4 地址, SOCK_STREAM 表示使用 TCP 协议
clientSocket = socket(AF_INET, SOCK_STREAM)

# 相比UDP 的客户端程序,需要额外一步,首先与 TCP 服务器建立连接
clientSocket.connect(serverName, serverPort)

message = raw_input('Input lowercase snetence: ')
# 建立连接完成后,不在需要添加目的服务器地址+端口,直接 send 数据
clientSocket.send(message)
modifiedMessage = clientSocket.recv(1024)
print modifiedMessage
clientSocket.close()

# TCPServer.py

from socket import *
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))

# 使用 serverSocket 来监听来自客户端的TCP 连接请求,参数1表示请求连接的最大数为1
serverSocket.listen(1)
print "Ther server is ready to receive"

# 死循环,serverSocket 建立连接完成
while 1:
# serverSocket 握手完成,建立一个数据传输连接 connectionSocket,
# addr 中存放客户端 socket 的 IP + 端口
connectionSocket, addr = serverSocket.accept()
message = connectionSocket.recv(1024)
capitalizedMessage = message.upper()
connectionSocket.send(capitalizedMessage)
# 用于传输的连接传输完成后关闭,等待 serverSocket 再次建立 TCP 连接
connectionSocket.close()