C++ asio网络编程(5)简单异步echo服务器
上一篇文章:C++ asio网络编程(4)异步读写操作及注意事项
文章目录
- 前言
- 一、Session类
- 1.代码
- 2.代码详解
- 3.实现Session类
- 1.构造函数
- 2.handle_read
- 3.介绍一下boost的封装函数和api
- 4.handle_write
- 二、Server类
- 1.代码
- 2.代码思路详解
- 三、客户端
- 四、运行截图与流程图
前言
提示:这里可以添加本文要记录的大概内容:
前文已经介绍了异步操作的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;//创建endpointboost::asio::ip::tcp::endpointremote_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是因为客户端每发送一次就会断开连接,这是我们之前代码设定的