一、本人設想的TCP服務器有如下特性:
1.啟動服務,一直監聽端口。
2.有新連接(客戶端)就通知用戶。並把連接接收到的數據回調給用戶。
3.客戶端連接上后用戶可在任意時間發送數據給它。
4.客戶端斷開時關閉或用戶可手動關掉。
以上操作都可以不同線程在完成。
二、使用libuv遇到的問題
由於對libuv不熟悉+其文檔,調用其函數時吃了不少苦頭。
1.libuv的特性
libuv是基於event驅動的,當調用uv_run后就會一直啟動event循環,阻塞其線程(event loop thread)直到沒有事件了uv_run返回。除了uv_async_send函數外,其他函數都是非線程安全的。即其他函數只能在event loop thread里調用,在其他線程調用libuv不保證其正確性。
libuv處處回調,多數有回調的函數都是直到回調函數被觸發時才算調用完成,而非該函數返回就算調用完成。
2.遇到的問題
libuv的這點特性對於我想通過多線程調用tcp sever中的不同操作是一大麻煩事。
2.1.我在另一線程里調用了uv_write發送數據,結果總提示Assertion failed: handle->write_queue_size >= req->queued_bytes, file src/win/tcp.c
最后在google group( https://groups.google.com/forum/#!msg/libuv/iHzv3x-VOr4/KzhJymI6lRkJ )中找到方法:
內部開辟一線程用於發送數據。用戶調用發送函數時把數據壓入隊列,發送線程從隊列中循環取數據,然后調用uv_async_send觸發真正的發送數據函數。數據參數可以通過uv_handle_t.data傳輸。可用uv_sem_wait/ uv_sem_post來控制數據發送先后。
2.2.對於想要關閉一個客戶端,可使用uv_close關閉其所關聯的uv_handle_t,然后把客戶端參數從客戶端隊列中刪除。
但是libuv這種處處回調的函數,調用uv_close返回后並不意味着真正close成功了,此時若把客戶端刪除,則會調用客戶端的析構函數,客戶端的所有變量地址都是未知的了。因為uv_close后對客戶端繼續操作,所以訪問這些未知變量地址會出錯。真正close成功是在uv_close_cb被觸發時。
所以想要delete掉一個客戶端,得調用uv_close,然后在uv_close_cb等待並判斷是哪個客戶端,再把客戶端刪除。
2.3 同uv_close, uv_tcp_connect也一樣,不能uv_tcp_connect就想發送數據,得等其回調函數觸發后才能進行發送數據操作。uv_write也一樣。
總結:libuv好不好,會用才好,不會用坑一大堆。
三、傳輸規則定義
網絡傳輸中不能只接受裸流,必須對數據進行卦包與拆包,一來可防止數據被篡改與丟失,二來方便數據解析。
CSDN上對網絡數據如何定義有討論過:http://bbs.csdn.net/topics/380167545
本人定義的包結構如下:
// 一個數據包的內存結構
//增加包頭與包尾數據,用於檢測包的完整性。檢驗值用於檢測包的完全性。
//|-----head----|--------------------------pack header-------------------|--------------------pack data------------|-----tail----|
//|--包頭1字節--|--[version][head][tail][check][type][datalen][reserve]--|--datalen長度的內存數據(根據type去解析)--|--包尾1字節--|
#pragma pack(1)//將當前字節對齊值設為1
#define NET_PACKAGE_VERSION 0x01
typedef struct _NetPacket{//傳輸自定義數據包頭結構
int32_t version; //封包的版本號,不同版本包的定義可能不同 :0-3
unsigned char header; //包頭-可自定義,例如0x02 :4
unsigned char tail; //包尾-可自定義,例如0x03 :5
unsigned char check[16];//pack data校驗值-16字節的md5二進制數據 :6-21
int32_t type; //包數據的類型 :22-25
int32_t datalen; //包數據的內容長度-不包括此包結構和包頭尾 :26-29
int32_t reserve; //包數據保留字段-暫時不使用 :30-33
}NetPacket;
#define NET_PACKAGE_HEADLEN sizeof(NetPacket)//包頭長度,為固定大小34字節
同時進行了封包與折包工作,詳見packet.h
——————————————————————————————————————————————————————————————————————
代碼已上傳到git: https://github.com/wqvbjhc/libuv_tcp
客戶端的測試例子有缺陷,但服務器完全正常。
服務器可以接收上百路連接。