当前位置:C++技术网 > 精选软件 > 基于libuv封装的TCP通信类-服务端类源代码

基于libuv封装的TCP通信类-服务端类源代码

更新时间:2016-04-01 19:42:51浏览次数:1+次

代码运行环境:VS2010

libuv基本静态lib库和头文件:点此去下载libuv基本静态lib库和头文件

注意:项目一定要忽略导入库libcmt.lib,以免导出的标识符冲突。

基于libuv封装的TCP通信类-服务端类的代码:

头文件tcpServer.h:


#pragma once
/*---------------------------------------
- 文件  tcpServer.h
- 简介  基于libuv封装的tcp服务端的类
- 来源  C++技术网 http://www.cjjjs.com
- 作者  codexia (精简)
- 封装  phata(原始封装作者)
- 日期  2016-4-1
- 说明  在phata封装libuv库的基础上精简了不必要的代码,主要是日志,然后整理了排版,更符合C++排版风格,并将服务器端类和客户端分离为两个类
- lib   VS2010版的lib静态库的使用,项目中要忽略LIBCMT.lib,否则出现导出的符号重复
-----------------------------------------*/

#pragma comment(lib,"Ws2_32.lib")
#pragma comment(lib,"Psapi.lib")
#pragma comment(lib,"Iphlpapi.lib")
#pragma comment(lib,"Userenv.lib")
#pragma comment(lib,"libuv.lib")

#include "uv.h"
#include <string>
#include <map>
#include <stdio.h>
typedef void(*newconnect)(int clientid);
typedef void(*server_recvcb)(int cliendid, const char* buf, int bufsize);
#define BUFFERSIZE (1024*1024)
class clientdata;
class CTcpServer
{
public:
	CTcpServer(uv_loop_t* loop = uv_default_loop());
	virtual ~CTcpServer();

	static std::string GetUVError(int retcode)
	{
		std::string err;
		err = uv_err_name(retcode);
		err += ":";
		err += uv_strerror(retcode);
		return std::move(err);
	}

public:
	//基本函数
	bool Start(const char *ip, int port);//启动服务器,地址为IP4
	bool Start6(const char *ip, int port);//启动服务器,地址为IP6
	void close();

	bool setNoDelay(bool enable);
	bool setKeepAlive(int enable, unsigned int delay);

	const char* GetLastErrMsg() const {	return errmsg_.c_str();	};

	virtual int  send(int clientid, const char* data, std::size_t len);
	virtual void setnewconnectcb(newconnect cb);
	virtual void setrecvcb(int clientid, server_recvcb cb);//设置接收回调函数,每个客户端各有一个
protected:
	int GetAvailaClientID()const;//获取可用的client id
	bool DeleteClient(int clientid);//删除链表中的客户端
									//静态回调函数
	static void AfterServerRecv(uv_stream_t *client, ssize_t nread, const uv_buf_t* buf);
	static void AfterSend(uv_write_t *req, int status);
	static void onAllocBuffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf);
	static void AfterServerClose(uv_handle_t *handle);
	static void AfterClientClose(uv_handle_t *handle);
	static void acceptConnection(uv_stream_t *server, int status);

private:
	bool init();
	bool run(int status = UV_RUN_DEFAULT);
	bool bind(const char* ip, int port);
	bool bind6(const char* ip, int port);
	bool listen(int backlog = 1024);

	uv_tcp_t server_;//服务器链接
	std::map<int, clientdata*> clients_list_;//子客户端链接
	uv_mutex_t mutex_handle_;//保护clients_list_
	uv_loop_t *loop_;
	std::string errmsg_;
	newconnect newconcb_;
	bool isinit_;//是否已初始化,用于close函数中判断
};

class clientdata
{
public:
	clientdata(int clientid) :client_id(clientid), recvcb_(nullptr) {
		client_handle = (uv_tcp_t*)malloc(sizeof(*client_handle));
		client_handle->data = this;
		readbuffer = uv_buf_init((char*)malloc(BUFFERSIZE), BUFFERSIZE);
		writebuffer = uv_buf_init((char*)malloc(BUFFERSIZE), BUFFERSIZE);
	}
	virtual ~clientdata() {
		free(readbuffer.base);
		readbuffer.base = nullptr;
		readbuffer.len = 0;

		free(writebuffer.base);
		writebuffer.base = nullptr;
		writebuffer.len = 0;

		free(client_handle);
		client_handle = nullptr;
	}
	int client_id;//客户端id,惟一
	uv_tcp_t* client_handle;//客户端句柄
	CTcpServer* tcp_server;//服务器句柄(保存是因为某些回调函数需要到)
	uv_buf_t readbuffer;//接受数据的buf
	uv_buf_t writebuffer;//写数据的buf
	uv_write_t write_req;
	server_recvcb recvcb_;//接收数据回调给用户的函数
};
源文件tcpServer.cpp:



