【高并发服务器】十、Connection连接管理模块设计与实现
文章目录
- Ⅰ. `Connection`连接管理模块设计思想
- Ⅱ. 完整代码实现
- 1、通用类型`Any`的实现
- 2、构造与析构函数
- 3、对外提供的核心接口
- 4、该模块的其它一些功能性函数
- 5、上述核心接口的实际实现部分
- Ⅲ. 测试代码
- 1、服务端
- 2、客户端
- 3、执行结果
Ⅰ. Connection连接管理模块设计思想
该模块是对 Buffer模块、Socket模块、Channel 模块的一个整体封装,实现了对一个通信套接字也就是连接的整体的管理,每一个进行数据通信的套接字(也就是 accept 获取到的新连接)都要使用 Connection 模块进行管理。
一个连接的任何事件该如何管理,其实是由使用者来决定的,但这对程序来说是未知的,所以我们 需要在 Connection 模块中提供事件回调的机制供使用者设置,而这个模块存在的意义也就是 为了连接操作的灵活以及便捷性。因为应用层的协议如果改变了,我们只需要修改模块中回调的事件即可,而不需要再次去创建一些重复的接口!
下面是该模块需要管理的内容:
- 套接字的管理:为了能够进行套接字的操作。
- 连接事件的管理:为了对可读、可写、错误、关闭、任意事件进行管理。
- 缓冲区的管理:便于套接字数据的发送和接收。
- 协议上下文的管理:记录请求数据的处理过程,才能在线程被切走然后又重新切回来之后知道之前处理到了哪里。
- 回调函数的管理:为了给使用者设置功能函数而提供的回调接口。
下面是该模块所包含的内容:
-
两个
Buffer对象:用户态接收缓冲区、用户态发送缓冲区,即Buffer模块。 -
一个
Socket对象:完成描述符面向系统的IO操作,即Socket模块。 -
一个
Channel对象:完成描述符IO事件就绪的处理,即Channel模块。 -
四个由组件使用者传入的回调函数:
- 连接建立完成的回调、接收新数据成功后的回调、关闭连接的回调、产生任何事件进行的回调。
-
五个提供给组件者使用的接口:
- 发送数据接口(就是将数据发送到
Buffer对象中的发送缓冲区,然后启动可写事件监控) - 连接关闭接口(方便在实际释放连接之前,先看看缓冲区中是否有剩余数据没有处理)
- 切换协议接口(这里的协议指的是应用层的协议,无非就是设置不同的使用者传入的回调函数罢了)
- 启动非活跃连接的销毁接口
- 取消非活跃连接的销毁接口
- 发送数据接口(就是将数据发送到

其实要提供的内容远不止上面提到的内容,下面给出 Connection 类的大体框架!其实 Connection 类是全部模块中接口最多的,因为我们对于连接的操作就是对 Connection 的操作,是我们接触最频繁的模块,但其实大部分都是调用子部件的函数去完成即可!
下面解释一些成员变量的含义:
- 之所以需要有连接的状态
ConnectionStatus,是因为我们在编写Connection中的函数的时候,有些函数需要根据当前连接的状态做不同的处理,此时就需要有状态表示,所以用枚举类型定义这四个连接状态!- 除了在我们提供给组件使用者设置的回调函数中需要一个连接关闭之后的回调
_closed_callback,我们还需要提供另一个给后面的服务器模块设置的回调函数_server_closed_callback,它们都是ClosedCallBack类型!
- 不同之处在于
_closed_callback的关闭事件回调则是给组件使用者使用的,具体执行何种操作是未知的;- 而
_server_closed_callback这个关闭事件回调,是给服务器模块内部设置的,是固定的,用于释放服务器内所管理的当前的Connection对象,因为后面我们服务器模块需要对所有的连接进行管理,也就是用哈希表管理,那么就需要涉及到连接的增加和删除,当删除的时候就是去掉哈希表中的Connection对象,所以要提供一个接口给服务器模块使用,这样子也能提高灵活性!
typedef enum {DISCONNECTED, // 连接关闭状态CONNECTING, // 连接建立成功,待处理状态CONNECTED, // 连接建立处理工作完成,可以通信的状态DISCONNECTING // 待关闭的状态
} ConnectionStatus;class Connection;
using ConnectionPtr = std::shared_ptr<Connection>; // 使用智能指针包装一下Connection对象,这也是为了后面给服务器模块管理时候使用的using ConnectedCallBack = std::function<void(const ConnectionPtr&)>;
using MessageCallBack = std::function<void(const ConnectionPtr&, Buffer*)>;
using ClosedCallBack = std::function<void(const ConnectionPtr&)>;
using ArbitraryCallBack = std::function<void(const ConnectionPtr&)>;// 注意这里Connction类中用到shared_from_this函数获取当前对象的shared_ptr,所以要继承于std中的enable_shared_from_this模板类才行
class Connection : public std::enable_shared_from_this<Connection>
{
private:uint64_t _id; // 该连接的唯一ID,便于连接的查找与管理,同时充当定时器的idint _sockfd; // 该连接的文件描述符EventLoop* _loop; // 方便找到对应的EventLoop线程Socket _socket; // 套接字操作管理Channel _channel; // 连接的事件管理Buffer _inbuffer; // 输入缓冲区--存放从socket中读取到的数据Buffer _outbuffer; // 输出缓冲区--存放要发送到对端的数据Any _context; // 通用类型,用于表示不同协议的请求处理的上下文ConnectionStatus _status; // 当前连接所处的状态(因为需要根据状态看看是否需要处理缓冲区中未处理完的数据)bool _enable_inactive_release; // 连接是否启动非活跃销毁的判断标志,默认为false// 下面是提供给组件使用者设置的回调函数ConnectedCallBack _connected_callback; // 连接建立之后的回调MessageCallBack _message_callback; // 有消息之后的回调ClosedCallBack _closed_callback; // 连接关闭之后的回调ArbitraryCallBack _arbitrary_callback; // 任意事件的回调// 上面的关闭事件回调则是给组件使用者使用的,具体执行何种操作是未知的// 而下面这个关闭事件回调,是后面服务器模块内部设置的,用于释放服务器内所管理的当前的Connection对象ClosedCallBack _server_closed_callback;private:/* 下面函数才是上面对应接口的实际实现部分,要放到对应的eventloop中执行 */// 发送数据的线程内执行函数(并不是直接发送数据,而是把数据放到发送缓冲区中,启动写事件监控)void send_data_inloop(Buffer& buffer);// 提供给组件使用者使用的关闭连接接口的线程内执行函数(不是实际的释放接口,而是需要先判断还有没有数据待处理或者待发送)void shutdown_inloop();// 启动非活跃销毁功能的线程内执行函数(并定义多长时间没通信就是非活跃,添加定时任务)void enable_inactive_release_inloop(int sec);// 取消非活跃销毁功能的线程内执行函数void cancel_inactive_release_inloop();// 切换协议的线程内执行函数(即重置上下文以及重新设置回调函数)void upgrade_inloop(const Any& context, const ConnectedCallBack& conn, const MessageCallBack& msg, const ClosedCallBack& closed, const ArbitraryCallBack& event);// 这个接口才是实际的释放连接接口void release();// 半连接状态过渡到连接状态要进行的处理(即启动读事件监控,调用_connected_callback回调)void connecting_to_connceted_inloop();/* 五个channel的事件回调函数 */// 连接触发可读事件void handle_read_event();// 连接触发可写事件void handle_write_event();// 连接触发错误事件void handle_error_event();// 连接触发挂断事件void handle_close_event();// 连接触发任意事件void handle_arbitrary_event();public:Connection(EventLoop* loop, uint64_t id, int sockfd);~Connection();/* 该模块核心接口 */// 发送数据(并不是直接发送数据,而是把数据放到发送缓冲区中,启动写事件监控)void send_data(const char* data, size_t len);// 提供给组件使用者使用的关闭连接接口(并不是真的直接关闭,而是先判断是否有数据没处理完等情况)void shutdown();// 启动非活跃销毁功能,并定义多长时间没通信就是非活跃,添加定时任务void enable_inactive_release(int sec);// 取消非活跃销毁功能void cancel_inactive_release();// 切换协议(即重置上下文以及重新设置回调函数)void upgrade(const Any& context, const ConnectedCallBack& conn, const MessageCallBack& msg, const ClosedCallBack& closed, const ArbitraryCallBack& event);public:/* 该模块的其它一些功能性函数 */// 返回该连接的套接字描述符 int get_sockfd() { return _sockfd; } // 返回该连接的id int get_connection_id() { return _id; }// 判断该连接当前是否处于连接建立完成状态bool is_connected() { return _status == CONNECTED; } // 返回上下文的指针(这样子外部拿到的才不是一个拷贝的新对象)Any* get_context() { return &_context; } // 设置上下文--连接建立完成时调用 void set_context(const Any& context) { _context = context; } // 设置对应回调函数的接口void set_connected_callback(const ConnectedCallBack& conn) { _connected_callback = conn; }void set_message_callback(const MessageCallBack& msg) { _message_callback = msg; }void set_closed_callback(const ClosedCallBack& closed) { _closed_callback = closed; }void set_arbitrary_callback(const ArbitraryCallBack& event) { _arbitrary_callback = event; }void set_server_closed_callback(const ClosedCallBack& server_closed) { _server_closed_callback = server_closed; }// 半连接状态过渡到连接状态要进行的处理(即启动读事件监控,调用_connected_callback回调)void connecting_to_connceted() { return _loop->run_in_thread(std::bind(&Connection::connecting_to_connceted_inloop, this)); }
};
Ⅱ. 完整代码实现
1、通用类型Any的实现
其实这部分我们之前已经讲过了,这里不再赘述,直接给出:
class Any
{
private:class holder{public:virtual ~holder() {} // 析构函数,父类需要设为虚函数才能正确释放子类virtual const std::type_info& type() = 0; virtual holder* clone() = 0; };template <class T>class placeholder : public holder{public:placeholder(const T& val) : _val(val) {}// 用于返回子类中持有的数据类型virtual const std::type_info& type() { return typeid(T); }// 针对当前的对象自身,克隆出一个新的子类对象virtual holder* clone() { return new placeholder<T>(_val); } T _val; // 任意类型的数据};holder* _content; // holder类对象,通过多态方式来操作placeholder对象
public:Any() : _content(nullptr) {}~Any() { delete _content; }// 任意类型数据的构造函数template <class T>Any(const T& val) : _content(new placeholder<T>(val)) {} // Any类型的构造函数Any(const Any& other) { if(other._content == nullptr)_content = nullptr;else_content = other._content->clone();}// 任意类型数据的赋值重载函数template <class T>Any& operator=(const T& val){// 为val构造一个临时的通用容器,然后与当前容器自身进行指针交换,临时对象释放的时候,原先保存的数据也就被释放Any(val).swap(*this);return *this;}// Any类型的赋值重载函数Any& operator=(const Any& other){Any(other).swap(*this);return *this;}// 返回placeholder对象保存的数据的指针template <class T>T* get(){if(_content->type() != typeid(T))return nullptr;return &((placeholder<T>*)_content)->_val;}const std::type_info& type() { return _content->type(); }
private:Any& swap(Any& other){std::swap(_content, other._content);return *this;}
};
2、构造与析构函数
Connection(EventLoop* loop, uint64_t id, int sockfd): _loop(loop), _id(id), _sockfd(sockfd), _socket(_sockfd), _channel(_sockfd, _loop), _status(CONNECTING), _enable_inactive_release(false)
{// 设置channel的回调函数_channel.set_read_callback(std::bind(&Connection::handle_read_event, this));_channel.set_write_callback(std::bind(&Connection::handle_write_event, this));_channel.set_error_callback(std::bind(&Connection::handle_error_event, this));_channel.set_close_callback(std::bind(&Connection::handle_close_event, this));_channel.set_arbitrary_callback(std::bind(&Connection::handle_arbitrary_event, this));
}~Connection() { DLOG("release connection:%p", this); }
3、对外提供的核心接口
这些核心接口,其实都需要放在对应 EventLoop 线程中执行,所以需要用到对应 EventLoop 中的 run_in_thread(),然后实际上的实现都放在各自的子函数中去实现,这里相当于只是一个套壳,将子函数套完放到同一线程去执行罢了!
要特别注意的是发送数据接口的细节,都在下面代码中提供注释了,具体可以参考注释!
// 发送数据(并不是直接发送数据,而是把数据放到发送缓冲区中,启动写事件监控)
void send_data(const char* data, size_t len)
{// 外界传入的data,可能是个临时的空间,我们现在只是把发送操作压入了任务池,有可能并没有被立即执行// 因此有可能执行的时候,data指向的空间有可能已经被释放了,所以我们要将其包装为一个缓冲区对象Buffer buf;buf.write_data_andMove(data, len);return _loop->run_in_thread(std::bind(&Connection::send_data_inloop, this, std::move(buf)));
}// 提供给组件使用者使用的关闭连接接口(并不是真的直接关闭,而是先判断是否有数据没处理完等情况)
void shutdown()
{ return _loop->run_in_thread(std::bind(&Connection::shutdown_inloop, this));
}// 启动非活跃销毁功能,并定义多长时间没通信就是非活跃,添加定时任务
void enable_inactive_release(int sec)
{ return _loop->run_in_thread(std::bind(&Connection::enable_inactive_release_inloop, this, sec));
}// 取消非活跃销毁功能
void cancel_inactive_release()
{ return _loop->run_in_thread(std::bind(&Connection::cancel_inactive_release_inloop, this));
}// 切换协议(即重置上下文以及重新设置回调函数)
void upgrade(const Any& context, const ConnectedCallBack& conn, const MessageCallBack& msg, const ClosedCallBack& closed, const ArbitraryCallBack& event)
{ // 因为该函数必须在对应eventloop线程中执行立即执行,防备新的事件触发后处理的时候,切换任务还没有被执行--会导致数据使用原协议处理了assert(_loop->is_in_thread());return _loop->run_in_thread(std::bind(&Connection::upgrade_inloop, this, context, conn, msg, closed, event));
}
4、该模块的其它一些功能性函数
下面的功能性函数中需要说明的就是 connecting_to_connceted() 函数,它是负责将半连接状态过渡到连接状态要进行的处理,它是由我们后面的 Acceptor 模块来调用的,当接收到一个新的连接,并不是马上让其变成连接状态,而是先处于一个半连接状态,再设置其它一些功能比如非活跃销毁等任务之后再变成连接状态的!
// 返回该连接的套接字描述符
int get_sockfd() { return _sockfd; } // 返回该连接的id
int get_connection_id() { return _id; }// 判断该连接当前是否处于连接建立完成状态
bool is_connected() { return _status == CONNECTED; } // 返回上下文的指针(这样子外部拿到的才不是一个拷贝的新对象)
Any* get_context() { return &_context; } // 设置上下文--连接建立完成时调用
void set_context(const Any& context) { _context = context; } // 设置对应回调函数的接口
void set_connected_callback(const ConnectedCallBack& conn) { _connected_callback = conn; }
void set_message_callback(const MessageCallBack& msg) { _message_callback = msg; }
void set_closed_callback(const ClosedCallBack& closed) { _closed_callback = closed; }
void set_arbitrary_callback(const ArbitraryCallBack& event) { _arbitrary_callback = event; }
void set_server_closed_callback(const ClosedCallBack& server_closed) { _server_closed_callback = server_closed; }// 半连接状态过渡到连接状态要进行的处理(即启动读事件监控,调用_connected_callback回调)
void connecting_to_connceted()
{ return _loop->run_in_thread(std::bind(&Connection::connecting_to_connceted_inloop, this));
}
5、上述核心接口的实际实现部分
下面实现部分直接参考代码中的注释即可!
/* 下面函数才是上面对应接口的实际实现部分,要放到对应的eventloop中执行 */// 发送数据的线程内执行函数(并不是直接发送数据,而是把数据放到发送缓冲区中,启动写事件监控)
void send_data_inloop(Buffer& buffer)
{// 1. 如果当前是连接关闭状态的话,则不需要处理if(_status == DISCONNECTED)return;// 2. 将数据写入发送缓冲区_outbuffer.write_Buffer_andMove(buffer);// 3. 启动写事件监控if(_channel.is_write_able() == false)_channel.enable_write();
}// 提供给组件使用者使用的关闭连接接口的线程内执行函数(不是实际的释放接口,而是需要先判断还有没有数据待处理或者待发送)
void shutdown_inloop()
{// 1. 将连接状态改为连接待关闭状态_status = DISCONNECTING;// 2. 判断一下接收缓冲区中是否有数据未处理,是的话处理一下if(_inbuffer.get_sizeof_read() > 0){if(_message_callback)_message_callback(shared_from_this(), &_inbuffer);}// 3. 判断一下发送缓冲区中是否有数据未发送,是的话启动可写事件监控去处理if(_outbuffer.get_sizeof_read() > 0){if(_channel.is_write_able() == false)_channel.enable_write();}// 4. 如果此时没有待发送数据的话,直接关闭连接即可(此时就不管上面的数据是否处理完毕了,直接断开连接,防止该连接一直没处理完数据)if(_outbuffer.get_sizeof_read() == 0)release();
}// 启动非活跃销毁功能的线程内执行函数(并定义多长时间没通信就是非活跃,添加定时任务)
void enable_inactive_release_inloop(int sec)
{// 1. 修改非活跃销毁的判断标志为true_enable_inactive_release = true;// 2. 判断是否已经存在非活跃销毁任务,是的话直接延迟一下该任务即可if(_loop->has_timer(_id) == true)return _loop->refresh_timer(_id);// 3. 否则的话就新增非活跃销毁任务_loop->add_timer(_id, sec, std::bind(&Connection::release, this));
}// 取消非活跃销毁功能的线程内执行函数
void cancel_inactive_release_inloop()
{// 1. 修改非活跃销毁的判断标志为false_enable_inactive_release = false;// 2. 取消非活跃销毁任务if(_loop->has_timer(_id))_loop->cancel_timer(_id);
}// 切换协议的线程内执行函数(即重置上下文以及重新设置回调函数)
void upgrade_inloop(const Any& context, const ConnectedCallBack& conn, const MessageCallBack& msg, const ClosedCallBack& closed, const ArbitraryCallBack& event)
{_context = context;_connected_callback = conn;_message_callback = msg;_closed_callback = closed;_arbitrary_callback = event;
}// 这个接口才是实际的释放连接接口
void release()
{// 1. 修改连接状态为连接关闭状态_status = DISCONNECTED;// 2. 移除连接的事件监控_channel.remove();_channel.clear_callback();// 3. 关闭套接字描述符_socket.Close();// 4. 判断是否需要关闭定时销毁任务,需要的话则进行关闭if(_loop->has_timer(_id))cancel_inactive_release_inloop();// 5. 调用组件使用者关闭连接后的回调函数if(_closed_callback)_closed_callback(shared_from_this());// 6. 调用服务器模块的关闭连接后的函数函数,// 注意该函数必须在_closed_callback()后调用,因为涉及到当前Connection对象的释放,如果先调用该函数的话,再调用_closed_callback()的话会非法访问已释放的空间if(_server_closed_callback)_server_closed_callback(shared_from_this());
}// 半连接状态过渡到连接状态要进行的处理(即启动读事件监控,调用_connected_callback回调)
void connecting_to_connceted_inloop()
{// 1. 先将连接状态设置为连接建立完成状态assert(_status == CONNECTING);_status = CONNECTED;// 2. 启动可读事件监控_channel.enable_read();// 3. 调用建立连接后的回调,也就是_connected_callback函数if(_connected_callback)_connected_callback(shared_from_this());
}/* 五个channel的事件回调函数 */
// 连接触发可读事件
void handle_read_event()
{// 1. 接收socket的数据char buffer[65536] = { 0 };ssize_t ret = _socket.recv_with_noblock(buffer, 65535); // 注意要使用非阻塞接口,不然缓冲区没数据的话会阻塞if(ret < 0){// 读取错误的话不能直接关闭连接,而是要判断是否有发送数据需要处理,此时在shutdown_inloop()函数中会去开启写事件监控return shutdown_inloop(); }// 2. 将数据写入接收缓冲区_inbuffer.write_data_andMove(buffer, ret);// 3. 调用message_callback进行业务处理if(_inbuffer.get_sizeof_read() > 0)_message_callback(shared_from_this(), &_inbuffer);
}// 连接触发可写事件
void handle_write_event()
{// 1. 将发送缓冲区中待发送的数据发送到socket中(即发送缓冲区中读指针开始就是待发送的数据)ssize_t ret = _socket.send_with_noblock(_outbuffer.start_of_read(), _outbuffer.get_sizeof_read());if(ret < 0){// 此时发送错误的话,先判断一下接收缓冲区是否有数据需要处理,是的话处理之后再直接释放if(_inbuffer.get_sizeof_read() > 0)_message_callback(shared_from_this(), &_inbuffer);/* 注意不能再调用shutdown_inloop(),只能调用release(),因为shutdown_inloop()是在读事件中调用的,而在shutdown_inloop()内部又启动了可写事件监控,此时触发了handle_write_event(),如果handle_write_event()还调用shutdown_inloop()的话,则会进行死循环调用,最后栈溢出 */return release(); }// 2. 别忘了将发送缓冲区中读指针向后偏移_outbuffer.push_reader_back(ret);// 3. 如果此时发送缓冲区没有待发送数据了,则关闭可写事件的监控if(_outbuffer.get_sizeof_read() == 0){_channel.disable_write();// 4. 并且如果当前连接就处于待关闭状态的话,则直接释放连接if(_status == DISCONNECTING)return release();}
}// 连接触发错误事件
void handle_error_event()
{return handle_close_event();
}// 连接触发挂断事件
void handle_close_event()
{// 连接发送挂断,意味着什么事情都干不了了,所以判断一下接收缓冲区是否还有数据没有处理,处理完毕之后直接释放连接即可if(_inbuffer.get_sizeof_read() > 0)_message_callback(shared_from_this(), &_inbuffer);release();
}// 连接触发任意事件
void handle_arbitrary_event()
{// 1. 判断一下释放需要刷新非活跃连接的活跃度,是的话则刷新if(_enable_inactive_release == true)_loop->refresh_timer(_id);// 2. 调用组件使用者设置的任意事件回调if(_arbitrary_callback)_arbitrary_callback(shared_from_this());
}
Ⅲ. 测试代码
和之前类似,创建一个用于监听套接字的 Channel 对象,然后利用 bind 函数设置可读回调函数,并且启动可读监控。其中 Acceptor 函数是该 Channel 对象要绑定的可读回调函数,其内部就是监听新连接,然后将新连接管理起来,给连接对象绑定回调函数(这里任意事件回调就不绑定了,只是做测试),启动非活跃销毁功能,设置三秒后就销毁,看看效果怎么样!
1、服务端
#include "../source/server.hpp"uint64_t id = 1; // 连接id
std::unordered_map<uint64_t, ConnectionPtr> connections; // 连接管理表void connected_handle(const ConnectionPtr& cptr)
{// 这里的连接建立处理,我们就简单的打印哪个连接建立即可DLOG("new connection: %p,the id is:%d", cptr.get(), cptr->get_connection_id());
}void message_handle(const ConnectionPtr& cptr, Buffer* buf)
{// 这里的消息事件处理,我们就做简单的打印以及回响即可DLOG("接收到:%s", buf->start_of_read());buf->push_reader_back(buf->get_sizeof_read());std::string str = "lirendada 你好啊!";cptr->send_data(str.c_str(), str.size());
}void closed_handle(const ConnectionPtr& cptr)
{// 就是将连接管理表中的该连接去掉DLOG("delete connection: %p,the id is:%d", cptr.get(), cptr->get_connection_id());connections.erase(cptr->get_connection_id());
}void Acceptor(Channel* listen_channel, EventLoop* loop)
{// 获取新链接int newfd = accept(listen_channel->get_fd(), nullptr, nullptr);if(newfd < 0){ELOG("accept error, the error is: ", strerror(errno));return;}// 用Connection包装该新链接,并且设置回调函数ConnectionPtr cptr(new Connection(loop, id, newfd));cptr->set_connected_callback(std::bind(connected_handle, std::placeholders::_1));cptr->set_message_callback(std::bind(message_handle, std::placeholders::_1, std::placeholders::_2));cptr->set_server_closed_callback(std::bind(closed_handle, std::placeholders::_1)); // 注意这里是服务器模块的关闭回调,也就是去掉与该连接的联系// 启动非活跃销毁功能,并将连接设置为建立完成状态cptr->enable_inactive_release(3);cptr->connecting_to_connceted();// 最后别忘了添加到服务器的连接管理表中connections[id++] = cptr;
}int main()
{// 创建服务器套接字Socket server;bool ret = server.create_server(8080);if(ret == false)return -1;// 创建一个EventLoop对象EventLoop loop;// 创建一个用于监听套接字的Channel对象,然后利用bind函数设置可读回调函数,并且启动可读监控Channel listen_channel(server.get_fd(), &loop);listen_channel.set_read_callback(std::bind(Acceptor, &listen_channel, &loop));listen_channel.enable_read();loop.start();return 0;
}
2、客户端
#include "../source/server.hpp"int main()
{// 创建客户端套接字Socket client_sock;client_sock.create_client(8080, "127.0.0.1");// 做五次简单的发送和回响,所以会刷新五次连接for(int i = 0; i < 5; ++i){std::string str = "lirendada";client_sock.Send(str.c_str(), str.size());char buf[1024] = { 0 };client_sock.Recv(buf, sizeof(buf) - 1);DLOG("%s", buf);sleep(1);}// 进入死循环while(1) sleep(1);return 0;
}
3、执行结果


