C/C+++服務器之libuv的使用實戰

libuv

libuv簡介

1: 開源跨平臺的異步IO庫, 主要功能有網絡異步,文件異步等。
2: libuv主頁: http://libuv.org/
3: libuv是node.js的底層庫;
4: libuv的事件循環模型:
epoll, kqueue, IOCP, event ports;
異步 TCP 與 UDP sockets;
DNS 解析
異步文件讀寫;
信號處理;
高性能定時器;
進程/線程池;

libuv原理

1:異步: 在用戶層同時管理多個句柄請求。
2: loop循環等待所有的事件和句柄,管理好所有的這些請求。
3: 當其中一個請求完成后,loop就會監測得到然后調用用戶指定的回掉函數處理;
4: 例如loop監聽所有的socket,有數據來了后,loop就會處理,然后轉到用戶指定的回調函數。
5: libuv編寫思想:
1> 創建一個對象, 例如socket;
2> 給loop管理這個對象;
3> 并指定一個回調函數,當有事件發生的時候調用這個回調函數, callback;

6: 1>向loop發送請求;
2>指定結束后的回調函數;
3>當請求結束后,調用調函數;

TCP服務器搭建

首先需要下載libuv庫,導入到工程中,設置好include的路徑和鏈接上對應的庫
代碼如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"/*
uv_handle_s 數據結構:
UV_HANDLE_FIELDSuv_stream_t  數據結構;
UV_HANDLE_FIELDS
UV_STREAM_FIELDSuv_tcp_t 數據結構;
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
UV_TCP_PRIVATE_FIELDSuv_tcp_t is uv_stream_t is uv_handle_t;
*/static uv_loop_t* loop = NULL;
static uv_tcp_t l_server; // 監聽句柄;// 當我們的event loop檢車到handle上有數據可以讀的時候,
// 就會調用這個函數, 讓這個函數給event loop準備好讀入數據的內存;
// event loop知道有多少數據,suggested_size,
// handle: 發生讀時間的handle;
// suggested_size: 建議我們分配多大的內存來保存這個數據;
// uv_buf_t: 我們準備好的內存,通過uv_buf_t,告訴even loop;
static void 
uv_alloc_buf(uv_handle_t* handle,size_t suggested_size,uv_buf_t* buf) {if (handle->data != NULL) {free(handle->data);handle->data = NULL;}buf->base = malloc(suggested_size + 1);buf->len = suggested_size;handle->data = buf->base;
}static void
on_close(uv_handle_t* handle) {printf("close client\n");if (handle->data) {free(handle->data);handle->data = NULL;}
}static void 
on_shutdown(uv_shutdown_t* req, int status) {uv_close((uv_handle_t*)req->handle, on_close);free(req);
}static void 
after_write(uv_write_t* req, int status) {if (status == 0) {printf("write success\n");}uv_buf_t* w_buf = req->data;if (w_buf) {free(w_buf);}free(req);
}// 參數: 
// uv_stream_t* handle --> uv_tcp_t;
// nread: 讀到了多少字節的數據;
// uv_buf_t: 我們的數據都讀到到了哪個buf里面, base;
static void after_read(uv_stream_t* stream,ssize_t nread,const uv_buf_t* buf) {// 連接斷開了;if (nread < 0) {uv_shutdown_t* reg = malloc(sizeof(uv_shutdown_t));memset(reg, 0, sizeof(uv_shutdown_t));uv_shutdown(reg, stream, on_shutdown);return;}// endbuf->base[nread] = 0;printf("recv %d\n", nread);printf("%s\n", buf->base);// 測試發送給我們的 客戶端;uv_write_t* w_req = malloc(sizeof(uv_write_t));uv_buf_t* w_buf = malloc(sizeof(uv_buf_t));w_buf->base = buf->base;w_buf->len = nread;w_req->data = w_buf;uv_write(w_req, stream, w_buf, 1, after_write);
}static void
uv_connection(uv_stream_t* server, int status) {printf("new client comming\n");// 接入客戶端;uv_tcp_t* client = malloc(sizeof(uv_tcp_t));memset(client, 0, sizeof(uv_tcp_t));uv_tcp_init(loop, client);uv_accept(server, (uv_stream_t*)client);// end// 告訴event loop,讓他幫你管理哪個事件;uv_read_start((uv_stream_t*)client, uv_alloc_buf, after_read);
}int main(int argc, char** argv) {int ret;loop = uv_default_loop();// Tcp 監聽服務;uv_tcp_init(loop, &l_server); // 將l_server監聽句柄加入到event loop里面;// 你需要event loop來給你做那種管理呢?配置你要的管理類型;struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", 6080, &addr); // ip地址, 端口ret = uv_tcp_bind(&l_server, (const struct sockaddr*) &addr, 0);if (ret != 0) {goto failed;}// 讓event loop來做監聽管理,當我們的l_server句柄上有人連接的時候;// event loop就會調用用戶指定的這個處理函數uv_connection;uv_listen((uv_stream_t*)&l_server, SOMAXCONN, uv_connection);uv_run(loop, UV_RUN_DEFAULT);failed:printf("end\n");system("pause");return 0;
}

UDP服務器搭建

