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异步服务器和客户端系统搭建完成,实际上真正的异步通信远不如这么简单,要实现一个完整的异步通信需要进行大量的思考和复杂的编程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/635751.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c语言:利用随机函数产生20个[120, 834] 之间互不相等的随机数, 并利用选择排序法将其从小到大排序后输出(每行输出5个)

利用随机函数产生20个[120, 834] 之间互不相等的随机数&#xff0c; 并利用选择排序法将其从小到大排序后输出&#xff08;每行输出5个&#xff09; 代码如下&#xff1a; #include <stdio.h> #include <time.h> #include <stdlib.h> int shenchen(int a[…

全栈实现发送验证码注册账号 全栈开发之路——全栈篇(3)

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇&#xff1a;setup语法&#xff0c;设置响应式数据。 第四篇&#xff1a;数据绑定、计算属性和watch监视 第五篇 : 组件…

分布式音乐播放器适配了Stage模型

OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;应用开发自API 8及其更早版本一直使用的是FA模型进行开发。FA模型是Feature Ability的缩写&#xff0c;它和PA&#xff08;Particle Ability&#xff09;两种类型是过往长期推广的术语&#xff0c;深入人心…

6.2 else if语句

本节必须掌握的知识点&#xff1a; 示例代码二十 代码分析 汇编解析 ■if语句表达形式3 if(表达式1) statement1 else if(表达式2) statement2 else if(表达式3) statement3 …… else statementN 解析&#xff1a; 如果表达式1非0&#xff0c;则执行statement1&#…

Java基础22(JSON解析 注解)

目录 一、JSON解析 1. JSON语法 2. JSON的用途 3. Java解析JSON 4. 使用Fastjson 4.1 Fastjson 的优点 4.2 Fastjson 导包 4.3 Fastjson的主要对象 4.4 常用方法 将Java对象 "序列化"&#xff08;转换&#xff09; 为JSON字符串&#xff1a; 将JSON字符串…

go语言中的一个特别的语法 //go:embed 可将将静态文件内容读取到string, []byte和 embed.FS 变量并直接打包到exe包中

go语言中的一个特别的语法 //go:embed 看上去像是注释&#xff0c;实则是golang中的一个内置的语法&#xff0c;而且是仅在你的go代码编译时生效的语法&#xff0c; 借助他我们可以将我们的静态资源文件读取到FS直接打包到我们的exe执行文件中。 同时他还支持文件的模式匹配…

C#中BufferedStream类详解与示例

文章目录 1. BufferedStream的基本介绍2. 创建BufferedStream对象从现有Stream创建指定缓冲区大小 3. 使用BufferedStream读取数据写入数据 4. BufferedStream的注意事项5. 示例代码 在C#中&#xff0c;BufferedStream是一个非常有用的流类&#xff0c;它提供了缓冲功能&#x…

出谈论点云文件pcd加载01

刚写完基于potree开发地图水印效果的时候&#xff0c;在网上分享实例&#xff0c;刚发出去&#xff0c;竟然被人喷了&#xff0c;这么简单的实例&#xff0c;竟然好意思发群里&#xff0c;哎… 好无奈&#xff01; 不过我还是坚持我的想法&#xff0c;大家看到文章后&#xff0…

JVM严镇涛版笔记【B站面试题】

前言 2023-06-19 18:49:33 出自B站 灰灰的Java面试 枫叶云链接&#xff1a;http://cloud.fynote.com/s/4976 JVM面试题大全 Lecturer &#xff1a;严镇涛 1.为什么需要JVM&#xff0c;不要JVM可以吗&#xff1f; 1.JVM可以帮助我们屏蔽底层的操作系统 一次编译&#xff0c…

Windows安全应急--应急排查的一些方法

前言&#xff1a; 非法BC植入网站安全应急&#xff0c; 在安全应急中&#xff0c; 总会需要大大小小的问题&#xff0c; 就像成长一样。 检测工具尽量使用轻量级的。。 本次演示环境 Windows Server 2008 问题排查步骤&#xff1a; 先判断服务器有没有被Rootkit 查看登录…

