Boost:asio网络编程从同步到异步

news2024/10/2 3:15:29

文章目录

  • 同步服务器
    • 客户端
    • 服务端
  • 异步服务器(有问题)
  • 异步服务器优化

在学TCP的时候,写的第一个服务器就是一个echo服务器,那在Boost网络编程中,自然也先写一个echo服务器练练手

同步服务器

客户端

#include <iostream>
#include <boost/asio.hpp>

using namespace boost::asio::ip;
using namespace std;

const int MAX_LENTH = 1024;

int main()
{
	try
	{
		// 创建上下文服务
		boost::asio::io_context ioc;
		// 构造endpoint
		tcp::endpoint remote_ep(address::from_string("127.0.0.1"), 10086);
		// 创建Socket
		tcp::socket sock(ioc);
		boost::system::error_code error = boost::asio::error::host_not_found;
		sock.connect(remote_ep, error);
		if (error)
		{
			cout << "connect fail, code is " << error.value() << " Message: " << error.what() << endl;
			return 0;
		}
		cout << "connect success" << endl;
		cout << "Enter message: ";
		char request[MAX_LENTH];
		cin.getline(request, MAX_LENTH);
		size_t request_length = strlen(request);
		boost::asio::write(sock, boost::asio::buffer(request, request_length));

		char reply[MAX_LENTH];
		size_t reply_length = boost::asio::read(sock, boost::asio::buffer(reply, request_length));
		cout << "Reply is ";
		cout.write(reply, reply_length);
		cout << endl;

	}
	catch(exception& e)
	{
		cerr << e.what() << endl;
	}
	return 0;
}

服务端

服务端这里采用的是一个线程处理一个连接的方式,同时使用一个智能指针集合来保证不被异常释放,主要是通过增加一个引用计数,使得不会在离开作用域后就被销毁

server的作用就是一个接收者,他负责进行连接的获取,并进行对应的分发,通过创建一个一个的线程,然后执行对应的session逻辑,进行对应的回报处理

#include <iostream>
#include <boost/asio.hpp>
#include <set>
#include <memory>

using boost::asio::ip::tcp;
using namespace std;

const int MAX_LENTH = 1024;
typedef shared_ptr<tcp::socket> socket_ptr;
set<shared_ptr<thread>> thread_set;

// 服务器处理客户端的读和写
void session(socket_ptr sock)
{
	try
	{
		for (;;)
		{
			char data[MAX_LENTH];
			memset(data, '\0', MAX_LENTH);
			boost::system::error_code error;
			// 直到读到最长为止,要读到1024字节
			//size_t length = boost::asio::read(sock, boost::asio::buffer(data, MAX_LENTH), error);
			// 部分读取,读多少算多少
			size_t length = sock->read_some(boost::asio::buffer(data, MAX_LENTH), error);
			if (error == boost::asio::error::eof)
			{
				cout << "connection closed" << endl;
				break;
			}
			else if (error)
			{
				throw boost::system::system_error(error);
			}
			cout << "receive from " << sock->remote_endpoint().address().to_string() << endl;
			cout << "receive message is " << data << endl;
			// 将数据传递回去
			boost::asio::write(*sock, boost::asio::buffer(data, MAX_LENTH));
		}
	}
	catch (exception& e)
	{
		cerr << "Exception in thread" << e.what() << endl;
	}
}

void server(boost::asio::io_context& ioc, unsigned short port)
{
	// 专门来接受连接,必须按照传递方式,Acceptor是归属于某个ioc,以及用ipv4的方式进行绑定到端口
	tcp::acceptor a(ioc, tcp::endpoint(tcp::v4(), port));
	for (;;)
	{
		socket_ptr socket(new tcp::socket(ioc));
		a.accept(*socket);
		// 创建线程,做session工作,参数是Socket,等待客户端发数据,回传等工作
		auto t = make_shared<thread>(session, socket);
		thread_set.insert(t);
	}
}