#include "tcpServer.h"
CTcpServer::CTcpServer(uv_loop_t* loop)	:newconcb_(nullptr), isinit_(false)
{
	loop_ = loop;
}
CTcpServer::~CTcpServer()
{
	close();
}

bool CTcpServer::init()
{
	//调用uv初始化TCP服务
	//下面的错误代码可调用GetUVError(iret)返回错误信息

	if (isinit_)return true;
	//loop循环为空
	if (!loop_)return false;

	int iret = uv_mutex_init(&mutex_handle_);
	if (iret) return false;
	
	iret = uv_tcp_init(loop_, &server_);
	if (iret) return false;

	isinit_ = true;
	server_.data = this;

	//调用uv_tcp_keepalive,后续函数会调用出错
	//iret = uv_tcp_keepalive(&server_, 1, 60);
	//if (iret) return false;

	return true;
}
void CTcpServer::close()
{
	//关闭服务器
	for (auto it = clients_list_.begin(); it != clients_list_.end(); ++it) 
	{
		auto data = it->second;
		uv_close((uv_handle_t*)data->client_handle, AfterClientClose);
	}
	clients_list_.clear();
	if (isinit_)uv_close((uv_handle_t*)&server_, AfterServerClose);
	isinit_ = false;
	uv_mutex_destroy(&mutex_handle_);
}
bool CTcpServer::run(int status)
{
	//开始运行
	int iret = uv_run(loop_, (uv_run_mode)status);
	if (iret)return false;
	return true;
}
bool CTcpServer::setNoDelay(bool enable)
{
	//属性设置--服务器与客户端一致
	int iret = uv_tcp_nodelay(&server_, enable ? 1 : 0);
	if (iret)return false;
	return true;
}
bool CTcpServer::setKeepAlive(int enable, unsigned int delay)
{
	int iret = uv_tcp_keepalive(&server_, enable, delay);
	if (iret)return false;
	return true;
}

bool CTcpServer::bind(const char* ip, int port)
{
	//服务器绑定端口ipv4
	struct sockaddr_in bind_addr;
	int iret = uv_ip4_addr(ip, port, &bind_addr);
	if (iret)return false;
	iret = uv_tcp_bind(&server_, (const struct sockaddr*)&bind_addr, 0);
	if (iret)return false;
	return true;
}
bool CTcpServer::bind6(const char* ip, int port)
{
	//服务器绑定端口ipv6
	struct sockaddr_in6 bind_addr;
	int iret = uv_ip6_addr(ip, port, &bind_addr);
	if (iret)return false;
	iret = uv_tcp_bind(&server_, (const struct sockaddr*)&bind_addr, 0);
	if (iret)return false;
	return true;
}
bool CTcpServer::listen(int backlog)
{
	//监听TCP
	int iret = uv_listen((uv_stream_t*)&server_, backlog, acceptConnection);
	if (iret)return false;
	return true;
}
bool CTcpServer::Start(const char *ip, int port)
{
	//开始运行TCP服务,ipv4版
	close();
	if (!init())return false;
	if (!bind(ip, port))return false;
	if (!listen(SOMAXCONN))return false;
	if (!run())return false;
	return true;
}
bool CTcpServer::Start6(const char *ip, int port)
{
	//开始运行TCP服务,ipv6版
	close();
	if (!init())return false;
	if (!bind6(ip, port))return false;
	if (!listen(SOMAXCONN))return false;
	if (!run())return false;
	return true;
}

int CTcpServer::send(int clientid, const char* data, std::size_t len)
{
	//服务器发送数据给客户端函数
	auto itfind = clients_list_.find(clientid);
	//找不到指定ID的客户端
	if (itfind == clients_list_.end()) return -1;
	//自己控制data的生命周期直到write结束
	if (itfind->second->writebuffer.len < len) 
	{
		itfind->second->writebuffer.base = (char*)realloc(itfind->second->writebuffer.base, len);
		itfind->second->writebuffer.len = len;
	}
	memcpy(itfind->second->writebuffer.base, data, len);
	uv_buf_t buf = uv_buf_init((char*)itfind->second->writebuffer.base, len);
	int iret = uv_write(&itfind->second->write_req, (uv_stream_t*)itfind->second->client_handle, &buf, 1, AfterSend);
	if (iret)return false;
	return true;
}
void CTcpServer::acceptConnection(uv_stream_t *server, int status)
{
	//服务器接收客户端连接
	if (!server->data)return;
	CTcpServer *tcpsock = (CTcpServer *)server->data;
	int clientid = tcpsock->GetAvailaClientID();
	clientdata* cdata = new clientdata(clientid);//uv_close回调函数中释放
	cdata->tcp_server = tcpsock;//保存服务器的信息
	int iret = uv_tcp_init(tcpsock->loop_, cdata->client_handle);//析构函数释放
	if (iret) 
	{
		delete cdata;
		return;
	}
	iret = uv_accept((uv_stream_t*)&tcpsock->server_, (uv_stream_t*)cdata->client_handle);
	if (iret) 
	{
		uv_close((uv_handle_t*)cdata->client_handle, NULL);
		delete cdata;
		return;
	}
	//加入到链接队列
	tcpsock->clients_list_.insert(std::make_pair(clientid, cdata));
	if (tcpsock->newconcb_)tcpsock->newconcb_(clientid);
	//服务器开始接收客户端的数据
	iret = uv_read_start((uv_stream_t*)cdata->client_handle, onAllocBuffer, AfterServerRecv);
	return;
}

