C++開發網絡通訊程序時用asio是個不錯的選擇,但asio自己是一套函數集,本身還要處理諸如「通訊線程池管理、鏈接及生命週期管理、多線程收發數據的同步保護等」。所以這裏對asio進行了一層封裝,大大簡化了對asio的使用。代碼使用了C++17相關功能,因此只能用在C++17以上。html
其中http和websocket部分用的是boost::beast,所以若是須要用到http或websocket的功能,則必須使用boost庫,若是用不到http則直接使用獨立的asio便可。在config.hpp中經過對ASIO_STANDALONE這個宏定義的開關,便可設置是使用boost::asio仍是使用asio standalone.linux
代碼大量使用了CRTP模板編程實現(沒有使用virtual而用CRTP實現的靜態多態),所以編譯比較耗時,但執行效率相對較好一點。c++
github地址:https://github.com/zhllxt/asio2git
碼雲地址:https://gitee.com/zhllxt/asio2github
A open source cross-platform c++ library for network programming based on asio,support for tcp,udp,http,rpc,ssl and so on.web
asio2::tcp_server server; server.bind_recv([&server](std::shared_ptr<asio2::tcp_session> & session_ptr, std::string_view s) { 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); });