1. 程式人生 > >Websocket協議

Websocket協議

最新 get protoc sha1 lin 每次 protocol 不用 成功

1Websocket簡介

1WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信(full-duplex)。

HTML5定義了WebSocket協議,能更好的節省服務器資源和帶寬並達到實時通訊



2Websocket原理

1他是基於TCP SOCKET上添加了一些上層協議。

2很多網站為了實現即時通信, 所用的技術都是輪詢(polling), 輪詢是在特定的時間間隔(比如1秒),

由瀏覽器對服務器發出HTTP request,然後由服務器返回最新數據給客戶端瀏覽器,這種傳統的HTTP

request的模式帶來明顯的缺點, 瀏覽器要不斷向服務器發請求, 而HTTP request的header(頭信息)非常長,

包含我們要用的數據卻很小, 這樣會占用很多寬帶,

3Websocket API,瀏覽器和服務器只需要做一個握手的動作,然後瀏覽器和服務器之間就形成了一條

快速通道, 兩者之間就直接可以數據互相傳送。這樣互相溝通的header還是很小的,大概是2Bytes



3長連接和短連接

長連接: 初始化的時候建立連接,一直不關閉

1服務器與客戶端能以最快的速度發送數據

2服務器能主動的向客戶端發送數據

3長連接長期占用網絡資源

4長連接的服務器不方便直接重啟


短連接: 發起連接-->發送數據-->獲取數據-->關閉

1不會長期占用資源

2方便服務器直接重啟

3每次請求與獲取數據都要重新建立連接

4服務器不能像客戶端主動發送數據


比如說正常訪問百度,一旦連上百度,他就會get網頁數據,然後就斷開連接了




4websocket 握手連接

1 客戶端向服務器發送請求websocket的http報文

然後服務器來驗證這個http報文是不是符合websocket的協議

2如果符合,服務器返回握手報文,給客戶端連接成功




5使用js寫一個客戶端

<!DOCTYPE html>
<html>
<head>
	<title>websocket test</title>
</head>

<body>
	<script>
		var ws = new WebSocket('ws://127.0.0.1:8002/ws');	
		//socket打開了
		ws.onopen = function(){
		alert("open");
		ws.send('WebSocket');
		};
		
		//websocket有數據過來了
		ws.onmessage = function(ev){
		alert(ev.data);
		};
		
		//socket關閉了
		ws.onclose = function(ev){
		alert("close");
		};
		
		//websocket有錯誤發生
		ws.onerror = function(ev){
		alert("error");
		};
	</script>
</body>
</html>

服務器接到http報文

技術分享圖片

現在服務器要做的就是驗證這個報文了,看他是否符合websocket規範

首先就是Upgrade:websocket 如果不是就要直接關掉這個請求

其中很重要的一點就是:Sec-WebSocket-key

這個key是客戶端隨機生成的。



6解析http報文

使用c語言的庫,http_parser 這個庫 百度一下就可下載到

github:https://github.com/nodejs/http-parser

我們最終要一部分就是要把Sec-WebSocket-key這個值取出來

我們用key+migic的方式,使用SHA-1加密,base-64加密編碼

migic是一個固定的值258EAFA5-E914-47DA-95CA-C5AB0DC85B11

然後生成報文發送給客戶端,這樣他們就建立了websocket的握手連接了.



初始化

//第一步 借助http_parser,解析客戶端給我們發送來的http報文頭部
struct http_parser p;
//初始化  第二個參數是解析類型
//HTTP_REQUEST請求,HTTP_RESPONSE響應,HTTP_BOTH都解析
http_parser_init(&p, HTTP_REQUEST);




解析http的時候 解析到頭的時候 用這個結構體回調

http_parser_settings 這個結構體


struct http_parser_settings {
  http_cb      on_message_begin;  //消息開始的時候
  http_data_cb on_url;            //解析到url的時候回調
  http_data_cb on_status;
  http_data_cb on_header_field;     // key
  http_data_cb on_header_value;     //  值
  http_cb      on_headers_complete;
  http_data_cb on_body;
  http_cb      on_message_complete;
  /* When on_chunk_header is called, the current chunk length is stored
   * in parser->content_length.
   */
  http_cb      on_chunk_header;
  http_cb      on_chunk_complete;
};

要用到這個回調 on_header_field 就是解析key的時候