int main()
{
	try
	{
		boost::asio::io_context ioc;
		server(ioc, 10086);
		for (auto& t : thread_set)
		{
			t->join();
		}
	}
	catch (exception& e)
	{
		cerr << "Exception in thread" << e.what() << endl;
	}
	return 0;
}

但是这样同步的结构必然是不可取的,对于小体量的项目还可以接收,但是对于大级别的项目还是要使用异步的方式进行服务器的搭建,因此下面就开始搭建一个异步的服务器

异步服务器(有问题)

首先创建一个会话类

class Session
{
public:
	Session(boost::asio::io_context& ioc)
		: _socket(ioc)
	{}

	tcp::socket& Socket()
	{
		return _socket;
	}

	void Start();

private:
	void handle_read(const boost::system::error_code& error, size_t byte_transferred);

	void handle_write(const boost::system::error_code& error);

private:
	tcp::socket _socket;
	enum { max_length = 1024 };
	char _data[max_length];
};

void Session::Start()
{
	memset(_data, 0, max_length);
	_socket.async_read_some(boost::asio::buffer(_data, max_length),
		bind(&Session::handle_read, this, placeholders::_1, placeholders::_2));
}

// 分批次读取事件回调函数
void Session::handle_read(const boost::system::error_code& error, size_t byte_transferred)
{
	if (!error)
	{
		cout << "server receive data is " << _data << endl;
		boost::asio::async_write(_socket, boost::asio::buffer(_data, byte_transferred),
			bind(&Session::handle_write, this, placeholders::_1));
	}
	else
	{
		cout << "read error" << endl;
		delete this;
	}
}

void Session::handle_write(const boost::system::error_code& error)
{
	if (!error)
	{
		memset(_data, 0, max_length);
		_socket.async_read_some(boost::asio::buffer(_data, max_length),
			bind(&Session::handle_read, this, placeholders::_1, placeholders::_2));
	}
	else
	{
		cout << "write error" << endl;
		delete this;
	}
}

这个会话主要负责的就是对应的echo的功能,将数据接收后再进行对应的发送的过程,收到数据后就进行回调,去将数据发送,同时进行数据发送的回调,进行数据的读取,直到当客户端关闭了连接,此时就会触发一个断开连接的操作,这个操作会被看成是一个读取操作失败,因此就会触发读回调函数的错误

但是这样的设计其实是有问题的,具体我们可以这样想,假设现在有一个内容,需要进行读取事件和写入事件,这是在一个作用域下要同时完成的,假如在进行读事件的时候触发了错误,比如客户端断开连接了,此时就会被捕获到异常,捕获到异常就会直接delete掉当前的这个session,而在作用域内还有一个写事件回调,这个写事件回调也可能会导致一个错误,也被捕获一次异常,进行一个delete函数,那这样就涉及到同一块内存析构两次的情况,这就会造成问题

那为了解决这样的问题,关键就是要对于session的生命周期进行一个合理的管理,因此就需要引入的是一个延长生命周期的操作,这里提供的是一个伪闭包来延长生命周期的操作,这里后续进行演示

为了保证模块的统一性,这里把后续的代码补充一下

class Server
{
public:
	Server(boost::asio::io_context& ioc, short port);

private:
	// 启动一个监听连接的描述符
	void start_accept();
	// 有连接到来后的回调函数
	void handle_accept(Session* new_session, const boost::system::error_code& error);

private:
	boost::asio::io_context& _ioc;
	tcp::acceptor _acceptor;
};

实现如下:

Server::Server(boost::asio::io_context& ioc, short port)
	: _ioc(ioc), _acceptor(ioc, tcp::endpoint(tcp::v4(), port))
{
	// 需要被Acceptor进行捕获
	cout << "port: " << port << endl;
	start_accept();
}

// 启动一个监听连接的描述符
void Server::start_accept()
{
	Session* new_session = new Session(_ioc);
	_acceptor.async_accept(new_session->Socket(), bind(&Server::handle_accept, this, new_session, placeholders::_1));
}

