ProDesc.txt
资源名称:p2p_vod.rar [点击查看]
上传用户:liguizhu
上传日期:2015-11-01
资源大小:2422k
文件大小:10k
源码类别:
P2P编程
开发平台:
Visual C++
- 1. 架构说明
- 目前的协议有如下一些特点:
- 1) 客户向服务器发送请求, 每个请求的长度不定. 请求的长度在第一个INT中指定.
- 2) 每个服务器通常会向多种客户提供服务, 例如, TS要同时向CP, NP提供服务,
- CP要向NP和其他CP提供服务, 同时还是其他CP, TS, SP的客户.
- 3) 每个服务器为客户服务时, 通常是长期的, 会涉及多次请求-应答的来回.
- 这样的结构, 主要是为了能够支持大量并发客户连接而设计的. 在具有大量并发客户
- 连接时, 无论采用线程还是进程, 都无法进行有效的服务, 因此必须采用select
- 轮询方式.
- 2. 基本数据结构说明
- 对于每个客户端, 需要保存该客户端相应的一些信息. 目前的CPnew.c, SPnew.c
- 和TSnew.c的核心数据结构基本相同, 都由Session,
- SessionCluster (TSnew.c中) 或者 ServerDesc (CPnew.c和SPnew.c)构成.
- 其中, Session是每个客户端相关的数据, SessionCluster(或者是ServerDesc)是
- 有关每种服务的信息, 其中有一个指向该服务相关的各个Session的指针. Session
- 这一数据结构不是在有客户请求时动态分配的, 而是在最开始初始化时就已经分配
- 好的, 当有新客户请求到来时, 服务器搜索这一预先分配好的这些Session, 发现其中
- 有空闲则使用, 如果没有空闲就报告错误.
- 对于TS和CP(SP)来说, 最大的区别是TS使用UDP协议, 而CP和SP则使用TCP协议, 二者的
- 不同在于:
- 1) 对于TCP协议的客户端, 由于每个客户端都使用不同的socket, 因此select之后
- 只需要看各个客户端的fd_set是否置位就可以了, 而对于UDP客户端, 找到相应的
- 客户端需要进行一次查找过程. TS使用了一些措施来减轻查找所带来的开销.
- 2) TCP协议中, 发来的数据是流形式的, 因此需要进行消息分块, 有可能两个消息
- 在一次read中读完, 也有可能一个消息需要读很多次, 这两种情况都需要考虑, 因此
- 每个Session中都有一个buf, rstart, rlen, 用来存储读来但还没有处理的消息,
- 同样, 写的过程中也需要考虑写的时候有可能没有一次写完, 因此也需要每个Session中
- 保留wbuf, wstart, wlen三项. UDP中则不同, 在协议实现中假设每个UDP数据包中
- 所包含的消息都是完整的, 因此没有这几项.
- SessionCluster(或者是ServerDesc)来说, 描述了一个服务, 这个服务由这样几个
- 主要的部分构成
- 1) sock: 描述所使用的socket
- 2) cur: 当前客户端的个数
- 3) max: 最多容纳客户端的个数
- 4) head: Session的头, head[0]为第一个Session, head[max-1]为最后一个session
- 5) init: 这一服务中每个Session需要执行的初始化操作. (函数指针)
- 6) process: 这一服务中消息的处理函数
- 7) closure: 这一服务中需要的析构函数
- 3. 主要结构说明
- process_child: 主要函数, 这一函数主要用来
- 设置socks和wsocks, 对于SP和CP, 只有Session的wlen>0的时候才设置wsocks;
- select;
- 对于每个ServerDesc(或者SessionCluster), 进行process_type
- 在SP和CP中, 为了支持PUSHLIST操作, 在每一次循环前先要进行processJob
- 在CP中, 还周期进行periodCheck, 用来将过期的连结清除
- 在TS中, 周期进行periodLog, 用来将过期的客户连接清除
- process_type:
- 对于每个Session, 检查是否可读. 如果可读, 检查是否有完整的消息,
- *(unsigned int *)(rbuf+rstart) <= rlen
- 调用相应的process直到没有完整的消息为止
- 检查是否可写, 如果可写且wlen>0, 则进行写
- 4. 其他重要的模块
- 1) 配置模块
- 配置模块主要由struct NamVal, read_config, free_config组成, NamVal结构中,
- Name是在cfg文件中的名字, ptr是指向存放的指针, type是数据的类型, 目前支持这样
- 几种类型
- 'd': 整数类型, ptr是一个整数指针
- 's': 字符串类型, ptr是一个指向指针的指针, (char **)
- 'b': 字符串buffer类型, ptr是一个char *, 使用这种类型时应当注意, 对于's'类型,
- read_config将为该val分配内存(malloc), 但是对于'b' 类型, ptr所指向的必须是已经
- 分配好的内存
- 两个重要的函数分别为:
- read_config, 参数为文件名, 一个struct NamVal *, 以及该struct NamVal的项数
- free_config, 参数为和read_config相同的struct NamVal *以及项数
- 2) mysql 模块
- mysql模块主要有MYSQL *local_mysql以及三个函数构成, 这三个函数是
- init_mysql, 初始化mysql, 返回一个MYSQL *, 一般用来初始化local_mysql
- query_mysql, 执行一个mysql语句, 格式为query_mysql (local_mysql, "mysql语句,
- 其中格式和printf的格式相同, 例如delete from %s等", 所需要的值)
- query_mysql_select, 执行一个mysql的select语句, 与上面不同的是, 它返回一个
- MYSQL_RES *.
- 3) network排序模块
- 这一模块主要由networks结构, readNETBLOCK函数, getnetwork函数, compareNet函数
- 构成, 其中,
- readNETBLOCK用来读入network配置文件, 初始化全局变量NETBLOCKS, NETBLOCKS是一个
- networks结构数组, 有MAX_NET项.
- getnetowrk用来查找和一个IP地址最接近的netblock
- compareNet是在qsort中用到的一个函数, 对找到的NPPeer进行排序, 让同一个网络
- 中的NPPeer排在前面.
- 4) 图管理
- 在目前的CP, SP, NP中, CP可以同时加入多个频道, 而NP也可以有多个资源, 为了描述
- 这种结构, 引入了图的概念. 每个边(Edge)存储了指向NP的指针, 指向Channel的指针,
- 在TS中还需要存储这一Session在这一Channel中的各个Interval. 每个Channel通过Edge
- 中的cnext串成一个链表, 这个链表的头是Channel结构中的PeerHead, 而每个Session
- 通过Edge中的enext也串成一个链表, 这个链表的头是Session结构中的header.
- 相关的函数有:
- newEdge: 新添一个边, 参数为Channel *, Session *, 对于TS还需要一个ChannelInfo来
- 初始化Edge中的信息
- delEdge: 删除一个边, 参数为Edge *
- 5) Channel模块
- Channel模块的功能主要是:
- TS中用来处理NEED_PEERS, SP中还需要保存和查找频道数据, 频道都使用图结构进行管理.
- 频道的搜索为了效率方面的因素, 采用了Hash进行搜索, ChannelHash中使用的是字符串
- hash, 如hash_str所示.
- TS中的Channel相对较为简单, SP和CP中Channel还需要管理Channel相关的数据. 这些
- 数据以文件的形式存在硬盘上/var/tmp/目录下, 文件名随机生成, 对于每一块的相关信息,
- 由BlockData来保存, BlockData中的firstsampl, message_size, message_id, offset分别
- 存储了firstsample信息, 快的长度, 块的id, 以及在文件中的offset.
- SP和CP的处理有所不同, 对于CP, 块是以hash的方式来存放的, 例如, 块的ID为1000, 而
- max_queue为100, 则存储位置为1000%100=0. 对于SP, 如果资源是一个CS发来的频道,
- 则是一个循环队列, 每一块按照次序分别存放在相应位置, 如果到了队列尾部, 就再从
- 队列头开始. 如果资源是文件, 就不保存BlockData信息, 直接根据blockID到原文件定位.
- 涉及Channel的函数有很多, 如locate_by_id, locate_order_by_id, newChannel,
- freeChannel, saveBlock等.
- 6) Berkeley DB模块
- 这只在SP中涉及, 主要是打开DB文件, 查询某个md5的位置. 主要涉及到DB* MediaDB,
- openDB, openMedia这两个函数
- openDB: 参数为DB文件的名
- openMedia: 参数为md5和一个整数指针, 返回FILE *以及该文件的长度, 在整数指针中
- 7) Job模块
- Job模块用在CP和SP中, 用来处理PUSHLIST, PUSHLIST消息可以重新设置Job的列表,
- 也可以添加Job或者是删除Job. 涉及到job.c中的函数和JobDes结构. JobDes结构
- 中一个Session *, 一个Channel *用于标识该Job所属的Session和Channel, num表示
- 所需要下载的BlockID数, job是一个指向整数的指针, mask也是一个指向整数的指针,
- job[i] 是需要下载的BlockID, 如果mask[i]为0,则需要进行下载, 如果为1, 则不需要.
- addJob: 添加job的时候, 不检查该Job是否已经在列表中, 直接生成一个Job然后
- 添加到链表中.
- deleteJob: 删除Job时, 检查所有Job列表中的具有相同Session和Channel的Job,
- 然后将需要删除的blockID的相应mask设置为1.
- processJob: 对于每个job, 从cur开始, 利用process_P2P_REQUEST_real来传输
- 第一个mask为0的块, 如果都为1, 就删除这个job.
- freeJob: 删除某个JobDes.
- freeJobList: 删除某个Session的所有JobDes, 通常用于该Session退出时使用.
- 8) Interval模块
- Interval模块用在TS中, 用来表示NP上面所有的快区间, 目前块区间由一个开始
- 字段和一个长度字段来标识. 对于Interval的主要操作是merge和delete, merge
- 是将原有的Interval和新的Interval列表合在一齐, 而delete则是从原有的当中
- 去掉新的.
- merge: 算法如下, 使用了缓冲Interval列表tmp.
- if (old[i] < new[j]) tmp[k] = old[i];
- else tmp[k] = new[j];
- 然后再看old和new中哪些能够可以和tmp[k]合并
- delete: 较为复杂一些, 考虑下面几种情况
- old[i]的开始在new[j]的结束后
- old[i]的结束在new[j]的开始前
- old[i]和new[j]有共同部分, 而且
- old[i]含在new[j] 中
- new[j]含在old[i] 中
- 互不包含, new[j] 在前
- 互不包含, old[i] 在前
- 5. 一些快速算法
- 1) 在使用UDP的TS中, 在客户初次登录时, 需要查找空闲的Session, 此外, 客户有可能
- 会重复发送LOGIN消息, 这时需要检查这一客户端是否已经在Session列表中, 当
- 客户端发送消息时, 需要找到相应的Session.
- 为了避免这些查询, 分别使用了如下方法.
- 首先, 建立一个Hash表, 开始的时候所有空闲Session都串到Hash[0]处, 每当来一个
- 新的客户端时,从Hash[0]中取出Session, 链到相应的hashid上. 为此, hash所得的值
- 不能为0, 如果为0, 就返回最大的可能hashid.
- 根据来源端口和IP地址查询Session也使用这一Hash表.
- 客户端发送消息时, 使用了用于验证的7个字节中的前3字节, 用这3字节来标识Session
- 的下标, 这样就避免了查询开销.
- 2) 使用maxid来减少搜索次数.
- 在TCP中没有使用Hash, 使用了maxid这一项, 用来记录Session中最大的id, 由于在Session
- 初始化的时候, 是查找ID最小的空闲Session, 因此可以认为Session是比较紧凑的,
- 由于SP和CP支持的客户端要比TS少得多, 因此这样的处理是可以接受的.
- 在客户退出的时候, 有可能需要更新maxid, 这一更新是由Clientclosure来完成的,
- Clientclosure更新maxid, 然后再调用相应的析构函数.
- 3) 长期idle的连接的超时处理. 由于超时处理需要遍历整个列表, 为了节约系统资源,
- IDLE时间比较长, 此外, 一般还需要定期报告系统统计数字, 因此需要及时性. 为此,
- 一般periodLog或者periodCheck都判断是执行这两者中的哪一种操作.
- 4) 查询CPPeer时, 考虑到目前只支持GCP, 因此直接采用了GCPCHOICE,设置为当前
- 负载最小的GCP, 在GCP报告或者是GCP登录, 退出的时候更新.
- 6. 消息处理
- 1) TS消息处理
- NP2TS_LOGIN: NP向TS登录, 按照来源IP地址和所报告的npport进行hash, 如果距离上次
- 发送NP2TS_LOGIN消息的时间小于SILENCE_TIME, 则直接返回, 否则发送WELCOME消息.
- NP2TS_REPORT: 报告Interval信息, 如果refresh为true, 则重置, 否则则先增加后删除.
- NP2TS_NEED_PEERS: 查询Peer信息, 使用findCPPeer寻找合适的CP, 使用findNPPeers
- 寻找合适的NP. NP寻找时, 找到结果后按照networks来排序, 保证在同一个网络中的
- 排在前面.
- NP2TS_LOGOUT: 退出
- NP2TS_RES_LIST:发送当前NP的所有RESOURCE, 使用addSession来进行处理, 如果还没有这
- 条边, 就添加
- NP2TS_REQ_RES: 添加RES, 并返回Peers
- NP2TS_DEL_RES: 删除RES
- CP2TS_REGISTER: 登录, CP向TS登录, 按照来源IP地址和所报告的npport进行hash,
- 如果距离上次发送CP2TS_REGISTER消息的时间小于SILENCE_TIME, 则直接返回, 否则发送
- WELCOME消息.
- CP2TS_UPDATE: 报告CP负载
- CP2TS_NEED_PEERS: ECP查询用, 目前尚未使用
- 2) SP消息处理
- P2P_HELLO: 加入某个频道,
- 如果频道存在
- 如果是个Media文件: 返回SPUPDATE, 表明这一频道的最小最大blockID
- 否则: 如果这一频道已经结束, 返回结束信息
- 如果频道不存在
- 如果是个Media文件: 返回SPUPDATE, 表明这一频道的最小最大blockID, 建立频道
- 否则: 返回一个SPUPDATE指示错误
- P2P_PUSHLIST: 重置或者是增加删除任务列表. 重置时, 先删除所有的相关任务, 然后
- 再增加或删除.
- CS2SP_REGISTER: 建立频道
- CS2SP_UPDATE: 更新频道信息
- CS2SP_BLOCK: 发送数据块
- 3) CP消息处理
- P2P_HELLO: 加入某个频道, 根据提供的SP地址来建立相应连接
- P2P_PUSHLIST: 重置或者是增加删除任务列表
- P2P_SPUPDATE: SP发来的SPUPDATE, 如果是Media文件, 则不转发给NP
- P2P_RESPONSE: SP发来的数据块.
- 此外CP还需要向TS注册.
- 目前只有GCP一种类型在使用.