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.mzph.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…

【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即可 …

【脚本篇】---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…

飞鸡:从小训练飞行的鸡能飞行吗?为什么野鸡能飞吗?是同一品种吗?今天自由思考

鸡的飞行能力在很大程度上受到其生理结构的限制。尽管鸡有翅膀&#xff0c;但与能够长时间飞行的鸟类相比&#xff0c;鸡的翅膀相对较小&#xff0c;且胸部肌肉较弱。再加上鸡的身体较重&#xff0c;这些因素共同限制了鸡的飞行能力。通常&#xff0c;鸡只能进行短暂的、低空的…

【wiki知识库】01.wiki知识库前后端项目搭建(SpringBoot+Vue3)

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 &#x1f33c;环境准备 想要搭建自己的wiki知识库&#xff0c;要提前搭建好自己的开发环境&#xff0c;后端我使用的是SpringBoot&#xff0c;前端使用的是Vue3&#xff0c;采用前后端分离的技术实现。同时使用了Mysql数…

单工无线发射接收系统

1 绪论 随着无线电技术的发展,通讯方式也从传统的有线通讯逐渐转向无线通讯。由于传统的有线传输系统有配线的问题,较不便利,而无线通讯具有成本廉价、建设工程周期短、适应性好、扩展性好、设备维护容易实现等特点,故未来通讯方式将向无线传输系统方向发展。同时,实现系…

mfc140.dll丢失原因和mfc140.dll丢失修复办法分享

mfc140.dll是与微软基础类库&#xff08;Microsoft Foundation Classes, MFC&#xff09;紧密相关的动态链接库&#xff08;DLL&#xff09;文件。MFC是微软为C开发者设计的一个应用程序框架&#xff0c;用于简化Windows应用程序的开发工作。以下是mfc140.dll文件的一些关键属性…

栈的实现(C语言)

文章目录 前言1.栈的概念及结构2.栈的实现3.具体操作3.1.初始化栈(StackInit)和销毁栈(StackDestory)3.2.入栈(StackPush)和出栈(StackPop)3.3.获得栈的个数(StackSize)、获得栈顶元素(StackTop)以及判空(StackEmpty) 前言 前段时间我们学习过了链表和顺序表等相关操作&#x…

go-zero 实战(4)

中间件 在 userapi 项目中引入中间件。go项目中的中间可以处理请求之前和之后的逻辑。 1. 在 userapi/internal目录先创建 middlewares目录&#xff0c;并创建 user.go文件 package middlewaresimport ("github.com/zeromicro/go-zero/core/logx""net/http&q…

经济寒冬下的黄金跳板:方案、活动、竞标一手掌握

推荐策划人必备的宝藏地产策划资源平台&#xff0c; 订阅浩叫&#xff1a;地产营销策划圈。这个平台简直是地产策划人的百宝箱&#xff0c;里面藏着无数的策划秘籍&#xff0c;等着你来挖掘。 这个平台就像是一个大型的方案库&#xff0c;里面收录了众多知名地产企业的内部资料…

leetcode:计数质数

class Solution { public:// 如果 x 是质数&#xff0c;那么大于 x 的 x 的倍数 2x,3x… 一定不是质数int countPrimes(int n) {vector<int> isPrime(n, 1);int ans 0;for (int i 2; i < n; i) {if (isPrime[i]) {ans 1;if ((long long)i * i < n) {for (int j …

leetcode-55 跳跃游戏

leetcode Problem: 55. 跳跃游戏 思路 假设我们是一个小人&#xff0c;从第一个下标开始&#xff0c;每次经过一个位置&#xff0c;我们就可以根据当前位置的数值nums[i]和位置下标i计算出该位置所能到达的后续位置的最大值rnums[i]i。而这个r之前的区域一定都是可以经过的。…

AI 谈“浔川AI翻译机”

在天工AI&#xff0c;天工AI在全网搜索“浔川AI翻译机”。 1 创作助手谈“浔川AI翻译机”&#xff1a; “浔川AI翻译机”是一个利用人工智能技术进行语言翻译的设备或应用程序。它可以将一种语言的文字或口语翻译成另一种语言&#xff0c;以实现不同语言之间的沟通和理解。浔…

Linux指令初识

ls:显示当前目录底下的指定文件或目录 ls -l更详细的信息 ls -a显示当前目录下的所有文件 命令中的选项可以一次传递多个 ,例如&#xff1a;ls -al 命令和选项有必须一个或多个空格 以.开头的文件&#xff0c;为隐藏文件ls -a可以看到,ls -l看不见 支持命令拼在一起&#…

牛客热题:滑动窗口的最大值

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;滑动窗口的最大值题目链接方法一…