云计算百科
云计算领域专业知识百科平台

asio2 网络通信框架学习笔记:TCP、UDP、HTTP、串口与定时器

asio2 网络通信框架代码使用示例(TCP / UDP / HTTP / 串口 / 定时器)

本文目录

  • 一、TCP 使用
    • 1. TCP 服务端完整流程示例
    • 2. TCP 客户端完整流程示例
    • 3. TCP 回调函数是否必须全部编写?
    • 4. TCP 服务端 / 客户端常用配置示例
  • 二、UDP 使用
    • 1. UDP 服务端示例
    • 2. UDP 客户端示例
  • 三、HTTP / WebSocket 使用
    • 1. HTTP 服务端基本示例
    • 2. AOP 拦截器机制
    • 3. defer 延迟响应机制
    • 4. WebSocket 服务端示例
  • 四、HTTP 客户端示例
    • 1. HTTP 文件下载
    • 2. HTTP 同步请求
    • 3. URL 编码与解码
  • 五、串口通信示例
  • 六、定时器使用
  • 七、手动触发事件(Condition Event)

一、TCP 使用

1. TCP 服务端完整流程示例

#include <asio2/asio2.hpp>

int main()
{
std::string_view host = "0.0.0.0";
std::string_view port = "8028";

asio2::tcp_server server;

server
.bind_init([]() {
// 第1步:触发init回调函数
// 当监听socket打开成功(即socket(AF_INET, SOCK_STREAM, 0)函数调用结束)之后,这里被触发。
// 这个函数只会在服务端启动时触发1次。
// 可以在这里给监听socket设置一些选项什么的。
})
.bind_start([&]() {
// 第2步:触发start回调函数
// 当监听socket监听成功(即bind,listen函数调用结束)之后,这里被触发。
// 这个函数只会在服务端启动时触发1次。
// 可以在这里记录服务端启动成功的日志。

printf("start tcp server : %s %u\\n", server.listen_address().c_str(), server.listen_port());
})
.bind_accept([](std::shared_ptr<asio2::tcp_session> &session_ptr) {
// 第3步:触发accept回调函数
// 当某个客户端刚连接上服务端之后,这里被触发。
// 这个函数会多次触发,每有1个客户端连接上来之后,就会触发1次。但1个客户端只
// 触发1次。由于客户端有掉线自动重连机制,所以客户端每重连1次,就会触发1次。
// 可以在这里判断该客户端是不是在黑名单中,如果是,直接调用stop将该客户端断开。
// 此时还不能给这个客户端发送数据,会由于连接还未完全建立而导致发送失败。

if (session_ptr->remote_address() == "192.168.1.100")
session_ptr->stop();
})
.bind_connect([&](auto &session_ptr) {
// 第4步:触发connect回调函数
// 当某个客户端和服务端的连接完全建立成功之后,这里被触发。
// 这个函数会多次触发,每有1个客户端连接完成之后,就会触发1次。但1个客户端只
// 触发1次。
// 可以在这里给该客户端发送数据了。

// 可能有人会问,对于tcp来说,只要连接建立了,不就立即可以发数据了吗?为什么在上面的
// bind_accept中不能发,在这里的bind_connect中才能发呢?

// 这是因为asio2框架人为的给这个连接做了一个状态标识(status),在bind_accept中该status
// 是starting,表示连接还未完全建立,当触发到bind_connect时才会给该连接的状态标识设置
// 为started,表示连接建立全部完成了。所以如果在bind_accept中发送数据的话,框架会判断
// 到状态标识不是started所以就不允许发送数据。

// 那为什么要做这样一个状态标识呢?
// 因为对于ssl来说,连接建立之后,还有ssl握手,只有ssl握手成功以后才能收发数据。对于
// websocket来说,连接建立之后,还有upgrade升级协商,只有upgrade升级之后才能收发数据,
// 所以为了整体框架流程统一,人为的做了一个规定,只有触发了bind_connect的回调函数之后
// 才表示整个连接完全建立结束了。

session_ptr->no_delay(true);

printf("client enter : %s %u %s %u\\n", session_ptr->remote_address().c_str(), session_ptr->remote_port(),
session_ptr->local_address().c_str(), session_ptr->local_port());
})
.bind_recv([&](std::shared_ptr<asio2::tcp_session> &session_ptr, std::string_view data) {
// 第5步:触发recv回调函数
// 当收到某个客户端发送过来的数据之后,这里被触发。
// 这个函数会多次触发,每有1个客户端发送数据过来之后,就会触发1次。且1个客户端也会
// 触发多次,即发送数据就触发1次。
// 在这里对接收到的数据进行解析处理。

printf("recv : %u %.*s\\n", (unsigned)data.size(), (int)data.size(), data.data());

session_ptr->async_send(data, [](std::size_t bytes_sent) { });
})
.bind_disconnect([&](auto &session_ptr) {
// 第6步:触发disconnect回调函数
// 当某个客户端关闭了,服务端收到该客户端的连接断开事件之后,
// 在该连接对应的socket关闭之前(即closesocket函数调用之前),这里被触发。
// 这个函数会多次触发,每有1个客户端连接完成之后,就会触发1次。但1个客户端只
// 触发1次。
// 可以在这里记录客户端的断开日志。

printf("client leave : %s %u %s\\n", session_ptr->remote_address().c_str(), session_ptr->remote_port(),
asio2::last_error_msg().c_str());
})
.bind_stop([&]() {
// 第7步:触发stop回调函数
// 当关闭服务端时,在所有的连接都全部断开了之后,在监听socket关闭之前,这里被触发。
// 注意:必须所有连接全部断开了,且所有的session_ptr的引用计数为0了,这里才会触发,
// 因此如果你将session_ptr保存在了其它地方,一定要记得将你保存的session_ptr删除,
// 否则server.stop()函数会一直阻塞无法结束,这里的回调函数也无法触发。
// 这个函数只会在服务端关闭时触发1次。
// 可以在这里记录服务端的停止日志。

printf("stop tcp server : %d %s\\n", asio2::last_error_val(), asio2::last_error_msg().c_str());
});

server.start(host, port);

while (std::getchar() != '\\n'); // press enter to exit this program

server.stop();

return 0;
}


