1、本人設想的TCP服務器有以下特性:git
1.啓動服務,一直監聽端口。github
2.有新鏈接(客戶端)就通知用戶。並把鏈接接收到的數據回調給用戶。安全
3.客戶端鏈接上後用戶可在任意時間發送數據給它。服務器
4.客戶端斷開時關閉或用戶可手動關掉。網絡
以上操做均可以不一樣線程在完成。多線程
2、使用libuv遇到的問題async
因爲對libuv不熟悉+其文檔,調用其函數時吃了很多苦頭。tcp
1.libuv的特性函數
libuv是基於event驅動的,當調用uv_run後就會一直啓動event循環,阻塞其線程(event loop thread)直到沒有事件了uv_run返回。除了uv_async_send函數外,其餘函數都是非線程安全的。即其餘函數只能在event loop thread裏調用,在其餘線程調用libuv不保證其正確性。oop
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好很差,會用纔好,不會用坑一大堆。
3、傳輸規則定義
網絡傳輸中不能只接受裸流,必須對數據進行卦包與拆包,一來可防止數據被篡改與丟失,二來方便數據解析。
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
客戶端的測試例子有缺陷,但服務器徹底正常。
服務器能夠接收上百路鏈接。