Mongoose 笔记系列用于记录学习 Mongoose 的一些内容。
Mongoose 是一个 C/C++ 的网络库。它为 TCP、UDP、HTTP、WebSocket、MQTT 实现了事件驱动的、非阻塞的 API。
项目地址:
https://github.com/cesanta/mongoose
下面通过学习 Mongoose 项目代码中的 websocket-client 示例程序 ,来学习如何使用 Mongoose 实现一个简单的 Websocket 客户端。使用树莓派平台进行开发验证。
websocket-client 的示例程序非常简短,代码如下:
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// Example Websocket client. Usage:
// 1. Start websocket server: cd ../websocket-server && make
// 2. In another terminal, start this client: make#include "mongoose.h"static const char *s_url = "ws://localhost:8000/websocket";// Print websocket response and signal that we're done
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {if (ev == MG_EV_OPEN) {c->is_hexdumping = 1;} else if (ev == MG_EV_ERROR) {// On error, log error messageMG_ERROR(("%p %s", c->fd, (char *) ev_data));} else if (ev == MG_EV_WS_OPEN) {// When websocket handshake is successful, send messagemg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT);} else if (ev == MG_EV_WS_MSG) {// When we get echo response, print itstruct mg_ws_message *wm = (struct mg_ws_message *) ev_data;printf("GOT ECHO REPLY: [%.*s]\n", (int) wm->data.len, wm->data.ptr);}if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE || ev == MG_EV_WS_MSG) {*(bool *) fn_data = true; // Signal that we're done}
}int main(void) {struct mg_mgr mgr; // Event managerbool done = false; // Event handler flips it to truestruct mg_connection *c; // Client connectionmg_mgr_init(&mgr); // Initialise event managermg_log_set(MG_LL_DEBUG); // Set log levelc = mg_ws_connect(&mgr, s_url, fn, &done, NULL); // Create clientwhile (c && done == false) mg_mgr_poll(&mgr, 1000); // Wait for echomg_mgr_free(&mgr); // Deallocate resourcesreturn 0;
}
下面我们从main函数开始分析代码。
首先是变量定义,其中struct mg_mgr是用于保存所有活动连接的事件管理器。变量done用于判断何时停止下面的mg_mgr_poll函数的循环调用。struct mg_connection是一个连接描述符,这里是客户端的连接。
struct mg_mgr mgr; // Event managerbool done = false; // Event handler flips it to truestruct mg_connection *c; // Client connection
初始化一个事件管理器,将上面的 struct mg_mgr变量 mgr 中的数据进行初始化。
mg_mgr_init(&mgr); // Initialise event manager
设置 Mongoose 日志记录级别,设置等级为 MG_LL_DEBUG。
mg_log_set(MG_LL_DEBUG); // Set log level
创建 WebSocket 客户端连接,它分配所需的资源并启动连接过程。其中mgr是事件管理器,s_url是指定的远程 URL,fn是事件处理函数。 这里还把变量done的地址给设置进去了,这个指针会在事件处理函数中作为参数fn_data传递,用于后续的断开连接设置。
c = mg_ws_connect(&mgr, s_url, fn, &done, NULL); // Create client
其中的s_url是一个全局静态变量。WebSocket 的协议标识符是ws,如果 Over SSL,则为wss。
static const char *s_url = "ws://localhost:8000/websocket";
接下来是事件循环,当创建 WebSocket 客户端连接成功,c不为NULL,且done为false时进行循环。mg_mgr_poll 遍历所有连接,接受新连接,发送和接收数据,关闭连接,并为各个事件调用事件处理函数。
while (c && done == false) mg_mgr_poll(&mgr, 1000); // Wait for echo
mg_mgr_free 用于关闭所有连接,释放所有资源。
mg_mgr_free(&mgr);
分析完main函数后,我们看下事件处理函数fn的代码。
首先是判断是否接收到 MG_EV_OPEN 事件,收到MG_EV_OPEN 事件表示已创建连接。该事件在分配连接并将其添加到事件管理器之后立即发送。
如果符合条件则将c->is_hexdumping置1,开启Hexdump in/out traffic功能,会将接收和发送的数据以16进制的形式打印出来。
if (ev == MG_EV_OPEN) {c->is_hexdumping = 1;}
接下来判断是否接收到的 MG_EV_ERROR 事件,如果是则表示出错,然后打印错误信息。
} else if (ev == MG_EV_ERROR) {// On error, log error messageMG_ERROR(("%p %s", c->fd, (char *) ev_data));}
判断如果接收到的是 MG_EV_WS_OPEN 事件,则表示 websocket 握手已完成,然后我们发送一条 hello 信息给 Websocket 服务器。mg_ws_send函数用于 WebSocket 消息的发送,WEBSOCKET_OP_TEXT是 WebSocket 消息类型之一,表示要发送的有效载荷数据为文本数据。
} else if (ev == MG_EV_WS_OPEN) {// When websocket handshake is successful, send messagemg_ws_send(c, "hello", 5, WEBSOCKET_OP_TEXT);}
判断如果接收到的是 MG_EV_WS_MSG 事件,表示接收到 WebSocket 消息。接着将函数参数ev_data转换为 struct mg_ws_message,这个结构体表示 WebSocket 消息。然后我们将收到的 WebSocket 消息打印出来。
} else if (ev == MG_EV_WS_MSG) {// When we get echo response, print itstruct mg_ws_message *wm = (struct mg_ws_message *) ev_data;printf("GOT ECHO REPLY: [%.*s]\n", (int) wm->data.len, wm->data.ptr);}
判断如果接收到的是 MG_EV_ERROR 事件,或者 MG_EV_CLOSE 事件,或者 MG_EV_WS_MSG 事件,则将fn_data的值设置为true,也就是main函数中的变量done设置为true,让循环结束,停止mg_mgr_poll函数的事件循环调用。
if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE || ev == MG_EV_WS_MSG) {*(bool *) fn_data = true; // Signal that we're done}
到这里 websocket-client 的示例程序代码就都解析完了,下面实际运行一下 websocket-client 程序。
在测试前首先需要一个 WebSocket 服务器,我们可以使用 Mongoose 的 websocket-server 示例程序,先编译并运行该示例程序:
pi@raspberrypi:~ $ cd Desktop/study/mongoose/examples/websocket-server/
pi@raspberrypi:~/Desktop/study/mongoose/examples/websocket-server $ make
cc ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 -o example main.c
./example
Starting WS listener on ws://localhost:8000/websocket
WebSocket 服务器准备好之后,打开 websocket-client 示例程序,编译并运行:
pi@raspberrypi:~ $ cd Desktop/study/mongoose/examples/websocket-client/
pi@raspberrypi:~/Desktop/study/mongoose/examples/websocket-client $ make
cc ../../mongoose.c -I../.. -W -Wall -o example main.c
./example
3d6170 3 mongoose.c:3473:mg_connect 1 0xffffffff ws://localhost:8000/websocket
3d6170 3 mongoose.c:4330:mg_connect_res 1 0x4 -> 7f000001:8000 pend
3d6170 3 mongoose.c:4255:write_conn 1 0x4 snd 157/2048 rcv 0/0 n=157 err=115
3d6170 2 mongoose.c:4075:iolog
-- 1 127.0.0.1:59732 -> 127.0.0.1:8000 157
0000 47 45 54 20 2f 77 65 62 73 6f 63 6b 65 74 20 48 GET /websocket H
0010 54 54 50 2f 31 2e 31 0d 0a 55 70 67 72 61 64 65 TTP/1.1..Upgrade
0020 3a 20 77 65 62 73 6f 63 6b 65 74 0d 0a 48 6f 73 : websocket..Hos
0030 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 0d 0a 43 6f t: localhost..Co
0040 6e 6e 65 63 74 69 6f 6e 3a 20 55 70 67 72 61 64 nnection: Upgrad
0050 65 0d 0a 53 65 63 2d 57 65 62 53 6f 63 6b 65 74 e..Sec-WebSocket
0060 2d 56 65 72 73 69 6f 6e 3a 20 31 33 0d 0a 53 65 -Version: 13..Se
0070 63 2d 57 65 62 53 6f 63 6b 65 74 2d 4b 65 79 3a c-WebSocket-Key:
0080 20 4e 48 45 49 6a 6f 63 76 6b 7a 47 44 44 6c 49 NHEIjocvkzGDDlI
0090 36 4f 38 30 58 30 41 3d 3d 0d 0a 0d 0a 6O80X0A==....
3d6170 3 mongoose.c:4244:read_conn 1 0x4 snd 0/2048 rcv 0/2048 n=129 err=115
3d6170 2 mongoose.c:4075:iolog
-- 1 127.0.0.1:59732 <- 127.0.0.1:8000 129
0000 48 54 54 50 2f 31 2e 31 20 31 30 31 20 53 77 69 HTTP/1.1 101 Swi
0010 74 63 68 69 6e 67 20 50 72 6f 74 6f 63 6f 6c 73 tching Protocols
0020 0d 0a 55 70 67 72 61 64 65 3a 20 77 65 62 73 6f ..Upgrade: webso
0030 63 6b 65 74 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e cket..Connection
0040 3a 20 55 70 67 72 61 64 65 0d 0a 53 65 63 2d 57 : Upgrade..Sec-W
0050 65 62 53 6f 63 6b 65 74 2d 41 63 63 65 70 74 3a ebSocket-Accept:
0060 20 63 61 59 75 78 5a 64 33 75 69 30 6f 37 74 33 caYuxZd3ui0o7t3
0070 6f 7a 36 52 6e 4d 67 4c 6b 68 42 51 3d 0d 0a 0d oz6RnMgLkhBQ=...
0080 0a .
3d6171 3 mongoose.c:4255:write_conn 1 0x4 snd 11/2048 rcv 0/2048 n=11 err=115
3d6171 2 mongoose.c:4075:iolog
-- 1 127.0.0.1:59732 -> 127.0.0.1:8000 11
0000 81 85 f6 5f 5f 15 9e 3a 33 79 99 ...__..:3y.
3d6171 3 mongoose.c:4244:read_conn 1 0x4 snd 0/2048 rcv 0/2048 n=7 err=115
3d6171 2 mongoose.c:4075:iolog
-- 1 127.0.0.1:59732 <- 127.0.0.1:8000 7
0000 81 05 68 65 6c 6c 6f ..hello
GOT ECHO REPLY: [hello]
3d6171 3 mongoose.c:3450:mg_close_conn 1 0x4 closed
3d6171 3 mongoose.c:3535:mg_mgr_free All connections closed
pi@raspberrypi:~/Desktop/study/mongoose/examples/websocket-client $
可以看到整个操作过程的收发消息都打印了出来,先是连接到服务器,接着通过 HTTP 使用Upgrade: websocket进行握手请求,然后返回得到101 Switching Protocols的响应同意握手,成功握手确立 WebSocket 连接,后续使用 WebSocket 的数据帧进行通讯,发送hello消息并接收到hello消息的应答,最后关闭连接释放资源。
examples/websocket-client
examples/websocket-server
Documentation
rfc6455
本文链接:https://blog.csdn.net/u012028275/article/details/128588507