基于boost库应答式服务器编写

185 阅读5分钟

一些问题的解答

初始化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销毁掉,实际这么做有隐患
	}
}