VS2022通过C++网络库Boost.asio搭建一个简单TCP异步服务器和客户端

news2024/12/27 13:08:26

基本介绍

上一篇博客我们介绍了通过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.coloradmin.cn/o/1685202.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL靶场搭建

概述 简单介绍一下SQL靶场的搭建&#xff0c;以及在搭建过程中遇到的一些问题。使用该软件搭建靶场相对简单&#xff0c;适合新手小白。当然&#xff0c;也可以在自己的虚拟机下进行搭建&#xff0c;相对来说就较为复杂。本章主要讲解使用Phpstudy进行SQL靶场搭建。 这里我推…

SpringBoot 实现 RAS+AES 自动接口解密

一、讲个事故 接口安全老生常谈了 过年之前做了过一款飞机大战的H5小游戏&#xff0c;里面无限模式-需要保存用户的积分&#xff0c;因为使用的Body传参&#xff0c;参数是可见的。 为了接口安全我&#xff0c;我和前端约定了传递参数是&#xff1a;用户无限模式的积分“我们…

一个简约高级视差效果PR动态图文开场视频模板

这是一个高质量且易于定制的pr模板。具有模块化结构&#xff0c;可以轻松更改内容。包括视频教程&#xff0c;即使是新手小白也可以轻松套用模板制作视频。 主要特点&#xff1a; 水平&#xff08;19201080&#xff09;和垂直&#xff08;10801920&#xff09;分辨率&#xff…

实验二 电子传输系统安全-进展2

上周任务完成情况&#xff08;代码链接&#xff0c;所写文档等&#xff09; 重新调通电子公文传输系统部署gmssl学习生成SM2证书学习gmssl中的CTLS实现将数据库从SqlServer迁移到Mysql调试Mysql驱动学习Bouncy Castle 代码链接 Mysql表设计 /* Navicat MySQL Data Transfer…

用Python pynput库捕捉每一次组合键的优雅舞步

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、初识pynput&#xff1a;键盘与鼠标的监控利器 当谈论计算机交互时&#xff0c;键盘和鼠标无疑是最常用的设备。无论是编写代码、浏览网页还是玩游戏&#xff0c;都依赖于这些输入设备与机器沟通。但在一些特殊的…

【AI大模型】Embedding模型解析 文本向量知识库的构建和相似度检索

&#x1f680; 作者 &#xff1a;“大数据小禅” &#x1f680; 文章简介 &#xff1a;本专栏后续将持续更新大模型相关文章&#xff0c;从开发到微调到应用&#xff0c;需要下载好的模型包可私。 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 目…

K8S/ hpa分享

在 Kubernetes 中&#xff0c;HorizontalPodAutoscaler 自动更新工作负载资源 &#xff08;例如 Deployment 或者 StatefulSet&#xff09;&#xff0c; 目的是自动扩缩工作负载以满足需求。 hpa的使用本身还是很简单的 示例如下&#xff1a; 官网示例 apiVersion: apps/v1 k…

【三数之和】python,排序+双指针

暴力搜索3次方的时间复杂度&#xff0c;大抵超时 遇到不会先排序 排序双指针 上题解 照做 class Solution:def threeSum(self, nums: List[int]) -> List[List[int]]:res[]nlen(nums)#排序降低复杂度nums.sort()k0#留两个位置给双指针i,jfor k in range(n-2):if nums[k]…

shell常见指令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、认识C语言二、操作系统 1.引入库2.读入数据总结 前言 嵌入式学习前期C基础内容总结 一、认识C语言 C语言是一门十分流行的编程语言&#xff0c;由美国贝尔…

Web3的时代:科技变革助力物联网智能化

引言 随着人类社会的不断发展&#xff0c;科技进步已经成为推动社会发展的重要引擎之一。在这个信息化时代&#xff0c;互联网已经深刻改变了人们的生活方式和工作方式&#xff0c;而Web3技术的出现&#xff0c;则为我们带来了全新的科技革命。本文将探讨Web3时代对物联网智能…

HIOKI日置阻抗分析仪IM7583

