网络应用层概述
网络应用体系结构
- 客户机/服务器
- P2P
- 混合结构
网络应用的服务需求
- 可靠性
- 带宽
- 时延
Internet传输层服务模型
- TCP
- UDP
特定网络应用及协议
- HTTP
- SMTP, POP, IMAP
- DNS
- P2P应用
Socket编程
- TCP
- UDP
网络应用的基本原理
网络应用体系结构
客户机/服务器结构(Client-Server, C/S)
服务器
- 7*24小时提供服务
- 永久性访问地址/域名
- 利用大量服务器实现可扩展性
客户机
- 与服务器通信,使用服务器提供的服务
- 间歇性接入网络
- 可能使用动态的IP地址
- 不会与其他客户机直接通信
点对点结构(Peer to peer, P2P)
- 没有永远在线的服务器
- 任意端系统/节点之间可以直接通讯
- 节点间歇性接入网络
- 节点可能改变IP地址
优点:高度可伸缩
缺点:难于管理
混合模式(Hybrid)
Napster
文件传输使用 P2P 结构
文件搜索采用 C/S 结构 –集中式
- 每个节点向中央服务器登记自己的内容
- 每个节点向中央服务器提交查询请求,查找感兴趣的内容
网络应用进程通信
不同主机上运行的进程如何通信 ? 消息交换
客户机进程: 发起通信的进程
服务器进程: 等待通信请求的进程
套接字(Socket)
- 进程间通信利用
socket
发送/接受消息实现 - 类似于寄信
发送方将消息送到门外邮箱 发送方依赖(门外的)传输基础设施将消息传输到接收方所在的主机,并送到接受方的门外 接收方从门外获取消息
- 传输基础设施向进程提供API
传输协议的选择 参数的设置
如何寻址进程
不同主机上的进程通信, 那么每个进程必须拥有标识符
如何寻址主机? –IP地址
端口号(Port number)
- 为主机上每个需要通信的进程分配一个端口号
- HTTP Server: 80
- Mail Server: 25
进程的标识符: IP地址 + 端口号
应用层协议
- 网络应用需遵循应用层协议
- 公开协议
- 由 RFC (Request For Comments) 定义
- 允许互操作
- HTTP, SMTP, ….
- 私有协议
- 多数P2P文件共享应用
应用层协议的内容
- 消息的类型(type)
- 请求消息
- 响应消息
- 消息的语法(syntax)/格式
- 消息中有哪些字段(field)
- 每个字段如何表示
- 字段的语义(semantics)
- 字段中信息的含义
- 规则(rules)
- 进程何时发送/响应消息
- 进程如何发送/响应消息
网络应用需求与传输层服务
网络应用对传输服务的需求
- 数据丢失(data loss) / 可靠性(reliability)
- 某些网络应用能够容忍一定的数据丢失:网络电话
- 某些网络应用要求100%可靠的数据传输:文件传输, telnet
- 时间(Timing)/延迟(delay)
- 某些应用只有在延迟足够低时才有效
- 网络电话/网络游戏
- 带宽(bandwidth)
- 某些应用只有在带宽达到最低要求时才“有效” : 网络视频
- 某些应用能够适应任何带宽:弹性应用: email
典型网络应用对传输服务的需求
Internet提供的传输服务
TCP服务
- 面向连接:客户机/服务器进程间需要建立连接
- 可靠的传输
- 流量控制:发送方不会发送速度过快, 超过接收方的处理能力
- 拥塞控制:当网络负载过重时能够限制发送方的发送速度
- 不提供时间/延迟保障
- 不提供最小带宽保障
UDP服务
- 无连接
- 不可靠的数据传输
- 不提供:
- 可靠性保障
- 流量控制
- 拥塞控制
- 延迟保障
- 带宽保障
典型网络应用所使用的传输层服务
Web应用
Web应用概述
Web
- World Wide Web : Tim Berners-Lee
- 网页
- 网页互相连接
- 网页(Web Page) 包含多个对象(objects)
- 对象:HTML文件、JPEG文件、视频文件、动态脚本等
- 基本HTML文件:包含对其他对象引用的链接
- 对象的寻址(addressing)
- URL(Uniform Resource Locator): 统一资源定位器
- Schemes://host:port/path
HTTP协议概述
万维网应用遵循什么协议 ?
- 超文本传输协议: HyperText Transfer Protocol
C/S结构
- 客户 —— Browser: 请求、接受、展示Web对象
- 服务器 —— Web Server : 响应客户的请求,发送对象
HTTP版本:
- 1.0:RFC 1945
- 1.1:RFC 2068
- 使用
TCP
传输服务- 服务器在80端口等待客户的请求
- 浏览器发起到服务器的TCP连接(创建套接字Socket)
- 服务器接受来自浏览器的TCP连接
- 浏览器(HTTP客户端)与Web服务器(HTTP服务器)交换HTTP消息
- 关闭TCP连接
- 无状态(stateless)
- 服务器不维护任何有关客户端历史请求的消息
HTTP连接类型
HTTP连接的两种类型
- 非持久性连接(Nopersistent HTTP)
- 每个TCP连接最多传输一个对象
- HTTP 1.0 版本使用非持久性连接
- 持久性连接(Persistent HTTP)
- 每个TCP连接允许传输多个对象
- HTTP 1.1版本默认使用持久性连接
非持久性连接
响应时间分析与建模
- RTT(Round Trip Time)
- 从客户端发送一个很小的数据包到服务器并返回所经历的时间
- 响应时间
- 发起、连接TCP : 一个
RTT
- 发送HTTP请求消息到达HTTP响应消息的前几个字节到达: 一个
RTT
- 响应消息中包含的文件/对象传输时间
- Total = 2RTT + 文件发送时间
- 发起、连接TCP : 一个
持久性HTTP
- 发送响应后,服务器保持TCP连接的打开
- 后续HTTP消息可以通过这个连接发送
无流水(pipelining)的持久性连接
- 客户端只有收到一个响应后才发送新的请求
- 每个被引用的对象耗时1个RTT
带有流水机制的持久性连接
- HTTP1.1 的默认选项
- 客户端只要遇到一个引用对象就尽快发出请求
- 理想情况下,收到的所有的引用对象只需要消耗1个RTT
HTTP消息格式
HTTP请求消息
HTTP协议有两类消息
- 请求消息(request)
- 响应消息(response)
请求消息
- ASCII:人直接可读
HTTP请求消息的通用格式
上传输入的方法
POST方法
- 网页经常需要填写表格(form)
- 在请求消息的消息体(entity body)中上传客户端的输入
URL方法
- 使用GET方法
- 输入信息通过
request
命令行的URL字段上传
方法的类型
HTTP/1.0
- GET
- POST
- HEAD
- 将Server不要将所请求的对象放入响应消息中
HTTP/1.1
- GET, POST, HEAD
- PUT
- 将消息体中的文件上传到URL字段所指定的路径
- Delete
- 删除URL字段所指定的文件
HTTP响应消息
HTTP响应状态代码
消息响应的第一行
示例:
- 200 OK
- 301 Moved Permanently
- 400 Bad Request
- 404 Not Found
- 505 HTTP Version Not Supported
Cookie技术
Cookie技术
- 某些网站为了辨别用户身份,进行session跟踪而储存在用户本地终端上的数据(通常经过加密)
- RFC6265
Cookie组件
- HTTP响应的消息的cookie头部行
- HTTP请求消息的cookie头部行
- 保存在客户端主机上cookie文件,由浏览器管理
- Web服务器后台数据库
Cookie技术原理
Web缓存/代理服务器技术
Web缓存/代理服务器技术
功能: 在不访问服务器的条件下满足客户端的HTTP请求
为什么发明这种技术?
- 缩短客户请求的响应时间
- 减少机构/组织的流量
- 在大范围(Internet)实现有效的内容分发
Web缓存/代理服务器
- 用户设定浏览器通过缓存进行Web访问
- 浏览器向缓存/代理服务器发送所有HTTP请求
- 如果所请求对象在缓存中,缓存返回对象
- 否则,缓存服务器向原始服务器发送HTTP请求,获取对象,然后返回给客户端并保存该对象
缓存既可以充当客户端,也可以充当服务器
一般由ISP(Internet服务供应商)架设
条件性GET方法
目标
如果缓存中有最新的版本,则不需要发送请求消息
缓存
- 在HTTP请求消息中声明所持有版本的日期
- If-modified-since:
服务器
- 如果缓存的版本是最新的,则响应消息中不包含对象
- HTTP/1.0 304 Not Modified
Email应用的构成
Email应用的构成组件
- 邮件客户端
- 邮件服务器
- SMTP协议
邮件客户端
- 读、写Email消息
- 与服务器交互,收、发Email消息
- Outlook, Foxmail, Thunderbird
- Web客户端
邮件服务器
- 邮箱:存储发给该用户的Email
- 消息队列(message queue): 存储等待发送的Email
SMTP协议
- 邮件服务器之间传递消息所使用的协议
- 客户端:发送消息的服务器
- 服务器:接受消息的服务器
SMTP协议:RFC 2821
- 使用TCP进行email消息的可靠传输
- 端口25
- 传输过程的三个阶段
- 握手
- 消息的传输
- 关闭
- 命令/响应交互模式
- 命令(command) : ASCII文本
- 响应(response) : 状态代码和语句
- Email消息只能包含7位ASCII码
SMTP交互示例
SMTP协议
- 必须使用持久性连接
- 要求消息必须由7位ASCII码构成
- SMTP服务器利用CRLF.CRLF确定消息的结束
与HTTP对比
- HTTP:拉式(pull)
- SMTP:退式(push)
- 都是使用命令/响应交互模式
- 命令和状态代码都是ASCII码
- HTTP: 每个对象封装在独立的响应消息中
- SMTP: 多个对象在由多个部分构成的消息中发送
Email 消息格式
- SMTP: Email消息的传输/交换协议
- RFC 822: 头部消息格式标准
- 头部行(header)
- To
- From
- Subject
- 消息体(body)
- 消息本身
- 只能是ASCII字符
- 头部行(header)
Email 消息格式(多媒体扩展)
- MIME:多媒体邮件扩展 RFC 2045, 2056
- 通过在邮件头部增加额外的行以声明MIME的内容类型
邮件访问协议
邮件访问协议:从服务器获取邮件
POP: Post Office Protocol[RFC 1939]
认证/授权(客户端<->服务器)和下载
IMAP: Internet Mail Access Protocol[RFC 1730]
HTTP:163, QQ, Mail等
POP协议
认证过程
- 客户端命令
- User: 声明用户名
- Pass: 声明密码
- 服务器响应
- +OK
- -ERR
- 客户端命令
事物过程
- list: 列出消息数量
- Retr: 用编号获取消息
- Dele: 删除消息
- Quit
“下载并删除“ 模式
用户如果换了客户端软件,无法重读该邮件”下载并保持“模式
不同客户端都可以保留消息的拷贝POP3是无状态的
IMAP协议
- 所有消息统一保存在一个地方:服务器
- 允许用户利用文件夹组织消息
- IMAP支持跨对话(Session)的用户状态:
- 文件夹的名字
- 文件夹与消息ID之间的映射等
- 有状态的协议
DNS应用
DNS概述
DNS(Domain Name System)
Internet 上主机/路由器的识别问题
- IP地址
- 域名:www.xxx.edu.cn
域名和IP地址如何映射?
域名解析系统DNS
- 多层命名服务器构成的分布式数据库
- 应用层协议:完成名字的解析
- Internet核心功能,由应用层协议实现
- 网络边界复杂
DNS服务
- 域名向IP地址的翻译
- 主机别名
- 邮件服务器别名
- 负载均衡:Web服务器
为什么不使用集中式的DNS?
- 单点失败问题
- 流量问题
- 距离问题
- 维护性问题
分布式层次式数据库
- 客户端想要查询
www.amazon.com
的IP- 客户端先查询根服务器,找到
com
域名的域名解析服务器 - 客户端查询
com
域名解析服务器,找到amazon.com
域名解析服务器 - 客户端查询
amazon.com
域名解析服务器,获得www.amazon.com
的IP地址
- 客户端先查询根服务器,找到
DNS根域名服务器
- 本地域名解析服务器无法解析域名时,访问根域名服务器
- 根域名服务器
- 如果不知道映射,访问权威域名服务器
- 获得映射
- 向本地域名服务器返回映射
TLD和权威域名解析服务器
- 顶级域名服务器(TLD, top-level domain)
- 负责顶级域名(com, org, net, edu等)和国家顶级域名(cn, uk, fr等)
- Network Solutions维护com顶级域名服务器
- Education维护edu顶级域名服务器
- 权威(Authoritative)域名服务器:组织的域名解析服务器,提供组织内部服务器的解析服务
- 组织负责维护
- 服务提供商负责维护
本地域名解析服务器
- 不严格属于层级体系
- 每个ISP有一个本地域名服务器
- 默认域名解析服务器
- 当主机进行DNS查询时,查询被发送到本地域名服务器
- 使用代理(proxy), 将查询转发给(层级式)域名解析服务器系统
DNS查询示例
cis.poly.edu
的主机想要获得gaia.cs.umass.edu
的IP地址
迭代查询
递归查询
DNS记录缓存和更新
只要域名解析服务器获得域名 –IP映射,即缓存这一映射
- 一段时间过后,缓存条目失效(删除)
- 本地域名服务器一般会缓存顶级域名服务器的映射(因此根域名服务器不经常被访问)
记录的更新/通知机制
- RFC 2136
- Dynamic Updates in Demain Name System(DNS UPDATE)
DNS记录和消息
DNS记录
- 记录资源(RR Resource records)
- Type = A
- Name: 主机域名
- Value: IP地址
- Type = NS
- Name: 域(edu.cn)
- Value: 该域权威域名解析服务器的主机域名
- Type = CNAME
- Name: 某一真实域名的别名
- www.bim.com – serverteast.backup2.ibm.com
- Values: 真实域名
- Name: 某一真实域名的别名
- Type = MX
- Value是与name相对应的邮件服务器
DNS协议与消息
DNS协议
- 查询(query)和回复(reply消息)
- 消息格式相同
- 消息头部
- Identification: 16位查询编号,回复使用相同的编号
- flag
- 查询或回复
- 期望递归
- 递归可用
- 权威回答
P2P应用
- 没有服务器
- 任意端系统之间直接通信
- 节点阶段性接入Internet
- 节点可能更换IP地址
BitTorrent
Socket 编程
网络程序设计接口
应用编程接口 API
应用编程接口API : 应用进程的控制权和操作系统的控制权进行转换的的一个系统调用接口
Socket API
标识通信端口(对外)
- IP地址 + 端口号
操作系统/进程如何管理套接字(对内)
- 套接字描述符
- 小整数
Socket抽象
类似于文件的抽象
当应用进程创建套接字时,操作系统分配一个数据结构存储套接字相关信息
返回套接字描述符
地址结构
已定义结构 socketaddr_in
1
2
3
4
5
6
7strucct socketaddr_in {
u_char sin_len; //地址长度
u_char sin_family; //地址族(TCP/IP: AF_INET)
u_short sin_port; //端口号
struct in_addr sin_addr; //IP地址
char sin_zero[8]; //未用置零
};使用
TCP/IP
协议簇的网络应用程序声明端口地址变量时,使用结构socketaddr_in
Socket API函数
WSAStartup
1 | int WSAStartup(WORD wVersionRequested, LPWSADATA IpWSAData); |
使用 Socket 的应用程序在使用Socket之前必须首先调用
WSAStartup
函数两个参数
- 第一个参数指明程序请求使用的WinSock版本,其中高位字节指明副版本、低位字节指明主版本
- 十六进制整数、例如0x102标识2.1版
- 第二个参数返回实际的WinSock的版本信息
- 返回指向
WSADATA
结构的指针
- 返回指向
- 第一个参数指明程序请求使用的WinSock版本,其中高位字节指明副版本、低位字节指明主版本
例: 使用2.1版本的WinSock的程序代码段
1
2wVersionRequested = MAKEWORD(1, 1);
err = WSAStartup(wVersionRequested, &wsaData);
WSACleanup
1 | int WSACleanup(void); |
- 应用程序在完成对请求的Socket库的使用,最后要调用
WSACleanup
函数 - 解除与
Socket库
的绑定 - 释放
Socket库
所占用的系统资源
Socket
1 | sd = socket(protofamily, type, proto); |
创建套接字
操作系统返回套接字描述符(sd)
第一个参数(协议簇):
1
protofamily = PF_INET(TCP/IP)
第二个参数(套接字类型):
1
type = SOCKET_STREAM,SOCKET_DGRAM or SOCKET_RAM (TCP/IP)
第三个参数(协议号):0为默认
例:创建一个流套接字的代码段
1
2
3struct protoent *p;
p = getprotobyname("TCP");
SOCKET sd = socket(PF_INET, SOCKET_STREAM, p->p_proto);
Closesocket
1 | int closesocket(SOCKET sd); |
关闭一个描述符为sd的套接字
如果有多个进程共享一个套接字,调用 closesocket 将套接字引用计数减1, 减到0才关闭
一个进程中的多线程对一个套接字的使用无计数
- 如果进程中一个线程调用closesocket将一个套接字关闭,该进程中的其他线程也不能访问该套接字
返回值
0: 成功
SOCKET_ERROR: 失败
bind
1 | int bind(sd, localaddr, addrlen); |
- 绑定套接字本地端点地址
- IP地址 + 端口号
- 参数
- 套接字描述符(sd)
- 端点地址(localaddr)
- 结构:socket_addrin
- 客户程序一般不需要调用
bind
通过地址通配符:INADDR_ANY 解决上图出现的问题
listen
1 | int listen(sd, queuesize); |
- 置服务器端的流式套接字处于监听状态
- 仅服务器端调用
- 仅用于面向连接的套接字
- 设置连接请求的队列大小(queuesize)
- 返回值:
- 0:成功
- SOCKET_ERROR: 失败
connect
1 | connect(sd, addr, saddrlen); |
- 客户端调用
connect
函数来使客户套接字(sd) 与指定计算机的特定端口(saddr)的套接字(服务)进行连接 - 仅用于客户端
- 可用于TCP客户端也可用于UDP客户端
- TCP客户端: 建立TCP连接
- UDP客户端:指定服务器端点地址
accept
1 | newsock = accept(sd, caddr, caddrlen); |
- 服务程序调用
accept
函数从处于监听状态的流套接字sd
的客户连接队列请求中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道- 仅用于TCP连接
- 仅用于服务器
- 利用新创建的套接字(netsock)与客户通信
send, sendto
1 | send(sd, *buf, len, flags); |
send函数用于TCP套接字(客户与服务器)或调用了connect函数的UDP套接字
sendto函数用于UDP服务器套接字或未调用connect函数的UDP套接字
recv, recvfrom
1 | recv(sd, *buffer, len, flags); |
- recv函数从TCP连接的另一端接受数据,或者从调用connect 函数的UDP客户端接受对端数据
- recvfrom函数用户UDP服务器端套接字与未调用connect函数的UDP客户端接受对端数据
Socket API函数小结
Socket面向TCP/IP的服务类型
- TCP : 可靠、面向连接、字节流传输、点对点
- UDP: 不可靠、无连接、数据包传输
关于网络字节顺序
TCP/IP定义了标准的用于协议头中的二进制整数表示:网络字节顺序(network byte order)
某些Socket API函数参数需要存储为网络字节顺序(如IP地址、端口号等)
可以实现本地字节顺序与网络字节顺序间转换的函数
htons: 本地字节顺序 -> 网络字节顺序(16bits)
ntohs: 网络字节顺序 ->本地字节顺序(16bits)
htonl: 本地字节顺序 -> 网络字节顺序(32bits)
ntohl: 网络字节顺序 ->本地字节顺序(32bits)
网络应用的Socket API(TCP)调用基本流程
客户端软件设计
解析服务器IP地址
- 客户端可能使用域名(如: study.163.com) 或 IP地址(如:123.58.180.121)标识服务器
- IP协议需要使用32位二进制IP地址
- 需要将域名或IP地址转换为32位的IP地址
- 函数 inet_addr() 实现点分十进制IP地址到32位IP地址的转换
- 函数 gethostbyname() 实现域名到32位IP地址的转换
- 返回一个指向结构 hostenet 的指针
解析服务器(熟知)端口号
- 客户端还可能使用 服务名 (如HTTP)标识服务器端口
- 需要将服务名转化为熟知的端口号
- 函数 getservbyname()
- 返回一个指向结构 servent 的指针
- 函数 getservbyname()
解析协议号
- 客户端可能使用协议名(如:TCP)来指定协议
- 需要将协议名转换为协议号(如 : 6)
- 函数 getprotobyname() 实现协议名到协议号的转换
- 返回一个指向结构 prototent 的指针
- 函数 getprotobyname() 实现协议名到协议号的转换
TCP客户端软件流程
- 确定服务器的IP地址与端口号
- 创建套接字
- 分配本地端口地址(IP地址 + 端口号)
- 连接服务器(套接字)
- 遵循应用层协议进行通信
- 关闭/释放连接
UDP客户端软件流程
- 确定服务器的IP地址与端口号
- 创建套接字
- 分配本地端口地址(IP地址 + 端口号)
- 指定服务器端点地址,构造UDP数据包
- 遵循应用层协议进行通信
- 关闭/释放连接
服务器软件设计
四种基本类型的服务器
循环无连接服务器
循环面向连接服务器
并发无连接服务器
并发面向连接服务器
循环无连接服务器基本流程
- 创建套接字
- 绑定端点地址(INADDR_ANY + 端口号)
- 反复接受来自客户端的请求
- 遵循应用层协议,构造响应报文,发送给客户
数据发送
- 服务器端不能使用 connect 函数
- 无连接服务器使用 sendto() 函数发送数据包
获取客户端点地址
调用 recvfrom() 函数接受数据时,自动提取
循环面向连接服务器基本流程
- 创建(主)套接字, 并绑定熟知端口号
- 设置(主)套接字为被动监听状态, 准备用于服务器
- 调用 accept() 函数接受下一个连接请求(通过主套接字),创建新套接字用于与该用户建立连接
- 遵循应用层协议,反复接受客户请求,构造并发送响应(通过新套接字)
- 完成为特定客户服务后,关闭与该客户之间的协议,返回步骤3
并发无连接服务器的基本流程
主线程1:创建套接字,并绑定熟知端口号
主线程2:反复调用 recvfrom() 函数, 接受下一个客户请求,并创建新线程处理该客户响应
子线程1:接受一个特定请求
子线程2:依据应用层协议构造响应报文,并调用 sendto() 发送;
子线程3:退出(一个子线程处理一个请求后即终止)。
并发面向连接服务器的基本流程
主线程1: 创建(主)套接字,并绑定熟知端口号
主线程2: 设置(主)套接字为被动监听模式,准备用于服务器
主线程3: 反复调用 accept() 函数接受下一个连接请求(通过主套接字),并创建一个新的子线程处理客户响应
子线程1: 接受一个客户的服务请求(通过创建新套接字)
子线程2: 遵循应用层协议与特定客户进行交互
子线程3: 关闭/释放连接并退出(线程终止)