码迷,mamicode.com
首页 > 编程语言 > 详细

我想写一个Linux下的C++程序库--记我的C++库设计历程:设计一个TCP服务程序

时间:2015-02-17 21:05:12      阅读:153      评论:0      收藏:0      [点我收藏+]

标签:c++   程序库   tcp   socket   设计   

我想写一个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:	
	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监听的自由,甚至网络比较差时收发数据的
处理也没有支持。




我想写一个Linux下的C++程序库--记我的C++库设计历程:设计一个TCP服务程序

标签:c++   程序库   tcp   socket   设计   

原文地址:http://blog.csdn.net/xumingxsh/article/details/43867477

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!