2. TCP 客户端完整流程示例

#include <asio2/asio2.hpp>

int main()
{
std::string_view host = "127.0.0.1";
std::string_view port = "8028";

asio2::tcp_client client;

client
.bind_init([]() {
// 第1步:触发init回调函数
// 当客户端socket打开成功(即socket(AF_INET, SOCK_STREAM, 0)函数调用结束)之后,这里被触发。
// 这个函数只会在客户端启动时触发1次。
// 可以在这里给socket设置一些选项什么的。
})
.bind_connect([&]() {
// 第2步:触发connect回调函数
// 当客户端连接服务端完成之后,这里被触发。
// 这个函数只会在客户端连接服务端时触发1次,不管连接成功还是连接失败都会触发。连接
// 成功还是失败,可以使用asio2::get_last_error()来进行判断。
// 注意:客户端有掉线自动重连机制,因此,如果客户端连接服务端失败,那么客户端过几秒后
// 会再次自动连接服务端,每连接一次服务端,不管连接成功还是失败,此函数就会被触发1次。
// 可以在这里给向服务端发送数据了。

if (asio2::get_last_error())
printf("connect failure : %d %s\\n", asio2::last_error_val(), asio2::last_error_msg().c_str());
else
printf("connect success : %s %u\\n", client.local_address().c_str(), client.local_port());

// 如果没有错误,就表示连接成功,可以向服务端发送数据了。
if (!asio2::get_last_error())
client.async_send("<abcdefghijklmnopqrstovuxyz0123456789>");
})
.bind_recv([&](std::string_view data) {
// 第3步:触发recv回调函数
// 当收到服务端发送过来的数据之后,这里被触发。
// 这个函数会多次触发,每接收到1次数据,就会触发1次。
// 可以在这里对接收到的数据进行解析处理。

printf("recv : %u %.*s\\n", (unsigned)data.size(), (int)data.size(), data.data());

client.async_send(data);
})
.bind_disconnect([&]() {
// 第4步:触发disconnect回调函数
// 当客户端关闭时(或者服务端关闭了),在socket即将关闭之前,这里被触发。
// 这个函数只会在客户端关闭时触发1次。且只有在客户端连接成功之后才会被触发,就是
// 说如果客户端没有成功连接上服务端,那么客户端在关闭时是不会触发disconnect的。
// 可以在这里记录客户端的关闭日志。

printf("disconnect : %d %s\\n", asio2::last_error_val(), asio2::last_error_msg().c_str());
});

client.start(host, port);

while (std::getchar() != '\\n');

return 0;
}