// 有连接到来后的回调函数
void Server::handle_accept(Session* new_session, const boost::system::error_code& error)
{
	if (!error)
	{
		new_session->Start();
	}
	else
	{
		delete new_session;
	}
	start_accept();
}

异步服务器优化

这样的异步服务器实际上是有问题的,问题主要在于前面说的析构的问题,那为了解决这样的问题,可以使用一个引用计数shared_ptr,并且使用一个Session会话进行管理,每一个Session创建一个uuid,作为一个字符串进行管理

那底层是如何进行管理的呢?本质上是维护了一个map,里面存储的是uid和对应的Session的智能指针的对应关系,前面我们说析构函数的问题在于,没有对于Session对象的生命周期进行合理的管理,因此我们就要想办法对于这个生命周期进行适度的延伸

下面理一下这个整体的调用逻辑

在这里插入图片描述
下面是代码实现:

#include "Session.h"
#include <iostream>
#include <boost/asio.hpp>

using namespace std;
using boost::asio::ip::tcp;

Session::Session(boost::asio::io_context& ioc, Server* server)
	: _socket(ioc), _server(server)
{
	memset(_data, 0, max_length);
	boost::uuids::uuid a_uuid = boost::uuids::random_generator()();
	_uuid = boost::uuids::to_string(a_uuid);
}

string& Session::GetUuid()
{
	return _uuid;
}

tcp::socket& Session::Socket()
{
	return _socket;
}

void Session::Start()
{
	memset(_data, 0, max_length);
	_socket.async_read_some(boost::asio::buffer(_data, max_length),
		bind(&Session::handle_read, this, placeholders::_1, placeholders::_2, shared_from_this()));
}

// 分批次读取事件回调函数
void Session::handle_read(const boost::system::error_code& error, size_t byte_transferred, shared_ptr<Session> _self_shared)
{
	if (!error)
	{
		cout << "server receive data is " << _data << endl;
		boost::asio::async_write(_socket, boost::asio::buffer(_data, byte_transferred),
			bind(&Session::handle_write, this, placeholders::_1, _self_shared));
	}
	else
	{
		cout << "read error" << endl;
		// 减少引用计数,直接从map中移除即可
		_server->ClearSession(_uuid);
		//delete this;
	}
}

void Session::handle_write(const boost::system::error_code& error, shared_ptr<Session> _self_shared)
{
	if (!error)
	{
		memset(_data, 0, max_length);
		_socket.async_read_some(boost::asio::buffer(_data, max_length),
			bind(&Session::handle_read, this, placeholders::_1, placeholders::_2, _self_shared));
	}
	else
	{
		cout << "write error" << endl;
		_server->ClearSession(_uuid);
		//delete this;
	}
}

Server::Server(boost::asio::io_context& ioc, short port)
	: _ioc(ioc), _acceptor(ioc, tcp::endpoint(tcp::v4(), port))
{
	// 需要被Acceptor进行捕获
	cout << "port: " << port << endl;
	start_accept();
}

void Server::ClearSession(string uuid)
{
	_sessions.erase(uuid);
}


// 启动一个监听连接的描述符
void Server::start_accept()
{
	// 使用智能指针来进行管理
	shared_ptr<Session> new_session = make_shared<Session>(_ioc, this);
	//Session* new_session = new Session(_ioc);
	_acceptor.async_accept(new_session->Socket(), bind(&Server::handle_accept, this, new_session, placeholders::_1));
}

// 有连接到来后的回调函数
void Server::handle_accept(shared_ptr<Session> new_session, const boost::system::error_code& error)
{
	if (!error)
	{
		new_session->Start();
		_sessions.insert(make_pair(new_session->GetUuid(), new_session));
	}
	else
	{
		// delete new_session;
	}
	start_accept();
}

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

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

相关文章