[STM32-HAL库]Flash库-HAL库-复杂数据读写-STM32CUBEMX开发-HAL库开发系列-主控STM32F103C6T6

目录 一、前言 二、实现步骤 1.STM32CUBEMX配置 2.导入Flash库 3.分析地址范围 4.找到可用的地址 5.写入读取普通数据 6.写入读取字符串 6.1 存储相关信息 6.2 存取多个参数 三、总结及源码 一、前言 在面对需要持久化存储的数据时&#xff0c;除了挂载TF卡&#xff0c;我们…

java技术:oauth2协议

目录 一、黑马程序员Java进阶教程快速入门Spring Security OAuth2.0认证授权详解 1、oauth服务 WebSecurityConfig TokenConfig AuthorizationServer 改写密码校验逻辑实现类 2、oauth2支持的四种方式&#xff1a; 3、oauth2授权 ResouceServerConfig TokenConfig 4、…

CSS学习笔记之高级教程(二)

10、CSS 3D 转换 通过 CSS transform 属性&#xff0c;您可以使用以下 3D 转换方法&#xff1a; rotateX()rotateY()rotateZ() 10.1 rotateX() 方法&#xff08;使元素绕其 X 轴旋转给定角度&#xff09; <!DOCTYPE html> <html lang"en"><head&g…

2024-05-23 vscode + clang + clangd 解锁 modules

点击 <C 语言编程核心突破> 快速C语言入门 vscode clang clangd 解锁 modules 前言一、准备二、使用备注: 总结 前言 要解决问题: 昨天解锁VS使用modules, 但是不完美, 没有代码提示和补全了, 今天用 vscode clang clangd 解锁 modules, 同时还有代码补全及提示. …

第十一章 文件及IO操作

第十一章 文件及IO操作 文件的概述及基本操作步骤 文件&#xff1a; 存储在计算机的存储设备中的一组数据序列就是文件不同类型的文件通过后缀名进行区分 文本文件&#xff1a;由于编码格式的不同&#xff0c;所占磁盘空间的字节数不同(例如GBK编码格式中一个中文字符占2字…

K8S集群再搭建

前述&#xff1a;总体是非常简单的&#xff0c;就是过程繁琐&#xff0c;不过都是些重复的操作 master成员: [controller-manager, scheduler, api-server, etcd, proxy,kubelet] node成员: [kubelet, proxy] master要修改的配置文件有 1. vi /etc/etcd/etcd.conf # 数…

【设计模式深度剖析】【4】【创建型】【建造者模式】| 类比选购汽车的过程,加深理解

&#x1f448;️上一篇:抽象工厂模式 | 下一篇:原型模式&#x1f449;️ 目录 建造者模式概览定义英文原话直译如何理解呢&#xff1f;建造者模式将对象的建造过程给抽象出来了类比选购汽车 4个角色UML类图1. 抽象建造者&#xff08;Builder&#xff09;角色2. 具体建造者…

盲人社会适应性训练:打开生活的新篇章

在现代社会的快节奏中&#xff0c;每一位成员都在寻求更好的方式来适应环境&#xff0c;对于盲人群体而言&#xff0c;这种适应性尤为关键。盲人社会适应性训练作为一个旨在提升盲人生活质量和独立性的系统性过程&#xff0c;正逐步受到广泛关注。在这一过程中&#xff0c;一款…

安灯呼叫系统解决方案在生产中的应用

工厂安灯呼叫系统是一种用于监控工厂设备运行情况和生产状况的系统。它通常包括各种传感器和监控设备&#xff0c;可以实时监测工厂的生产流程&#xff0c;提供运行状态、故障警报、生产效率等信息。通过工厂安灯系统&#xff0c;工厂管理人员可以及时了解生产情况&#xff0c;…

探数API统计分享-中国各省人均消费支出

根据2017年至2021年的统计数据&#xff0c;我国各省&#xff08;市、区&#xff09;的人均消费支出情况各不相同。其中&#xff0c;上海的人均消费支出最高&#xff0c;达到了2021年的48879元&#xff0c;位居全国之首。紧随其后的是北京&#xff0c;人均消费支出为43640元。 相…