VS2022通過C++網絡庫Boost.asio搭建一個簡單TCP異步服務器和客戶端

基本介紹

上一篇博客我們介紹了通過Boost.asio搭建一個TCP同步服務器和客戶端,這次我們再通過asio搭建一個異步通信的服務器和客戶端系統,由于這是一個簡單異步服務器,所以我們的異步特指異步服務器而不是異步客戶端,同步服務器在處理一個請求時會阻塞其他請求,而異步服務器可以同時處理多個請求,不會阻塞其他請求的處理,客戶端一般是不會處理其他客戶端請求的,所以客戶端仍舊使用同步模式。(本次博客使用的Boost庫版本是1.84.0)

服務器端

main.cpp

#include<boost/asio.hpp>
#include"Server.h"
#include<iostream>
int main()
{try{boost::asio::io_context ioc;Server s(ioc, 56789);ioc.run();}catch (const std::exception& e){std::cout << e.what() << std::endl;}return 0;
}

其中ioc是boost.asio的核心類對象,用于管理和調度異步操作,負責處理事件循環和IO事件的分派,尤其對于異步通信模式來說更為重要,56789就是我們要監聽的端口號,至于Server類就是用來接收客戶端連接的,之所以將ioc和端口號傳給Server,是因為我們要在Server類中初始化一個acceptor套接字,用來接收客戶端的連接,而創建套接字需要使用上下文對象,這是必要條件,要使得服務器能夠監聽客戶端的請求,就需要創建端點對象endpoint,并將它綁定到acceptor,而創建端點對象,不就需要我們的端口號和IP地址嘛,接下來我們會把它實現

ioc.run()這句話是異步通信模式的核心,同步通信模式并不會通過ioc對象調用run函數,因為同步通信模式是阻塞式的,它會一直等待操作完成后再繼續執行后續代碼,相反,異步通信模式中的操作是非阻塞的,需要通過調用上下文對象的run()函數來啟動事件循環,以便處理異步操作的完成事件和回調函數,run函數會啟動io_context的事件循環,處理代處理的異步操作,直到沒有更多的客戶端響應要處理為止,其實就是類似一個循環的效果,可以使服務器同時不斷處理不同客戶端的請求

Server.h

#pragma once
#include<boost/asio.hpp>
#include"Session.h"
class Server
{
public:Server(boost::asio::io_context& ioc, int port);void accept_handle(Session* s, const boost::system::error_code&error);void start_accept();boost::asio::io_context &ioc;boost::asio::ip::tcp::acceptor act;
};

Server類用來接收客戶端的連接,實際上異步和同步之間差的就是一個封裝,同步通信中我們同樣要接收客戶端的連接,同樣要使用到acceptor套接字,但是我們是直接使用的,不用再創建一個類什么的去封裝這個acceptor套接字,到了異步中,這就相當必要了,因為存在回調函數的原因,所以通過Server類將acceptor套接字進行封裝,可以使我們的思路更加清晰,不至于被一推回調函數繞暈。

Server.cpp

