C++开发网络通信程序时用asio是个不错的选择,但asio本身是一套函数集,自己还要处理诸如“通信线程池管理、连接及生命周期管理、多线程收发数据的同步保护等”。因此这里对asio进行了一层封装,大大简化了对asio的使用。代码使用了C++17相关功能,所以只能用在C++17以上。
其中http和websocket部分用的是boost::beast,因此如果需要用到http或websocket的功能,则必须使用boost库,如果用不到http则直接使用独立的asio即可。在config.hpp中通过对ASIO_STANDALONE这个宏定义的开关,即可设置是使用boost::asio还是使用asio standalone.
代码大量使用了CRTP模板编程实现(没有使用virtual而用CRTP实现的静态多态),因此编译比较耗时,但执行效率相对较好一点。
github地址:https://github.com/zhllxt/asio2
码云地址:https://gitee.com/zhllxt/asio2
A open source cross-platform c++ library for network programming based on asio,support for tcp,udp,http,rpc,ssl and so on.
服务端:
asio2::tcp_server server; server.bind_recv([&server](std::shared_ptr<asio2::tcp_session> & session_ptr, std::string_view s) { session_ptr->no_delay(true); printf("recv : %u %.*s\n", (unsigned)s.size(), (int)s.size(), s.data()); // 异步发送(所有发送操作都是异步且线程安全的) session_ptr->send(s); // 发送时指定一个回调函数,当发送完成后会调用此回调函数,bytes_sent表示实际发送的字节数, // 发送是否有错误可以用asio2::get_last_error()函数来获取错误码 // session_ptr->send(s, [](std::size_t bytes_sent) {}); }).bind_connect([&server](auto & session_ptr) { 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([&server](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"); //server.start("0.0.0.0", "8080", '\n'); // 按\n自动拆包(可以指定任意字符) //server.start("0.0.0.0", "8080", "\r\n"); // 按\r\n自动拆包(可以指定任意字符串) //server.start("0.0.0.0", "8080", match_role('#')); // 按match_role指定的规则自动拆包(match_role请参考demo代码)(用于对用户自定义的协议拆包) //server.start("0.0.0.0", "8080", asio::transfer_exactly(100)); // 每次接收固定的100字节 //server.start("0.0.0.0", "8080", asio2::use_dgram); // 数据报模式的TCP,无论发送多长的数据,双方接收的一定是相应长度的整包数据
客户端:
asio2::tcp_client client; client.bind_connect([&](asio::error_code ec) { 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()); client.send("<abcdefghijklmnopqrstovuxyz0123456789>"); }).bind_disconnect([](asio::error_code ec) { printf("disconnect : %d %s\n", asio2::last_error_val(), asio2::last_error_msg().c_str()); }).bind_recv([&](std::string_view sv) { printf("recv : %u %.*s\n", (unsigned)sv.size(), (int)sv.size(), sv.data()); client.send(sv); }) //.bind_recv(on_recv) // 绑定全局函数 //.bind_recv(std::bind(&listener::on_recv, &lis, std::placeholders::_1)) // 绑定成员函数(具体请查看demo代码) //.bind_recv(&listener::on_recv, lis) // 按lis对象的引用来绑定成员函数(具体请查看demo代码) //.bind_recv(&listener::on_recv, &lis) // 按lis对象的指针来绑定成员函数(具体请查看demo代码) ; client.async_start("0.0.0.0", "8080"); // 异步连接服务端 //client.start("0.0.0.0", "8080"); // 同步连接服务端 //client.async_start("0.0.0.0", "8080", '\n'); // 按\n自动拆包(可以指定任意字符) //client.async_start("0.0.0.0", "8080", "\r\n"); // 按\r\n自动拆包(可以指定任意字符串) //client.async_start("0.0.0.0", "8080", match_role); // 按match_role指定的规则自动拆包(match_role请参考demo代码)(用于对用户自定义的协议拆包) //client.async_start("0.0.0.0", "8080", asio::transfer_exactly(100)); // 每次接收固定的100字节 //client.start("0.0.0.0", "8080", asio2::use_dgram); // 数据报模式的TCP,无论发送多长的数据,双方接收的一定是相应长度的整包数据
// 发送时也可以指定use_future参数,然后通过返回值future来阻塞等待直到发送完成,发送结果的错误码和发送字节数 // 保存在返回值future中(注意,不能在通信线程中用future去等待,这会阻塞通信线程进而导致死锁) // std::future<std::pair<asio::error_code, std::size_t>> future = client.send("abc", asio::use_future);
服务端:
asio2::udp_server server; // ... 绑定监听器(请查看demo代码) server.start("0.0.0.0", "8080"); // 常规UDP //server.start("0.0.0.0", "8080", asio2::use_kcp); // 可靠UDP
客户端:
asio2::udp_client client; // ... 绑定监听器(请查看demo代码) client.start("0.0.0.0", "8080"); //client.async_start("0.0.0.0", "8080", asio2::use_kcp); // 可靠UDP
服务端:
asio2::rpc_server server; // ... 绑定监听器(请查看demo代码) A a; // A的定义请查看demo代码 server.bind("add", add); // 绑定RPC全局函数 server.bind("mul", &A::mul, a); // 绑定RPC成员函数 server.bind("cat", [&](const std::string& a, const std::string& b) { return a + b; }); // 绑定lambda表达式 server.bind("get_user", &A::get_user, a); // 绑定成员函数(按引用) server.bind("del_user", &A::del_user, &a); // 绑定成员函数(按指针) //server.start("0.0.0.0", "8080", asio2::use_dgram); // 使用TCP数据报模式作为RPC通信底层支撑,启动服务端时必须要使用use_dgram参数 server.start("0.0.0.0", "8080"); // 使用websocket作为RPC通信底层支撑(需要到rcp_server.hpp文件末尾代码中选择使用websocket)
客户端:
asio2::rpc_client client; // ... 绑定监听器(请查看demo代码) //client.start("0.0.0.0", "8080", asio2::use_dgram); // 使用TCP数据报模式作为RPC通信底层支撑,启动服务端时必须要使用use_dgram参数 client.start("0.0.0.0", "8080"); // 使用websocket作为RPC通信底层支撑 asio::error_code ec; // 同步调用RPC函数 int sum = client.call<int>(ec, std::chrono::seconds(3), "add", 11, 2); printf("sum : %d err : %d %s\n", sum, ec.value(), ec.message().c_str()); // 异步调用RPC函数,第一个参数是回调函数,当调用完成或超时会自动调用该回调函数,如果超时或其它错误, // 错误码保存在ec中,这里async_call没有指定返回值类型,则lambda表达式的第二个参数必须要指定类型 client.async_call([](asio::error_code ec, int v) { printf("sum : %d err : %d %s\n", v, ec.value(), ec.message().c_str()); }, "add", 10, 20); // 这里async_call指定了返回值类型,则lambda表达式的第二个参数可以为auto类型 client.async_call<int>([](asio::error_code ec, auto v) { printf("sum : %d err : %d %s\n", v, ec.value(), ec.message().c_str()); }, "add", 12, 21); // 返回值为用户自定义数据类型(user类型的定义请查看demo代码) user u = client.call<user>(ec, "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([](asio::error_code ec) { }, "del_user", std::move(u));
asio2::http_server server; server.bind_recv([&](std::shared_ptr<asio2::http_session> & session_ptr, http::request<http::string_body>& req) { // 在收到http请求时尝试发送一个文件到对端 { // 如果请求是非法的,直接发送错误信息到对端并返回 if (req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) { session_ptr->send(http::make_response(http::status::bad_request, "Illegal request-target")); session_ptr->stop(); // 同时直接断开这个连接 return; } // Build the path to the requested file std::string path(req.target().data(), req.target().size()); path.insert(0, std::filesystem::current_path().string()); if (req.target().back() == '/') path.append("index.html"); // 打开文件 beast::error_code ec; http::file_body::value_type body; body.open(path.c_str(), beast::file_mode::scan, ec); // 如果打开文件失败,直接发送错误信息到对端并直接返回 if (ec == beast::errc::no_such_file_or_directory) { session_ptr->send(http::make_response(http::status::not_found, std::string_view{ req.target().data(), req.target().size() })); return; } // Cache the size since we need it after the move auto const size = body.size(); // 生成一个文件形式的http响应对象,然后发送给对端 http::response<http::file_body> res{ std::piecewise_construct, std::make_tuple(std::move(body)), std::make_tuple(http::status::ok, req.version()) }; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::content_type, http::extension_to_mimetype(path)); res.content_length(size); res.keep_alive(req.keep_alive()); res.chunked(true); // Specify a callback function when sending //session_ptr->send(std::move(res)); session_ptr->send(std::move(res), [&res](std::size_t bytes_sent) { auto opened = res.body().is_open(); std::ignore = opened; auto err = asio2::get_last_error(); std::ignore = err; }); //session_ptr->send(std::move(res), asio::use_future); return; } std::cout << req << std::endl; if (true) { // 用make_response生成一个http响应对象,状态码200表示操作成功,"suceess"是HTTP消息的body部分内容 auto rep = http::make_response(200, "suceess"); session_ptr->send(rep, []() { auto err = asio2::get_last_error(); std::ignore = err; }); } else { // 也可以直接发送一个http标准响应字符串,内部会将这个字符串自动转换为http响应对象再发送出去 std::string_view rep = "HTTP/1.1 404 Not Found\r\n"\ "Server: Boost.Beast/181\r\n"\ "Content-Length: 7\r\n"\ "\r\n"\ "failure"; // test send string sequence, the string will automatically parsed into a standard http request session_ptr->send(rep, [](std::size_t bytes_sent) { auto err = asio2::get_last_error(); std::ignore = err; }); } }); server.start(host, port);
asio2::error_code ec; auto req1 = http::make_request("http://www.baidu.com/get_user?name=a"); // 通过URL字符串生成一个http请求对象 auto req2 = http::make_request("GET / HTTP/1.1\r\nHost: 127.0.0.1:8443\r\n\r\n"); // 通过http协议字符串生成一个http请求对象 req2.set(http::field::timeout, 5000); // 给请求设置一个超时时间 auto rep1 = asio2::http_client::execute("http://www.baidu.com/get_user?name=a", ec); // 通过URL字符串直接请求某个网址,返回结果在rep1中,如果有错误,错误码保存在ec中 auto rep2 = asio2::http_client::execute("127.0.0.1", "8080", req2); // 通过IP端口以及前面生成的req2请求对象来发送一个http请求 std::cout << rep2 << std::endl; // 显示http请求结果 std::stringstream ss; ss << rep2; std::string result = ss.str(); // 通过这种方式将http请求结果转换为字符串
class ping_test // 模拟在一个类对象中使用ping组件(其它所有如TCP/UDP/HTTP等组件一样可以在类对象中使用) { asio2::ping ping; public: ping_test() : ping(10) // 构造函数传入的10表示只ping 10次后就结束,传入-1表示一直ping { ping.timeout(std::chrono::seconds(3)); // 设置ping超时 ping.interval(std::chrono::seconds(1)); // 设置ping间隔 ping.body("0123456789abcdefghijklmnopqrstovuxyz"); ping.bind_recv(&ping_test::on_recv, this) // 绑定当前这个类的成员函数作为监听器 .bind_start(std::bind(&ping_test::on_start, this, std::placeholders::_1)) // 也是绑定成员函数 .bind_stop([this](asio::error_code ec) { this->on_stop(ec); }); // 绑定lambda } void on_recv(asio2::icmp_rep& rep) { if (rep.lag.count() == -1) // 如果延时的值等于-1表示超时了 std::cout << "request timed out" << std::endl; else std::cout << rep.total_length() - rep.header_length() << " bytes from " << rep.source_address() << ": icmp_seq=" << rep.sequence_number() << ", ttl=" << rep.time_to_live() << ", time=" << std::chrono::duration_cast<std::chrono::milliseconds>(rep.lag).count() << "ms" << std::endl; } void on_start(asio::error_code ec) { printf("start : %d %s\n", asio2::last_error_val(), asio2::last_error_msg().c_str()); } void on_stop(asio::error_code ec) { printf("stop : %d %s\n", asio2::last_error_val(), asio2::last_error_msg().c_str()); } void run() { if (!ping.start("127.0.0.1")) //if (!ping.start("123.45.67.89")) //if (!ping.start("stackoverflow.com")) printf("start failure : %s\n", asio2::last_error_msg().c_str()); while (std::getchar() != '\n'); ping.stop(); // ping结束后可以输出统计信息,包括丢包率,平均延时时长等 printf("loss rate : %.0lf%% average time : %lldms\n", ping.plp(), std::chrono::duration_cast<std::chrono::milliseconds>(ping.avg_lag()).count()); } };
asio2::tcps_server server; // 从内存字符串加载SSL证书(具体请查看demo代码) server.set_cert("test", cer, key, dh); // cer,key,dh这三个字符串的定义请查看demo代码 // 从文件加载SSL证书 //server.set_cert_file("test", "server.crt", "server.key", "dh512.pem");
// 框架中提供了定时器功能,使用非常简单,如下: 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); });
基于c++和asio的网络编程框架asio2教程基础篇:1、基本概念和使用说明 由于asio2没有写技术文档,因此打算写几篇文章介绍一下如何使用它,主要是针对新手。 1、asio2如何使用? asio2这个框架的使用很简单,以VS2017举例:在VS2017的项目上点右键 - 属性 - C/C++ - 常规 - 附加包含目录,将asio2-master/3rd和asio2-master/inclu
一,asio2 网络库 介绍 简单理解 asio2 是一个支持 tcp, udp, http, websocket, rpc, ssl, icmp, serial_port的c++第三方库。项目需要,目前我仅仅用到了rpc 这部分功能,使用它来替换QtRo 实现进程间通信,QtRo 用起来太麻烦,需要接口文件,并称生成对应的代码,还要去继承,实现。这个相对简单点。 https://github.c
开源基于asio的网络通信框架asio2,支持TCP,UDP,HTTP,WEBSOCKET,RPC,ICMP,SSL,串口,跨平台,支持可靠UDP,支持TCP自动拆包等 C++开发网络通信程序时用asio是个不错的选择,但asio本身是一套函数集,自己还要处理诸如“通信线程池管理、连接及生命周期管理、多线程收发数据的同步保护等”。因此这里对asio进行了一层封装,大大简化了对asio的使用。代码使
关于asio2项目example目录中的几个tcp示例的说明 在 /asio2/example/tcp/ 目录里有以下这几个关于tcp的示例代码: tcp_client_character,tcp_client_custom,tcp_client_datagram,tcp_client_general 这几个示例是演示怎么做tcp拆包的,下面详细说明一下(服务端相对应的示例是一样的意思,不再描述)
rpc的基础概念这里就不再介绍了,不熟悉的可以网络搜索,先了解一下。asio2框架实现了轻量级的rpc功能,使用起来非常简单。 最简单的例子 服务端代码 int add(int a, int b) { return a + b; } asio2::rpc_server server; server.bind("add", add); // 绑定rpc函数,第1个参数是字符串,表示rpc函数的
基于c++和asio的网络编程框架asio2教程基础篇:3、各个回调函数的触发线程以及多线程总结 关于asio的多线程的知识点感觉挺多的,需要对“服务端,客户端,tcp,udp”分别来总结。 而且了解各个函数分别在哪个线程中执行的,对于多线程编程以及和做具体业务时变量要不要用锁来保护,非常重要。 asio2是“one io_context per cpu”的多线程模式。这个概念主要是asio本身的
使用asio2开发一个简易的http server 大约200行代码开发一个简易的静态的http server,同时支持http和https,源代码地址:https://github.com/zhllxt/http_server_lite 下载该项目后,打开bin目录里的http_server_lite.exe,然后在你的浏览器输入http://localhost可以测试一下效果,实际上和这个网址
开源基于asio的网络通信框架asio2,支持TCP,UDP,HTTP,RPC,SSL,跨平台,支持可靠UDP,支持TCP自动拆包,TCP数据报模式等 C++开发网络通信程序时用asio是个不错的选择,但asio本身是一套函数集,自己还要处理诸如“通信线程池管理、连接及生命周期管理、多线程收发数据的同步保护等”。因此这里对asio进行了一层封装,大大简化了对asio的使用。代码使用了C++17相
asio2框架实现了网络速度限制功能,使用起来很简单(可以参考example文件夹的示例:/asio2/example/rate_limit/rate_limit.cpp): 首先定义一个宏开启这个功能 #define ASIO2_INCLUDE_RATE_LIMIT 服务端限速 asio2::tcp_rate_server tcp_server; tcp_server.bind_connec
基于c++和asio的网络编程框架asio2教程基础篇:2、各个回调函数的触发顺序和执行流程 以tcp举例: tcp服务端流程: #include <asio2/asio2.hpp> int main() { std::string_view host = "0.0.0.0"; std::string_view port = "8028"; asio2::tcp_server serv
本文向大家介绍java 基础知识之网络通信(TCP通信、UDP通信、多播以及NIO)总结,包括了java 基础知识之网络通信(TCP通信、UDP通信、多播以及NIO)总结的使用技巧和注意事项,需要的朋友参考一下 java 基础知识之网路通信总结 在这篇文章里,我们主要讨论如何使用Java实现网络通信,包括TCP通信、UDP通信、多播以及NIO。 TCP连接 TCP的基础是Socket,在T
随着分布式技术和微服务思想流行,技术公司逐步将服务拆分为独立运行的小模块,提高系统整体的健壮性,加快特性的演进速度。微服务通过定义完善的接口进行交互,解耦系统、敏捷迭代、方便服务治理。RPC是目前微服务最广泛的通信方式。然而,众多团队各自研发具备服务治理功能的RPC通信框架,一方面增加开发成本,消耗人力重复造轮子;另一方面不同序列化协议的RPC服务,无法互相通信,影响可用性。因此,通用的RPC通信
对服务开发者, MTransport 屏蔽了底层网络通信细节,从而更专注于业务自身逻辑实现。支持不同语言版本的代码实现, 保持通信协议的一致性,支持服务注册、服务发现、异步通信、负载均衡等丰富的服务治理功能。
本文向大家介绍基于NIO的Netty网络框架(详解),包括了基于NIO的Netty网络框架(详解)的使用技巧和注意事项,需要的朋友参考一下 Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。 Netty的优点有: a、功
提示 视频 PPT 下载 TCP 通信原理 TCP 把连接作为最基本的对象,每一条 TCP 连接都有两个端点,这种端点我们叫作套接字(socket),它的定义为端口号拼接到 IP 地址即构成了套接字,例如,若 IP 地址为 192.3.4.16 而端口号为 80,那么得到的套接字为192.3.4.16:80。IP 协议虽然能把数据报文送到目的主机,但是并没有交付给主机的具体应用进程。而端到端的通信
3. 基于UDP协议的网络程序 下图是典型的UDP客户端/服务器通讯过程(该图出自[UNPv13e])。 图 37.3. UDP通讯流程 以下是简单的UDP服务器和客户端程序。 /* server.c */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #include "wrap.h" #define MAXLIN
主要内容:本节引言:,1.服务端实现步骤:,2.客户端实现步骤:,本节小结:本节引言: 本节给大家带来Socket的最后一节:基于UDP协议的Socket通信,在第一节中我们已经详细地 比较了两者的区别,TCP和UDP最大的区别在于是否需要客户端与服务端建立连接后才能进行 数据传输,如果你学了前两节TCP的,传输前先开服务端,accept,等客户端接入,然后获得 客户端socket然后进行IO操作,而UDP则不用,UDP以数据报作为数据的传输载体,在进行传输时 首先要把传
2. 基于TCP协议的网络程序 下图是基于TCP协议的客户端/服务器程序的一般流程: 图 37.2. TCP协议通讯流程 服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()