客戶端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")int main(int argc, char** argv) {WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);SOCKET client = socket(AF_INET, SOCK_DGRAM, 0);// 可以bind也可以不綁,如果不要求別人先發給你可以不bind;// end SOCKADDR_IN addr;addr.sin_family = AF_INET;addr.sin_port = htons(6080);addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");int len = sizeof(SOCKADDR_IN);int send_len = sendto(client, "Hello", 5, 0, (const SOCKADDR*)&addr, len);printf("send_len = %d\n", send_len);char buf[128];SOCKADDR_IN sender_addr; // 收到誰發的數據包的地址;int recv_len = recvfrom(client, buf, 128, 0, &sender_addr, &len);if (recv_len > 0) {buf[recv_len] = 0; // 加上結尾符號;printf("%s\n", buf);}WSACleanup();system("pause");return 0;
}
不用libuv版本的服務器
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")int main(int argc, char** argv) {WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);SOCKET client = socket(AF_INET, SOCK_DGRAM, 0);// 可以bind也可以不綁,如果不要求別人先發給你可以不bind;// end SOCKADDR_IN addr;addr.sin_family = AF_INET;addr.sin_port = htons(6080);addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");int len = sizeof(SOCKADDR_IN);int send_len = sendto(client, "Hello", 5, 0, (const SOCKADDR*)&addr, len);printf("send_len = %d\n", send_len);char buf[128];SOCKADDR_IN sender_addr; // 收到誰發的數據包的地址;int recv_len = recvfrom(client, buf, 128, 0, &sender_addr, &len);if (recv_len > 0) {buf[recv_len] = 0; // 加上結尾符號;printf("%s\n", buf);}WSACleanup();system("pause");return 0;
}
libuv的UDP服務器
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"static uv_loop_t* event_loop = NULL;
static uv_udp_t server; // UDP的句柄;static void 
uv_alloc_buf(uv_handle_t* handle,size_t suggested_size,uv_buf_t* buf) {if (handle->data != NULL) {free(handle->data);handle->data = NULL;}handle->data = malloc(suggested_size + 1); // +1測試的時候,我要收字符串,所以呢要加上1來訪結尾符號;buf->base = handle->data;buf->len = suggested_size;
}static void
on_uv_udp_send_end(uv_udp_send_t* req, int status) {if (status == 0) {printf("send sucess\n");}free(req);
}static void 
after_uv_udp_recv(uv_udp_t* handle,ssize_t nread,const uv_buf_t* buf,const struct sockaddr* addr, // 發過來數據包的IP地址 + 端口;unsigned flags) {char ip_addr[128];uv_ip4_name((struct sockaddr_in*)addr, ip_addr, 128);int port = ntohs(((struct sockaddr_in*)addr)->sin_port);printf("ip: %s:%d nread = %d\n", ip_addr, port, nread);char* str_buf = handle->data;str_buf[nread] = 0;printf("recv %s\n", str_buf);uv_buf_t w_buf;w_buf = uv_buf_init("PING", 4);// 寫數據;uv_udp_send_t* req = malloc(sizeof(uv_udp_send_t));uv_udp_send(req, handle, &w_buf, 1, addr, on_uv_udp_send_end);// end 
}int main(int argc, char** argv) {event_loop = uv_default_loop();memset(&server, 0 ,sizeof(uv_udp_t));uv_udp_init(event_loop, &server);// bind端口;struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", 6080, &addr);uv_udp_bind(&server, (const struct sockaddr*)&addr, 0);// end // 告訴事件循環,你要他管理recv事件;uv_udp_recv_start(&server, uv_alloc_buf, after_uv_udp_recv);uv_run(event_loop, UV_RUN_DEFAULT);system("pause");return 0;
}

定時器設計

源碼
 #include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"
