1. 程式人生 > >Linux下c語言實驗Websocket通訊 含客戶端和伺服器測試程式碼

Linux下c語言實驗Websocket通訊 含客戶端和伺服器測試程式碼

Websocket是一種可雙向通訊的網路協議,其底層的資料收發是基於socket的,所以使用c語言來實現理論上是沒有問題的,主要難點在於協議中要求對個別資料進行加密處理,這些加密方法(庫)在java、c#等專門開發web的平臺中都是自帶的API(隨調隨到),而在用到c語言時則苦於去尋找這些加密方法的原始碼和庫,這使得用c來實現Websocket變得繁瑣而吐血!所以非要用c語言來實現Websocket的童鞋,要做好刀耕火種的準備。。。

前面已經翻閱過很多博文,不管是協議還是c的程式碼都可以找到很多,本文也是參考了這些前輩的資料而得,但苦在蒐羅到的程式碼都是片段或不夠工整的,所以本文重點在嘗試整合c實現Websocket的程式碼,以方便後來的小白快速上手使用(大牛隨便喵喵留下點建議就好)。協議解析部分比較粗糙,已經瞭解過的人可以不用看一、二章。

一、建立連線

一切的開始,先上一張網路資料抓包圖(這裡用的Wireshark軟體,還不知道抓包的童鞋可自行百度先玩玩)

GET /null HTTP/1.1
Connection: Upgrade
Host: 172.16.104.78:9999
Sec-WebSocket-Key: J2BJc+GQuSw34hi2TjyVpg==
Sec-WebSocket-Version: 13
Upgrade: websocket

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Server: Microsoft-HTTPAPI/2.0
Connection
: Upgrade Sec-WebSocket-Accept: uGY1yScptmHNgZqrDnpq1Ws1xho= Date: Sat, 15 Jul 2017 15:35:16 +08 ..Hello !...lx!. .M.LY..I am Server_Test...lx!.L.L./.H...~. .U..You are carefree ......lx!.L.L...S. D.LY..You are carefree ....!\O^O/ <-.<- TAT =.=# -.-! .....h.R"K...(..f<.w|N.y}A.z.6.sj ...You are carefree ....!\O^O/ <-.<- TAT =.=# -.-! .....n../M.B...\k:.9qH.7pG.4.0.=g&...You are carefree ....!\O^O/ <-.<- TAT =.=# -.-! ........e.>.F.[.!.\.;.=.:.0.O.>.-.R..You are carefree ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

這裡寫圖片描述

這是一張websocket通訊下,伺服器和客戶端互動時的資料抓包,圖中紅色、藍色分別是客戶端、伺服器發出的資料。

websocket實現資料通訊的步驟: 
1.client向server傳送http請求,資料內容如同圖中第一大段紅色字串,其中攜帶了3個引數。 
①要呼叫server的介面的路徑字串(不明白先不管) 
②伺服器的IP和埠 
③握手Key 
大家可以把這當作一個模版放到程式碼裡。

const char httpDemo[] = "GET %s HTTP/1.1\r\n"
                        "Connection: Upgrade\r\n"
                        "Host: %s:%d\r\n"
                        "Sec-WebSocket-Key: %s\r\n"
                        "Sec-WebSocket-Version: 13\r\n"
                        "Upgrade: websocket\r\n\r\n";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.server收到http請求後,檢查3個引數內容OK,然後返回攜帶引數的特定內容 
④迴應的握手Key 
⑤時間字串(格式: Date: 星期, 日 月 年 時:分:秒 時區) 
同樣大家可以作為模版使用

const char httpDemo[] = "HTTP/1.1 101 Switching Protocols\r\n"
                        "Upgrade: websocket\r\n"
                        "Server: Microsoft-HTTPAPI/2.0\r\n"
                        "Connection: Upgrade\r\n"
                        "Sec-WebSocket-Accept: %s\r\n"
                        "%s\r\n\r\n";  //時間打包 // 格式如 "Date: Tue, 20 Jun 2017 08:50:41 CST\r\n"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.client收到返回後,檢查返回狀態(主要看”101”)以及拿自己發出的握手Key (J2BJc+GQuSw34hi2TjyVpg==) 和收到server返回的Key (uGY1yScptmHNgZqrDnpq1Ws1xho=) 按照一套加密方式(這裡用的sha1雜湊加密,只一次握手)進行匹配OK,之後client和server就可以互發資料了

二、資料傳輸

通過上面抓包的截圖還看到,建立連線之後互發的資料是比較奇怪的,很多字元並不是可見的ASCII碼,這是因為Websocket協議裡對資料的傳輸做了一些規定,簡單說就是有一定的打包格式,其權威的解釋請看這裡draft-ietf-hybi-thewebsocketprotocol-13 - The WebSocket Protocol,不權威的解釋請繼續往下看

把上面的抓包資料按Hex16顯示(這裡只看後面互發資料的內容)

    000000C5  81 07 48 65 6c 6c 6f 20  21                        ..Hello  !
000000A1  81 87 c3 6c 78 21 8b 09  14 4d ac 4c 59            ...lx!.. .M.LY
    000000CE  81 10 49 20 61 6d 20 53  65 72 76 65 72 5f 54 65   ..I am S erver_Te
    000000DE  73 74                                              st
000000AE  81 90 c3 6c 78 21 8a 4c  19 4c e3 2f 14 48 a6 02   ...lx!.L .L./.H..
000000BE  0c 7e 97 09 0b 55                                  .~...U
    000000E0  81 14 59 6f 75 20 61 72  65 20 63 61 72 65 66 72   ..You ar e carefr
    000000F0  65 65 20 2e 2e 2e                                  ee ...
000000C4  81 8f c3 6c 78 21 8a 4c  19 4c e3 0f 19 53 a6 0a   ...lx!.L .L...S..
000000D4  0a 44 a6 4c 59                                     .D.LY
    000000F6  81 14 59 6f 75 20 61 72  65 20 63 61 72 65 66 72   ..You ar e carefr
    00000106  65 65 20 2e 2e 2e                                  ee ...
    0000010C  81 21 5c 4f 5e 4f 2f 20  20 3c 2d 2e 3c 2d 20 20   .!\O^O/   <-.<-  
    0000011C  54 41 54 20 20 3d 2e 3d  23 20 20 2d 2e 2d 21 20   TAT  =.= #  -.-! 
    0000012C  2e 2e 2e                                           ...
000000D9  81 9a 68 b6 52 22 4b 93  0c 01 28 f6 12 66 3c f1   ..h.R"K. ..(..f<.
000000E9  77 7c 4e 90 79 7d 41 9d  7a 08 36 93 73 6a 20 ff   w|N.y}A. z.6.sj .
    0000012F  81 14 59 6f 75 20 61 72  65 20 63 61 72 65 66 72   ..You ar e carefr
    0000013F  65 65 20 2e 2e 2e                                  ee ...
    00000145  81 21 5c 4f 5e 4f 2f 20  20 3c 2d 2e 3c 2d 20 20   .!\O^O/   <-.<-  
    00000155  54 41 54 20 20 3d 2e 3d  23 20 20 2d 2e 2d 21 20   TAT  =.= #  -.-! 
    00000165  2e 2e 2e                                           ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

額~還是先放一張官方的圖。。。 
這裡寫圖片描述 
翻譯一下 
這裡寫圖片描述 
這裡寫圖片描述 
簡單概括一下,每包資料組成按順序歸類為: 
資料頭(1位元組) + 是否掩碼標誌(1個二進位制位) + 資料長度(半位元組~8位元組) + 掩碼(4位元組或沒有) + 資料內容

如果覺得亂,看不明白,那這裡用對應的顏色標註再解釋 
這裡寫圖片描述 
可以看到0x81是資料頭:最高位FIN必須置1即0x80,最低位用0x1代表該包資料型別為文字類,所以得到資料頭0x81。

先看server打包的資料的第二位0x07:最高位表示是否使用掩碼,這裡為0表示不用;後7位表示資料長度,這裡為0x07 = 7,也就是“48 65 6c 6c 6f 20 21”這7個數據的長度咯~

再看client打包的資料的第二位0x87:最高位這裡為0x80表示使用掩碼;後7位這裡為0x07 = 7,所以0x87後面緊跟著的是4位元組是掩碼“c3 6c 78 21”,然後才是7個數據“8b 09 14 4d ac 4c 59”。

以上博主測試的只是小資料包,還不能完全反映圖示的資訊,這裡記錄資料長度的方式有三種:

第一種: 0<資料長度<126(0x7E),使用半個位元組來記錄資料長度,也就是上面例子

81 07 48 65 6c 6c 6f 20  21
81 8f c3 6c 78 21 8a 4c  19 4c e3 0f 19 53 a6 0a
  • 1
  • 2

第二種: 126=<資料長度<65536(0x10000),資料包的第二位強制為 0x7E(再並上掩碼位),然後緊跟的2個位元組表示資料長度,再接著才是掩碼和資料,例如

81 fe 00 a3 3a d2 f4 7c 59 b3 96 1a 0a ...
  • 1

第三種: 65536=<資料長度<=0xFFFFFFFFFFFFFFFF(誰tm一包資料上G,8個位元組記錄長度),資料包的第二位強制為 0x7F(再並上掩碼位),然後緊跟的8個位元組表示資料長度,再接著才是掩碼和資料。

==注意== 協議嚴格規定資料量不足的必須使用少位元組的記錄方式,也就是你不能裝逼給個 7f 然後跟 00 00 00 00 00 00 00 01。

說完資料長度,再來解析下掩碼,這裡掩碼是固定4個位元組長度的,內容可以用隨機數生成。 
這4個掩碼會依次和要傳送的資料進行異或處理,最後得到資料區裡的資料,例如

81 87 c3 6c 78 21 8b 09  14 4d ac 4c 59
  • 1

掩碼為”c3 6c 78 21”,資料區”8b 09 14 4d ac 4c 59”,實際資料是”Helo !”即”48 65 6C 6C 6F 20 21”,打包過程為:

0x8b = 0xc3 X0r 0x48 
0x09 = 0x6c X0r 0x65 
0x14 = 0x78 X0r 0x6c 
0x4d = 0x21 X0r 0x6c 
0xac = 0xc3 X0r 0x6f // 這裡迴圈使用4個掩碼 
0x4c = 0x6c X0r 0x20 
0x59 = 0x78 X0r 0x21

那麼解碼呢?異或的解碼任然是異或! 
附帶一提,掩碼處理後的資料會出現大量的非可見ASCII字元,甚至資料0x00,不要隨便使用0x00來判斷資料結束。

==注意== 規定client發資料給server時必須使用掩碼處理,而server下發資料給client時一般不進行掩碼處理

三、程式碼實驗

websocket_common.h


#ifndef _WEBSOCKET_COMMON_H_
#define _WEBSOCKET_COMMON_H_

#include <stdio.h>   
#include <stdlib.h>  
#include <string.h>     // 使用 malloc, calloc等動態分配記憶體方法
#include <stdbool.h>
#include <time.h>       // 獲取系統時間

#include <errno.h>
#include <netinet/in.h>  
#include <fcntl.h>      // socket設定非阻塞模式
#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/un.h> 
#include <sys/epoll.h>  // epoll管理伺服器的連線和接收觸發

#include <pthread.h>    // 使用多執行緒

// websocket根據data[0]判別資料包型別    比如0x81 = 0x80 | 0x1 為一個txt型別資料包
typedef enum{
    WCT_MINDATA = -20,      // 0x0:標識一箇中間資料包
    WCT_TXTDATA = -19,      // 0x1:標識一個txt型別資料包
    WCT_BINDATA = -18,      // 0x2:標識一個bin型別資料包
    WCT_DISCONN = -17,      // 0x8:標識一個斷開連線型別