#include"Server.h"
#include<iostream>
Server::Server(boost::asio::io_context& ioc, int port) :ioc(ioc), act(ioc, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{start_accept();
}
void Server::start_accept()
{try{Session* s = new Session(ioc);act.async_accept(s->get_socket(), std::bind(&Server::accept_handle, this, s, std::placeholders::_1));}catch (boost::system::system_error &e){std::cout << e.what() << std::endl;}
}
void Server::accept_handle(Session* s, const boost::system::error_code& error)
{if (!error){s->Start();}else{delete s;}start_accept();
}

Server類的構造函數,可以用來幫助我們初始化acceptor套接字act,以及上下文對象ioc,Server類中有一個上下文對象的成員變量,這是用來創建客戶端處理套接字的,我們知道服務器本身的acceptor套接字不會直接處理客戶端發來的請求,它接收了客戶端的連接后,就會新創建一個套接字專門用來處理這個客戶端的請求,而創建套接字就要用到上下文對象,因此我們也要通過構造函數初始化這個上下文成員變量,初始化完了這些變量后,就調用start_accept()函數開始接收客戶端的連接。

start_accept函數,之前我們就說了,異步相比于同步,最大的區別就是封裝,start_accept函數就是對async_accept函數的封裝,async_accept函數是Boost.Asio庫中用于異步接受傳入連接的函數,它的第一個參數其實就是我們要接收的客戶端處理套接字,而第二個參數就是一async_accept回調函數的函數對象。

void async_accept(basic_socket<Protocol, Executor>& socket,AcceptHandler&& handler);
//socket:表示服務器偵聽的套接字對象。
//handler:是一個回調函數,當接受操作完成時將被調用。回調函數必須具有以下簽名:void handler(const boost::system::error_code& error)

回調函數可以使用std::bind()來創建一個函數對象,用于作為異步操作完成后的回調處理函數,std::bind()函數可以將成員函數與指定的對象綁定,以及在調用時傳遞其他參數,我們使用std::bind()綁定Server類的成員函數handle_accept(),并將當前對象指針(this)、new_session參數(作為客戶端處理對象的指針,里面包含了客戶端處理套接字)以及placeholders::_1(表示接受操作的錯誤代碼參數)作為參數進行綁定。

this關鍵字表示指向當前Server對象的指針。由于回調函數需要訪問Server類的成員函數(start_accept())和成員變量,因此將this作為第一個參數傳遞給std::bind()來綁定成員函數handle_accept()

std::placeholders::_1是一個占位符,用于在使用std::bind()函數時表示第一個參數的位置。它是C++標準庫中的一部分,可以用于綁定函數的參數。在給定的代碼中,std::placeholders::_1被用作異步操作完成后回調函數的參數位置的占位符。具體來說,它代表了async_accept()函數的回調函數中的錯誤代碼參數,即接受操作的結果。通過使用std::placeholders::_1,可以將回調函數與一個參數進行綁定,而不需要提供實際的值。當異步操作完成后,實際的錯誤代碼將傳遞給回調函數,并填充到占位符的位置上,從而在回調函數中可以訪問和處理該值。因此,std::placeholders::_1在這里充當了待綁定參數的占位符,以便在異步操作完成后正確地傳遞相應的參數給回調函數。

如果服務器接收到了客戶端的連接,那么接下來就會調用回調函數accept_handle,用來處理連接后的操作。

Session.h

#pragma once
#include<boost/asio.hpp>
class Session
{public:Session(boost::asio::io_context& ioc);boost::asio::ip::tcp::socket &get_socket();void Start();void handle_send(const::boost::system::error_code &error);void handle_recive(const::boost::system::error_code& error,size_t recived_len);boost::asio::ip::tcp::socket soc;int max_len = 1024;char data[1024];
};

Sesion類用來處理客戶端的連接,包括接收和發送數據給客戶端等操作,它里面封裝了客戶端處理套接字socket soc。

Session.cpp

#include"Session.h"
#include<iostream>
Session::Session(boost::asio::io_context& ioc):soc(ioc)
{}
boost::asio::ip::tcp::socket& Session::get_socket()
{return soc;
}
void Session::Start()
{memset(data, 0, max_len);soc.async_read_some(boost::asio::buffer(data, max_len),std::bind(&Session::handle_recive, this, std::placeholders::_1, std::placeholders::_2));}
void Session::handle_recive(const::boost::system::error_code& error, size_t recived_len)
{if (!error){std::cout << "收到的數據是: " << data<<std::endl;soc.async_write_some(boost::asio::buffer(data, recived_len),std::bind(&Session::handle_send, this, std::placeholders::_1));}else{delete this;}
}
void Session::handle_send(const::boost::system::error_code& error)
{if (!error){memset(data, 0, max_len);soc.async_read_some(boost::asio::buffer(data, max_len), std::bind(&Session::handle_recive, this, std::placeholders::_1, std::placeholders::_2));}else{delete this;}
}

Start函數用來開啟服務器對客戶端請求的處理,我們知道服務器連接后對客戶端的第一個操作都是接收客戶端的數據或請求,所以我們在這個函數里面調用了async_read_some函數用來接收客戶端的請求,并且將這個函數綁定了一個回調函數handle_recive。

std::bind(&Session::handle_recive, this, std::placeholders::_1, std::placeholders::_2)綁定了handle_recive成員函數作為回調函數。當讀取操作完成時,會調用該回調函數,并將錯誤碼和實際傳輸的字節數作為參數傳遞給該函數,placeholders的作用和之前的一樣,只是一個函數參數的占位符。

handle_recive和handle_send函數分別是異步讀和異步寫的回調函數,這兩個函數其實互相封裝了對方的異步操作函數,handle_recive封裝的是異步寫,而handle_send封裝的是異步讀,你會發現兩個回調函數封裝的異步操作函數和它們本身是相反的。

handle_recive函數和handle_send函數是相互調用的原因是為了實現一個基本的回顯服務器(echo server)的功能。當客戶端發送數據到服務器時,服務器會先讀取接收到的數據并打印出來(在handle_recive函數中),然后將相同的數據寫回給客戶端(在handle_send函數中)。調用handle_send函數后,當寫操作完成時,又會調用handle_recive函數,以便繼續等待下一個來自客戶端的數據。這種循環的設計方式可以保持與客戶端的持續通信,并確保服務器能夠及時處理客戶端發送的新數據。通過在讀取和寫入操作之間相互調用,可以實現數據的來回傳輸。

客戶端

客戶端采用同步的通信模式,所以代碼相當簡單。

main.cpp

#include<boost/asio.hpp>
#include<iostream>
int main()
{boost::asio::io_context ioc;boost::asio::ip::tcp::socket soc(ioc);boost::asio::ip::tcp::endpoint ed(boost::asio::ip::address::from_string("127.0.0.1"), 56789);char buf[1024]="";try{soc.connect(ed);std::cout << "請輸入發送的消息:";std::cin >> buf;soc.send(boost::asio::buffer(buf, strlen(buf)));char rec[1024]="";soc.receive(boost::asio::buffer(rec, 1024));std::cout << "收到了消息:" << rec << std::endl;}catch (boost::system::system_error &e){std::cout << e.what()<<std::endl;}return 0;
}

代碼運行

首先運行服務器端的代碼,然后再兩次運行客戶端的代碼,在兩個客戶端窗口中輸入要發送的消息,先不要回車。

先在二號客戶端進行回車,我們發現比1號客戶端晚一步運行的二號客戶端既然可以在一號客戶端的前面向服務器發送消息,要知道,1號客戶端雖然沒有回車,但是沒報異常就是說明1號客戶端是成功連接上了服務器的,而且比二號客戶端要早連接上,這說明了1號并沒有阻塞2號的請求發送,這就是異步通信如果是同步通信,只要1號客戶端不會車,服務器就會一直等待1號回車,等1號回車完了服務器才會釋放1號的連接,這時候2號回車的消息才會被服務器接收到,也就是說2號被1號阻塞了。

?將1號也回車,正常執行,至此一個簡單的TCP異步服務器和客戶端系統搭建完成,實際上真正的異步通信遠不如這么簡單,要實現一個完整的異步通信需要進行大量的思考和復雜的編程。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/16110.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/16110.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/16110.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

BGP選路規則

配置地址&#xff0c;AS123使用ospf保證通訊&#xff0c;修改接口類型保證ospf學習環回20.0,30.0,100.0 地址時&#xff0c;是以24位掩碼學習&#xff0c;R1&#xff0c;R2&#xff0c;R3都處于BGP邊界&#xff0c;各自都需要宣告三者的私網環回 1&#xff0c; [R4]ip ip-prefi…

點分治練習

P3806 【模板】點分治 1 #include <bits/stdc.h> using namespace std;inline long long read() {char ch getchar();long long f 1,x 0;while (ch > 9 || ch < 0) { if (ch -)f -1; ch getchar(); }while (ch > 0 && ch < 9) { x (x <&l…

Thrift學習深入

Thrift學習深入 https://zhuanlan.zhihu.com/p/22934974 https://zhuanlan.zhihu.com/p/26993406 從具體的demo入手,我們需要學習的是三部分 IDLserver端client端一、IDL深入 IDL定義的通用類型有: 基礎數據類型結構體容器 list、set、map異常:語法與結構體無異,不過用…

第十二周筆記

微信小程序的自定義事件是指開發者可以自行定義并觸發的事件&#xff0c;以實現特定的功能或邏輯。通過自定義事件&#xff0c;開發者可以更靈活地管理小程序的交互和數據流動&#xff0c;提升用戶體驗和開發效率。下面我將詳細講解微信小程序自定義事件&#xff0c;包括定義、…

容器化部署

目錄 docker容器化部署 怎樣使用Docker Compose或Kubernetes等容器編排工具來管理和擴展聯邦學習系統 使用Docker Compose

【Qnx 】Qnx IPC通信PPS

Qnx IPC通信PPS Qnx自帶PPS服務&#xff0c;PPS全稱Persistent Publish/Subscribe Service&#xff0c;就是常見的P/S通信模式。 Qnx PPS的通信模式是異步的&#xff0c;Publisher和Subscriber也無需關心對方是否存在。 利用Qnx提供的PPS服務&#xff0c;Publisher可以通知多…

嵌入式進階——LED呼吸燈(PWM)

&#x1f3ac; 秋野醬&#xff1a;《個人主頁》 &#x1f525; 個人專欄:《Java專欄》《Python專欄》 ??心若有所向往,何懼道阻且長 文章目錄 PWM基礎概念STC8H芯片PWMA應用PWM配置詳解占空比 PWM基礎概念 PWM全稱是脈寬調制&#xff08;Pulse Width Modulation&#xff09…

Arduino下載與安裝(Windows 10)

Arduino下載與安裝(Windows 10) 官網 下載安裝 打開官網&#xff0c;點擊SOFTWARE&#xff0c;進入到軟件下載界面&#xff0c;選擇Windows 選擇JUST DOWNLOAD 在彈出的界面中&#xff0c;填入電子郵件地址&#xff0c;勾選Privacy Policy&#xff0c;點擊JUST DOWNLOAD即可 …

深入解析:Element Plus 與 Vite、Nuxt、Laravel 的結合使用

在現代前端開發中&#xff0c;選擇合適的工具和框架來提高開發效率和應用性能是至關重要的。 Element-Plus 是一個基于 Vue.js 3.0 的流行 UI組件庫&#xff0c;它可以與多種前端和后端框架結合使用&#xff0c;如 Vite、Nuxt 和 Laravel。本文將深入探討這三者與 Element Plus…

【腳本篇】---spyglass lint腳本

目錄結構 sg_lint.tcl &#xff08;頂層&#xff09; #1.source env #date set WORK_HOME . set REPORT_PATH ${WORK_HOME}/reports puts [clock format [clock second] -format "%Y-%m-%d %H:%M:%S"] #2.generate source filelist #3.set top module puts "##…

qt-C++筆記之QThread使用

qt-C筆記之QThread使用 ——2024-05-26 下午 code review! 參考博文&#xff1a; qt-C筆記之使用QtConcurrent異步地執行槽函數中的內容&#xff0c;使其不阻塞主界面 qt-C筆記之QThread使用 文章目錄 qt-C筆記之QThread使用一:Qt中幾種多線程方法1.1. 使用 QThread 和 Lambda…

ubuntu server 24.04 網絡 SSH等基礎配置

1 安裝參考上一篇: VMware Workstation 虛擬機安裝 ubuntu 24.04 server 詳細教程 服務器安裝圖形化界面-CSDN博客 2 網絡配置 #安裝 sudo apt install net-tools#查看 ifconfig #修改網絡配置 sudo vim /etc/netplan/50-cloud-init.yaml network:version: 2ethernets:en…

課時136:變量進階_變量實踐_高級賦值

2 變量進階 2.1 變量實踐 2.1.1 高級賦值 學習目標 這一節&#xff0c;我們從 基礎知識、簡單實踐、小結 三個方面來學習 基礎知識 簡介 所謂的高級賦值&#xff0c;是另外的一種變量值獲取方法&#xff0c;這里涉及到更多我們學習之外的一些shell內置變量格式,其實這部分…

飛雞:從小訓練飛行的雞能飛行嗎?為什么野雞能飛嗎?是同一品種嗎?今天自由思考

雞的飛行能力在很大程度上受到其生理結構的限制。盡管雞有翅膀&#xff0c;但與能夠長時間飛行的鳥類相比&#xff0c;雞的翅膀相對較小&#xff0c;且胸部肌肉較弱。再加上雞的身體較重&#xff0c;這些因素共同限制了雞的飛行能力。通常&#xff0c;雞只能進行短暫的、低空的…

【wiki知識庫】01.wiki知識庫前后端項目搭建(SpringBoot+Vue3)

&#x1f4dd;個人主頁&#xff1a;哈__ 期待您的關注 &#x1f33c;環境準備 想要搭建自己的wiki知識庫&#xff0c;要提前搭建好自己的開發環境&#xff0c;后端我使用的是SpringBoot&#xff0c;前端使用的是Vue3&#xff0c;采用前后端分離的技術實現。同時使用了Mysql數…

C++ vector,dequeu,list容器中元素的引用失效問題

文章目錄 一、std::list不會產生引用失效問題二、std::vector中元素引用失效問題三、std::deque中元素引用失效問題 一、std::list不會產生引用失效問題 在C中&#xff0c;std::list&#xff08;雙向鏈表&#xff09;提供了一種非常靈活的容器類型&#xff0c;其設計使其在插入…

微信小程序的事件對象屬性,事件綁定

微信小程序 小程序簡介 1 小程序與普通網頁開發的區別&#xff1f; 1運行環境的不同&#xff1a;網頁運行在瀏覽器&#xff0c;小程序運行在微信環境&#xff1b; 2.API 不同&#xff1a;小程序無法調用 DOM 和 BOM 的 API&#xff0c;但可以調用微信環境提供的 API&#xff1…

單工無線發射接收系統

1 緒論 隨著無線電技術的發展,通訊方式也從傳統的有線通訊逐漸轉向無線通訊。由于傳統的有線傳輸系統有配線的問題,較不便利,而無線通訊具有成本廉價、建設工程周期短、適應性好、擴展性好、設備維護容易實現等特點,故未來通訊方式將向無線傳輸系統方向發展。同時,實現系…

mfc140.dll丟失原因和mfc140.dll丟失修復辦法分享

mfc140.dll是與微軟基礎類庫&#xff08;Microsoft Foundation Classes, MFC&#xff09;緊密相關的動態鏈接庫&#xff08;DLL&#xff09;文件。MFC是微軟為C開發者設計的一個應用程序框架&#xff0c;用于簡化Windows應用程序的開發工作。以下是mfc140.dll文件的一些關鍵屬性…