#include "time_list.h"#define my_malloc malloc
#define my_free freestruct timer {uv_timer_t uv_timer; // libuv timer handlevoid(*on_timer)(void* udata);void* udata;int repeat_count; // -1一直循環;
};static struct timer* 
alloc_timer(void(*on_timer)(void* udata),void* udata, int repeat_count) {struct timer* t = my_malloc(sizeof(struct timer));memset(t, 0, sizeof(struct timer));t->on_timer = on_timer;t->repeat_count = repeat_count;t->udata = udata;uv_timer_init(uv_default_loop(), &t->uv_timer);return t;
}static void
free_timer(struct timer* t) {my_free(t);
}static void
on_uv_timer(uv_timer_t* handle) {struct timer* t = handle->data;if (t->repeat_count < 0) { // 不斷的觸發;t->on_timer(t->udata);}else {t->repeat_count --;t->on_timer(t->udata);if (t->repeat_count == 0) { // 函數time結束uv_timer_stop(&t->uv_timer); // 停止這個timerfree_timer(t);}}}struct timer*
schedule(void(*on_timer)(void* udata),void* udata,int after_msec,int repeat_count) {struct timer* t = alloc_timer(on_timer, udata, repeat_count);// 啟動一個timer;t->uv_timer.data = t;uv_timer_start(&t->uv_timer, on_uv_timer, after_msec, after_msec);// end return t;
}void
cancel_timer(struct timer* t) {if (t->repeat_count == 0) { // 全部觸發完成,;return;}uv_timer_stop(&t->uv_timer);free_timer(t);
}struct timer*
schedule_once(void(*on_timer)(void* udata),void* udata,int after_msec) {return schedule(on_timer, udata, after_msec, 1);
}
#ifndef __MY_TIMER_LIST_H__
#define __MY_TIMER_LIST_H__// on_timer是一個回掉函數,當timer觸發的時候調用;
// udata: 是用戶傳的自定義的數據結構;
// on_timer執行的時候 udata,就是你這個udata;
// after_sec: 多少秒開始執行;
// repeat_count: 執行多少次, repeat_count == -1一直執行;
// 返回timer的句柄;
struct timer;
struct timer*
schedule(void(*on_timer)(void* udata), void* udata, int after_msec,int repeat_count);// 取消掉這個timer;
void 
cancel_timer(struct timer* t);struct timer*
schedule_once(void(*on_timer)(void* udata), void* udata, int after_msec);
#endif
使用
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"// 獲取當前系統從開機到現在運行了多少毫秒;
#ifdef WIN32
#include <windows.h>
static unsigned int
get_cur_ms() {return GetTickCount();
}
#else
#include <sys/time.h>  
#include <time.h> 
#include <limits.h>static unsigned int
get_cur_ms() {struct timeval tv;// struct timezone tz;gettimeofday(&tv, NULL);return ((tv.tv_usec / 1000) + tv.tv_sec * 1000);
}
#endif static uv_loop_t* event_loop = NULL;#include "time_list.h"struct timer* t = NULL;
static void
on_time_func(void* udata) {static int count = 0;char* str = (udata);printf("%s\n", str);count++;if (count == 10) {cancel_timer(t);}
}static void
on_time_func2(void* udata) {char* str = (udata);printf("%s\n", str);
}int main(int argc, char** argv) {event_loop = uv_default_loop();// 每隔5秒掉一次,掉4次;t = schedule(on_time_func, "HelloWorld!!!", 1000, -1);// schedule_once(on_time_func2, "CallFunc!!!", 1000);uv_run(event_loop, UV_RUN_DEFAULT);system("pause");return 0;
}

異步文件讀寫

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>#include "uv.h"/*
uv_fs_open:loop: 事件循環,uv_fs_t req請求對象;path: 文件路徑flags: 標志0mode: 可讀,可寫... O_RDONLY O_RDWR...
*/
static uv_loop_t* event_loop = NULL;
static uv_fs_t req;
static uv_fs_t w_req;
static uv_file fs_handle;
static char mem_buffer[1024];
/*
uv_file
文件句柄對象: 打開文件以后的文件handle
uv_fs_tresult,每次請求的結果都是這個值來返回;打開文件: result返回打開文件句柄對象uv_file;讀文件: result讀到的數據長度;寫文件: result為寫入的數據長度;
*//*
釋放掉這個請求req所占的資源
uv_req_cleanup(req);*//*
stdin: 標注的輸入文件, scanf, cin>>
stdout: 標準的輸出文件 printf;
fprintf(stdout, "xxxxxxx");每個進程在運行的時候:
stdin文件句柄與stdout這個文件句柄始終是打開的;
stdin:標準的輸入文件, 
stdout: 標準的輸出;
*/static void 
after_read(uv_fs_t* req) {printf("read %d byte\n", req->result);mem_buffer[req->result] = 0; // 字符串結尾;printf("%s\n", mem_buffer);uv_fs_req_cleanup(req);uv_fs_close(event_loop, req, fs_handle, NULL);uv_fs_req_cleanup(req);
}static void 
on_open_fs_cb(uv_fs_t* req) {// 打開文件fs_handle = req->result;uv_fs_req_cleanup(req);printf("open success end\n");uv_buf_t buf = uv_buf_init(mem_buffer, 1024);uv_fs_read(event_loop, req, fs_handle, &buf, 1, 0, after_read);
}int main(int argc, char** argv) {event_loop = uv_default_loop();// step1:打開文件:uv_fs_open(event_loop, &req, "./test.txt", 0, O_RDONLY, on_open_fs_cb);uv_buf_t w_buf = uv_buf_init("Good! BYCW!!!!", 12);uv_fs_write(event_loop, &w_req, (uv_file)1, &w_buf, 1, 0, NULL);uv_fs_req_cleanup(&w_req);uv_run(event_loop, UV_RUN_DEFAULT);system("pause");return 0;
}

websocket協議

1: websocket是基于TCP的一種協議,是H5的一種傳輸協議;
2: websocket連接協議;
3: websocket 發送數據協議;
4: websocket 接受數據協議;
5: websocket 關閉協議;

建立連接

1:客戶端向服務器發送http報文,服務器處理后回客戶端連接報文;
2: 客戶端發過來的報文:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

3: 服務器回應客戶端報文:
:key+migic , SHA-1 加密, base-64 加密
key=”來自客戶端的隨機”, migic = “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”;
static 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-Protocol:chat\r\n\r\n”;

Sec-WebSocket-Key/Accept的作用

  • 避免服務端收到非法的websocket連接(比如http客戶端不小心請求連接websocket服務,此時服務端可以直接拒絕連接)
  • 確保服務端理解websocket連接。因為ws握手階段采用的是http協議,因此可能ws連接是被一個http服務器處理并返回的,此時客戶端可以通過Sec-WebSocket-Key來確保服務端認識ws協議。(并非百分百保險,比如總是存在那么些無聊的http服務器,光處理Sec-WebSocket-Key,但并沒有實現ws協議。。。)
  • 用瀏覽器里發起ajax請求,設置header時,Sec-WebSocket-Key以及其他相關的header是被禁止的。這樣可以避免客戶端發送ajax請求時,意外請求協議升級(websocket upgrade)
    可以防止反向代理(不理解ws協議)返回錯誤的數據。比如反向代理前后收到兩次ws連接的升級請求,反向代理把第一次請求的返回給cache住,然后第二次請求到來時直接把cache住的請求給返回(無意義的返回)。
  • Sec-WebSocket-Key主要目的并不是確保數據的安全性,因為Sec-WebSocket-Key、Sec-WebSocket-Accept的轉換計算公式是公開的,而且非常簡單,最主要的作用是預防一些常見的意外情況(非故意的)。
關閉連接

1: 主動關閉socket
2: 客戶端關閉socket:
收到 0x88 開頭的數據包;
收到tcp socket關閉事件;

發送數據
  1. 固定字節(0x81)
  2. 包長度字節
  3. 原始數據
接收數據

1)固定字節(1000 0001或1000 0010);
2)包長度字節, 去掉最高位, 剩下7為得到一個整數(0, 127);125以內的長度直接表示就可以了;
126表示后面兩個字節表示大小,127表示后面的8個字節是數據的長度;(高位存在低地址)
3)mark 掩碼為包長之后的 4 個字節
4)兄弟數據:
得到真實數據的方法:將兄弟數據的每一字節 x ,和掩碼的第 i%4 字節做 xor 運算,其中 i 是 x 在兄弟數據中的索引

代碼
客戶端網頁
<!DOCTYPE html>
<html>
<head><title>skynet WebSocket example</title>
</head>
<body>   <script>var ws = new WebSocket('ws://127.0.0.1:8001/ws');ws.onopen = function(){alert("open");ws.send('WebSocket'); };ws.onmessage = function(ev){alert(ev.data);};ws.onclose = function(ev){alert("close");};ws.onerror = function(ev){console.log(ev);alert("error");};</script>
</body>
</html>
服務器

