一些问题的解答
初始化server为什么用初始化列表?
1:io_context官方不支持拷贝赋值啥的,所以用引用作为一个server类的成员变量,这是前因
2:而对于引用的成员变量要通过初始化列表形式初始化.除此之外需要用初始化列表去初始化的还有常量成员变量(用const修饰),因为他必须初始化但又不能在函数体内赋值;没有默认构造函数的类类型成员变量等
这样一个应答式有什么隐患?
事实上echo模式不容易出这个问题,而公司代码一般都是监听读事件之后还会接着听,再触发写回调 正常情况下应该是这样的情况,这种正常情况才会有风险,而echo式规避了这个风险因为读事件直接调用写回调,中间没有继续监听了
void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred) {
if (!ec) {
cout << "server recervi data is" << _data << endl;
auto buffer_send = boost::asio::buffer(_data, bytes_transferred);
memset(_data, 0, max_length);
_socket.async_read_some(boost::asio::buffer(_data, max_length),
bind(&Session::handle_read, this, placeholders::_1, placeholders::_2));
boost::asio::async_write(_socket,boost::asio::buffer("Hello World"),
bind(&Session::handle_write, this, placeholders::_1));
}
else {//读出错
cout << "read error" << endl;
delete this;//把Session销毁掉,实际这么做有隐患
}
}
客户通信的时候,服务器写就绪,正在写呢对端关闭了,读事件触发了进行一次析构,而写事件也被触发进行里面的的错误处理,又析构一次,(事实上这两次析构的先后顺序不绝对),造成double free 产生原因:对于同一个socket既给他绑定了读的监听事件又给他绑定了写的监听事件,当对端关闭的时候,我们先触发了一个读事件里面的错误处理,delete了一次,写的回调函数还没回调完呢你就把内存释放掉了,写回调函数再次触发的时候,事实上都不需要走到错误处理,就会报错了
解决办法:这样一个异步回调的时候,能不能保证这个session的生命周期还在?
尝试通过智能指针和伪闭包管理他的生命周期
一些逻辑:
拿ioc和端口号初始化一台服务器,调用构造函数生成欢迎套接字acceptor
开始迎宾服务:创建一个新会话(预留一个会议室),acceptor套接字和这个会话的soket,连接(accept)以接待客户(进入会议室等待)
接收到客户在socket里面的请求(进入会议室)之后,或调用开始会议的回调函数,或出现问题就关闭这个会议室。最后记得再开始一个迎宾服务(会议室满了就再开一个等下位客人,会议室被鸽了就关闭再开一个会议室等客人)
开始会话:对socket进行异步读,有东西读的话调用异步写回调函数写过去,写的时候看客户还在不在说话,还有话说就调用读的回调函数
主函数:创建了一个服务器
int main()
{
try {
boost::asio::io_context ioc;
using namespace std;
Server s(ioc,10086);
ioc.run();
}
catch(std::exception& e){
std::cerr << "Exception:" << e.what() << "\n";
}
return 0;
}
服务器类如下:初始化的时候就已经创建并绑好accepetor套接字了
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;//io_context官方不支持拷贝赋值啥的,所以用引用作为一个成员变量
tcp::acceptor _acceptor;
};
直接开始迎宾服务:新整一个会议室,监听客户的链接,accepetor听socket
void Server::start_accept() {
Session* new_session = new Session(_ioc);//创建新会话绑定好;
//把新的socket传进去和欢迎套接字建立一个新的连接
//有客户连接就要调用回调函数了
_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();
}
这是会话类的设计:
class Session
{
public:
Session(boost::asio::io_context& ioc) :_socket(ioc) {}
//传入的ioc初始化绑定socket为什么没有方法体?
//当你使用Boost.Asio库创建一个socket对象并将其与io_context对象绑定时
// 通常在构造函数中完成。即使你的构造函数体看起来是空的
// Boost.Asio的构造函数会负责完成必要的初始化工作。
tcp::socket& Socket() {
return _socket;
}
void Start();//监听客户端的读和写
private:
tcp::socket _socket;
enum{max_length=1024};
char _data[max_length];
void handle_read(const boost::system::error_code& ec,size_t bytes_transferred);//当监听到客户端发了数据的时候调用读的回调函数
void handle_write(const boost::system::error_code& ec);//直接调用async_send,所以不需要第二个参数
//当服务端要发数据的时候,asio通过写的回调函数告诉我们写了多少,是否有问题
};
Start():收到客户说话的请求调用听的回调函数
void Session::Start() {
memset(_data, 0, max_length);//数据数组全设0;
_socket.async_read_some(boost::asio::buffer(_data, max_length),
bind(&Session::handle_read, this, placeholders::_1, placeholders::_2));//绑定的是类成员函数,要用this传入那个类的指针或者
}
读到了就调用写的回调函数,注意两个回调函数的参数为什么不同
void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred) {
if (!ec) {
cout << "server recervi data is" << _data << endl;
boost::asio::async_write(_socket,
boost::asio::buffer(_data, bytes_transferred),
bind(&Session::handle_write, this, placeholders::_1));
}
else {//读出错
cout << "read error" << endl;
delete this;//把Session销毁掉,实际这么做有隐患
}
}
void Session::handle_write(const boost::system::error_code& ec) {
if (!ec) {//发完之后又要开始读了
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;//把Session销毁掉,实际这么做有隐患
}
}