计算机网络(第三章,网络应用(下))
这是本人初学《计算机网络》做的一些笔记,随着深入理解与学习,会对笔记进行改进
P2P应用
P2P应用_原理与文件分发
纯P2P架构:没有服务器;任意端系统之间直接通信;节点阶段性接入Internet;节点可能更换IP地址
对于C/S:
服务器串行地发送N个副本,时间为:NF/us
客户机i需要F/di时间下载
所以从一个服务器向N个节点分发一个文件需要dcs=max{NF/us,F/min{di}}的时间
对于P2P:
服务器必须发送一个副本:F/us
客户机i需要F/di时间下载
总共需要下载NF比特
最快的可能上传速度为us+Σui
所以一个服务器向N个节点分发一个文件需要dP2P=max{F/us,F/min{di},NF/(us+Σui)}的时间
BitTorrent
- 文件划分为256KB的chunk
- 节点加入torrent
没有chunk,但是会逐渐积累;
向tracker注册以获得节点清单,与某些节点(“邻居”)建立连接; - 下载的同时,节点需要向其他节点上传chunk
- 节点可能加入或离开
- 一旦节点获得完整的文件,它可能(自私地)离开或(无私地)留下
- 获取chunk
给定任一时刻,不同的节点持有文件的不同chunk集合
节点(Alice)定期查询每个邻居所持有的chunk列表
节点发送请求,请求获取缺失的chunk(稀缺优先) - 发送chunk:tit-for-tat
Alice向4个邻居发送chunk,正在向其发送chunk,速率最快的4个(每10s重新评估top4)
每30s随机选择一个其他节点,向其发送chunk(新选择的节点可能加入top4;“optimistically unchoke”)
P2P应用_索引
P2P系统的索引:信息到节点位置(IP地址+端口号)的映射
例如:
- 文件共享(电驴):
利用索引动态跟踪节点所共享的文件的位置;
节点需要告诉索引它拥有哪些文件;
节点搜索索引,从而获知能够得到哪些文件 - 即时消息(QQ):
索引负责将用户名映射到位置;
当用户开启IM应用时,需要通知索引它的位置
节点检索索引,确定用户的IP地址
集中式索引
Napster最早采用这种设计:
1)节点加入时,通知中央服务器:IP地址,内容
2)Alice查找“Hey Jude”
3)Alice从Bob处请求文件
集中式索引的问题:单点失效问题;性能瓶颈;版权问题
洪泛式查询
层次式覆盖网络
介于集中式索引和洪泛式查询之间的方法,每个节点或者是一个超级节点,或者被分配一个超级节点(节点和超级节点间维持TCP连接;某些超级节点对之间维持TCP连接),超级节点负责跟踪子节点的内容
Socket编程
API:Application Programming Interface 应用编程接口
WSAStartup
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
使用Socket的应用程序在使用Socket之前必须首先调用WSAStartup函数
两个参数:
- 第一个参数指明程序请求使用的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);
创建套接字
操作系统返回套接字描述符(sd)
第一个参数(协议族):
protofamily=PF_INET(TCP/IP)
第二个参数(套接字类型):
type = SOCK_STREAM, SOCK_DGRAM or SOCK_RAW (TCP/IP)
例如:创建一个流套接字的代码段
struct protoent *p;
p = getprotobyname("tcp");
SOCKET sd = socket(PF_INET, SOCK_STREAM, p->p_proto);
Socket面向TCP/IP:流套接字,特点:可靠、面向连接、字节流传输、点对点
Socket面向UDP:数据报套接字,特点:不可靠、无连接、数据报传输
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函数
服务器端需要调用bind函数来熟知端口号
地址通配符:INADDR_ANY
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的客户连接请求队列中取出排在最前的一个客户请求,并创建一个新的套接字来与客户套接字创建连接通道(仅用于TCP套接字,仅用于服务器)
利用新创建的套接字(newsock)与客户通信
send,sendto
send(sd, *buf, len, flags);
sendto(sd, *buf, len, flags, destaddr, 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, *optname, *optval, socklen_t *optlen);
==setsockopt()==函数用来设置套接字sd的选项参数
==getsockopt()==函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval
Socket API函数小结
函数名 | 功能 |
---|---|
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定义了标准的用于协议头中的二进制整数表示:网络字节顺序
某些Socket API函数的参数需要存储为网络字节顺序(如IP地址,端口号等)
可以实现本地字节顺序与网络字节顺序间转换的函数:
htons:本地字节顺序→网络字节顺序(16bits)
ntohs:网络字节顺序→本地字节顺序(16bits)
htonl:本地字节顺序→网络字节顺序(32bits)
ntohl:网络字节顺序→本地字节顺序(32bits)
4种基本类型服务器:
循环无连接服务器
循环面向连接服务器
并发无连接服务器
并发面向连接服务器
循环无连接服务器基本流程:
1.创建套接字
2.绑定端点地址(INADDR_ANY+端口号)
3.反复接收来自客户端的请求
4.遵循应用层协议,构造响应报文,发送给客户
其中服务器端不能使用connect()函数
无连接服务器使用sendto()函数发送数据
调用recvfrom()函数接收数据时,自动提取
循环面向连接服务器基本流程:
1.创建(主)套接字,并绑定熟知端口号
2.设置(主)套接字为被动监听模式,准备用于服务器
3.调用accept()函数接收下一个连接请求(通过主套接字),创建新套接字用于与该客户建立连接
4.遵循应用层协议,反复接收客户请求,构造并发送响应(通过新套接字)
5.完成为特定客户服务后,关闭与该客户之间的连接,返回步骤3
并发无连接服务器基本流程:
主线程1:创建套接字,并绑定熟知端口号
主线程2:反复调用recvfrom()函数,接收下一个客户请求,并创建新线程处理该客户响应
子线程1:接收一个特定请求
子线程2:依据应用层协议构造响应报文,并调用sendto()发送
子线程3:退出(一个子线程处理一个请求后即终止)
并发面向连接服务器基本流程:
主线程1:创建(主)套接字,并绑定熟知端口号
主线程2:设置(主)套接字为被动监听模式,准备用于服务器
主线程3:反复调用accept()函数接收下一个连接请求(通过主套接字),并创建一个新的子线程处理该客户响应
子线程1:接收一个客户的服务请求(通过新创建的套接字)
子线程2:遵循应用层协议与特定客户进行交互
子线程3:关闭/释放连接并退出(线程终止)