我想写一个Linux下的C++程序库,实现一些常用的功能。
我首先想到的就是实现一个TCP监听程序。该程序应该具有哪些功能呢?
1: 启动/停止监听
2: 有客户端连接时,通知调用者
3: 与客户端断开时,通知调用者
4: 有消息到达时,通知调用者
这是我想到的TCP监听程序的最初接口(假定HiTcpServer放在net文件夹下的hiTcpServer.h文件中)。
怎么用呢?测试代码如下:
初步想法就是这样了。
TcpEvent::on_open_函数参数有点多,而且估计每个连接都有远端地址和本地地址,将其抽象成
一个类比较合适,该类可以叫SocketChannel。
具体定义(为了简单,可以将其放入hiTcpServer.h中):
TcpEvent::on_open_的定义就成了:
std::function<void(bool,int,SocketChannel&)> on_open_;
修正后的HiTcpServer就成了:
在新的hiTcpServer.h中,除了添加SocketChannel类,修改了TcpEvent::on_open_的参数外,还添加了TcpServerImpl
的指针,并声明了构造函数。
HiTcpServer的impl_变量主要负责实现程序逻辑。
HiTcpServer的拷贝构造函数和赋值指针被delete,以防止创建的HiTcpServer对象被拷贝。
再看一眼hiTcpServer.h感觉有点复杂,SocketChannel,TcpEvent放在这儿有点乱,
而且实现的时候很可能还会用到,所以需要将其从hiTcpServer.h中移除,写入到另一个头文件
中(net下的hiNetCommon.h)。
新的程序:
hiNetCommon.h:
hiTcpServer.h:
测试程序:
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监听的自由,甚至网络比较差时收发数据的
处理也没有支持。
我首先想到的就是实现一个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:
int sock_; ///< socket文件描述符
std::string remote_ip_; ///< 对端IP
unsigned short remote_port_; ///< 对端端口
std::string local_ip_; ///< 本地IP
unsigned 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:
int sock_; ///< socket文件描述符
std::string remote_ip_; ///< 对端IP
unsigned short remote_port_; ///< 对端端口
std::string local_ip_; ///< 本地IP
unsigned 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:
int sock_; ///< socket文件描述符
std::string remote_ip_; ///< 对端IP
unsigned short remote_port_; ///< 对端端口
std::string local_ip_; ///< 本地IP
unsigned 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监听的自由,甚至网络比较差时收发数据的
处理也没有支持。