3. TCP 回调函数是否必须全部编写?

  • 不是必须
  • 可按需绑定任意数量回调
  • 最少只写 bind_recv 即可工作
仅接收数据的 TCP 服务端示例

#include <asio2/asio2.hpp>

int main()
{
std::string_view host = "0.0.0.0";
std::string_view port = "8028";

asio2::tcp_server server;

server.bind_recv([&](std::shared_ptr<asio2::tcp_session> &session_ptr, std::string_view data) {
printf("recv : %u %.*s\\n",
(unsigned)data.size(),
(int)data.size(),
data.data());

session_ptr->async_send(data, [](std::size_t bytes_sent) { });
});

server.start(host, port);

while (std::getchar() != '\\n');
server.stop();
return 0;
}


4. TCP 服务端/客户端常用配置示例

服务端

asio2::tcp_server server;
server
.bind_recv([&](std::shared_ptr<asio2::tcp_session> &session_ptr, std::string_view s) {
printf("recv : %zu %.*s\\n", s.size(), (int)s.size(), s.data());
session_ptr->async_send(s);
})
.bind_connect([&](auto &session_ptr) {
session_ptr->no_delay(true);
printf("client enter : %s %u %s %u\\n",
session_ptr->remote_address().c_str(),
session_ptr->remote_port(),
session_ptr->local_address().c_str(),
session_ptr->local_port());
})
.bind_disconnect([&](auto &session_ptr) {
printf("client leave : %s %u %s\\n",
session_ptr->remote_address().c_str(),
session_ptr->remote_port(),
asio2::last_error_msg().c_str());
});

server.start("0.0.0.0", "8080");

客户端

asio2::tcp_client client;
client.auto_reconnect(true, std::chrono::seconds(3));

client
.bind_connect([&]() {
if (asio2::get_last_error())
printf("connect failure : %d %s\\n",
asio2::last_error_val(),
asio2::last_error_msg().c_str());
else
printf("connect success : %s %u\\n",
client.local_address().c_str(),
client.local_port());

if (!asio2::get_last_error())
client.async_send("<abcdefghijklmnopqrstovuxyz0123456789>");
})
.bind_disconnect([]() {
printf("disconnect : %d %s\\n",
asio2::last_error_val(),
asio2::last_error_msg().c_str());
})
.bind_recv([&](std::string_view sv) {
printf("recv : %zu %.*s\\n",
sv.size(),
(int)sv.size(),
sv.data());

client.async_send(sv);
});

client.start("0.0.0.0", "8080");


二、UDP 使用

UDP 服务端

asio2::udp_server server;
// … 绑定监听器(请查看TCP的example代码)
server.start("0.0.0.0", "8080"); // 普通 UDP
// server.start("0.0.0.0", "8080", asio2::use_kcp); // 可靠 UDP

UDP 客户端

asio2::udp_client client;
// … 绑定监听器(请查看TCP的example代码)
client.start("0.0.0.0", "8080");
// client.async_start("0.0.0.0", "8080", asio2::use_kcp);


三、HTTP / WebSocket 使用

HTTP 服务端(含 AOP 拦截器、WebSocket)

// http 请求拦截器
struct aop_log {
bool before(http::web_request &req, http::web_response &rep)
{
asio2::detail::ignore_unused(rep);
printf("aop_log before %s\\n", req.method_string().data());
// 返回true则后续的拦截器会接着调用,返回false则后续的拦截器不会被调用
return true;
}
bool after(std::shared_ptr<asio2::http_session> &session_ptr, http::web_request &req, http::web_response &rep)
{
asio2::detail::ignore_unused(session_ptr, req, rep);
printf("aop_log after\\n");
return true;
}
};

struct aop_check {
bool before(std::shared_ptr<asio2::http_session> &session_ptr, http::web_request &req, http::web_response &rep)
{
asio2::detail::ignore_unused(session_ptr, req, rep);
printf("aop_check before\\n");
return true;
}
bool after(http::web_request &req, http::web_response &rep)
{
asio2::detail::ignore_unused(req, rep);
printf("aop_check after\\n");
return true;
}
};

asio2::http_server server;

server.bind<http::verb::get, http::verb::post>(
"/index.*",
[](http::web_request &req, http::web_response &rep) {
std::cout << req.path() << std::endl;
std::cout << req.query() << std::endl;

rep.fill_file("../../../index.html");
rep.chunked(true);
},
aop_log {});