static int on_header_field(http_parser* p, const char* at, size_t length);
static int on_header_value(http_parser* p, const char* at, size_t length);

struct http_parser_settings s;
http_parser_settings_init(&s); //初始化要用
s.on_header_field = on_header_field;
s.on_header_value = on_header_value;


開始解析http報文 http_str就是你的報文

http_parser_execute(&p, &s, http_str, strlen(http_str));


//解析head報文 key回調函數


//http 解析回調函數 key
static int on_header_field(http_parser* p, const char* at, size_t length)
{
	strncpy(key_buffer,at,length);
	key_buffer[length] = 0;
	printf("%s:",key_buffer);


	return 0;
}


//http 解析回調函數 value
static int on_header_value(http_parser* p, const char* at, size_t length)
{
	char buffer[128];
	strncpy(buffer, at, length);
	buffer[length] = 0;
	printf("%s\n",buffer);

	return 0;
}


成功解析出來了 至於怎麽拿到想要的 就不用多說了吧

技術分享圖片


通過加密後然後回給客戶端報文


buffer 就是你通過 migic+key通過 sha1 + base64加密獲得的

這兩個加密 網上搜索一大堆 c語言寫的就行了


//會給客戶端的報文

char accept_str[256];

char* wb_accept = "HTTP/1.1 101 Switching Protocols\r\n" \

"Upgrade:websocket\r\n" \

"Connection:Upgrade\r\n" \

"Sec-WebSocket-Accept:%s\r\n" \

"WebSocket-Location: ws://%s:%d\r\n" \

"WebSocket-Protocol:\r\n\r\n";

sprintf(accept_str, wb_accept, buffer,"127.0.0.1",8002);

printf(accept_str);


//發送給客戶端

send(socket, accept_str,strlen(accept_str),0);







7websocket接收數據,

只需要在開始的時候握手,後面就不用去動了2

websocket接收數據的協議

技術分享圖片

1 固定字節(1000 0001 或 1000 0010) 也就是一個字節 十進制:129-130 十六進制:0x81-0x82


2包長度字節,第一位是1,他是固定的 也就是

剩下7位得到一個整數(0,127)數據範圍(表示數據的長度);

也就是他125以內就用一個字節表示長度

如果是126就是2個字節表示長度

如果是127就是8個字節表示長度

就是最後面 數據的長度 不包含前面這些的長度


3mask掩碼為包長之後的4個字節,掩碼後面才是數據,

利用這個掩碼,我們可以將這個數據,將他還原回來,

也就是服務器收到的數據實際上,還是要自己還原的.



4兄弟數據: 得到真實數據的方法:將兄弟數據的每一個字節x,和掩碼的

的第i%4字節做異或運算,其中i是x在兄弟數據中的索引。

技術分享圖片






8websocket發送數據

static void 
on_ws_send_data(struct session*s, unsigned char* pkg_data, int pkg_len)
{
//printf("================send_ws_data============================\n");
//
static unsigned char send_buffer[8096];
//固定的頭
send_buffer[0] = 0x81;
unsigned int ws_send_len;
if (pkg_len <= 125){
//最高位是0  不用管
send_buffer[1] = pkg_len;
ws_send_len = 2;
}
else if (pkg_len <= 0xffff){ //255
send_buffer[1] = 126;
send_buffer[2] = (pkg_len & 0x000000ff);
send_buffer[3] = (pkg_len & 0x0000ff00>>8);
ws_send_len = 4;
}
else{
send_buffer[1] = 127;
send_buffer[2] = (pkg_len & 0x000000ff);
send_buffer[3] = ((pkg_len & 0x0000ff00) >> 8);
send_buffer[4] = ((pkg_len & 0x00ff0000)>>16 );
send_buffer[5] = ((pkg_len & 0xff000000) >> 24);
send_buffer[6] = 0;
send_buffer[7] = 0;
send_buffer[8] = 0;
send_buffer[9] = 0;
ws_send_len = 10;
}
//原始數據
memcpy(send_buffer + ws_send_len,pkg_data,pkg_len);
ws_send_len += pkg_len;
send(s->c_sock, send_buffer, ws_send_len,0);
//printf("send_len%d\n", pkg_len);
//printf("send_data%s\n", pkg_data);
//printf("============================================\n");
}










Websocket協議