一、P2P文件分发
P2P体系结构的自扩展性
- 成对间歇连接的主机(称为对等方)彼此相互通信
- 每个对等方能够向任何其他对等方重新分发它已经收到的文件的任何部分
- 节点阶段性接入Internet,可能更换IP地址
设usu_sus服务器上传带宽,uiu_iui结点i的对等方接入链路的上载速率,did_idi表示第i对等方介入链路的下载速率。用F表示被分发的文件长度。
-
客户机-服务器模型DcsD_{cs}Dcs:
- 服务器向N个对等方分别上传文件的副本。分发时间至少为NF/usNF/u_sNF/us
- 考虑最小下载速率的结点,不可能在少于F/dminF/d_{min}F/dmin的时间内获取该文件的一个副本。
- 因此Dcs≥max{NF/us,F/dmin}D_{cs} \geq max\{NF/u_s,F/d_{min}\}Dcs≥max{NF/us,F/dmin}
- 当N足够大时,随对等方数量线性增加
-
P2P模型DP2PD_{P2P}DP2P:
- 服务器必须经过链路发送该文件至少一次,时间为F/usF / u_sF/us
- 最低下载速率的结点所需要的下载时间为F/dminF/d_{min}F/dmin
- 系统的总上载能力 = 服务器上载能力 + 每个对等方上载能力。系统必须向这N个对等放每个交付F,因此总共需要上载NF。这不能以快于us+∑uiu_s + \sum u_ius+∑ui的速率完成。因此时间为NF/us+∑uiNF /u_s + \sum u_iNF/us+∑ui。
- 于是DP2P≥max{F/us,F/dmin,NF/us+∑ui}D_{P2P} \geq max\{F / u_s,F/d_{min},NF /u_s + \sum u_i\}DP2P≥max{F/us,F/dmin,NF/us+∑ui}
- 一旦每个对等方接收到一个比特就能够重发一个比特那么存在一个重新分发方案实际取得这个下界。但是实际分发的是文件块而不是文件。
- P2P应用是自扩展的。对等方数量增加时,分发时间总是少于1小时。
BitTorrent协议
- 参与一个特定文件分发的所有对等方的集合称为一个洪流(Torrent)
- 每个洪流拥有一个基础设施节点追踪器(Tracker):
- 当一个对等放加入洪流时向追踪器注册自己。并通知追踪器在此洪流中
- 文件被划分为Chunk
- 对等方加入时,没有Chunk,但会积累。下载时也会上载。
- 当获取文件结束后,可能离开洪流。也可能继续上传
- 工作流程:
- 新的对等方Alice加入洪流,追踪器从洪流中选择一个子集,并将IP地址发送给Alice
- Alice试图与这些新的对等方建立并行的TCP连接
- 在任何给定时间,每个对等放拥有文件的子集可能不同。Alice周期性的询问每个对等放所具有的块列表。Alice对自身还没有的块发出请求
- 请求哪些块:最稀缺优先(请求邻居中副本数量最少的块)。
- 相应哪个请求:Alice根据当前能够以最高速率向他提供数据的邻居给出其优先权。
- 对每个邻居持续测量接收到的比特的速率。确定以最高速率流入的4个邻居
- 每过10秒,重新计算速率并修改这4个邻居。称之为疏通
- 每过30秒,随机向另外一个邻居发送块。(称之为Bob)
- Alice可能称为Bob的这4个邻居之一(相对于Bob来说)
- 于是Bob可能向Alice发送数据。如果Bob向Alice发送数据的速率足够高,那么它特能成为Alice的前4位上载者之一。
- 趋于找到彼此协调的速率上载。
二、P2P索引技术
P2P系统的索引:信息到节点位置(IP地址+端口号)的映射
- eg:QQ索引负责将用户名映射到位置 ;节点检索索引,确定用户的IP地址;电驴利用索引动态跟踪节点所共享的文件的位置
集中式索引
- 节点加入时,通知中央服务器IP地址+内容
- Alice向中央服务器查找“Hey Jude,发现文件在Bob上
- Alice从Bob中请求文件
- 单点失效(服务器一die全die),性能瓶颈,版权问题
洪泛式查询
- 每个节点对它共享的文件进 行索引,且只对它共享的文 件进行索引
- 覆盖网络(overlay network): Graph 节点X与Y之间如果有TCP连接, 那么构成一个边 ;所有的活动节点和边构成覆盖网 络;边:虚拟链路 ;节点一般邻居数少于10个
- 查询流程:
结合分布式和集中式:层次式覆盖网络
- 每个节点或者是一个超级节点,或者被 分配一个超级节点
- 节点和超级节点间维持TCP连接 ;某些超级节点对之间维持TCP连接;超级结点跟踪子节点内容
- 典型应用:skype
二、socket套接字编程
-
多种网络编程接口
-
应用编程接口API:就是应用进程的控制权和操作系统的控制
权进行转换的一个系统调用接口.
Socket API Overview
- Internet网络应用最典型的API接口
- 通信模型:服务器客户机。是一种应用进程间通信的抽象机制
- 对外标志通信端点(IP地址和端口号),对内操作系统和进程利用
套接字描述符管理套接字(一个小整数)
- 套接字的抽象:
- 当应用进程创建套接字时,操作系统分配一个数据结构存
储该套接字相关信息。并返回套接字描述符。每个进程具有一个套接字描述符表。
- 当应用进程创建套接字时,操作系统分配一个数据结构存
- 套接字地址结构:使用TCP/IP协议簇的网络应用程序声明端点地址
变量时,使用结构sockaddr_in
套接字API
- WinSock使用pipeline overview:
WSAstartup函数
- 使用Socket的应用程序在使用Socket之前必须首先调用
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
- 第一个参数指明程序请求使用的WinSock版本,其中高位字节指明
副版本、低位字节指明主版本.第二个参数返回实际的WinSock的版本信息(指向WSADATA的指针)
WSAcleanup函数
int WSACleanup (void);
- 应用程序在完成对请求的Socket库的使用, 最后要调用WSACleanup函数
Socket函数
sd = socket(protofamily,type,proto);
- 用于创建套接字,操作系统返回套接字描述符。
- 第一个参数(协议族): protofamily = PF_INET(TCP/IP);第二个参数(套接字类型):type = SOCK_STREAM,SOCK_DGRAM or SOCK_RAW(TCP/IP) ;第三个参数(协议号):0为默认
- 创建一个流套接字的代码段:
Socket面向TCP/IP服务类型
closesocket函数
int closesocket(SOCKET sd);
- 用于关闭一个描述符为sd的套接字
- 如果多个进程共享一个套接字,调用closesocket将套接字引用计数减1,减至0才关闭
- 如果进程中的一个线程调用closesocket将一个套接字关闭,该进程中的其他线程也将不能访问该套接字。(线程无计数)
- 返回值:0成功,SOCKET_ERROR失败
bind函数
int bind(sd,localaddr,addrlen);
- 用IP地址+端口号绑定套接字本地端点地址。
- 参数:套接字描述符sd,端点地址。(结构socket_addrin)
- 客户端不必调用
- 服务器端应该绑定哪个地址?:地址通配符:INADDR_ANY
listen函数
int listen(sd,queuesize);
- 置服务器端的流套接字处于监听状态` 仅服务器端调用。仅用于面向连接的流套接字;设置连接请求队列大小(queuesize).返回值0成功,SOCKET_ERROR失败
connet函数
connect(sd,saddr,saddrlen);
- 客户端调用用connect函数来使客户套接字(sd)与特定计算机的特定端口(saddr)的套接字(服务)进行连接。仅用于客户端。可用于TCP客户端(用于建立TCP连接)也可用于UDP客户端(用于制定服务器端点地址)
accept函数
newsock = accept(sd,caddr,caddrlen);
- 服务程序调用accept函数从处于监听状态的流套接字sd的客户连接请求队列中取出排在最前的一个客户请求
- 并且创建一个新的套接字来与客户套接字创建连接通道
- 仅用于TCP套接字,仅用于服务器
- 使用新创建的套接字与客户机进行通信
send和sendto函数
send(sd,*buf,len,flags);
- send函数TCP套接字(客户与服务器)或调用了connect函数的UDP客户端套接字
sendto(sd,*buf,len,flags,destaddr,addrlen);
- sendto函数用于UDP服务器端套接字与未调用connect函数的UDP客户端套接字
recv, recvfrom函数
recv(sd,*buffer,len,flags);
- recv函数从TCP连接的另一端接收数据,或者从调用了connect函数的UDP客户端套接字接收服务器发来的数据
recvfrom(sd,*buf,len,flags,senderaddr,saddrlen);
- recvfrom函数用于从UDP服务器端套接字与未调用connect函数的UDP客户端套接字接收对端数据
setsockopt, getsockopt函数
int setsockopt(int sd, int level, int optname, *optval, int optlen);
- setsockopt()函数用来设置套接字sd的选项参数
int getsockopt(int sd, int level, int optname, *optval, socklen_t *optlen);
- getsockopt()函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval
一句话描述各套接字API
网络字节顺序
- TCP/IP定义了标准的用于协议头中的二进制整数表示:网络字节顺序
- 某些Socket API函数的参数需要存储为网络字节顺序(如IP地址、端口号等)
- 可以实现本地字节顺序与网络字节顺序间转换的函数
- 如果本地字节顺序和网络字节顺序不一致可能出错
- 因此要及时进行转换
Socket API(TCP) Programming Pipeline
-
首先启动的一定是服务器
- 调用WSASTARTUP
- 调用socket创建服务器端的套接字
- 调用bind函数为该创建的套接字绑定端点地址(IP地址被设置为IN_ADDR_ANY。标准web服务器:80端口)
- 调用listen函数(监听模式,队列大小)
- 调用accept函数接收客户端的一个连接请求(阻塞的过程)
-
客户端启动
- 调用socket函数创建客户端套接字(不需要调用bind函数绑定地址,操作系统自动完成)
- 调用connect函数(阻塞函数)如果调用成功意味着TCP连接建立成功,否则如果没有成功需要等待。调用完成后与服务器成功建立连接。
-
此时服务器端的accept函数接收到客户端的connet请求,创建一个新的套接字为ns,完成连接确认。如下图所示。
-
此时客户端可以利用建立好的套接字s发送数据,服务器端可以利用新创建好的套接字ns通信。如2,3步骤所示。
-
如果到此时客户端通信到此结束,客户端调用closesocket释放掉创建的套接字s。(实际上和服务器是有交互过程的,例如关闭TCP连接)
-
如果服务器与此客户端的通信也结束,那么服务器释放掉套接字ns;
-
接下来客户端可以调用wsacleanup关闭套接字库的使用
-
服务器端可以继续调用accept函数接收下一个客户的请求。如下图中虚线所示。
-
如果服务器此时也要终止,那么调用WSACLEANUP
-
整个Pipeline流程图如下图所示。
Socket Programming - 客户端设计
解析服务器IP地址
- 客户端可能使用域名(如:study.163.com)或IP地址(如:123.58.180.121)标识服务器
- IP协议需要使用32位二进制IP地址
- 需要将域名或IP地址转换为32位IP地址
- 函数inet_addr( ) 实现点分十进制IP地址到32位IP地址转换
- 函数gethostbyname( ) 实现域名到32位IP地址转换,可以请求域名系统完成解析;返回一个指向结构hostent 的指针。结构如图所示:
- 两个函数返回的已经是网络字节顺序可以为套接字API所使用
解析服务器端口号
- 客户端使用服务名(如HTTP标志服务器端口)
- 函数getservbyname( )返回一个指向servant结构的指针
- 函数getservbyname( )返回一个指向servant结构的指针
解析协议号
- 客户可能使用协议名(如TCP协议)
- 将协议名转换为协议号(如6)
- 函数getprotobyname ( ) 实现协议名到协议号的转换(返回指向下图所示的指针)
客户端套接字编程流程
-
TCP客户端流程
- 确定服务器IP地址与端口号
- 创建套接字
- 分配本地端点和地址(IP地址 + 端口号)
- 连接服务器(套接字)
- 遵循应用层协议进行通信
- 关闭和释放连接
-
UDP客户端流程(本质上有很大差异)
- 确定服务器IP地址与端口号
- 创建套接字
- 分配IP地址和端口号(也不需要客户显式地完成)
- 与TCP客户端差异较大: 指定服务器端点地址,构造UDP数据报(connect函数)UDP不是面向连接的
- 遵循应用层协议进行通信(永远是UDP客户端先发送数据)
- 关闭和释放套接字
客户端软件的实现connectsock函数(一种封装)
- 进行一系列的转换
- 创建套接字并调用connect函数(UDP制定服务器端点地址,TCP建立连接)
- 最后返回套接字
example1:访问DAYTIME服务的客户端(TCP)
需求定义:
- localhost表明本地主机。客户端和服务器都用于同一个主机上
- 调用WSAstartup和WSAcleanup;中间函数为主程式
- connectTCP函数创建一个用于和服务器通信的套接字,同时建立了一个与服务器通信的TCP连接
- recv用于数据的接收
- 由于TCP协议是流传输协议,需要循环接收数据。发送端发送一次数据并不意味着接收端就能一次性收到这些数据。
example2:访问DAYTIME服务的客户端(TCP)
- 上图红圈所示的消息可以改为任何值。
- UDP为数据报传输的协议
- 意味着发送数据和接收数据都将是一个完整的数据报
- 时间信息量不大,作为一个数据报传输
- 因此不需要while循环
Socket Programming - 服务器设计
基本类型
- 循环无连接(Iterative connectionless)
- 循环面向连接
- 并发无连接
- 并发面向连接
循环无连接服务器
- 一个客户请求处理完成后再处理下一个客户的请求
- 类似于UDP(无连接)的传输层协议
- 工作 流程:
- 不会使用connet函数
- 使用sendto函数发送数据包
- 如何获取上图中的客户端端点地址?:调用recvfrom接收数据时自动提取。该函数也用于该服务器的接收数据
循环面向连接服务器
- 创建(主)套接字,并绑定熟知端口号
- 设置(主)套接字为被动监听模式,准备用于服务器;
- 调用accept()函数接收下一个连接请求(通过主套接字),创建新套接字用于与该客户建立连接;
- 遵循应用层协议,反复接收客户请求,构造并发送响应(通过新套接字);
- 完成为特定客户服务后,关闭与该客户之间的连接,返回步骤3.
并发无连接服务器基本流程
- 主线程1: 创建套接字,并绑定熟知端口号;
- 主线程2:反复调用recvfrom()函数,接收下一个客户请求,并**创建新线程(称为子线程)**处理该客户响应;
- 子线程1:接收一个特定请求;
- 子线程2:依据应用层协议构造响应报文,并调用sendto()发送;
- 子线程3:退出(一个子线程处理一个请求后即终止)。
- 同时主线程是在循环调用recvfrom,每有一个客户请求时重新创建一个新的子线程
并发面向连接服务器基本流程
- 主线程1: 创建(主)套接字,并绑定熟知端口号;
- 主线程2: 设置(主)套接字为被动监听模式,准
备用于服务器; - 主线程3: 反复调用accept()函数接收下一个连接请求(通过主套接字),并创建一个新的子线程处理该客户响应;
- 子线程1: 接收一个客户的服务请求(通过新创建的套接字);
- 子线程2: 遵循应用层协议与特定客户进行交互;
- 子线程3: 关闭/释放连接并退出(线程终止);
- 主线程同时不断循环accept
服务器的实现
- 和客户端类似
passivesock函数
- 创建套接子和绑定端口号。
- 流式套接字TCP需要调用listen,UDP不需要调用。
- 最终返回主套接字。
passiveUDP与passiveTCP
- qlen用于指定队列大小。对于UDP而言设置为0即可;
example1:无循环连接DAYTIME服务器
- 调用recvfrom接收客户端发来的数据包(可以获取客户端地址信息)
- 获取系统时间后,调用sendto函数发送数据给刚请求的客户端
example2:面向连接并发DAYTIME服务器
- attention:服务器不利用主套接字给客户端发送任何信息
- 红色标注部分为主从套接字变量
- 在主线程中调用passiveTCP创建主套接字msock
- 无线循环:
- 主线程调用accept通过一个客户端,创建新套接字ssock
- 调用beginthreaded创建线程,使得该线程调用TCPdaytimed函数,并且将ssock作为参数传递给它
- TCPdaytimed函数:每个子线程利用刚刚accept的套接字发送时间信息,发送完成后关闭该套接字。注意TCP用send而不用sendto
- 主线程在该过程中同时在接收新的客户请求。