server.bind<http::verb::get>(
"/del_user",
[](std::shared_ptr<asio2::http_session> &session_ptr, http::web_request &req, http::web_response &rep) {
// 回调函数的第一个参数可以是会话指针session_ptr(这个参数也可以不要)
printf("del_user ip : %s\\n", session_ptr->remote_address().data());

// fill_page函数用给定的错误代码构造一个简单的标准错误页,<html>…</html>这样
rep.fill_page(http::status::ok, "del_user successed.");
},
aop_check {});

server.bind<http::verb::get>(
"/api/user/*",
[](http::web_request &req, http::web_response &rep) {
rep.fill_text("the user name is hanmeimei, …..");
},
aop_log {}, aop_check {});

server.bind<http::verb::get>(
"/defer",
[](http::web_request &req, http::web_response &rep) {
// 使用defer让http响应延迟发送,defer的智能指针销毁时,才会自动发送response
std::shared_ptr<http::response_defer> rep_defer = rep.defer();

std::thread([rep_defer, &rep]() mutable {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));

auto newrep = asio2::http_client::execute("http://www.baidu.com");

rep = std::move(newrep);
}).detach();
},
aop_log {}, aop_check {});

// 对websocket的支持
server.bind("/ws", websocket::listener<asio2::http_session> {}
.on("message",
[](std::shared_ptr<asio2::http_session> &session_ptr, std::string_view data) {
printf("ws msg : %zu %.*s\\n", data.size(), (int)data.size(), data.data());

session_ptr->async_send(data);
})
.on("open",
[](std::shared_ptr<asio2::http_session> &session_ptr) {
printf("ws open\\n");

// 打印websocket的http请求头
std::cout << session_ptr->request() << std::endl;

// 如何给websocket响应头填充额外信息
session_ptr->ws_stream().set_option(
websocket::stream_base::decorator([](websocket::response_type &rep) {
rep.set(http::field::authorization, " http-server-coro");
}));
})
.on("close", [](std::shared_ptr<asio2::http_session> &session_ptr) {
printf("ws close\\n");
}));

server.bind_not_found([](http::web_request &req, http::web_response &rep) {
// fill_page函数可以构造一个简单的标准错误页
rep.fill_page(http::status::not_found);
});


四、HTTP 客户端示例

// 1. http下载大文件并直接保存
// The file is in this directory: /asio2/example/bin/x64/QQ9.6.7.28807.exe
asio2::https_client::download("https://dldir1.qq.com/qqfile/qq/PCQQ9.6.7/QQ9.6.7.28807.exe", "QQ9.6.7.28807.exe");

// 2. http下载大文件并循环调用回调函数,可在回调函数中写入文件,可实现进度条之类功能
std::fstream hugefile("CentOS-7-x86_64-DVD-2009.iso", std::ios::out | std::ios::binary | std::ios::trunc);
asio2::https_client::download(asio::ssl::context { asio::ssl::context::tlsv13 },
"https://mirrors.tuna.tsinghua.edu.cn/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso",
//[](auto& header) // http header callback. this param is optional. the body callback is required.
//{
// std::cout << header << std::endl;
//},
[&hugefile](std::string_view chunk) // http body callback.
{
hugefile.write(chunk.data(), chunk.size());
});
hugefile.close();

// 通过URL字符串生成一个http请求对象
auto req1 = http::make_request("http://www.baidu.com/get_user?name=abc");
// 通过URL字符串直接请求某个网址,返回结果在rep1中
auto rep1 = asio2::http_client::execute("http://www.baidu.com/get_user?name=abc");
// 通过asio2::get_last_error()判断是否发生错误
if (asio2::get_last_error())
std::cout << asio2::last_error_msg() << std::endl;
else
std::cout << rep1 << std::endl; // 打印http请求结果

// 通过http协议字符串生成一个http请求对象
auto req2 = http::make_request("GET / HTTP/1.1\\r\\nHost: 192.168.0.1\\r\\n\\r\\n");
// 通过请求对象发送http请求
auto rep2 = asio2::http_client::execute("www.baidu.com", "80", req2, std::chrono::seconds(3));
if (asio2::get_last_error())
std::cout << asio2::last_error_msg() << std::endl;
else
std::cout << rep2 << std::endl;