該代碼在前面的TCP服務器的基礎上修改

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "../3rd/http_parser/http_parser.h"
#include "../3rd/crypto/sha1.h"
#include "../3rd/crypto/base64_encoder.h"#include "uv.h"struct ws_context {int is_shake_hand; // 是否已經握手char* data; // 讀取數據的buf
};static uv_loop_t* loop = NULL;
static uv_tcp_t l_server; // 監聽句柄;static void 
uv_alloc_buf(uv_handle_t* handle,size_t suggested_size,uv_buf_t* buf) {struct ws_context* wc = handle->data;if (wc->data != NULL) {free(wc->data);wc->data = NULL;}buf->base = malloc(suggested_size + 1);buf->len = suggested_size;wc->data = buf->base;
}static void
on_close(uv_handle_t* handle) {printf("close client\n");if (handle->data) {struct ws_context* wc = handle->data;free(wc->data);wc->data = NULL;free(wc);handle->data = NULL;}free(handle);
}static void 
on_shutdown(uv_shutdown_t* req, int status) {uv_close((uv_handle_t*)req->handle, on_close);free(req);
}static void 
after_write(uv_write_t* req, int status) {if (status == 0) {printf("write success\n");}uv_buf_t* w_buf = req->data;if (w_buf) {free(w_buf->base);free(w_buf);}free(req);
}static void
send_data(uv_stream_t* stream, unsigned char* send_data, int send_len) {uv_write_t* w_req = malloc(sizeof(uv_write_t));uv_buf_t* w_buf = malloc(sizeof(uv_buf_t));unsigned char* send_buf = malloc(send_len);memcpy(send_buf, send_data, send_len);w_buf->base = send_buf;w_buf->len = send_len;w_req->data = w_buf;uv_write(w_req, stream, w_buf, 1, after_write);
}static char filed_sec_key[512];
static char value_sec_key[512];
static int is_sec_key = 0;
static int has_sec_key = 0;static int 
on_ws_header_field(http_parser* p, const char *at, size_t length) {if (strncmp(at, "Sec-WebSocket-Key", length) == 0) {is_sec_key = 1;}else {is_sec_key = 0;}return 0;
}static int
on_ws_header_value(http_parser* p, const char *at, size_t length) {if (!is_sec_key) {return 0;}strncpy(value_sec_key, at, length);value_sec_key[length] = 0;has_sec_key = 1;return 0;
}// 
static char* wb_migic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// base64(sha1(key + wb_migic))
static 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-Protocol:chat\r\n\r\n";static void
ws_connect_shake_hand(uv_stream_t* stream, unsigned char* data, int data_len) {http_parser_settings settings;http_parser_settings_init(&settings);settings.on_header_field = on_ws_header_field;settings.on_header_value = on_ws_header_value;http_parser p;http_parser_init(&p, HTTP_REQUEST);is_sec_key = 0;has_sec_key = 0;http_parser_execute(&p, &settings, data, data_len);if (has_sec_key) { // 解析到了websocket里面的Sec-WebSocket-Keyprintf("Sec-WebSocket-Key: %s\n", value_sec_key);// key + migicstatic char key_migic[512];static char sha1_key_migic[SHA1_DIGEST_SIZE];static char send_client[512];int sha1_size;sprintf(key_migic, "%s%s", value_sec_key, wb_migic);crypt_sha1((unsigned char*)key_migic, strlen(key_migic), (unsigned char*)&sha1_key_migic, &sha1_size);int base64_len;char* base_buf = base64_encode(sha1_key_migic, sha1_size, &base64_len);sprintf(send_client, wb_accept, base_buf);base64_encode_free(base_buf);send_data(stream, (unsigned char*)send_client, strlen(send_client));}
}static void 
ws_send_data(uv_stream_t* stream, unsigned char* data, int len) {int head_size = 2;if (len > 125 && len < 65536) { // 兩個字節[0, 65535]head_size += 2;}else if (len >= 65536) { // 不做處理head_size += 8;}unsigned char* data_buf = malloc(head_size + len);data_buf[0] = 0x81;if (len <= 125) {data_buf[1] = len;}else if (len > 125 && len < 65536) {data_buf[1] = 126;data_buf[2] = (len & 0x0000ff00) >> 8;data_buf[3] = (len & 0x000000ff);}else { // 127不寫了return;}memcpy(data_buf + head_size, data, len);send_data(stream, data_buf, head_size + len);free(data_buf);
}// 收到的是一個數據包;
static void 
ws_on_recv_data(uv_stream_t* stream, unsigned char* data, unsigned int len) {if (data[0] != 0x81 && data[0] != 0x82) {return;}unsigned int data_len = data[1] & 0x0000007f;int head_size = 2;if (data_len == 126) { // 后面兩個字節表示的是數據長度;data[2], data[3]data_len = data[3] | (data[2] << 8);head_size += 2;}else if (data_len == 127) { // 后面8個字節表示數據長度; 2, 3, 4, 5 | 6, 7, 8, 9unsigned int low = data[5] | (data[4] << 8) | (data[3] << 16) | (data[2] << 24);unsigned int hight = data[9] | (data[8] << 8) | (data[7] << 16) | (data[6] << 24);data_len = low;head_size += 8;}unsigned char* mask = data + head_size;unsigned char* body = data + head_size + 4;for (unsigned int i = 0; i < data_len; i++) { // 遍歷后面所有的數據;body[i] = body[i] ^ mask[i % 4];}// teststatic char test_buf[4096];memcpy(test_buf, body, data_len);test_buf[data_len] = 0;printf("%s\n", test_buf);// 發送協議ws_send_data(stream, "Hello", strlen("Hello"));// end }static void after_read(uv_stream_t* stream,ssize_t nread,const uv_buf_t* buf) {// 連接斷開了;if (nread < 0) {uv_shutdown_t* reg = malloc(sizeof(uv_shutdown_t));memset(reg, 0, sizeof(uv_shutdown_t));uv_shutdown(reg, stream, on_shutdown);return;}// endprintf("start websocket!!!\n");struct ws_context* wc = stream->data;// 如果沒有握手,就進入websocket握手協議if (wc->is_shake_hand == 0) {ws_connect_shake_hand(stream, buf->base, buf->len);wc->is_shake_hand = 1;return;}// end // 如果客戶端主動關閉;0x88, 狀態碼if ((unsigned char)(buf->base[0]) == 0x88) { // 關閉printf("ws closing!!!!");return;}// end // ws正常的數據, 暫時不處理粘包這些問題;// 假設我們一次性都可以收完websocket發過來的數據包;ws_on_recv_data(stream, buf->base, nread);// end }static void
uv_connection(uv_stream_t* server, int status) {printf("new client comming\n");uv_tcp_t* client = malloc(sizeof(uv_tcp_t));memset(client, 0, sizeof(uv_tcp_t));uv_tcp_init(loop, client);uv_accept(server, (uv_stream_t*)client);struct ws_context* wc = malloc(sizeof(struct ws_context));memset(wc, 0, sizeof(struct ws_context));client->data = wc;uv_read_start((uv_stream_t*)client, uv_alloc_buf, after_read);
}int main(int argc, char** argv) {int ret;loop = uv_default_loop();uv_tcp_init(loop, &l_server); struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", 8001, &addr); // ip地址, 端口ret = uv_tcp_bind(&l_server, (const struct sockaddr*) &addr, 0);if (ret != 0) {goto failed;}uv_listen((uv_stream_t*)&l_server, SOMAXCONN, uv_connection);uv_run(loop, UV_RUN_DEFAULT);failed:printf("end\n");system("pause");return 0;
}
總結

WebSocket是一種基于TCP協議的雙向通信協議,與HTTP/HTTPS協議相比,具有更低的延遲和更高的實時性。使用WebSocket協議可以實現實時通信、數據推送、在線游戲等功能。但是,WebSocket協議并沒有被廣泛采用的原因有以下幾個方面:

兼容性問題:WebSocket協議是HTML5標準中新增的協議,對于較老的瀏覽器可能不支持。雖然現代主流瀏覽器已經支持WebSocket協議,但是在一些特殊情況下(例如企業內部應用、舊版瀏覽器等),WebSocket協議的兼容性可能成為問題。

安全問題:由于WebSocket協議實現了雙向通信,因此在網絡安全方面需要更加關注。例如,在進行WebSocket通信時需要進行恰當的身份驗證和加密,以避免數據泄露和劫持等問題。

部署和負載問題:WebSocket協議需要建立長連接,因此在部署WebSocket服務時需要考慮服務器負載和連接管理等問題。如果不恰當地部署WebSocket服務,可能會導致服務器資源浪費、連接管理不當等問題。

用處有限:對于大多數網站來說,HTTP請求已經能滿足需求。使用WebSocket可以帶來實時性改善,但對許多網站功能影響不大。所以如果沒有實時交互等強需求,就不一定需要引入WebSocket。

盡管WebSocket協議存在一些問題,但是在特定的場景下仍然是一種非常有用的協議。例如,在實時通信、數據推送、在線游戲等場景下,WebSocket協議可以發揮出其優勢。

HTTP服務器

步驟:
1: 等待socket 連接進來;
2: 接收socket發送過來的數據;–> http協議格式的請求數據包
3: http解析url
4: 根據url來找對應的響應處理;
5: 將要返回的數據打包成http響應格式,發給客戶端;
6: 關閉客戶端的連接;

以Get請求為例:
1: 客戶端提交請求;
2: 服務端解析get的url找到對應的響應;
3: 服務端解析get參數;
5: 處理,返回結果給客戶端:
“HTTP/1.1 %d %s\r\n” 狀態碼,狀態描述
“transfer-encoding:%s\r\n”, “identity”
“content-length: %d\r\n\r\n” 內容長度
body數據

6: get攜帶參數格式?uname=xiaoming&key=18074532323

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"
#include "../3rd/http_parser/http_parser.h"/*
url 注冊管理模塊
*/typedef void(*web_get_handle)(uv_stream_t* stream, char* url);
typedef void(*web_post_handle)(uv_stream_t* stream, char* url, char* body);struct url_node {char* url; // url地址web_get_handle get; // url地址對應的處理函數;web_post_handle post; // url地址對應的post函數
};static struct url_node*
alloc_url_node(char* url, web_get_handle get, web_post_handle post) {struct url_node* node = malloc(sizeof(struct url_node));memset(node, 0, sizeof(struct url_node));node->url = strdup(url);node->get = get;node->post = post;return node;
}static struct url_node* url_node[1024];
static int url_count = 0;static void
register_web_handle(char* url, web_get_handle get, web_post_handle post) {url_node[url_count] = alloc_url_node(url, get, post);url_count ++;
}static struct url_node* 
get_url_node(char* url, int len) {for (int i = 0; i < url_count; i++) {if (strncmp(url, url_node[i]->url, len) == 0) {return url_node[i];}}return NULL;
}/*
{
[100] = "Continue",
[101] = "Switching Protocols",
[200] = "OK",
[201] = "Created",
[202] = "Accepted",
[203] = "Non-Authoritative Information",
[204] = "No Content",
[205] = "Reset Content",
[206] = "Partial Content",
[300] = "Multiple Choices",
[301] = "Moved Permanently",
[302] = "Found",
[303] = "See Other",
[304] = "Not Modified",
[305] = "Use Proxy",
[307] = "Temporary Redirect",
[400] = "Bad Request",
[401] = "Unauthorized",
[402] = "Payment Required",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[406] = "Not Acceptable",
[407] = "Proxy Authentication Required",
[408] = "Request Time-out",
[409] = "Conflict",
[410] = "Gone",
[411] = "Length Required",
[412] = "Precondition Failed",
[413] = "Request Entity Too Large",
[414] = "Request-URI Too Large",
[415] = "Unsupported Media Type",
[416] = "Requested range not satisfiable",
[417] = "Expectation Failed",
[500] = "Internal Server Error",
[501] = "Not Implemented",
[502] = "Bad Gateway",
[503] = "Service Unavailable",
[504] = "Gateway Time-out",
[505] = "HTTP Version not supported",
}
*//*
uv_handle_s 數據結構:
UV_HANDLE_FIELDSuv_stream_t  數據結構;
UV_HANDLE_FIELDS
UV_STREAM_FIELDSuv_tcp_t 數據結構;
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
UV_TCP_PRIVATE_FIELDSuv_tcp_t is uv_stream_t is uv_handle_t;
*/static uv_loop_t* loop = NULL;
static uv_tcp_t l_server; // 監聽句柄;// 當我們的event loop檢車到handle上有數據可以讀的時候,
// 就會調用這個函數, 讓這個函數給event loop準備好讀入數據的內存;
// event loop知道有多少數據,suggested_size,
// handle: 發生讀時間的handle;
// suggested_size: 建議我們分配多大的內存來保存這個數據;
// uv_buf_t: 我們準備好的內存,通過uv_buf_t,告訴even loop;
static void 
uv_alloc_buf(uv_handle_t* handle,size_t suggested_size,uv_buf_t* buf) {if (handle->data != NULL) {free(handle->data);handle->data = NULL;}buf->base = malloc(suggested_size + 1);buf->len = suggested_size;handle->data = buf->base;
}static void
on_close(uv_handle_t* handle) {printf("close client\n");if (handle->data) {free(handle->data);handle->data = NULL;}
}static void 
on_shutdown(uv_shutdown_t* req, int status) {uv_close((uv_handle_t*)req->handle, on_close);free(req);
}static void 
after_write(uv_write_t* req, int status) {if (status == 0) {printf("write success\n");}uv_buf_t* w_buf = req->data;if (w_buf) {free(w_buf);}free(req);
}static void
send_data(uv_stream_t* stream, unsigned char* send_data, int send_len) {uv_write_t* w_req = malloc(sizeof(uv_write_t));uv_buf_t* w_buf = malloc(sizeof(uv_buf_t));unsigned char* send_buf = malloc(send_len);memcpy(send_buf, send_data, send_len);w_buf->base = send_buf;w_buf->len = send_len;w_req->data = w_buf;uv_write(w_req, stream, w_buf, 1, after_write);
}static char req_url[4096];
int on_url(http_parser* p, const char *at, size_t length) {strncpy(req_url, at, length);req_url[length] = 0;return 0;
}static int 
filter_url(char* url) {char* walk = url;int len = 0;while (*url != '?' && *url != '\0') {len++;url++;}return len;
}static void 
on_http_request(uv_stream_t* stream, char* req, int len) {http_parser_settings settings;http_parser_settings_init(&settings);settings.on_url = on_url;http_parser p;http_parser_init(&p, HTTP_REQUEST);http_parser_execute(&p, &settings, req, len);// http get是可以攜帶參數的:// http://www.baidu.com:6080/test?name=xiaoming&age=34int url_len = filter_url(req_url);struct url_node* node = get_url_node(req_url, url_len);printf("%s\n", req_url);if (node == NULL) {return;}switch (p.method) { // 請求方法case HTTP_GET:if (node->get != NULL) {node->get(stream, req_url);}break;case HTTP_POST:if (node->post != NULL) {}break;}
}
// 參數: 
// uv_stream_t* handle --> uv_tcp_t;
// nread: 讀到了多少字節的數據;
// uv_buf_t: 我們的數據都讀到到了哪個buf里面, base;
static void after_read(uv_stream_t* stream,ssize_t nread,const uv_buf_t* buf) {// 連接斷開了;if (nread < 0) {uv_shutdown_t* reg = malloc(sizeof(uv_shutdown_t));memset(reg, 0, sizeof(uv_shutdown_t));uv_shutdown(reg, stream, on_shutdown);return;}// endbuf->base[nread] = 0;printf("recv %d\n", nread);printf("%s\n", buf->base);// 處理on_http_request(stream, buf->base, buf->len);// end
}static void
uv_connection(uv_stream_t* server, int status) {printf("new client comming\n");// 接入客戶端;uv_tcp_t* client = malloc(sizeof(uv_tcp_t));memset(client, 0, sizeof(uv_tcp_t));uv_tcp_init(loop, client);uv_accept(server, (uv_stream_t*)client);// end// 告訴event loop,讓他幫你管理哪個事件;uv_read_start((uv_stream_t*)client, uv_alloc_buf, after_read);
}static void 
test_get(uv_stream_t* stream, char* url) {printf("%s\n", url);char* body = "SUCCESS TEST1";static char respons_buf[4096];char* walk = respons_buf;sprintf(walk, "HTTP/1.1 %d %s\r\n", 200, "OK");walk += strlen(walk);sprintf(walk, "transfer-encoding:%s\r\n", "identity");walk += strlen(walk);sprintf(walk, "content-length: %d\r\n\r\n", strlen(body));walk += strlen(walk);sprintf(walk, "%s", body);send_data(stream, respons_buf, strlen(respons_buf));
}static void 
test2_get(uv_stream_t* stream, char* url) {printf("%s\n", url);char* body = "SUCCESS TEST2";static char respons_buf[4096];char* walk = respons_buf;sprintf(walk, "HTTP/1.1 %d %s\r\n", 200, "OK");walk += strlen(walk);sprintf(walk, "transfer-encoding:%s\r\n", "identity");walk += strlen(walk);sprintf(walk, "content-length: %d\r\n\r\n", strlen(body));walk += strlen(walk);sprintf(walk, "%s", body);send_data(stream, respons_buf, strlen(respons_buf));
}int main(int argc, char** argv) {// 注冊一下web請求函數register_web_handle("/test", test_get, NULL);register_web_handle("/test2", test2_get, NULL);// end int ret;loop = uv_default_loop();// Tcp 監聽服務;uv_tcp_init(loop, &l_server); // 將l_server監聽句柄加入到event loop里面;// 你需要event loop來給你做那種管理呢?配置你要的管理類型;struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", 6080, &addr); // ip地址, 端口ret = uv_tcp_bind(&l_server, (const struct sockaddr*) &addr, 0);if (ret != 0) {goto failed;}// 讓event loop來做監聽管理,當我們的l_server句柄上有人連接的時候;// event loop就會調用用戶指定的這個處理函數uv_connection;uv_listen((uv_stream_t*)&l_server, SOMAXCONN, uv_connection);uv_run(loop, UV_RUN_DEFAULT);failed:printf("end\n");system("pause");return 0;
}

多線程與工作隊列

1: 線程相關函數:
uv_thread_create: 創建一個線程;
uv_thread_self: 獲取當前線程id號;
uv_thread_join: 等待線程結束;
2: 鎖:
uv_mutex_init: 初始化鎖;
uv_mutex_destroy: 銷毀鎖;
uv_mutex_lock: 獲取鎖,如果被占用,就等待;
uv_mutex_trylock: 嘗試獲取鎖,如果獲取不到,直接返回,不等待;
uv_mutex_unlock: 釋放鎖;
3: 等待/觸發事件;
uv_cond_init: 創建條件事件;
uv_cond_destroy: 銷毀條件事件;
uv_cond_signal: 觸發條件事件;
uv_cond_broadcast: 廣播條件事件;
uv_cond_wait/uv_cond_timedwait: 等待事件/等待事件超時;

工作隊列:
1: libuv在啟動的時候會創建一個線程池;
2: 線程池默認啟動的線程數目是4個,最大是128個線程;
3: uv_queue_work工作隊列原理:
step1: libuv啟動線程池,等待任務的調度;
step2: 用戶給工作隊列一個執行函數,工作隊列執行完后,通知主線程;
step3: 主線程在執行之前設置的回掉函數;
4: 工作隊列的使用:
有很多操作,比如讀文件,比如讀數據庫,比如讀redis都會等待,所以使用工作隊列,
可以工作隊列來執行,然后通知主線程,這樣就不會卡主線程了。給工作隊列一個任務(函數),并指定好任務完成的回掉函數,線程池就會調度去執行這個任務函數,完成后,通知主線程,主線程調用回掉函數;

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"// 工作隊列的用處:
// 1:復雜的算法放到工作隊列 ;
// 2:IO放到我們工作隊列,獲取數據庫結果;
// ....// 是在線程池里面另外一個線程里面調用,不在主線程;
static void 
thread_work(uv_work_t* req) {// printf("user data = %d \n", (int)req->data);printf("thread_work id 0x%d:\n", uv_thread_self());
}// 當工作隊列里面的線程執行完上面的任務后,通知主線程;
// 主線程調用這個函數;
static void 
on_work_complete(uv_work_t* req, int status) {printf("on_work_complete thread id 0x%d:\n", uv_thread_self());
}int main(int argc, char** argv) {uv_work_t uv_work;printf("main id 0x%d:\n", uv_thread_self());uv_work.data = (void*)6;uv_queue_work(uv_default_loop(), &uv_work, thread_work, on_work_complete);uv_run(uv_default_loop(), UV_RUN_DEFAULT);system("pause");return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/42786.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/42786.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/42786.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python結合MobileNetV2:圖像識別分類系統實戰

一、目錄 算法模型介紹模型使用訓練模型評估項目擴展 二、算法模型介紹 圖像識別是計算機視覺領域的重要研究方向&#xff0c;它在人臉識別、物體檢測、圖像分類等領域有著廣泛的應用。隨著移動設備的普及和計算資源的限制&#xff0c;設計高效的圖像識別算法變得尤為重要。…

設計模式-結構型-08-組合模式

文章目錄 1、學校院系展示需求2、組合模式基本介紹3、組合模式示例3.1、 解決學校院系展示&#xff08;透明模式1&#xff09;3.2、高考的科目&#xff08;透明模式2&#xff09;3.3、高考的科目&#xff08;安全組合模式&#xff09; 4、JDK 源碼分析5、注意事項和細節 1、學校…

存儲過程編程-創建(CREATE PROCEDURE)、執行(EXEC)、刪除(DROP PROCEDURE)

一、定義 1、存儲過程是在SQL服務器上存儲的已經編譯過的SQL語句組。 2、存儲過程分為三類&#xff1a;系統提供的存儲過程、用戶定義的存儲過程和擴展存儲過程 &#xff08;1&#xff09;系統提供的存儲過程&#xff1a;在安裝SQL Server時&#xff0c;系統創建了很多系統存…

AI機器人在企業拓客上常見的功能有哪些

AI機器人具備多種功能&#xff0c;這些功能主要基于其被設計和訓練的目的。整理了一些常見的AI機器人功能&#xff1a; 1. 語音識別與自然語言處理&#xff1a; - 語音識別&#xff1a;將用戶的語音輸入轉換為文本&#xff0c;以便機器人可以理解和處理。 - 自然語言處理…

QCC5181 歌詞歌曲名多國語言顯示替代QCC5125 CSR8675

QCC518X作為Qualcomm新一代藍牙技術芯片&#xff0c;支持最新藍牙協議V5.4&#xff0c;較QCC512X系列&#xff0c;它有更強大的DSP、CPU。除支持USB、I2S、SPDIF等接口外&#xff0c;還擴展了LE Audio功能&#xff0c;擴展支持AptX Lossless。以5181為例&#xff0c;我們還擴展…

vscode語言模式

1.背景 寫vue3ts項目的時候&#xff0c;用到了volar插件&#xff0c;在單文件使用的時候&#xff0c;鼠標懸浮在代碼上面會有智能提示&#xff1b; 但是最近volar插件提示被棄用了&#xff0c;然后我按照它的官方提示&#xff0c;安裝了Vue-official擴展插件&#xff0c;但是…

Banana Pi BPI-M5 Pro 低調 SBC 采用 Rockchip RK3576 八核 Cortex-A72/A53 AIoT SoC

Banana Pi BPI-M5 Pro&#xff0c;也稱為 Armsom Sige5&#xff0c;是一款面向 AIoT 市場的低調單板計算機 (SBC)&#xff0c;由 Rockchip RK3576 八核 Cortex-A72/A53 SoC 驅動&#xff0c;提供Rockchip RK3588和RK3399 SoC 之間的中檔產品。 該主板默認配備 16GB LPDDR4X 和…

如何大幅減少 Vue.js 中的包大小和加載時間,提升用戶體驗!

大家好,我是CodeQi! 一位熱衷于技術分享的碼仔。 你知道嗎,根據Google 的一項研究,如果網站加載時間超過 3 秒,53% 的移動用戶會離開該網站? 性能優化是一個經常討論的話題,但很多開發人員并不關心提高應用的速度。 在前端開發中,優化包大小和加載時間對于提升用戶體…

下一代 CLI 工具,使用Go語言用于構建令人驚嘆的網絡應用程序

大家好&#xff0c;今天給大家分享一個創新的命令行工具Gowebly CLI&#xff0c;它專注于使用Go語言來快速構建現代Web應用程序。 Gowebly CLI 是一款免費開源軟件&#xff0c;有助于在后端使用 Go、在前端使用 htmx 和 hyperscript 以及最流行的 CSS 框架輕松構建令人驚嘆的 W…

入門PHP就來我這(高級)15 ~ 圖書刪除功能

有膽量你就來跟著路老師卷起來&#xff01; -- 純干貨&#xff0c;技術知識分享 路老師給大家分享PHP語言的知識了&#xff0c;旨在想讓大家入門PHP&#xff0c;并深入了解PHP語言。 今天給大家接著上篇文章實現圖書刪除功能&#xff0c;來實現刪除圖書信息記錄行的功能。 1 刪…

高顏值官網(3):家居用品網站12個,好的創意都在這里。

hello&#xff0c;大家好&#xff0c;我是大千UI工場&#xff0c;本文為大家帶來家居用品網站UI&#xff0c;供大家欣賞。

項目代碼優化(1)——下單邏輯

給一個電商開發的系統排查&#xff0c;發現漏洞很多。很多經驗不夠的開發者很容易忽視的邏輯錯誤陷阱。在給一個項目做二次開發時候&#xff0c;檢測到的相關經典案例。這里整理支付和產品相關的邏輯&#xff0c;方便后續查看。&#xff0c;這里進行一些簡單的邏輯漏洞梳理與修…

Ubuntu 22.04 LTS 上安裝 MySQL8.0.23(在線安裝)

目錄 在線安裝MySQL 步驟1&#xff1a;更新軟件包列表 步驟2&#xff1a;安裝MySQL服務器 步驟3&#xff1a;啟動MySQL服務 步驟4&#xff1a;檢查MySQL狀態 步驟5&#xff1a;修改密碼、權限 在線安裝MySQL 步驟1&#xff1a;更新軟件包列表 在進行任何軟件安裝之前&a…

p9函數(1)

int Add(int x,int y) { int z0; zxy; return z; } int main() { int a10; int b20; int sumAdd(a,b); printf("%d\n",sum); return 0; } 字符串求長度 int main() { char arr1[]"bit"; char arr2[20]"###…

移動UI: 什么特征會被認為是簡潔風格,用案例告訴你

什么是簡潔風格&#xff0c;恐怕一百個人有一百個是理解&#xff0c;本文通過理論分析案例的方式進行探討。 移動 UI 中的簡潔風格通常具有以下幾個特征&#xff1a; 1. 平面化設計&#xff1a; 簡潔風格的移動 UI 善于運用平面化設計&#xff0c;即去除過多的陰影、漸變和立…

水冷液冷負載系統的六種基本類型

您可以選擇六種基本類型的冷卻系統&#xff0c;以滿足負載的冷卻需求。每個人都有其優點和缺點。本文旨在識別不同類型的冷卻系統并確定它們的優缺點&#xff0c;以便您可以根據自己的需求做出明智的選擇。 液體冷卻系統有六種基本類型&#xff1a; 1.液對液 2.閉環干燥系統…

聚類標簽的藝術:SKlearn中的數據聚類標簽分配策略

聚類標簽的藝術&#xff1a;SKlearn中的數據聚類標簽分配策略 在機器學習領域&#xff0c;聚類是一種無監督學習方法&#xff0c;旨在將數據集中的樣本劃分為若干個簇&#xff0c;使得同一簇內的樣本相似度高&#xff0c;而不同簇之間的樣本相似度低。聚類標簽分配是聚類過程中…

深度講解 UUID/GUID 的結構、原理以及生成機制

目錄 一. 前言 二. 被廣泛使用 三. UUID 的結構 3.1. 必須了解的 3.2. 十六進制數字字符&#xff08;hexDigit&#xff09; 3.3. UUID 基本結構 3.4. 類型&#xff08;變體&#xff09;和保留位 3.5. 版本&#xff08;子類型&#xff09; 3.6. 時間戳 3.7. 時鐘序列 …

管理《歐盟數字服務法》交易者要求

《數字服務法》合規性 根據《數字服務法》(DSA) 的要求&#xff0c;對于在歐盟地區 (EU) 通過 App Store 分發 App 的所有交易商&#xff0c;Apple 需要驗證并顯示其聯系信息。請指明你是否將以交易商或非交易商的身份在歐盟地區分發任何內容。進一步了解你是否應為交易商。 …

[激光原理與應用-101]:南京科耐激光-激光焊接-焊中檢測-智能制程監測系統IPM介紹 - 5 - 3C行業應用 - 電子布局類型

目錄 前言&#xff1a; 一、激光在3C行業的應用概述 1.1 概述 1.2 激光焊接在3C-電子行業應用 二、3C電子行業中激光焊接 2.1 紐扣電池 2.2 均溫板 2.3 指紋識別器 2.4 攝像頭模組 2.5 IC芯片切割 三、3C行業中激光切割 四、激光在3C行業中的其他應用 4.1 涂層去除…