第二章 网络应用
目录
1.网络应用体系结构
客户机/服务器(C/S):
服务器:提供服务,永久域名,可扩展性
客户机:使用服务,间歇性,动态IP,不与其他客户机直接通信
P2P(peer to peer):
无永远在线的服务器,任意结点可直接通讯,间歇性,动态IP->高度可伸缩、难于管理
(存在客户机进程和服务器进程之分)
补充:P2P是指网上各台计算机有相同的功能,无主从之分,一台计算机都是既可作为服务器,设定共享资源供网络中其他计算机所使用,又可以作为工作站,没有专用的服务器,也没有专用的工作站。对等网络是小型局域网常用的组网方式。
P2P拓扑结构性能对比图:来源(https://www.cnblogs.com/linsanshu/p/5546948.html)
混合结构(Hybrid):
•Napster
文件传输使用P2P结构(速度快)
文件的搜索使用C/S结构——集中式
每个节点向中央服务器登记自己的内容
每个节点向中央服务器提交查询请求,查找感兴趣的内容
2.网络应用的服务需求
可靠性/数据丢失(data loss)/可靠性(reliability)
带宽(bandwidth)
时延(delay)/时间(timing)
3.Internet传输层服务模型
TCP
面向连接:C/S进程间需建立连接
可靠的传输
流量控制
拥塞控制(网络负载过重时可限制发方的发送速度)
不提供时间延迟保障
不提供最小带宽保障
UDP
无连接、不可靠、无控制
功能交给应用层实现
简洁对比图:
4.特定网络应用协议
Web应用
•对象的寻址(addressing):
URL(Uniform Resource Locator):统一资源定位器
Scheme://host:port/path
HTTP
HTTP协议
•超文本传输协议(Hypertext Transfer Protocol)
•采用客户/服务器架构
客户-Browser:请求、接收、展示web对象
服务器-Web Server:响应客户的请求,发送对象
•HTTP版本:
1.0:RFC 1945
1.1:RFC 2068
•使用TCP传输服务
•无状态(stateless)
服务器不维护任何有关客户端过去所发请求的信息
HTTP连接
非持久性链接(Nonpersistent HTTP)
每个TCP连接最多允许传输一个对象
HTTP 1.0版本使用非持久性连接
分析:
RTT(Round Trip Time):从客户端发送一个很小的数据包到服务器并返回所经历的时间
持久性链接(Persistent HTTP)
每个TCP连接允许传输多个对象
HTTP 1.1版本默认使用持久性连接
•无流水的持久性连接:
客户端只有收到前一个响应后才发送新请求
每个被引用对象耗1个RTT
•带流水机制的持久性连接:
HTTP1.1默认选项
客户端只要遇到一个引用对象就尽快发出请求
理想情况下,收到所有引用对象只耗时约1个RTT
HTTP消息格式
1.请求消息(ASCII):
通用格式:
上传输入的方法:
POST方法:网页经常需要填写表格、在请求消息的Entity body中上传客户端的输入
URL方法:使用GET方法,输入信息通过request line的URL字段上传
其他的一些方法:
HEAD:请求Server不要将所请求的对象放入响应消息中
PUT:将消息体中的文件上传到URL字段所指定的路径(HTTP/1.1)
DELETE:删除URL字段所指定的文件(HTTP/1.1)
2.响应消息:
(status line)状态行示例:200 OK 、404 Not Found、...
3.HTTP中的Cookie技术:
(关于Cookie的内容推荐博客:http://www.cnblogs.com/jasonwang2y60/p/6563875.html)
Cookie技术:某些网站为了辨别用户身份、进行session跟踪而存储在用户本地终端上的数据(通常经过加密)
Cookie补充:从定义上来说,Cookie就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。让我们说得更具体一些:当用户使用浏览器访问一个支持Cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体(Response Body)中的,而是存放于HTTP响应头(Response Header);当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在一个统一的位置
Cookie中的组件:
(Cookie其实是HTTP头的一部分)
Cookie的原理:
Cookie的作用:身份认证、购物车、推荐...
缺点:隐私问题;会增加宽带,增加流量消耗
SMTP,POP,IMAP
Email应用
Email应用(异步)的构成组件:
邮件客户端:读写收发信息,与服务器交互
邮件服务端:存储发给用户的Email
消息队列:存储等待发送的Email
SMTP协议:
•邮件服务器之间传递消息所使用的协议
•SMTP协议:Email消息的传输/交换协议
•使用TCP进行Email消息的可靠传输
•端口25
•传输过程的三个阶段
握手、消息的传输、关闭
•命令/响应交互模式
命令(Command): ASCII文本
响应(Response): 状态代码和语句
•Email消息只能包含7位ASCII码
•使用持久性连接
•要求消息必须由7位ASCII码构成
•SMTP服务器利用CRLF.CRLF确定消息的结束
•与HTTP对比
HTTP: 拉式(Pull)
SMTP: 退式(Push)
都使用命令/响应交互模式
命令和状态代码都是ASCII码
HTTP: 每个对象封装在独立的响应消息中
SMTP: 多个对象在由多个部分构成的消息中发送
Email消息格式
•RFC 822文本消息格式标准
头部行(Header):
To
From
Subject
消息体(Body)
消息本身
只能是ASCII字符
•MIME:多媒体邮件扩展
通过在邮件头部增加额外行以声明MIME的内容类型
邮件访问协议
从服务器获取邮件
•POP:Post Office Protocol
认证/授权(客户端<——>服务器)和下载
POP协议模式:
(1)下载并删除模式
用户如果换了客户端软件,无法重读该邮件
(2)下载并保持模式:不同客户端都可以保留消息的拷贝
(3)POP是无状态的
•IMAP:Internet Mail Access Protocol
更多功能、更加复杂
能够操纵服务器上存储的消息
(1)所有消息保存在服务器
(2)允许用户利用文件夹组织消息
(3)支持跨会话(Session)的用户状态:文件夹的名字、文件夹与消息ID之间的映射等
•HTTP:163,QQ Mail等,通过浏览器使用邮箱
DNS
解决Internet上主机/路由器的识别问题.(在网络应用层实现)
域名解析系统DNS(Internet核心功能):IP地址<->域名
1、由多层命名服务器构成的分布式数据库
2、应用层协议:完成名字的解析
DNS的服务:
1、域名向IP地址的翻译
2、主机别名
3、邮件服务器别名
4、负载均衡:web服务器(?)
问题:为什么不使用集中式的DNS?
1、单点失败问题
2、流量问题->流量大、成本高
3、距离问题->RTT大
4、维护性问题
分布式层次式数据库示意图:
运作过程:
根域名服务器(Root DNS Servers):
本地域名解析服务器无法解析域名时,需要访问根域名服务器。若根域名服务器不知道IP映射,则需访问权威服务器,从而获得映射,接着向本地域名服务器返回映射。
顶级域名服务器TLD(top-level domain):负责com,org,net,edu等,国家顶级域名:如cn,uk,fr等。
权威域名服务器:组织的域名解析服务器,提供组织内部服务器的解析服务。可由组织自身负责维护或者服务提供商负责维护。
本地域名解析服务器:每个ISP(Internet Server Provider)都有一个本地域名服务器(默认域名解析服务器)。当主机进行DNS查询时,本地域名服务器作为代理,会将查询转发给层级时的域名解析服务器系统。
补充:
DNS查询示例:
迭代查询:
递归查询:
DNS记录缓存和更新:
只要域名解析服务器获得域名-IP映射,即缓存这一映射;本地域名服务器一般会缓存顶级域名服务器的映射
DNS记录:
资源记录RR(resource records)
fomat:(name,value,type,ttl)
Type=A:
Name:主机域名
Value:IP地址
Type=NS:
Name:域
Value:该域权威域名解析服务器的主机域名
Type=CNAME:
Name:某一真实域名的别名
Value:真实域名
Type=MX:
Value是与name相对应的邮件服务器
DNS协议与消息格式:
DNS协议:
查询/回复
消息格式(查询/回复格式相同):
详细解析推荐博客:https://blog.****.net/tianxuhong/article/details/74922454
DNS->TCP or UDP?
注册域名
P2P应用
1.原理与文件分发
客户机/服务器 |
P2P |
•服务器串行地发送N个副本 时间:NF/Us
•客户机i需要F/di时间下载
•Max{NF/Us, F/min(di)} |
•服务器必须发送一个副本 时间:F/Us •客户机i需要F/di时间下载 •总共需要下载NF比特 •互相分享,最快可能上传速率:Us+Σui •Max{F/Us, F/min(di), NF/(Us+Σui)} |
客户端上传速率=U,F/U=1小时,Us=10U,dmin>=Us.
•客户端/服务器随N线性增长
•P2P随N增长趋于稳定
文件分发:BitTorrent:
•文件划分为256KB的chunk
•节点加入torrent
没有chunk,但是会逐渐积累;
向tracker注册以获得节点清单,与某些节点(“邻居”)建立连接
•下载的同时,节点需要向其他节点上传chunk
•节点可能加入或离开
•一旦节点获得完整的文件,它可能(自私地)离开或(无私地)留下
•获取chunk
给定任一时刻,不同的节点持有文件的不同chunk集合
节点(Alice)定期查询每个邻居所持有的chunk列表
节点发送请求,请求获取缺失的chunk(稀缺优先)
•发送chunk: tit-for-tat(一报还一报)
Alice向4个邻居发送chunk: 正在向其发送Chunk, 速率最快的4个。
每10秒重新评估top 4。
每30秒 随机选择一一个其他节点,向其发送chunk。
新选择节点可能加入top 4。
“optimistically unchoke"
思考题:BitTorrent技术对网络性能有哪些潜在的危害?
答:答案参考https://zhidao.baidu.com/question/2078727157674570108
对硬盘的损害。BT三大指控:高温、重复读写、扇区断块;对网络带宽的损害;助长了病毒的传播;可能面临着版权侵害的风险
2.索引技术
P2P系统的索引:信息到节点位置(IP地址+端口号)的映射
例如:文件共享(电驴)
例如:即时消息(QQ)
一、集中式索引
Napster最早采用这种设计
1)节点加入时,通知中央服务器:
IP地址
内容
2) Alice查找“Hey Jude”
3) Alice从Bob处请求文件
缺点:
内容和文件传输是分布式的,但是内容定位是高度集中式的。
•单点失效问题
•性能瓶颈
•版权问题
二、洪泛式查询:Query flooding
•完全分布式架构
•Gnutella采用这种架构
•每个节点对它共享的文件进行索引,且只对它共享的文件进行索引
利用覆盖网络进行洪泛式搜索。什么是覆盖网络?
覆盖网络(overlay network): Graph
•节点X与Y之间如果有TCP连接, 那么构成一个边
•所有的活动节点和边构成覆盖网络
•边;虚拟链路
•节点一般邻居数少于10个
洪泛式查询工作原理:
•查询消息通过已有的TCP连接发送
•节点转发查询消息
•如果查询命中,则利用反向路径发回查询节点
洪泛式查询缺点:
•像洪水一样泛滥,给网络带来很大负担。大量消耗网络带宽,导致网络拥塞。
•节点刚加入时需要复杂的处理。
三、层次式覆盖网络
•介介于集中式索引和洪泛查询之间的方法
•每个节点或者是一个超级节点,或者被分配一个超级节点
1)节点和超级节点间维持TCP连接;
2)某些超级节点对之间维持TCP连接。
•超级节点负责跟踪子节点的内容
案例应用:Skype
•本质上是P2P的:用户/节点对之间直接通信
•私有应用层协议
•采用层次式覆盖网络架构
•索引负责维护用户名与IP地址间的映射
•索引分布在超级节点上
5.Socket编程
Socket API
网络程序设计接口
Socket API:
•标识通信端点(对外) :IP地址+端口号
•操作系统/进程管理套接字(对内) :套接字描述符(socket descriptor)
Socket抽象:
•类似于文件的抽象
•当应用进程创建套接字时,操作系统分配一个数据结构存储该套接字相关信息
•返回套接字描述符
地址结构:
Socket API函数(WInSock):
WSAStartup:
int WSAStartup(WORD wVersionRequested, LPWSADATA IpWSAData);
Socket的应用程序在使用Socket之前必须首先调用该函数;
两个参数:
第一个参数:指明程序请求使用的WinSock版本,其中,高位字节指明副版本号、低位字节指明主版本。如0x102表示2.1版
第二个参数:返回实际的WinSock版本信息,指向WSADATA结构的指针
•例:使用2.1 版本的WinSock的程序代码段
wVersionRequested = MAKEWORD(2, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
WSACleanup:
int WSACleanup (void)
应用程序完成对Socket库的使用后,最后要调用WSACleanup函数->解除与Socket库的绑定,释放Socket库占用的系统资源
socket:
sd = socket(protofamily,type,proto);
调用socket创建套接字,操作系统返回套接字描述符(sd)
•第一个参数(协议族):protofamily= PF_ INET (TCP/IP),判断是面向什么协议的。
•第二个参数(套接字类型):type = SOCK_STREAM,SOCK_ DGRAM or SOCK_ RAW (TCP/IP)
•第三个参数(协议号):0为默认
•例: 创建一个流套接字的代码段
struct protoent *p; p=getprotobyname("tcp");
SOCKET sd=socket(PF_ INET,SOCK_ STREAM,p->p_ _proto);
其中,第二个参数的具体应用:
Closesocket:
int closesocket(SOCKET sd);
性质:
•关闭一个描述符为sd的套接字
•如果多个进程共享一一个套接字,调用closesocket将套接字引用计数减1,减至0才关闭
•一个进程中的多线程对一个套接字的使用无计数.
如果进程中的一个线程调用closesocket将-一个套接字关闭, 该进程中的其他线程也将不能访问该套接字
•返回值:
0: 成功
SOCKET_ ERROR:失败
bind:
int bind (sd, localaddr,addrlen) ;
绑定套接字的本地端点地址
• IP地址+端口号
参数:
• 套接字描述符(sd)
• 端点地址(localaddr)
结构sockaddr_ in
客户程序一般不必调用bind函数
服务器端
• 熟知端口号
• IP地址:绑定服务器运行的主机的IP地址
在不同的网络中拥有不同的IP地址->通过 地址通配符 INADDR_ANY解决,不应该指定确定的IP地址
listen:
int listen (sd, queuesize) ;
•置服务器端的流套接字处于监听状态
仅服务器端调用
仅用于面向连接的流套接字
•设置连接请求队列大小(queuesize)
当有很多请求到达时,就存放到队列中缓存,再从队列中一一提取。
•返回值:
0:成功
SOCKET_ ERROR:失败
connect:
connect ( sd, saddr , saddrlen)
•客户程序调用connect函数来使客户套接字(sd)与特定计算机的特定端口(saddr) 的套接字(服务)进行连接心
•仅用于客户端
•可用于TCP客户端也可以用于
UDP客户端
TCP客户端:建立TCP连接
UDP客户端:指定服务器端点地址
accept:
newsock = accept (sd, caddr,caddrlen);
•服务程序调用accept函数从处于监听状态的流套接字sd的客户连接请求队列中取出排在最前的一个客户请求,并且创建一一个新的套接字来与客户套接字创建连接通道
1)仅用于TCP套接字
2)仅用于服务器
•利用新创建的套接字
(newsock)与客户通信
•利于实现并发TCP服务器
send,sendto:
send (sd, *buf ,len, flags) ;
sendto (sd, *buf len, flags , des taddr , addrlen) ;
•send函数TCP套接字(客户与服务器)或调用了connect函数的UDP客户端套接字
•sendto函数用于UDP服务器端套接字与未调用connect函数的UDP客户端套接字
recv,recvfrom:
recv (sd, *buffer,len, flags) ;
recvfrom sd, *buf,len , flags,senderaddr,saddrlen) ;
•recv函数从TCP连接的另一端接收数据,或者从
调用了connect函数的UDP客户端套接字接收服务器发来的数据
•recvfrom函数用于从UDP服务器端套接字与未调
用connect函数的UDP客户端套接字接收对端数据
setsockopt,getsockopt:
int setsockopt(int sd, int level, int optname, *optval, int optlen);
int getsockopt(int sd, int level, int optname, *optval, socklen_ t *optlen);
•setsockopt()函数用来设置套接字sd的选项参数
• getsockopt()函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval
小节:
•WSAStartup:初始化socket库(仅对WinSock)
•WSACleanup:清楚/终止socket库的使用(仅对WinSock)socket:创建套接字
•connect:“连接”远端服务器(仅用于客户端)closesocket.释放/关闭套接字
•bind:绑定套接字的本地IP地址和端口号(通常客户端不需要)
•listen:置服务器端TCP套接字为监听模式,并设置队列大小(仅用于服务器端TCP套接字)
•accept:接受/提取一个连接请求,创建新套接字,通过新套接(仅用于服务器端的TCP套接字)
•recv:接收数据(用于TCP套接字或连接模式的客户端UDP套接字)
•recvfrom:接收数据报(用于非连接模式的UDP套接字)
•send:发送数据(用于TCP套接字或连接模式的客户端UDP套接字)
•sendto:发送数据报(用于非连接模式的UDP套接字)
•setsockopt:设置套接字选项参数
•getsockopt:获取套接字选项参数
网络字节顺序:
•TCP/IP定义了标准的用于协议头中的二进制整数表示:网络字节顺序(network byte order)
•某些SocketAPI函数的参数需要存储为网络字节顺序(如IP地址、端口号等)
•可以实现本地字节顺序与网络字节顺序间转换的函数
•htons:本地字节顺序-→网络字节顺序(16bits)
•ntohs:网络字节顺序- +本地字节顺序(16bits)
•htonl:本地字节顺序- >网络字节顺序(32bits)
•ntohl:网络字节顺序_本地字节顺序(32bits)
SocketAPI 调用基本流程:
客户端软件设计
解析服务器IP地址:
IP协议需要使用32位二进制IP地址。
将域名或IP地址转换位32位IP地址的函数:
1)函数inet_ add( )实现点分十进制IP地址到32位IP地址转换
2)函数gethostbyname( )实现域名到32位IP地址转换
返回一个指向结构hostent的指针
解析服务器(熟知)端口号:
将服务名(如http)转换为熟知端口号的函数:
解析协议号:
需要将协议名转换为协议号:
TCP客户端软件流程 |
UDP客户端软件流程 |
1.确定服务器IP地址与端口号 2.创建套接字 3.分配本地端点地址(IP地址+端口号) 4.连接服务器(套接字) 5.遵循应用层协议进行通信 6.关闭/释放连接 |
1.确定服务器IP地址与端口号 2.创建套接字 3.分配本地端点地址(IP地址+端口号) 4.指定服务器端点地址,构造UDP数据报 5.遵循应用层协议进行通信 6.关闭/释放套接字 |
客户端软件的实现-connectsock()
客户端软件的实现-UDP客户端
客户端软件的实现-TCP客户端:
客户端软件的实现-异常处理
例:
访问DAYTIME服务的客户端(TCP):
(数据流传输)
访问DATTIME服务的客户端(UDP):
(数据报传输)
MSG:给DAYTIME服务器发送的消息
服务器软件设计
基于套接字编程的服务器端软件设计:
4种类型基本服务器:
•循环无连接(Iterative connectionless)服务器
•循环面向连接(terative connection-oriented)服务器
•并发无连接(Concurrent connectionless)服务器
•并发面向连接(Concurrent connection-oriented)服务器
循环无连接服务器基本流程:
1.创建套接字
2.绑定端点地址(INADDR_ ANY+端口号)
3.反复接收来自客户端的请求
4.遵循应用层协议,构造响应报文,发送给客户
数据发送:
•服务器端不能使用connect()函数
•无连接服务器使用sendto()函数发送数据报
retcode=sendto (socket , data , length , flags , destaddr , addrlen) ;
注释:
•socket:服务器(UDP)套接字
•data:存储待发送数据缓存的地址
•length:缓存中数据字节数
•flags:调试或控制选项
•destaddr:指向結枸sockaddr_ in的指籵(客戸端端点地址)
•addrlen:地址结构长度
客户的端点地址在调用recvfrom()函数接收数据时 自动提取:
retcode=recvf com ( socket ,buf , length, flags , from, fromlen) ;
注释:
•socket : (UDP)服务器套接字
•buf:存放数据报的缓存地址
•length:缓存可用空间
•flags:调试或控制选项
•from:存放源地址的缓存地址
•fromlen:源地址长度
循环面向连接服务器的基本流程:
1.创建(主)套接字,并绑定熟知端口号;
2.设置(主)套接字为被动监听模式,准备用于服务器;
3. 调用accept()函数接收下一一个连接请求(通过主套接字),创建新套接字用于与该客户建立连接;
4. 遵循应用层协议,反复接收客户请求,构造并发送响应(通过新套接字);
5. 完成为特定客户服务后,关闭与该客户之间的连接,返回步骤3.
并发无连接服务器基本流程:
•主线程1:创建套接字,并绑定熟知端口号;
•主线程2:反复调用recvfrom()函数,接收下一个客户请求,并创建新线程处理该客户响应;
•子线程1:接收一个特定请求;
•子线程2:依据应用层协议构造响应报文,并调用sendto()发送;
•子线程3:退出(一个子线程处理-一个请求后即终止)。
并发面向连接服务器的基本流程:
•主线程1:创建(主)套接字,并绑定熟知端口号;
•主线程2:设置(主)套接字为被动监听模式,准备用于服务器;
•主线程3:反复调用accept()函数接收下一一个连接请求(通过主套接字),并创建一一个新的子线程处理该客户响应;
•子线程1:接收一一个客户的服务请求(通过新创建的套接字) ;
•子线程2:遵循应用层协议与特定客户进行交互;
•子线程3:关闭/释放连接并退出(线程终止).
服务器的实现:
passivesock:
passiveUDP():
passiveTCP():
(同时参考https://www.cnblogs.com/cellphone7/p/9520438.html,十分感谢)