【Mongoose笔记】Websocket 客户端
admin
2024-05-08 05:09:04
0

【Mongoose笔记】Websocket 客户端

简介

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,且donefalse时进行循环。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

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...