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⒌氖奔湫∮賟ILENCE_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一種類型在使用.
展開