C++ asio网络编程(5)简单异步echo服务器

发布于:2025-05-16 ⋅ 阅读:(14) ⋅ 点赞:(0)

上一篇文章:C++ asio网络编程(4)异步读写操作及注意事项


前言

提示:这里可以添加本文要记录的大概内容:

前文已经介绍了异步操作的api,今天写一个简单的异步echo服务器,以应答为主
在这里插入图片描述


提示:以下是本篇文章正文内容,下面案例可供参考

一、Session类

Session类主要是处理客户端消息收发的会话类,为了简单起见,我们不考虑粘包问题,也不考虑支持手动调用发送的接口只以应答的方式发送和接收固定长度(1024字节长度)的数据。

1.代码

#pragma once
#include<iostream>
#include<boost/asio.hpp>
using boost::asio::ip::tcp;

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

	};

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


	void Start();//监听客户端的读写

private:
	//回调函数
	void handle_read(const boost::system::error_code& ec,size_t bytes_transferred);
	void handle_write(const boost::system::error_code& ec, size_t bytes_transferred);
	tcp::socket _socket;
	enum {max_length=1024};
	char _data[max_length];
};


2.代码详解

1.构造函数中传入一个上下文用来绑定socket
2.Start()用来监听客户端是否发来信息
3.回调函数中 handle_read 是读的回调函数,我们这里用上一个文章介绍的async_read_some
因为不会一次性读完,所以我们有第二个参数来显示已经读了多少数据
4.回调函数中的handle_write是写的回调函数,我们用boost::asio::async_write来一次性发送数据
5. _data用来接收客户端传递的数据
6. _socket为单独处理客户端读写的socket

3.实现Session类

1.构造函数

我们在构造函数中把数组初始化为0,然后直接调用读函数就行,他会一直等着,当用户发送数据,tcp底层有内容的时候,就会进行读

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

然后读了以后,我们要进行回调函数
这个回调函数的参数是两个的原因是因为源码中async_read_some要求了回调函数参数如下
在这里插入图片描述

2.handle_read

//读的回调函数
void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred)
{
	if (ec)//0正确  1错误
	{
		cout << "read error" << endl;
		delete this;
	}
	else
	{
		cout << "server receivr data is "<<_data << endl;
		//将收到的数据发送回去
		boost::asio::async_write(_socket, boost::asio::buffer(_data, bytes_transferred),
			std::bind(&Session::handle_write,this, placeholders::_1, placeholders::_2));
		
	}
}

读完以后我们打印一下收到的数据
然后原封不动的将这个数据发送回去就行

我们用boost::asio::async_write来发送,这样可以保证数据一次性发送完,但其实用socket自带也都行,因为我们这样只做一个简单的应答式的而已,本身就不会出现长数据,只是一个模型而已,但这里用一个boost库自带的封装函数,就是为了介绍一下!!!!!!!!!!!!!!!!!!!!!

3.介绍一下boost的封装函数和api

在这里插入图片描述
在这里插入图片描述

参数列表有三个,第一个绑定socket,第二个传入的buffer,第三个std::bind

4.handle_write

在之前读完以后我们就准备将数据发送回去,所以就写,写的时候也调用我们这个回调函数,参数的原因与上面同理,查看源码即可

//写的回调函数
void Session::handle_write(const boost::system::error_code& ec, size_t bytes_transferred)
{ 
	if (ec)//0正确  1错误
	{
		cout << "write error" << endl;
		delete this;
	}
	else
	{
		//发完了 就清除掉原先的
		memset(_data, 0, sizeof(_data));
		
		//继续读
		_socket.async_read_some(boost::asio::buffer(_data, bytes_transferred),
			std::bind(&Session::handle_read, this, placeholders::_1, placeholders::_2));

	}
}

二、Server类

这个用来管理客户端连接的,我们初始化一个监听器,当有客户端进行连接的时候,我们就内部new一个Session去单独服务这个客户端

1.代码

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& ec);//当有连接的时候触发这个回调函数
	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))
{
	start_accept();
}

void Server::start_accept()
{
	Session* new_session = new Session(_ioc);
	_acceptor.async_accept(new_session->Socket(),std::bind(&Server::handle_accept,this,new_session,placeholders::_1));
}

void Server::handle_accept(Session* new_session, const boost::system::error_code& ec)
{
	if (!ec)//成功
	{
		new_session->Start();
	}
	else
	{
		delete new_session;
	}

	start_accept();//再次准备
}

2.代码思路详解

首先我们初始化一个上下文ioc准备好,因为创建一个socket连接需要这个东西
然后我们调用这个函数start_accept()
这个函数内部会创建一个Session准备好,相当于我提前准备一个服务员准备好
然后当监听器 _acceptor 被触发的时候,我们就调用回调函数(相当于一个房间),让之前准备好的服务员去服务这个客人

_acceptor.async_accept(new_session->Socket(),std::bind(&Server::handle_accept,this,new_session,placeholders::_1));

但是这里要提前绑定一个new_session这个参数,因为根据源码,回调函数的参数只能有一个,就是错误码

在这里插入图片描述
然后进入handle_accept函数,如果没有错误我们就调用这个创建的session去进行Start()
在Start中就会进行读写循环调用

三、客户端

客户端这么我们就不进行说明,因为今天做的是简单的异步echo模型,应答式的,并且客户端我们在第二节文章讲过了,用的客户端代码也是第二个文章的代码,所以就不进行说明

#include<boost/asio.hpp>
#include <iostream>
const int MAX_LENGTH = 1024;//表示发送和接收最大长度为1024字节
int main()
{
    while (1)
    {
        try
        {

            //创建上下文服务
            boost::asio::io_context ios;
            //创建endpoint
            boost::asio::ip::tcp::endpoint
                remote_ep(boost::asio::ip::make_address("127.0.0.1"), 10086);
            //127.0.0.1是本机的回路地址 因为客户端和服务端在同一地址
            //服务器就是本机
            boost::asio::ip::tcp::socket sock(ios, remote_ep.protocol());
            boost::system::error_code error = boost::asio::error::host_not_found;
            sock.connect(remote_ep, error);
            if (error)
            {
                std::cout << "connect failed,code is:" << error.value() <<
                    "error message is" << error.message() << std::endl;
                return 0;
            }

            std::cout << "请输入需要发送的信息:";
            char request[MAX_LENGTH];
            std::cin.getline(request, MAX_LENGTH);
            size_t request_length = strlen(request);//获取实际要发送的数据长度
            //发送数据
            boost::asio::write(sock, boost::asio::buffer(request, request_length));

            char reply[MAX_LENGTH];//存储接收数据
            //用read读
            size_t reply_length = 0;
            boost::system::error_code ec;
            reply_length = sock.read_some(boost::asio::buffer(reply, MAX_LENGTH), ec);
            if (ec)
            {
                std::cerr << "Read failed: " << ec.message() << std::endl;
                return 0;
            }
            std::cout << "回复:";
            std::cout.write(reply, reply_length);
            std::cout << "\n";



        }
        catch (std::exception& e)
        {
            std::cerr << "Exception is: " << e.what() << std::endl;
        }
    }
    
    return 0;
}



四、运行截图与流程图

在这里插入图片描述
服务端接收完直接原文回给客户端就行
这里服务端报错 read error是因为客户端每发送一次就会断开连接,这是我们之前代码设定的

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到