<数据集>BDD100K人车识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;15807张 标注数量(xml文件个数)&#xff1a;15807 标注数量(txt文件个数)&#xff1a;15807 标注类别数&#xff1a;7 标注类别名称&#xff1a; [pedestrian, car, bus, rider, motorcycle, truck, bicycle] 序号…

Java-变量,运算符,输入与输出

目录 一&#xff0c;语法基础 1.基本Java程序 2.语法基础 2.1 变量 2.2 常量限制(fiinal)类比C中的const 2.3 类型转化 2.4 运算符 2.5 表达式 2.5 输入与输出 2.5.1 输入 2.5.2 输出 一&#xff0c;语法基础 1.基本Java程序 public class Main{public static void…

差分放大电路

目录 引出 复合管 直接耦合放大电路 问题: 怎么抑制 初代电路(已引入负反馈之后) 分析 怎么解决 镜像电路 两个概念 分析直流通路: 分析交流电路: 差分放大电路的分析 交流通路 简化 H参数等效 可以得到 其他接法 引出 复合管 目的:获得更大的放大倍数 多只…

3个二创文案生成器,让文案创作变简单

在当今数字时代&#xff0c;内容创作已经成为了一项非常重要的工作。无论是为了推广产品、营销服务&#xff0c;还是仅仅为了吸引读者&#xff0c;优质的文案都是至关重要的。然而&#xff0c;对于许多人来说&#xff0c;写出令人印象深刻的文案并不容易。这就是为什么二创文案…

基于Django框架的挂号诊疗系统(源码+论文+部署讲解等)

博主介绍&#xff1a;✌全网粉丝10W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术栈介绍&#xff1a;我是程序员阿龙&#xff…

【优秀python系统案例】基于python Flask的电影票房数据爬取与可视化系统的设计与实现

第1章 绪论 1.1 研究背景及意义 进入21世纪&#xff0c;特别是第二个十年&#xff0c;世界互联网取得了惊人的快速发展。根据分析师玛丽米克尔 (Mary Mikel) 2016年发布的一份互联网趋势报告&#xff0c;到2016年年中&#xff0c;全球互联网用户超过30亿&#xff0c;约占全球…

职场上的人情世故,你知多少?

对于职场新人来说&#xff0c;在学习人情世故时&#xff0c;不仅要学会哪些事情该做&#xff0c;还需要知道哪些事情千万不能做&#xff0c;这样才能让自己起码不会得罪别人&#xff0c;甚至得到更多的晋升机会。学会下面的四大职场规则&#xff0c;能让你的职场生涯更顺利。 …

MUSE Multi-View Contrastive Learningfor Heterophilic Graphs

发表于:CIKM 推荐指数: #paper/⭐ 一句话总结:融合了GCN(A,X)和GCN(A,I),创新性不足,因此只能B会 流程: 融合部分: h i f h i s λ i h i c h_i^fh_i^s\lambda_ih_i^c hif​his​λi​hic​ 由于有n个 λ \lambda λ.因此作者加了如下优化: L ϕ ∑ i 1 N λ i s ( h i …

贪心算法之货仓选址问题

#include<stdio.h> #include<stdlib.h> #include<math.h>//贪心算法之货仓选址问题/*** void* p是万能指针&#xff0c;可以和其它任意类型的指针进行转换&#xff0c;前提是确保转换是合法的*/ //写好用于qsort的比较函数&#xff0c;这里写的函数一般用于…

【K8S】为什么需要Kubernetes?

文章目录 1 什么是Kubernetes&#xff1f;2 三种常见的应用部署方式2.1 传统部署2.2 虚拟化部署2.3 容器化部署 3 Kubernetes的特点写在最后 1 什么是Kubernetes&#xff1f; Kubernetes是 一个开源的&#xff0c;用于管理云平台中多个主机上的容器化应用&#xff0c;Kubernet…

分享6款有助于写论文能用到的软件app!

在学术写作中&#xff0c;选择合适的软件和工具可以大大提高效率和质量。以下是六款有助于写论文的软件app推荐&#xff0c;其中特别重点介绍千笔-AIPassPaPer这款AI原创论文写作平台。 1. 千笔-AIPassPaPer 千笔-AIPassPaPer是一款功能全面且高效的AI原创论文写作平台。它能…

RabbitMQ高级特性 - 非持久化 / 持久化(交换机、队列、消息)

文章目录 RabbitMQ 持久化机制概述实现非持久化&#xff08;交换机、队列、消息&#xff09;实现持久化&#xff08;交换机、队列、消息&#xff09; RabbitMQ 持久化机制 概述 前面讲到了 生产者消息确认机制 和 消费者消息确认机制&#xff0c;保证了消息传输的可靠性&#…

断电引起redo和数据文件不一致故障恢复---惜分飞

有些时候故障总是来的让人非常意外,这个在准备停机迁移数据库之前的几分钟由于某种原因直接导致主机掉电,再次开机数据库无法启动 Sat Aug 03 23:10:37 2024 Successful mount of redo thread 1, with mount id 3696805928 Database mounted in Exclusive Mode Lost write prot…

【考研高数】tan(arcsin x)、tan(arccos x)、sin(arccos x)、cos(arcsin x) 等于多少?

在做题的时候&#xff0c;我们可能会用到下面这几个式子的值&#xff0c;在这里&#xff0c;「荒原之梦考研数学」先给出结论&#xff0c;在这些结论的后面&#xff0c;也给同学们放上了具体的证明过程&#xff1a; tan ⁡ ( arcsin ⁡ x ) x 1 − x 2 \tan(\arcsin x) \frac…

深度分析AI大模型赋能后,内容生产行业的发展现状

国内首部由AI全流程参与制作的微短剧《补天》近日正式发布&#xff0c;从剧本构思、角色设定到场景设计以及后期剪辑&#xff0c;各个环节均深度融入了AI技术&#xff0c;展现了人工智能在内容创作方面的全面能力。 AI大模型的兴起正在深刻地改变着内容生产的方式&#xff0c;…

Animate软件基本概念:帧及关键帧

FlashASer&#xff1a;AdobeAnimate2021软件零基础入门教程https://zhuanlan.zhihu.com/p/633230084 FlashASer&#xff1a;实用的各种Adobe Animate软件教程https://zhuanlan.zhihu.com/p/675680471 FlashASer&#xff1a;Animate教程及作品源文件https://zhuanlan.zhihu.co…

VBA读取不带后缀名文本文件的方法(解决Unix文本文件在Windows下变成一行的读取)

VBA在操作Excel等Office软件方面有天然的优势&#xff0c;虽说现在Python的Pandas&#xff0c;openpyxl和Java的poi包都可以处理Excel文件&#xff0c;但有两个问题&#xff1a;首先&#xff0c;目标电脑上必须先按照Java或python环境&#xff0c;如果在一些机构内部处于安全原…

库存超卖问题解决方式

文章目录 超卖问题解决方式什么是库存超卖问题&#xff1f;乐观锁和悲观锁的定义超卖问题解决方式一、悲观锁1.jvm单机锁2.通过使用mysql的行锁&#xff0c;使用一个sql解决并发访问问题3.使用mysql的悲观锁解决4. 使用redis分布式锁来解决 二、乐观锁解决1.版本号2. CAS法&…

Problems with OpenAI Authentification

题意&#xff1a;OpenAI 认证问题 问题背景&#xff1a; Ive got some problems with the Authentification to OpenAI in my python code. It seems like, OpenAI doesnt accept my key. I did a new on and tried it with other ones before. I always get the same issues.…

机械学习—零基础学习日志(高数21——泰勒展开)

零基础为了学人工智能&#xff0c;真的开始复习高数 求解自然对话e&#xff0c;证明e是如何从极限&#xff08;11/x&#xff09;x中得来的&#xff0c;需要了解泰勒展开式。现在终于学习到泰勒展开式。 泰勒公式定义 泰勒公式具体展开 泰勒公式考点常用 这里也可以等价到函数…