【C++boost::asio网络编程】有关异步Server样例以及伪闭包延长连接生命周期方法的笔记
异步Server
- 客户端源码
- Session类
- start函数
- handle_read
- handle_write
- Server类
- 构造函数
- start_accept
- handle_accept
- 可能会造成的隐患
- 利用伪闭包延长连接的生命周期
客户端源码
#include <iostream>
#include <boost/asio.hpp>
#include <string>
int main()
{
try
{
boost::asio::io_context ioc;
std::string ip = "127.0.0.1";
unsigned short port = 8888;
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(ip), port);
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
boost::system::error_code ec = boost::asio::error::host_not_found;
sock.connect(ep,ec);
if (ec.value() != 0)
{
std::cout << ec.value() << ":" << ec.message() << std::endl;
return ec.value();
}
const int MAX_SIZE = 1024;
char request[MAX_SIZE];
std::cout << "Enter Message:";
std::cin.getline(request, MAX_SIZE);
size_t request_length = strlen(request);
boost::asio::write(sock, boost::asio::buffer(request, request_length));
//收到服务端的消息
char response[MAX_SIZE];
memset(response, '\0', MAX_SIZE);
sock.read_some(boost::asio::buffer(response, MAX_SIZE));
std::cout << "server echo# " << response << std::endl;
}
catch (std::exception& e)
{
std::cout << e.what() << std::endl;
}
return 0;
}
Session类
Session类的作用是服务端为某个已连接的客户端处理消息收发的会话类(在这里先不考虑粘包问题,并采用和客户端进行应答的方式发送和接收一个固定长度的数据)
class Session
{
public:
Session(boost::asio::io_context& ioc)
:_socket(ioc)
{}
boost::asio::ip::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);
boost::asio::ip::tcp::socket _socket;
const static int max_length = 1024;
char _data[max_length];
};
其中,成员变量_data
就是用来存储数据的,而_socket
则是用来服务当前客户端的,handle_read
和handle_write
分别是异步写和异步读的回调函数
start函数
void Session::Start()
{
memset(_data, 0, max_length);
_socket.async_read_some(boost::asio::buffer(_data, max_length),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2));
}
在Start函数中,先将_data中的数据清空防止数据污染。然后采用异步读取方式来准备接收客户端发送过来的数据。
handle_read
void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred)
{
if (ec.value() == 0)
{
//将读到的数据全部发回去
std::cout << "server has received:" << _data << std::endl;
boost::asio::async_write(_socket,boost::asio::buffer(_data, bytes_transferred),
std::bind(&Session::handle_write, this, std::placeholders::_1));
}
else
{
std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;
delete this;
}
}
当一次异步读事件结束之后,会调用回调函数handle_read
。在这个回调函数中,会先将收到的数据打印出来,然后进行异步写操作。
handle_write
void Session::handle_write(const boost::system::error_code& ec)
{
if (ec.value() == 0)
{
memset(_data, 0, max_length);
_socket.async_read_some(boost::asio::buffer(_data, max_length),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2));
}
else
{
std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;
delete this;
}
}
当一次写操作结束之后,又会调用handle_write
,此时服务端又开始在准备接收客户端的发送过来的消息
Server类
Server类为服务器接收连接的管理类
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;
boost::asio::ip::tcp::acceptor _acceptor;
};
构造函数
Server::Server(boost::asio::io_context& ioc, short port)
:_ioc(ioc)
,_acceptor(ioc,boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(),port))
{
start_accept();
}
在构造函数中,先创建一个acceptor对象并绑定ip和端口,然后调用start_accept函数
start_accept
void Server::start_accept()
{
Session* session = new Session(_ioc);
_acceptor.listen();
_acceptor.async_accept(session->Socket(), std::bind(&Server::handle_accept, this, session, std::placeholders::_1));
}
在start_accept
函数中,先创建一个会话类,然后_acceptor
对象去监听是否有客户端连接
_acceptor.async_accept(session->Socket(), std::bind(&Server::handle_accept, this, session, std::placeholders::_1));
以上这段代码就是_acceptor
在等待客户端的连接,如果有客户端来连接的话,就用先前创建的会话类对象来管理这个连接,并且调用handle_accept
handle_accept
void Server::handle_accept(Session* new_session, const boost::system::error_code& ec)
{
if (ec.value() == 0)
{
new_session->Start();
}
else
{
delete new_session;
}
start_accept();
}
如果发现没有什么问题的话,就启用这个会话类让它去接收客户端发来的消息并将消息回传回去
可能会造成的隐患
在实际的应用中,很少会出现异步读和异步写事件只挂起一个的现象,通常情况下,它们是相互独立的,即可能服务端正在接收客户端发来的数据同时,它也正在向客户端发送数据。这样的话,代码就要修改成这样
void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred)
{
if (ec.value() == 0)
{
//将读到的数据全部发回去
memset(_data, 0, max_length);
_socket.async_read_some(boost::asio::buffer(_data, max_length),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2));
std::cout << "server has received:" << _data << std::endl;
boost::asio::async_write(_socket,boost::asio::buffer(_data, bytes_transferred),
std::bind(&Session::handle_write, this, std::placeholders::_1));
}
else
{
std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;
delete this;
}
}
即在读回调事件处理完之后,又同时挂起一个读事件和一个写事件(暂不考虑数据冲突的问题)。但是如果在服务端正要写数据的时候,客户端关闭连接,就会同时触发读回调和写回调,
利用伪闭包延长连接的生命周期
在这里采用了智能指针的方式来构造一个伪闭包用来延长session
对象的生命周期
class Server
{
public:
Server(boost::asio::io_context& ioc, short port);
void ClearSession(std::string uuid);
private:
void start_accept();
void handle_accept(std::shared_ptr<Session> new_session,const boost::system::error_code& ec);
boost::asio::io_context& _ioc;
boost::asio::ip::tcp::acceptor _acceptor;
std::map<std::string, std::shared_ptr<Session>> _sessions;
};
void Server::ClearSession(std::string uuid)
{
_sessions.erase(uuid);
}
在Server
类中添加了一个map
结构用来管理每个Session
。ClearSession
函数用来将session
从map
中清除出去
class Server;
class Session:public std::enable_shared_from_this<Session>
{
public:
Session(boost::asio::io_context& ioc,Server* server)
:_socket(ioc)
,_server(server)
{
boost::uuids::uuid a_uuid = boost::uuids::random_generator()();
_uuid = boost::uuids::to_string(a_uuid);
}
boost::asio::ip::tcp::socket& Socket()
{
return _socket;
}
const std::string& uuid();
void Start();
private:
void handle_read(const boost::system::error_code& ec, size_t bytes_transferred,std::shared_ptr<Session> self_shared);
void handle_write(const boost::system::error_code& ec, std::shared_ptr<Session> self_shared);
boost::asio::ip::tcp::socket _socket;
const static int max_length = 1024;
char _data[max_length];
Server* _server;
std::string _uuid;
};
在Session
类中,首先在构造函数中构造一个uuid用来为每个会话类对象创建一个“key值”以方便Server
对每个连接进行管理
void Session::Start()
{
memset(_data, 0, max_length);
_socket.async_read_some(boost::asio::buffer(_data, max_length),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2,shared_from_this()));
}
在Start函数中,由于handle_read函数和handle_write函数添加了指向自身的智能指针,所以在bind操作的最后一个参数采用了shared_from_this
的写法用来确保每个智能指针共享的是同一份引用计数。
void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred, std::shared_ptr<Session> self_shared)
{
if (ec.value() == 0)
{
//memset(_data, 0, max_length);
_socket.async_read_some(boost::asio::buffer(_data, max_length),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2,self_shared));
//将读到的数据全部发回去
std::cout << "server has received:" << _data << std::endl;
boost::asio::async_write(_socket,boost::asio::buffer(_data, bytes_transferred),
std::bind(&Session::handle_write, this, std::placeholders::_1,self_shared));
}
else
{
std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;
//delete this;
_server->ClearSession(_uuid);
}
}
void Session::handle_write(const boost::system::error_code& ec, std::shared_ptr<Session> self_shared)
{
if (ec.value() == 0)
{
memset(_data, 0, max_length);
_socket.async_read_some(boost::asio::buffer(_data, max_length),
std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2,self_shared));
}
else
{
std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;
//delete this;
_server->ClearSession(_uuid);
}
}
在handle_read
和handle_write
函数中,都取消了之前直接delete
的做法而是采用将当前对象从Server
的map
中清除出去,如果map
中没有找到当前对象就当作无事发生。这样写的好处是最大程度上保证了session
对象的生命周期和当前所在函数的生命周期至少是一致的,不会发生当前正在使用一个对象,但是这个对象已经在其他函数内部被释放了造成异常的现象。