您好,登錄后才能下訂單哦!
1數據協議
1TCP,websocket,http這些是屬于底層的傳輸協議。保障服務器和客戶端可以收發數據。
2假設接收到了數據之后,有什么用呢,比如服務器需要知道客戶端發來的數據是干嘛的,
所以就需要用到數據協議。 也就是客戶端和服務器商量好一種數據協議.
根據這種自定義的協議收發數據, 服務器就能聽懂 客戶端的協議了。
3比如說登陸協議,用戶名密碼啊這些. 這就是上層的協議。
4游戲數據協議每一個數據包都不能過大, 比如64k,如果超出可以在發送的
時候,把這些數據進行分包. 這樣做可以防止惡意***,
6分包協議:
第一種模式:包頭+包體模式
第二種模式:\r\n為結束符號的模式;
命令組成的協議: 比如一個登陸協議
發送:
命令主類型號:用戶登陸的命令,用戶注冊的命令
命令子類型號 用戶名 密碼
返回:
命令類型號,命令子類型號,返回碼, 為多少就返回用戶的數據
2二進制數據協議
1 二進制協議原理:
直接將內存里的對象保存為二進制數據,然后通過封包(size+(二進制數據))
的方式發送出去, 解包的時候,讀取size,然后讀取二進制數據,再根據二進制的
結構體描述文件來解開這個包,獲取每個數據成員的數據.
2設計原理:
//協議的結構體 struct person{ int main_type. int sub_type, char* name, int uid, int age, int sex, ... } //首先將這個結構體里面的每個數據成員對應的值寫入到內存,然后一個封包好的地址 //序列化二進制 打包 unsigned char* pack_person(結構體) 然后進行封包 [包頭 size ]+[包體] //反序列號 解包 struct person* unpack_person(unsigned char* data,int len);
3二進制協議的設計優點:
體積小,傳輸的性能好,高效
4二進制協議的缺點:
有一些語言的支持不好,比如javascript,腳本語言去解析內存,
本身需要c/c++的支持,然后到處腳本的接口,所以這種模式非常不適合h6游戲.
不適合服務器和客戶端使用不同的語言.
3json數據協議
1 json數據協議,為了改變二進制的不足,改變二進制的封包與解包需要
以來于每個協議的對象,使用跨語言的數據交換格式json與xml相比,體積會比
xml小,可讀性比二進制好,跨語言的封包和解包,每個語言只需要實現json的解碼編碼即可
2json數據格式的封包格式,不采用size+body的方式,因為腳本語言不適合直接操作字節,
所以采用\r\n的模式, 收到/r/n后認為就是一個數據包,所以在編碼好的json字符串里
不能有\r\n, 但是字符串里面有\r\n的話呢
這樣的話就是要把這些數據轉換成base64編碼
4二進制數據傳輸服務器的設計
首先設計協議的結構,客戶端和服務器公用同一個封包和拆包的代碼。
這個包是這樣設計的,前面兩個字節放長度,然后緊接著4個字節放協議類型,后面再放數據包。
//使用#define 或者 enum來定義一個協議 enum{ USER_LOGIN = 0, //用戶登錄 }; //設計登陸的 數據包1 struct user_login{ char* name; //賬號 char* passwd;//密碼 int channel; 一個標志 }; //返回給客戶端登陸結果 數據包2 struct user_login_response { int status;//登陸狀態 1就是登陸成功 char* name; //名字 int level; //等級 }; 不同的包要不同的處理API 數據包1 就是對登陸的請求進行 封包 int command_login_pack(int cmd_type,struct user_login_req* req, unsigned char* dst){ //unsigned 防止最高位擴展 //無符號比有符號能保存2倍于有符號類型的正整數 //cmd_type就是 協議類型 然后就是這個結構體,out是一個輸出的指針 //執行輸出的內存的指針 unsigned cahr* walk = dst; int len= 0; //前面4個字節cmd_type 因為你可能有很多這樣的命令 *((int*)walk) = cmd_type;//將這個變量以地址形式顯示,然后取他的值 walk += 4; //內存向前4個字節 //用戶名和密碼 sprintf(walk, "%s", req->uname) //跳過這個字符串的長度 + 1 是因為有0的結尾符 walk += strlen(walk) + 1; sprintf(walk, "%s", req->upasswd); walk += strlen(walk) + 1; *((int*)walk) = req->channel; walk += 4; len = walk - dst;//長度 return len; } //登陸請求的解包設計 void command_login_unpack(unsigned char* data, int len, struct user_login_req* out){ //data就是服務器收到的二進制數據 char* walk = (char*)data; out->uname = _strdup(walk); walk += strlen(walk) + 1; //+1就是結尾符 out->upasswd = _strdup(walk); //字符串拷貝函數 需要free釋放內存 walk += strlen(walk) + 1; out->channel = *(int*)walk; } //返回結果封包和解包 int login_response_pack(int cmd_type,struct user_login_response* respons, unsigned char* out) { //unsigned 防止最高位擴展 //無符號比有符號能保存2倍于有符號類型的正整數 unsigned char* walk = out; *(int*)walk = cmd_type; walk += 4; *(int*)walk = respons->status; walk += 4; sprintf(walk,"%s", respons->name); walk += (strlen(walk) + 1); *(int*)walk = respons->level; walk += 4; return (walk - out); } void login_response_unpack(unsigned char* data, int len,struct user_login_response* out) { unsigned char* walk = data; out->status = *(int*)walk; walk += 4; out->name = _strdup(walk); walk += (strlen(walk) + 1); out->level = *(int*)walk; walk += 4; }
然后 客戶端和服務器 需要公用這個 協議文件
首先客戶端會創建一個請求
//從這里開始發送登錄請求 //////////////////////////////客戶端發送請求 struct user_login req; // 用戶登陸的結構體 req.uname = "小明"; //賬號 req.upasswd = "1123456"; //密碼 req.channel = 10; //一個標記 char send_buf[4096]; //封包 跳過前面兩個字節 用來存長度 int len = command_login_pack(USER_LOGIN, &req ,send_buf+2); //返回的len就是長度直接把這個長度個 這個 緩沖區 *((unsigned int *)send_buf) = (len + 2); send(s, send_buf, len + 2, 0); //發送給客戶端 // 從這里收取服務器的處理結果了 ////////////////////////////////服務器收到處理請求 int size = (*(unsigned short*)io_data->pkg); //獲取前面兩個字節的長度 //內存這里要+2個字節 才是協議 data += 2; //前面4個字節總是包的命令 也就是協議 switch (*(int*)data){ case USER_LOGIN:{ //判斷是不是登陸協議 //解包 //之后先調用回調函數 SERVER.cmd_func[USER_LOGIN](s,data+4,len-4); +4個字節 就是協議的長度 }break; } /////////////////////////////////登陸處理回調函數 // 解包 struct user_login_req req; command_login_unpack(data, len, &req); //然后就能拿到完整的數據了 printf("%s:%s==%d登錄請求\n", req.uname,req.upasswd,req.channel); /////////////////////////////到這里 應該就是要查詢數據庫了 //返回ok 隨便返回一數據 struct user_login_response res; res.level = 100; res.name = "張三"; res.status = 1; //登錄OK //封包 unsigned char send_buf[256]; len = login_response_pack(USER_LOGIN, &res,send_buf); 在發送前我們要在這個字符串前面加兩個字節的表示長度 //len就是你要的長度 char* send_buf = malloc(len + 2); //先申請一個內存 memcpy(send_buf + 2, data,len); //把數據跳過前面兩個字節進行拷貝 //把長度賦值給前面兩個字節 首先len+2 把len的值以地址顯示,然后取值 *(unsigned short*)send_buf = (len + 2); 發送給客戶端 ////////////////////////////////////////////////////客戶端 收到響應 len = recv(s, send_buf,4096,0); struct user_login_response respons; //解包 if ((*(int*)(send_buf + 2)) == USER_LOGIN){ login_response_unpack(send_buf + 2 + 4, len - 2 - 4, &respons); printf("請求結果:%d==%s\n", respons.status, respons.status ? "成功" : "失敗"); printf("用戶等級:%d=用戶姓名:%s\n", respons.level, respons.name); }
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。