void CTcpServer::setrecvcb(int clientid, server_recvcb cb)
{
	//设置服务器端接收客户端发过来的数据的回调函数
	auto itfind = clients_list_.find(clientid);
	if (itfind != clients_list_.end())itfind->second->recvcb_ = cb;
}
void CTcpServer::setnewconnectcb(newconnect cb)
{
	//设置服务器处理客户端连接的回调函数
	newconcb_ = cb;
}

void CTcpServer::onAllocBuffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
	//服务器分配空间函数
	if (!handle->data)return;
	clientdata *client = (clientdata*)handle->data;
	*buf = client->readbuffer;
}

void CTcpServer::AfterServerRecv(uv_stream_t *handle, ssize_t nread, const uv_buf_t* buf)
{
	if (!handle->data)return;
	clientdata *client = (clientdata*)handle->data;//服务器的recv带的是clientdata
	if (nread < 0) 
	{
		/* 错误 或 EOF结尾 */
		CTcpServer *server = (CTcpServer *)client->tcp_server;
		if (nread == UV_EOF) 
		{
			fprintf(stdout, "客户端(%d)连接断开,关闭此客户端\n", client->client_id);
		}
		else if (nread == UV_ECONNRESET)
		{
			fprintf(stdout, "客户端(%d)异常断开\n", client->client_id);
		}
		else 
		{
			fprintf(stdout, "%s\n", GetUVError(nread).c_str());
		}
		//连接断开,关闭客户端
		server->DeleteClient(client->client_id);
		return;
	}
	else if (0 == nread) 
	{
		/* 一切正常,只是没有读取任何数据 */
	}
	else if (client->recvcb_) 
	{
		client->recvcb_(client->client_id, buf->base, nread);
	}
}
void CTcpServer::AfterSend(uv_write_t *req, int status)
{
	if (status < 0)fprintf(stderr, "发送数据错误 %s\n", GetUVError(status).c_str());
}
void CTcpServer::AfterServerClose(uv_handle_t *handle)
{
	//服务器关闭
}
void CTcpServer::AfterClientClose(uv_handle_t *handle)
{
	clientdata *cdata = (clientdata*)handle->data;
	delete cdata;
}

int CTcpServer::GetAvailaClientID() const
{
	static int s_id = 0;
	return ++s_id;
}
bool CTcpServer::DeleteClient(int clientid)
{
	uv_mutex_lock(&mutex_handle_);
	auto itfind = clients_list_.find(clientid);
	if (itfind == clients_list_.end()) 
	{
		errmsg_ = "can't find client ";
		errmsg_ += std::to_string((long long)clientid);
		uv_mutex_unlock(&mutex_handle_);
		return false;
	}
	if (uv_is_active((uv_handle_t*)itfind->second->client_handle)) 
		uv_read_stop((uv_stream_t*)itfind->second->client_handle);
	uv_close((uv_handle_t*)itfind->second->client_handle, AfterClientClose);

	clients_list_.erase(itfind);
	uv_mutex_unlock(&mutex_handle_);
	return true;
}

基于libuv封装的TCP通信类-服务端类的测试项目代码:


#include "tcpServer.h"
//服务器对象
CTcpServer srv;
void recv_cb(int cliendid, const char* buf, int bufsize)
{
	printf("客户端:%d %s \n", cliendid, buf);
	srv.send(cliendid, buf, bufsize);
}
void new_conn_cb(int clientid)
{
	//设置新连接接收数据回调函数
	srv.setrecvcb(clientid, recv_cb);
	printf("设置连接回调函数成功!\n");
}
void main()
{
	//设置连接客户端时处理的回调函数
	srv.setnewconnectcb(new_conn_cb);
	//启动服务器
	srv.Start("127.0.0.1", 6100);
}



此代码是服务器端使用的代码,测试代码可以让你直接跑起来TCP服务器端程序。代码是测试通过的,下面是截图:

基于libuv封装的TCP通信类-服务端类的测试项目代码

最底部的窗口就是服务端,顶上的两个窗口是客户端。因为此代码也是网友封装共享的,我只是对原来封装的代码精简抽离出了TCP最关键的部分。很感谢封装的作者,我也就是借花献佛,整理了编码排版、去掉了不必要的代码,只保留最基本的必要的代码,将原本客户端和服务端类混在一起的分离为单独的两个类,这样使用时不容易出错。