记我的C++库设计历程:设计一个TCP服务程序

我想写一个Linux下的C++程序库,实现一些常用的功能。我首先想到的就是实现一个TCP监听程序。该程序应该具有哪些功能呢?1: 启动/停止监听2: 有客户端连接时,通知调用者3: 与客户端断开时,通知调用者4: 有消息到达时,通知调用者

5: 尽量避免程序退出时有没有close的socket。

该程序的大体接口及结构主要用一个类表示,内容如下:#pragma once#include <functional>namespace Hi{/** @ brief TCP监听会发送的通知*/class TcpEvent{public:/* brief 客户端连接成功通知,* 参数分别为socket描述符,远端ip,远端端口,本地ip,本地端口 */std::function<void(int,const char*, unsigned short,const char*,unsigned short)> on_open_;/* brief 客户端连接断开通知 */std::function<void(int)> on_close_;/* brief 接收到客户端消息通知 */std::function<void(int)> on_receive_;};/** @ brief TCP监听类*/class HiTcpServer{public:/** @brief 启动监听* @param [in] evt 通知对象* @param [in] port 简体端口* @param [in] ip 监听IP,如果为空时,表示监听本机所有的IP* @retval true:成功;false:失败*/bool open(const TcpEvent& evt, unsigned short port, const char* ip = NULL);/** @brief 停止监听* @retval true:成功;false:失败*/bool close();};}这是我想到的TCP监听程序的最初接口(假定HiTcpServer放在net文件夹下的hiTcpServer.h文件中)。怎么用呢?测试代码如下:#pragma once#include <string>#include <sys/socket.h>#include <iostream>#include "net/hiTcpServer.h"using namespace std;// 客户端连接成功static void on_client_open(int sock,const char* remote_ip, unsigned short remote_port,const char* local_ip,unsigned short local_port){cout<<"accept a client connect,socket["<<sock<<"] remote["<<remote_ip<<","<<remote_port<<"]local["<<local_ip<<","<<local_port<<"]"<<endl;}// 接收到客户端消息static void on_client_recv_data(int sock){char rece_buf[256];memset(rece_buf, 0, 256);int n = recv(sock, rece_buf, 256, 0);cout<<"receive client(socket:"<<sock<<") data,len:"<<n<<endl;}// 客户端连接断开static void on_client_close(int sock){cout<<"client (socket:"<<sock<<") is close"<<endl;}void main(){Hi::TcpEvent evt;evt.on_open_ = std::bind(&on_client_open);evt.on_close_ = std::bind(&on_client_close);evt.on_receive_ = std::bind(&on_client_recv_data);Hi::HiTcpServer server;server.open(evt, 6000);sleep(60);}初步想法就是这样了。TcpEvent::on_open_函数参数有点多,而且估计每个连接都有远端地址和本地地址,将其抽象成一个类比较合适,该类可以叫SocketChannel。具体定义(为了简单,可以将其放入hiTcpServer.h中):/** @brief socket通道信息类*/class SocketChannel{public:SocketChannel(): sock_(-1), remote_port_(0), local_port_(0){}public:intsock_;///< socket文件描述符std::string remote_ip_;///< 对端IPunsigned short remote_port_;///< 对端端口std::string local_ip_;///< 本地IPunsigned short local_port_;///< 本地端口};TcpEvent::on_open_的定义就成了:std::function<void(bool,int,SocketChannel&)> on_open_;修正后的HiTcpServer就成了:#pragma once#include <functional>namespace Hi{/** @brief socket通道信息类*/class SocketChannel{public:SocketChannel(): sock_(-1), remote_port_(0), local_port_(0){}public:intsock_;///< socket文件描述符std::string remote_ip_;///< 对端IPunsigned short remote_port_;///< 对端端口std::string local_ip_;///< 本地IPunsigned short local_port_;///< 本地端口};/** @ brief TCP监听会发送的通知*/class TcpEvent{public:/* brief 客户端连接成功通知 */std::function<void(int,SocketChannel&)> on_open_;/* brief 客户端连接断开通知 */std::function<void(int)> on_close_;/* brief 接收到客户端消息通知 */std::function<void(int)> on_receive_;};/* @ brief 逻辑实现类*/class TcpServerImpl;/** @ brief TCP监听类*/class HiTcpServer{public:HiTcpServer();~HiTcpServer();HiTcpServer& operator =(const HiTcpServer&) = delete;HiTcpServer(const HiTcpServer&) = delete;public:/** @brief 启动监听* @param [in] evt 通知对象* @param [in] port 监听端口* @param [in] ip 监听IP,如果为空时,表示监听本机所有的IP* @retval true:成功;false:失败*/bool open(const TcpEvent& evt, unsigned short port, const char* ip = NULL);/** @brief 停止监听* @retval true:成功;false:失败*/bool close();private:TcpServerImpl* impl_;/* brief 实现逻辑的指针 */};}在新的hiTcpServer.h中,除了添加SocketChannel类,修改了TcpEvent::on_open_的参数外,还添加了TcpServerImpl的指针,并声明了构造函数。HiTcpServer的impl_变量主要负责实现程序逻辑。HiTcpServer的拷贝构造函数和赋值指针被delete,以防止创建的HiTcpServer对象被拷贝。再看一眼hiTcpServer.h感觉有点复杂,SocketChannel,TcpEvent放在这儿有点乱,而且实现的时候很可能还会用到,所以需要将其从hiTcpServer.h中移除,写入到另一个头文件中(net下的hiNetCommon.h)。新的程序:hiNetCommon.h:#pragma once#include <functional>#include <string>#include <netinet/in.h>namespace Hi{/** @brief socket通道信息类*/class SocketChannel{public:SocketChannel(): sock_(-1), remote_port_(0), local_port_(0){}public:intsock_;///< socket文件描述符std::string remote_ip_;///< 对端IPunsigned short remote_port_;///< 对端端口std::string local_ip_;///< 本地IPunsigned short local_port_;///< 本地端口};/** @ brief TCP监听会发送的通知*/class TcpEvent{public:TcpEvent();public:/* brief 客户端连接成功通知 */std::function<void(int,SocketChannel&)> on_open_;/* brief 客户端连接断开通知 */std::function<void(int)> on_close_;/* brief 接收到客户端消息通知 */std::function<void(int)> on_receive_;};}hiTcpServer.h:#pragma once#include "net/hiNetCommon.h"namespace Hi{/* @ brief 逻辑实现类*/class TcpServerImpl;/** @ brief TCP监听类*/class HiTcpServer{public:HiTcpServer();~HiTcpServer();HiTcpServer& operator =(const HiTcpServer&) = delete;HiTcpServer(const HiTcpServer&) = delete;public:/** @brief 启动监听* @param [in] evt 通知对象* @param [in] port 监听端口* @param [in] ip 监听IP,如果为空时,表示监听本机所有的IP* @retval true:成功;false:失败*/bool open(const TcpEvent& evt, unsigned short port, const char* ip = NULL);/** @brief 停止监听* @retval true:成功;false:失败*/bool close();private:TcpServerImpl* impl_;/* brief 实现逻辑的指针 */};}测试程序:#pragma once#include <string>#include <sys/socket.h>#include <iostream>#include "net/hiTcpServer.h"using namespace std;// 客户端连接成功static void on_client_open(int sock,Hi::SocketChannel& channel){cout<<"accept a client connect,socket["<<sock<<"] remote["<<channel.remote_ip<<","<<channel.remote_port<<"]local["<<channel.local_ip<<","<<channel.local_port<<"]"<<endl;}// 接收到客户端消息static void on_client_recv_data(int sock){char rece_buf[256];memset(rece_buf, 0, 256);int n = recv(sock, rece_buf, 256, 0);cout<<"receive client(socket:"<<sock<<") data,len:"<<n<<endl;}// 客户端连接断开static void on_client_close(int sock){cout<<"client (socket:"<<sock<<") is close"<<endl;}void main(){Hi::TcpEvent evt;evt.on_open_ = std::bind(&on_client_open);evt.on_close_ = std::bind(&on_client_close);evt.on_receive_ = std::bind(&on_client_recv_data);Hi::HiTcpServer server;server.open(evt, 6000);sleep(60);}HiTcpServer类看起来比较简单,但是我不会一上来就实现HiTcpServer,因为它的实现不像看起来那样简单,涉及到socket的监听和IO事件的分发。所以还需要有其他的程序支持。我打算添加两个类HiTcpListen和HiTcpEPoll分贝来实现socket的监听和IO事件的分发。后续1:1: 该库只有在支持C++11的gcc才能编译2: 对stl不太熟悉的调用者,需要了解的知识点有:function,bind,delete函数(C++11新特性)3: 其实我还想为HiTcpServer和TcpEvent添加免派生功能的,以防止类被继承,但是我没有好的方法实现这样的功能。希望调用者,最好不要从HiTcpServer和TcpEvent派生子类。4: 在HiTcpServer的最初版本中,有三个重载的open函数,最后我调整了参数的顺序(将可为空的ip调整到最后),将三个函数合并为一个,总感觉opend函数的参数顺序有点怪异。5: 在这个接口中,我尽量避免通过类继承来实现功能扩展。6: 为了保持简单明了,我没有对socket文件描述符做任何处理直接暴露给了调用者,感觉这样对调用者更友好。7: 远端地址和本地地址其实可以通过socket文件描述符方便的获得,所以感觉TcpEvent::on_open_的第二个参数有点多余,以后的版本中可能会修改该接口。如果真的这样做,,会添加一个根据socket文件描述符获得相关信息的接口,或者用户直接用getpeername和getsockname也行。《unix编程艺术》中,强调“薄胶合层”,现在的TcpEvent::on_open_好像是违反了这个原则。8: 在我最初的想法中,HiTcpServer::open有三个function参数,分别对应于TcpEvent的三个成员变量,我觉得HiTcpServer::open参数有点多,才抽象出TcpEvent的。限制:1: HiTcpServer类限制了用户选择IO复用机制的自由,启动socket监听的自由,甚至网络比较差时收发数据的处理也没有支持。

人总是珍惜未得到的,而遗忘了所拥有的

记我的C++库设计历程:设计一个TCP服务程序

相关文章:

你感兴趣的文章:

标签云: