Websocket Echo Server Demo
背景
嵌入式設備的應用開發大都依靠C語言來完成,我去研究如何用c語言實現websocket服務器也是為了在嵌入式設備中實現一個ip camera的功能,用戶通過網頁訪問到嵌入式設備的攝像頭以及音頻,在學習的過程中先實現echo server是最基本的。
主要參考資源
具體實現
整個websocket從握手到數據傳輸幀頭的格式不在這里展開,具體參考編寫 WebSocket 服務器——MDN,在這里只介紹一下websocket echo server的實現。
- 頭文件及宏定義
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> /*在握手時需要進行sha1編碼和base64編碼, 在這里用openssl的庫來實現*/ #include <openssl/sha.h> #include <openssl/pem.h> #include <openssl/bio.h> #include <openssl/evp.h> #define BUFFER_SIZE 1024 #define RESPONSE_HEADER_LEN_MAX 1024 #define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 數據幀頭
/*-----------為了便於理解,在這里吧數據幀格式粘出來-------------------
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ --------------------------------------------------------------------*/ typedef struct _frame_head { char fin; char opcode; char mask; unsigned long long payload_length; char masking_key[4]; } frame_head;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 封裝套接字函數
為了使套接字使用看起來簡潔一些,封裝一個被動套接字函數,只需要傳入監聽端口和監聽隊列個數就可以返回套接字描述符,調用者可以直接用這個描述符accept去接收客戶端連接。
int passive_server(int port,int queue) { ///定義sockfd int server_sockfd = socket(AF_INET,SOCK_STREAM, 0); ///定義sockaddr_in struct sockaddr_in server_sockaddr; server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(port); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); ///bind,成功返回0,出錯返回-1 if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1) { perror("bind"); exit(1); } ///listen,成功返回0,出錯返回-1 if(listen(server_sockfd,queue) == -1) { perror("listen"); exit(1); } printf("監聽%d端口\n",port); return server_sockfd; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- Base64編碼函數
握手函數會用到
int base64_encode(char *in_str, int in_len, char *out_str) { BIO *b64, *bio; BUF_MEM *bptr = NULL; size_t size = 0; if (in_str == NULL || out_str == NULL) return -1; b64 = BIO_new(BIO_f_base64()); bio = BIO_new(BIO_s_mem()); bio = BIO_push(b64, bio); BIO_write(bio, in_str, in_len); BIO_flush(bio); BIO_get_mem_ptr(bio, &bptr); memcpy(out_str, bptr->data, bptr->length); out_str[bptr->length-1] = '\0'; size = bptr->length; BIO_free_all(bio); return size; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 逐行讀取函數
握手函數循環調用,每次獲得一行字符串,返回下一行開始位置
/** * @brief _readline * read a line string from all buffer * @param allbuf * @param level * @param linebuf * @return */ int _readline(char* allbuf,int level,char* linebuf) { int len = strlen(allbuf); for (;level<len;++level) { if(allbuf[level]=='\r' && allbuf[level+1]=='\n') return level+2; else *(linebuf++) = allbuf[level]; } return -1; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 握手函數
負責處理新客戶端的連接,接收客戶端http格式的請求,從中獲得Sec-WebSocket-Key對應的值,與魔法字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 進行連接后進行sha1 hash,再將結果(sha1的直接結果,不是轉化為字符串后的結果)進行Base64編碼。最后構造響應頭部,發送響應,與客戶端建立websocket連接。
int shakehands(int cli_fd) { //next line's point num int level = 0; //all request data char buffer[BUFFER_SIZE]; //a line data char linebuf[256]; //Sec-WebSocket-Accept char sec_accept[32]; //sha1 data unsigned char sha1_data[SHA_DIGEST_LENGTH+1]={0}; //reponse head buffer char head[BUFFER_SIZE] = {0}; if (read(cli_fd,buffer,sizeof(buffer))<=0) perror("read"); printf("request\n"); printf("%s\n",buffer); do { memset(linebuf,0,sizeof(linebuf)); level = _readline(buffer,level,linebuf); //printf("line:%s\n",linebuf); if (strstr(linebuf,"Sec-WebSocket-Key")!=NULL) { strcat(linebuf,GUID); // printf("key:%s\nlen=%d\n",linebuf+19,strlen(linebuf+19)); SHA1((unsigned char*)&linebuf+19,strlen(linebuf+19),(unsigned char*)&sha1_data); // printf("sha1:%s\n",sha1_data); base64_encode(sha1_data,strlen(sha1_data),sec_accept); // printf("base64:%s\n",sec_accept); /* write the response */ sprintf(head, "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade: websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "\r\n",sec_accept); printf("response\n"); printf("%s",head); if (write(cli_fd,head,strlen(head))<0) perror("write"); break; } }while((buffer[level]!='\r' || buffer[level+1]!='\n') && level!=-1); return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 字符串反轉函數
用於解決大端小端問題
void inverted_string(char *str,int len) { int i; char temp; for (i=0;i<len/2;++i) { temp = *(str+i); *(str+i) = *(str+len-i-1); *(str+len-i-1) = temp; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 接收及存儲數據幀頭
調用者傳一個數據幀頭結構體指針用於獲取解析后的幀頭
解析過程依照MDN中說的結構解析就好。
int recv_frame_head(int fd,frame_head* head)
{
char one_char;
/*read fin and op code*/ if (read(fd,&one_char,1)<=0) { perror("read fin"); return -1; } head->fin = (one_char & 0x80) == 0x80; head->opcode = one_char & 0x0F; if (read(fd,&one_char,1)<=0) { perror("read mask"); return -1; } head->mask = (one_char & 0x80) == 0X80; /*get payload length*/ head->payload_length = one_char & 0x7F; if (head->payload_length == 126) { char extern_len[2]; if (read(fd,extern_len,2)<=0) { perror("read extern_len"); return -1; } head->payload_length = (extern_len[0]&0xFF) << 8 | (extern_len[1]&0xFF); } else if (head->payload_length == 127) { char extern_len[8]; if (read(fd,extern_len,8)<=0) { perror("read extern_len"); return -1; } inverted_string(extern_len,8); memcpy(&(head->payload_length),extern_len,8); } /*read masking-key*/ if (read(fd,head->masking_key,4)<=0) { perror("read masking-key"); return -1; } return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 去掩碼函數
從客戶端發來的數據是經過異或加密的,我們在解析幀頭的時候獲取到了掩碼,我們通過掩碼可以解碼出原數據。
/** * @brief umask * xor decode * @param data 傳過來時為密文,解碼后的明文同樣存儲在這里 * @param len data的長度 * @param mask 掩碼 */ void umask(char *data,int len,char *mask) { int i; for (i=0;i<len;++i) *(data+i) ^= *(mask+(i%4)); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 發送數據幀頭
int send_frame_head(int fd,frame_head* head) { char *response_head; int head_length = 0; if(head->payload_length<126) { response_head = (char*)malloc(2); response_head[0] = 0x81; response_head[1] = head->payload_length; head_length = 2; } else if (head->payload_length<0xFFFF) { response_head = (char*)malloc(4); response_head[0] = 0x81; response_head[1] = 126; response_head[2] = (head->payload_length >> 8 & 0xFF); response_head[3] = (head->payload_length & 0xFF); head_length = 4; } else { response_head = (char*)malloc(12); response_head[0] = 0x81; response_head[1] = 127; memcpy(response_head+2,head->payload_length,sizeof(unsigned long long)); inverted_string(response_head+2,sizeof(unsigned long long)); head_length = 12; } if(write(fd,response_head,head_length)<=0) { perror("write head"); return -1; } free(response_head); return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 主函數
接收一個連接並循環回射。
int main() { int ser_fd = passive_server(4444,20); struct sockaddr_in client_addr; socklen_t addr_length = sizeof(client_addr); int conn = accept(ser_fd,(struct sockaddr*)&client_addr, &addr_length); shakehands(conn); while (1) { frame_head head; int rul = recv_frame_head(conn,&head); if (rul < 0) break; // printf("fin=%d\nopcode=0x%X\nmask=%d\npayload_len=%llu\n",head.fin,head.opcode,head.mask,head.payload_length); //echo head send_frame_head(conn,&head); //read payload data char payload_data[1024] = {0}; int size = 0; do { int rul; rul = read(conn,payload_data,1024); if (rul<=0) break; size+=rul; umask(payload_data,size,head.masking_key); printf("recive:%s",payload_data); //echo data if (write(conn,payload_data,rul)<=0) break; }while(size<head.payload_length); printf("\n-----------\n"); } close(conn); close(ser_fd); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 客戶端測試用例:
將以下代碼保存為websocket_client.html,用Chrome瀏覽器打開測試。
代碼中console.log是在瀏覽器中按F12在控制台中查看輸出
<button onclick="svc_connectPlatform()"> connect</button> <button onclick="svc_send('hello web')"> send</button> <script> function svc_connectPlatform() { //alert(""); var wsServer = 'ws://192.168.25.157:4444/'; try { svc_websocket = new WebSocket(wsServer); } catch (evt) { console.log("new WebSocket error:" + evt.data); svc_websocket = null; if (typeof(connCb) != "undefined" && connCb != null) connCb("-1", "connect error!"); return; } //alert(""); svc_websocket.onopen = svc_onOpen; svc_websocket.onclose = svc_onClose; svc_websocket.onmessage = svc_onMessage; svc_websocket.onerror = svc_onError; } function svc_onOpen(evt) { console.log("Connected to WebSocket server."); } function svc_onClose(evt) { console.log("Disconnected"); } function svc_onMessage(evt) { console.log('Retrieved data from server: ' + evt.data); } function svc_onError(evt) { console.log('Error occured: ' + evt.data); } function svc_send(msg) { if (svc_websocket.readyState == WebSocket.OPEN) { svc_websocket.send(msg); } else { console.log("send failed. websocket not open. please check."); } } </script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52