SkillAgentSearch skills...

Asio2

Header only c++ network library, based on asio,support tcp,udp,http,websocket,rpc,ssl,icmp,serial_port,socks5.

Install / Use

/learn @zhllxt/Asio2
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

asio2

中文 | English

Header only c++ network library, based on asio,support tcp,udp,http,websocket,rpc,ssl,icmp,serial_port.

<a href="https://996.icu"><img src="https://img.shields.io/badge/link-996.icu-red.svg" alt="996.icu" /></a> 996.icu LICENSE

  • header only,不依赖boost库,不需要单独编译,在工程的Include目录中添加asio2路径,在源码中#include <asio2/asio2.hpp>即可使用;
  • 支持tcp, udp, http, websocket, rpc, ssl, icmp, serial_port;
  • 支持可靠UDP(基于KCP),支持SSL;
  • TCP支持各种数据拆包功能(单个字符或字符串或用户自定义协议等);
  • 跨平台,支持windows,linux,macos,arm,android,32位,64位等;在msvc gcc clang ndk mingw下编译通过;
  • 基于C++17,基于asio (standalone asio或boost::asio均可);
  • example目录包含大量示例代码,各种使用方法请参考示例代码;

QQ交流群:833425075

一些基础用法文章教程:

与其它框架的一点区别:

目前看到的很多基于asio的框架的模式大都如下:
tcp_server server; // 声明一个server
server.run();      // 调用run函数,run函数是阻塞的,run之后怎么退出却不知道.
这种模式需要用户自己去处理程序退出后的逻辑,包括连接的正常关闭,
资源释放等问题,而这些问题自己处理起来是很烦琐的.
asio2框架已经处理过了这些问题,你可以在如MFC的OnInitDialog等地方调用server.start(...),
start(...)函数是非阻塞的,什么时候想退出了只需要server.stop()即可.stop()是阻塞的,
stop时如果有未发送完的数据,会保证一定在数据发送完之后才会退出,
tcp下也会保证所有连接都正常关闭以后才会退出,你不用考虑资源的正确释放等一系列琐碎的问题.

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);
	// 发送时指定一个回调函数,当发送完成后会调用此回调函数,bytes_sent表示实际发送的字节数,
	// 发送是否有错误可以用asio2::get_last_error()函数来获取错误码
	// session_ptr->async_send(s, [](std::size_t bytes_sent) {});
}).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());
	// 可以用session_ptr这个会话启动一个定时器,这个定时器是在这个session_ptr会话的数据收
	// 发线程中执行的,这对于连接状态的判断或其它需求很有用(尤其在UDP这种无连接的协议中,有
	// 时需要在数据处理过程中使用一个定时器来延时做某些操作,而且这个定时器还需要和数据处理
	// 在同一个线程中安全触发)
	//session_ptr->start_timer(1, std::chrono::seconds(1), []() {});
}).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");

// 按\n自动拆包(可以指定任意字符)
//server.start("0.0.0.0", "8080", '\n');

// 按\r\n自动拆包(可以指定任意字符串)
//server.start("0.0.0.0", "8080", "\r\n");

// 按自定义规则自动拆包(match_role请参考example代码)(用于对用户自定义的协议拆包)
// 对自定义协议拆包时,match_role如何使用的详细说明请看:https://blog.csdn.net/zhllxt/article/details/104772948
//server.start("0.0.0.0", "8080", match_role('#'));

// 每次接收固定的100字节
//server.start("0.0.0.0", "8080", asio::transfer_exactly(100));

// 数据报模式的TCP,无论发送多长的数据,双方接收的一定是相应长度的整包数据
//server.start("0.0.0.0", "8080", asio2::use_dgram);
客户端:
asio2::tcp_client client;
// 客户端在断开时默认会自动重连

// 禁止自动重连
//client.auto_reconnect(false);

// 启用自动重连 默认在断开连接后延时1秒就会开始重连
//client.auto_reconnect(true);

// 启用自动重连 并设置自定义的延时
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_connect的回调函数就位于通信线程中)
	// client.send("abc");

}).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);
})
	//// 绑定全局函数
	//.bind_recv(on_recv)
	//// 绑定成员函数(具体请查看example代码)
	//.bind_recv(std::bind(&listener::on_recv, &lis, std::placeholders::_1)) 
	//// 按lis对象的引用来绑定成员函数(具体请查看example代码)
	//.bind_recv(&listener::on_recv, lis) 
	//// 按lis对象的指针来绑定成员函数(具体请查看example代码)
	//.bind_recv(&listener::on_recv, &lis) 
	;
// 异步连接服务端
//client.async_start("0.0.0.0", "8080");

// 同步连接服务端
client.start("0.0.0.0", "8080");

// 连接成功后,可以调用发送函数(这里是主线程不在通信线程中)
// 同步发送和异步发送可以混用,是线程安全的(一定会在A发送完之后才会发送B)
std::size_t bytes_sent = client.send("abc");
// 同步发送函数的返回值为发送的字节数 可以用get_last_error()查看是否发生错误
if(asio2::get_last_error())
{
	printf("同步发送数据失败:%s\n", asio2::last_error_msg().data());
}

// 按\n自动拆包(可以指定任意字符)
//client.async_start("0.0.0.0", "8080", '\n');

// 按\r\n自动拆包(可以指定任意字符串)
//client.async_start("0.0.0.0", "8080", "\r\n"); 

