【Mongoose笔记】TCP 客户端与服务器
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
【Mongoose笔记】TCP 客户端与服务器
简介
Mongoose 笔记系列用于记录学习 Mongoose 的一些内容。
Mongoose 是一个 C/C++ 的网络库。它为 TCP、UDP、HTTP、WebSocket、MQTT 实现了事件驱动的、非阻塞的 API。
项目地址
https://github.com/cesanta/mongoose
学习
下面通过学习 Mongoose 项目代码中的 tcp 示例程序 来学习如何使用 Mongoose 实现简单的 TCP 通讯。使用树莓派平台进行开发验证。
tcp 的示例程序内同时包含了 TCP 客户端与服务器的实现同时创建一个客户端和一个服务器客户端连接到服务器发送一些文本信息到服务器然后服务器将信息原原本本地回复回来客户端断开连接。
代码如下
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
static const char *s_lsn = "tcp://localhost:8765"; // Listening address
static const char *s_conn = "tcp://localhost:8765"; // Connect to address
// client resources
static struct c_res_s {
int i;
struct mg_connection *c;
} c_res;
// CLIENT event handler
static void cfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
int *i = &((struct c_res_s *) fn_data)->i;
if (ev == MG_EV_OPEN) {
MG_INFO(("CLIENT has been initialized"));
} else if (ev == MG_EV_CONNECT) {
MG_INFO(("CLIENT connected"));
#if (MG_ENABLE_MBEDTLS == 1) || (MG_ENABLE_OPENSSL == 1)
struct mg_tls_opts opts = {.ca = "ss_ca.pem"};
mg_tls_init(c, &opts);
#endif
*i = 1; // do something
} else if (ev == MG_EV_READ) {
struct mg_iobuf *r = &c->recv;
MG_INFO(("CLIENT got data: %.*s", r->len, r->buf));
} else if (ev == MG_EV_CLOSE) {
MG_INFO(("CLIENT disconnected"));
// signal we are done
((struct c_res_s *) fn_data)->c = NULL;
} else if (ev == MG_EV_ERROR) {
MG_INFO(("CLIENT error: %s", (char *) ev_data));
} else if (ev == MG_EV_POLL && *i != 0) {
switch ((*i)++) {
case 50: // 50 x 100ms = 5s
mg_send(c, "Hi, there", 9);
MG_INFO(("CLIENT sent data"));
break;
case 100: // another 5s
// send any possible outstanding data and close the connection
c->is_draining = 1;
break;
}
}
}
// SERVER event handler
static void sfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_OPEN && c->is_listening == 1) {
MG_INFO(("SERVER is listening"));
} else if (ev == MG_EV_ACCEPT) {
MG_INFO(("SERVER accepted a connection"));
#if (MG_ENABLE_MBEDTLS == 1) || (MG_ENABLE_OPENSSL == 1)
struct mg_tls_opts opts = {
//.ca = "ss_ca.pem", // Uncomment to enable two-way SSL
.cert = "ss_server.pem", // Certificate PEM file
.certkey = "ss_server.pem", // This pem contains both cert and key
};
mg_tls_init(c, &opts);
#endif
} else if (ev == MG_EV_READ) {
struct mg_iobuf *r = &c->recv;
MG_INFO(("SERVER got data: %.*s", r->len, r->buf));
mg_send(c, r->buf, r->len); // echo it back
} else if (ev == MG_EV_CLOSE) {
MG_INFO(("SERVER disconnected"));
} else if (ev == MG_EV_ERROR) {
MG_INFO(("SERVER error: %s", (char *) ev_data));
}
(void) fn_data;
}
// Timer function - recreate client connection if it is closed
static void timer_fn(void *arg) {
struct mg_mgr *mgr = (struct mg_mgr *) arg;
if (c_res.c == NULL) {
// connect
c_res.i = 0;
c_res.c = mg_connect(mgr, s_conn, cfn, &c_res);
if (c_res.c == NULL)
MG_INFO(("CLIENT cant' open a connection"));
else
MG_INFO(("CLIENT is connecting"));
}
}
int main(void) {
struct mg_mgr mgr; // Event manager
struct mg_connection *c;
mg_log_set(MG_LL_INFO); // Set log level
mg_mgr_init(&mgr); // Initialize event manager
mg_timer_add(&mgr, 15000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn,
&mgr); // Init timer for demo purposes, 15s
c = mg_listen(&mgr, s_lsn, sfn, NULL); // Create server connection
if (c == NULL) {
MG_INFO(("SERVER cant' open a connection"));
return 0;
}
while (true)
mg_mgr_poll(&mgr, 100); // Infinite event loop, blocks for upto 100ms
// unless there is network activity
mg_mgr_free(&mgr); // Free resources
return 0;
}
下面从main
函数开始分析代码。
定义变量struct mg_mgr
是用于保存所有活动连接的事件管理器struct mg_connection
是单个连接描述符。
struct mg_mgr mgr; // Event manager
struct mg_connection *c;
设置 Mongoose 日志记录级别设置等级为 MG_LL_INFO
。
mg_log_set(MG_LL_INFO); // Set log level
初始化一个事件管理器也就是将上面定义的struct mg_mgr
变量 mgr
中的数据进行初始化。
mg_mgr_init(&mgr); // Initialize event manager
调用mg_timer_add
设置一个定时器这会将其添加到事件管理器的内部定时器列表中。其中的参数15000
表示 15000 毫秒也就是 15 秒;MG_TIMER_REPEAT | MG_TIMER_RUN_NOW
是定时器标志其中MG_TIMER_REPEAT
表示定时重复调用函数MG_TIMER_RUN_NOW
表示设置定时器后立即调用;timer_fn
是要调用的函数&mgr
是要传递的参数。事件管理器将以参数 15 秒的时间间隔调用 timer_fn
函数并将参数 &mgr
传递给它。
mg_timer_add(&mgr, 15000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn,
&mgr); // Init timer for demo purposes, 15s
这个定时器函数的作用是如果客户端连接已关闭则重新创建该连接。
下面我们先看下timer_fn
的实现
如果c_res.c
的值为NULL
则开始创建连接。将c_res.i
置 0然后调用mg_connect
创建连接s_conn
是要连接的 URLcfn
是客户端事件处理函数c_res
是要传入的参数。创建连接成功或者失败均会打印对应的日志。
// Timer function - recreate client connection if it is closed
static void timer_fn(void *arg) {
struct mg_mgr *mgr = (struct mg_mgr *) arg;
if (c_res.c == NULL) {
// connect
c_res.i = 0;
c_res.c = mg_connect(mgr, s_conn, cfn, &c_res);
if (c_res.c == NULL)
MG_INFO(("CLIENT cant' open a connection"));
else
MG_INFO(("CLIENT is connecting"));
}
}
其中s_conn
是一个静态全局变量默认参数如下
static const char *s_conn = "tcp://localhost:8765"; // Connect to address
分析完timer_fn
的实现回到main
函数。
通过 mg_listen
创建一个监听连接监听地址s_lsn
该参数为tcp://localhost:8765
sfn
是服务器事件处理函数传入的参数为NULL
。如果创建失败则打印失败日志并返回 0 结束程序。
c = mg_listen(&mgr, s_lsn, sfn, NULL); // Create server connection
if (c == NULL) {
MG_INFO(("SERVER cant' open a connection"));
return 0;
}
其中s_lsn
是一个静态全局变量。
static const char *s_lsn = "tcp://localhost:8765"; // Listening address
接下来是事件循环mg_mgr_poll
遍历所有连接接受新连接发送和接收数据关闭连接并为各个事件调用事件处理函数。设置参数100
每次循环调用后阻塞 100 毫秒。
while (true)
mg_mgr_poll(&mgr, 100); // Infinite event loop, blocks for upto 100ms
// unless there is network activity
调用 mg_mgr_free
关闭所有连接释放所有资源。
mg_mgr_free(&mgr); // Free resources
接下来看下服务器的事件处理函数sfn
的实现。
// SERVER event handler
static void sfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
判断是否接收到MG_EV_OPEN
事件收到MG_EV_OPEN
事件表示已创建连接该事件在分配连接并将其添加到事件管理器之后立即发送。is_listening
表示监听连接。打印服务器正在监听的日志。
if (ev == MG_EV_OPEN && c->is_listening == 1) {
MG_INFO(("SERVER is listening"));
}
判断是否接收到MG_EV_ACCEPT
事件表示已接受连接服务器接受了一个来自客户端的连接。打印服务器接受连接的日志。如果代码被编译为支持 TLS定义opts
参数然后通过调用mg_tls_init
初始化 TLS 。其中cert
是服务器的证书certkey
是证书的密钥。如果cert
为 NULL则不进行身份验证。
} else if (ev == MG_EV_ACCEPT) {
MG_INFO(("SERVER accepted a connection"));
#if (MG_ENABLE_MBEDTLS == 1) || (MG_ENABLE_OPENSSL == 1)
struct mg_tls_opts opts = {
//.ca = "ss_ca.pem", // Uncomment to enable two-way SSL
.cert = "ss_server.pem", // Certificate PEM file
.certkey = "ss_server.pem", // This pem contains both cert and key
};
mg_tls_init(c, &opts);
#endif
}
判断是否接收到MG_EV_READ
事件MG_EV_READ
表示从套接字socket接收到数据。当有从套接字socket接收到数据时就会发送MG_EV_READ
事件。接收到的数据存放在c->recv
将接收到的数据打印出来然后通过mg_send
将接收到的数据发回给客户端。
} else if (ev == MG_EV_READ) {
struct mg_iobuf *r = &c->recv;
MG_INFO(("SERVER got data: %.*s", r->len, r->buf));
mg_send(c, r->buf, r->len); // echo it back
}
判断是否接收到MG_EV_CLOSE
事件MG_EV_CLOSE
表示连接关闭。打印服务器断开连接的日志。
} else if (ev == MG_EV_CLOSE) {
MG_INFO(("SERVER disconnected"));
}
最后判断是否接收到MG_EV_ERROR
事件表示有错误。打印出错误的日志。
} else if (ev == MG_EV_ERROR) {
MG_INFO(("SERVER error: %s", (char *) ev_data));
}
接下来看下客户端事件处理函数cfn
。
// CLIENT event handler
static void cfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
定义变量i
实际指向全局变量c_res
中的i
用于后续的计数。
int *i = &((struct c_res_s *) fn_data)->i;
如果接收到MG_EV_OPEN
事件表示已创建连接。打印客户端已经初始化的日志。
if (ev == MG_EV_OPEN) {
MG_INFO(("CLIENT has been initialized"));
}
如果接收到的是MG_EV_CONNECT
事件表示连接已建立。打印客户端连接的日志。如果代码被编译为支持 TLS定义opts
参数通过调用mg_tls_init
初始化 TLS 。其中ca
表示证书颁发机构(Certificate Authority)用于验证另一端发送过来的证书如果为 NULL则禁用证书检查。最后将*i
的值置 1。
} else if (ev == MG_EV_CONNECT) {
MG_INFO(("CLIENT connected"));
#if (MG_ENABLE_MBEDTLS == 1) || (MG_ENABLE_OPENSSL == 1)
struct mg_tls_opts opts = {.ca = "ss_ca.pem"};
mg_tls_init(c, &opts);
#endif
*i = 1; // do something
}
MG_EV_READ
事件表示从套接字socket接收到的数据。接收到的数据存放在c->recv
定义指针变量r
指向它然后将接收到的数据打印出来。
} else if (ev == MG_EV_READ) {
struct mg_iobuf *r = &c->recv;
MG_INFO(("CLIENT got data: %.*s", r->len, r->buf));
}
如果接收到的是MG_EV_CLOSE
事件表示连接关闭。打印客户端断开连接的日志。将((struct c_res_s *) fn_data)->c
的值置NULL
表示连接已结束以便于下次在timer_fn
函数中判断c_res.c
的值时等于NULL
重新创建客户端连接。
} else if (ev == MG_EV_CLOSE) {
MG_INFO(("CLIENT disconnected"));
// signal we are done
((struct c_res_s *) fn_data)->c = NULL;
}
如果接收到MG_EV_ERROR
事件表示有错误。打印出客户端的错误日志。
} else if (ev == MG_EV_ERROR) {
MG_INFO(("CLIENT error: %s", (char *) ev_data));
}
如果收到MG_EV_POLL
事件并且*i != 0
。其中MG_EV_POLL
由mg_mgr_poll
函数调用时发送。在MG_EV_CONNECT
事件中客户端与服务器已建立连接后会将*i
的值置 1。
当符合条件后每次调用都会(*i)++
每次调用间隔 100 毫秒。
当*i
的值等于 50 时也就是连接建立 5 秒后调用mg_send
函数发送消息Hi, there
给服务器然后打印客户端发送数据的日志。
当*i
的值等于 100 时也就是连接建立 10 秒后将is_draining
的值置 1发送剩余数据然后关闭连接。
} else if (ev == MG_EV_POLL && *i != 0) {
switch ((*i)++) {
case 50: // 50 x 100ms = 5s
mg_send(c, "Hi, there", 9);
MG_INFO(("CLIENT sent data"));
break;
case 100: // another 5s
// send any possible outstanding data and close the connection
c->is_draining = 1;
break;
}
}
tcp 的示例程序代码就都解析完了下面实际运行一下 tcp 程序。
打开示例程序编译并运行 tcp 示例程序
pi@raspberrypi:~ $ cd Desktop/study/mongoose/examples/tcp/
pi@raspberrypi:~/Desktop/study/mongoose/examples/tcp $ make
cc ../../mongoose.c -I../.. -W -Wall -o example main.c
./example
2fa28 2 main.c:53:sfn SERVER is listening
2fa8c 2 main.c:19:cfn CLIENT has been initialized
2fa8c 2 main.c:86:timer_fn CLIENT is connecting
2fa8c 2 main.c:21:cfn CLIENT connected
2fa8c 2 main.c:55:sfn SERVER accepted a connection
运行 5 秒后客户端发送数据Hi, there
给服务器服务器收到消息后给客户端回复Hi, there
消息。
30e1b 2 main.c:40:cfn CLIENT sent data
30e1c 2 main.c:66:sfn SERVER got data: Hi, there
30e1c 2 main.c:29:cfn CLIENT got data: Hi, there
运行 10 秒后客户端与服务器断开连接。
3201a 2 main.c:31:cfn CLIENT disconnected
3201a 2 main.c:69:sfn SERVER disconnected
运行 15 秒后客户端与服务器重新建立连接。
33537 2 main.c:19:cfn CLIENT has been initialized
33538 2 main.c:86:timer_fn CLIENT is connecting
33538 2 main.c:21:cfn CLIENT connected
33538 2 main.c:55:sfn SERVER accepted a connection
接下来重复这个过程
348c6 2 main.c:40:cfn CLIENT sent data
348c6 2 main.c:66:sfn SERVER got data: Hi, there
348c6 2 main.c:29:cfn CLIENT got data: Hi, there
35ac3 2 main.c:31:cfn CLIENT disconnected
35ac3 2 main.c:69:sfn SERVER disconnected
36fe0 2 main.c:19:cfn CLIENT has been initialized
36fe0 2 main.c:86:timer_fn CLIENT is connecting
36fe0 2 main.c:21:cfn CLIENT connected
36fe0 2 main.c:55:sfn SERVER accepted a connection
3836d 2 main.c:40:cfn CLIENT sent data
3836d 2 main.c:66:sfn SERVER got data: Hi, there
3836d 2 main.c:29:cfn CLIENT got data: Hi, there
3956a 2 main.c:31:cfn CLIENT disconnected
3956a 2 main.c:69:sfn SERVER disconnected
【参考资料】
本文链接https://blog.csdn.net/u012028275/article/details/128745580