std::stringstream ss;
ss << rep2;
std::string result = ss.str(); // 通过这种方式将http请求结果转换为字符串

// 获取url中的path部分的值
auto path = asio2::http::url_to_path("/get_user?name=abc");
std::cout << path << std::endl;

// 获取url中的query部分的值
auto query = asio2::http::url_to_query("/get_user?name=abc");
std::cout << query << std::endl;

std::cout << std::endl;

auto rep3 = asio2::http_client::execute("www.baidu.com", "80", "/api/get_user?name=abc");
if (asio2::get_last_error())
std::cout << asio2::last_error_msg() << std::endl;
else
std::cout << rep3 << std::endl;

// URL编解码
std::string en = http::url_encode(R"(http://www.baidu.com/json={"qeury":"name like '%abc%'","id":1})");
std::cout << en << std::endl;
std::string de = http::url_decode(en);
std::cout << de << std::endl;

// 其它的更多用法请查看example示例代码

五、串口通信示例

std::string_view device = "COM1"; // windows
// std::string_view device = "/dev/ttyS0"; // linux
std::string_view baud_rate = "9600";

asio2::serial_port sp;
sp.bind_init([&]() {
// 设置串口参数
sp.socket().set_option(asio::serial_port::flow_control(asio::serial_port::flow_control::type::none));
sp.socket().set_option(asio::serial_port::parity(asio::serial_port::parity::type::none));
sp.socket().set_option(asio::serial_port::stop_bits(asio::serial_port::stop_bits::type::one));
sp.socket().set_option(asio::serial_port::character_size(8));
}).bind_recv([&](std::string_view sv) {
printf("recv : %zu %.*s\\n", sv.size(), (int)sv.size(), sv.data());

// 接收串口数据
std::string s;
uint8_t len = uint8_t(10 + (std::rand() % 20));
s += '<';
for (uint8_t i = 0; i < len; i++) {
s += (char)((std::rand() % 26) + 'a');
}
s += '>';

sp.async_send(s, []() { });
});

// 没有指定如何解析串口数据,需要用户自己去解析串口数据
// sp.start(device, baud_rate);

// 按照单个字符'>'作为数据分隔符自动解析串口数据
sp.start(device, baud_rate, '>');

// 按照字符串"\\r\\n"作为数据分隔符自动解析串口数据
// sp.start(device, baud_rate, "\\r\\n");

// 按照用户自定义的协议自动解析,关于match_role如何使用请参考tcp部分说明
// sp.start(device, baud_rate, match_role);

// sp.start(device, baud_rate, asio::transfer_at_least(1));
// sp.start(device, baud_rate, asio::transfer_exactly(10));


六、定时器使用

// 框架中提供了定时器功能,使用非常简单,如下(更多示例请参考example/timer/timer.cpp):
asio2::timer timer;
// 参数1表示定时器ID,参数2表示定时器间隔,参数3为定时器回调函数
timer.start_timer(1, std::chrono::seconds(1), [&]() {
printf("timer 1\\n");
if (true) // 满足某个条件时关闭定时器,当然也可以在其它任意地方关闭定时器
timer.stop_timer(1);
});
// 执行5次的定时器,定时器id是字符串"id2",定时器间隔是2000毫秒
timer.start_timer("id2", 2000, 5, []() {
printf("timer id2, loop 5 times\\n");
});
// 首次执行会延时5000毫秒的定时器,定时器id是5,定时器间隔是1000毫秒
timer.start_timer(5, std::chrono::milliseconds(1000), std::chrono::milliseconds(5000), []() {
printf("timer 5, loop infinite, delay 5 seconds\\n");
});
// 所有的server,client,session等都继承了timer,所以server,client,session也可以使用定时器功能.


七、手动触发事件(Condition Event)

asio2::tcp_client client;

// 投递一个异步条件事件,除非这个事件被主动触发,否则永远不会执行
std::shared_ptr<asio2::condition_event> event_ptr = client.post_condition_event([]() {
// do something.
});

client.bind_recv([&](std::string_view data) {
// 比如达到某个条件
if (data == "some_condition") {
// 触发事件让事件开始执行
event_ptr->notify();
}
});

赞(0)
未经允许不得转载:网硕互联帮助中心 » asio2 网络通信框架学习笔记:TCP、UDP、HTTP、串口与定时器
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!