// 按自定义规则自动拆包(match_role请参考example代码)(用于对用户自定义的协议拆包)
// 对自定义协议拆包时,match_role如何使用的详细说明请看:https://blog.csdn.net/zhllxt/article/details/104772948
//client.async_start("0.0.0.0", "8080", match_role); 

// 每次接收固定的100字节
//client.async_start("0.0.0.0", "8080", asio::transfer_exactly(100)); 

// 数据报模式的TCP,无论发送多长的数据,双方接收的一定是相应长度的整包数据
//client.start("0.0.0.0", "8080", asio2::use_dgram); 

// 发送时也可以指定use_future参数,然后通过返回值future来阻塞等待直到发送完成,发送结果的错误码和发送字节数
// 保存在返回值future中(注意,不能在通信线程中用future去等待,这会阻塞通信线程进而导致死锁)
// std::future<std::pair<asio::error_code, std::size_t>> future = client.async_send("abc", asio::use_future); 

UDP:

服务端:
asio2::udp_server server;
// ... 绑定监听器(请查看example代码)
server.start("0.0.0.0", "8080"); // 常规UDP
//server.start("0.0.0.0", "8080", asio2::use_kcp); // 可靠UDP
客户端:
asio2::udp_client client;
// ... 绑定监听器(请查看example代码)
client.start("0.0.0.0", "8080");
//client.async_start("0.0.0.0", "8080", asio2::use_kcp); // 可靠UDP

RPC:

服务端:
// 全局函数示例,当服务端的RPC函数被调用时,如果想知道是哪个客户端调用的,将这个RPC函数的第一
// 个参数设置为连接对象的智能指针即可(如果不关心是哪个客户端调用的,第一个参数可以不要),如下:
int add(std::shared_ptr<asio2::rpc_session>& session_ptr, int a, int b)
{
	return a + b;
}

// rpc默认是按照"数据长度+数据内容"的格式来发送数据的,因此客户端可能会恶意组包,导致解析出的
// "数据长度"非常长,此时就会分配大量内存来接收完整数据包.避免此问题的办法就是是指定缓冲区最
// 大值,如果发送的数据超过缓冲区最大值,就会将该连接直接关闭.所有tcp udp http websocket,server
// client 等均支持这个功能.
asio2::rpc_server server(
	512,  // 接收缓冲区的初始大小
	1024, // 接收缓冲区的最大大小
	4     // 多少个并发线程
);

// ... 绑定监听器(请查看example代码)

// 绑定RPC全局函数
server.bind("add", add);

// 绑定RPC成员函数
server.bind("mul", &A::mul, a);

// 绑定lambda表达式
server.bind("cat", [&](const std::string& a, const std::string& b) { return a + b; });

// 绑定成员函数(按引用) a的定义请查看example代码
server.bind("get_user", &A::get_user, a);

// 绑定成员函数(按指针) a的定义请查看example代码
server.bind("del_user", &A::del_user, &a);

// 服务端也可以调用客户端的RPC函数(通过连接对象session_ptr)
session_ptr->async_call([](int v)
{
	printf("sub : %d err : %d %s\n", v, asio2::last_error_val(), asio2::last_error_msg().c_str());
}, std::chrono::seconds(10), "sub", 15, 6);

//server.start("0.0.0.0", "8080");
客户端:
asio2::rpc_client client;
// ... 绑定监听器(请查看example代码)
// 不仅server可以绑定RPC函数给client调用,同时client也可以绑定RPC函数给server调用。请参考example代码。
client.start("0.0.0.0", "8080");

// 同步调用RPC函数
int sum = client.call<int>(std::chrono::seconds(3), "add", 11, 2);
printf("sum : %d err : %d %s\n", sum, asio2::last_error_val(), asio2::last_error_msg().c_str());

// 异步调用RPC函数,
// 第一个参数是回调函数,当调用完成或超时会自动调用该回调函数
// 第二个参数是调用超时,可以不填,如果不填则使用默认超时
// 第三个参数是rpc函数名,之后的参数是rpc函数的参数
client.async_call([](int v)
{
	// 如果超时或发生其它错误,可以通过asio2::get_last_error()等一系列函数获取错误信息
	printf("sum : %d err : %d %s\n", v, asio2::last_error_val(), asio2::last_error_msg().c_str());
}, "add", 10, 20);

// 上面的调用方式的参数位置很容易搞混,因此也支持链式调用,如下(其它示例请查看example):
client.timeout(std::chrono::seconds(5)).async_call("mul", 2.5, 2.5).response(
	[](double v)
{
	std::cout << "mul1 " << v << std::endl;
});
int sum = client.timeout(std::chrono::seconds(3)).call<int>("add", 11, 32);

// 返回值为用户自定义数据类型(user类型的定义请查看example代码)
user u = client.call<user>("get_user");
printf("%s %d ", u.name.c_str(), u.age);
for (auto &[k, v] : u.purview)
{
	printf("%d %s ", k, v.c_str());
}
printf("\n");

u.name = "hanmeimei";
u.age = ((int)time(nullptr)) % 100;
u.purview = { {10,"get"},{20,"set"} };

// 如果RPC函数的返回值为void,则用户回调函数参数为空
client.async_call([]()
{
}, "del_user", std::move(u));

// 只调用rpc函数,不需要返回结果
client.async_call("del_user", std::move(u));

HTTP:

服务端:
// 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::stat
View on GitHub
GitHub Stars922
CategoryCustomer
Updated1d ago
Forks194

Languages

C++

Security Score

100/100

Audited on Mar 27, 2026

No findings