HIOKI日置阻抗分析仪IM7583 HIOKI日置阻抗分析仪IM7583 HIOKI日置阻抗分析仪IM7583 功率分析仪 PW6001 基本参数 测量线路 单相2线&#xff0c;单相3线&#xff0c;三相3线&#xff0c;三相4线 输入通道数 zui大6ch&#xff0c;电压/电流同时单位是1ch &#xff08;电压测…

基于MetaGPT构建单智能体

前言 在之前的文章中&#xff0c;我们详细地描述了Agent的概念和组成&#xff0c;在代码案例中体验了Agent的记忆、工具、规划决策模块&#xff0c;并通过几个Agent框架来加强读者对Agent开发设计与应用的理解&#xff0c;接下来我们就要进入智能体Agent的实际开发中&#xff0…

三维模型相互转换(obj文件转inp文件)

三维模型文件根据其含义都是可以进行相互转换的&#xff0c;这里主要介绍obj文件转化为inp文件。 什么是inp文件&#xff1f; inp文件是以.inp为后缀的文本文件&#xff0c;它包括了模型的全部数据信息&#xff0c;ABAQUS求解器分析的对象是inp文件&#xff0c;软件生成的.ca…

AI - 各类AI针对Excel分析对比

一个水果销量表&#xff0c;Excel包含多个年份sheet&#xff0c;需要提取某个品种的水果每年的销量&#xff0c;看看几个AI的分析结果吧 1、文心一言3.5&#xff08;不支持Excel&#xff09; 不支持上传Excel文件 2、 通义千问2.5&#xff08;完成★&#xff09; 顺利完成…

在windows中使用wsl下的unbuntu环境

1 unbuntu下载编译环境 编译环境安装命令&#xff1a; sudo apt install gdb sudo apt install gcc sudo apt install g 2 使用vscode正常打开项目&#xff0c;在window中打开的项目&#xff08;官方推荐将项目放在linux中的home目录&#xff09; 但在windows中也可以使用&a…

电脑卸载linux安装windows后每次开机都出现grub

原因分析 这是因为电脑硬盘中还存在linux系统的引导程序&#xff0c;并且启动顺序还在windows之前&#xff0c;有时候通过bios根本找不到它的存在&#xff0c;以至于每次windows开机出现grub之后都要输入exit退出linux的引导之后才能使得电脑进入windows&#xff0c;这个有时会…

Vue3学习-用 vite@latest 初始化项目后,遇到无法识别 .vue 文件

引入app界面遇到 我的解决方案 1.根目录创建 env.d.ts&#xff0c;添加 declare module "*.vue" {import type { DefineComponent } from "vue"const vueComponent: DefineComponent<{}, {}, any>export default vueComponent }2.在 tsconfig.json…

​​​【收录 Hello 算法】9.3 图的遍历

目录 9.3 图的遍历 9.3.1 广度优先遍历 1. 算法实现 2. 复杂度分析 9.3.2 深度优先遍历 1. 算法实现 2. 复杂度分析 9.3 图的遍历 树代表的是“一对多”的关系&#xff0c;而图则具有更高的自由度&#xff0c;可以表示任意的“多对多”关系。因此&…

C++三剑客之std::any(二) : 源码剖析

目录 1.引言 2.std::any的存储分析 3._Any_big_RTTI与_Any_small_RTTI 4.std::any的构造函数 4.1.从std::any构造 4.2.可变参数模板构造函数 4.3.赋值构造与emplace函数 5.reset函数 6._Cast函数 7.make_any模版函数 8.std::any_cast函数 9.总结 1.引言 C三剑客之s…

开源与闭源AI模型的对决:数据隐私、商业应用与社区参与

引言 在人工智能&#xff08;AI&#xff09;领域&#xff0c;模型的发展路径主要分为“开源”和“闭源”两条。这两种模型在数据隐私保护、商业应用以及社区参与与合作方面各有优劣&#xff0c;是创业公司、技术巨头和开发者们必须仔细权衡的重要选择。那么&#xff0c;面对这些…