代码运行环境:VS2010

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

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

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

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

头文件tcpClient.h:


#pragma once
/*---------------------------------------
- 文件  tcpClient.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> 
#define BUFFERSIZE (1024*1024)

typedef void(*newconnect)(int clientid);
typedef void(*server_recvcb)(int cliendid, const char* buf, int bufsize);
typedef void(*client_recvcb)(const char* buf, int bufsize, void* userdata);

class CTcpClient
{
	//直接调用connect/connect6会进行连接
public:
	CTcpClient(uv_loop_t* loop = uv_default_loop());
	virtual ~CTcpClient();
	static std::string GetUVError(int retcode)
	{
		std::string err;
		err = uv_err_name(retcode);
		err += ":";
		err += uv_strerror(retcode);
		return std::move(err);
	}
public:
	//基本函数
	virtual bool connect(const char* ip, int port);//启动connect线程,循环等待直到connect完成
	virtual bool connect6(const char* ip, int port);//启动connect线程,循环等待直到connect完成
	virtual int  send(const char* data, std::size_t len);
	virtual void setrecvcb(client_recvcb cb, void* userdata);////设置接收回调函数,只有一个
	void close();

	//是否启用Nagle算法
	bool setNoDelay(bool enable);
	bool setKeepAlive(int enable, unsigned int delay);

	const char* GetLastErrMsg() const {
		return errmsg_.c_str();
	};
protected:
	//静态回调函数
	static void AfterConnect(uv_connect_t* handle, int status);
	static void AfterClientRecv(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 AfterClose(uv_handle_t *handle);

	static void ConnectThread(void* arg);//真正的connect线程
	static void ConnectThread6(void* arg);//真正的connect线程

	bool init();
	bool run(int status = UV_RUN_DEFAULT);
private:
	enum {
		CONNECT_TIMEOUT,
		CONNECT_FINISH,
		CONNECT_ERROR,
		CONNECT_DIS,
	};
	uv_tcp_t client_;//客户端连接
	uv_loop_t *loop_;
	uv_write_t write_req_;//写时请求
	uv_connect_t connect_req_;//连接时请求
	uv_thread_t connect_threadhanlde_;//线程句柄
	std::string errmsg_;//错误信息
	uv_buf_t readbuffer_;//接受数据的buf
	uv_buf_t writebuffer_;//写数据的buf
	uv_mutex_t write_mutex_handle_;//保护write,保存前一write完成才进行下一write

	int connectstatus_;//连接状态
	client_recvcb recvcb_;//回调函数
	void* userdata_;//回调函数的用户数据
	std::string connectip_;//连接的服务器IP
	int connectport_;//连接的服务器端口号
	bool isinit_;//是否已初始化,用于close函数中判断
};
源文件tcpClient.cpp:



#include "tcpClient.h"

CTcpClient::CTcpClient(uv_loop_t* loop)	:recvcb_(nullptr), userdata_(nullptr)
	, connectstatus_(CONNECT_DIS), isinit_(false)
{
	readbuffer_ = uv_buf_init((char*)malloc(BUFFERSIZE), BUFFERSIZE);
	writebuffer_ = uv_buf_init((char*)malloc(BUFFERSIZE), BUFFERSIZE);
	loop_ = loop;
	connect_req_.data = this;
	write_req_.data = this;
}
CTcpClient::~CTcpClient()
{
	free(readbuffer_.base);
	readbuffer_.base = nullptr;
	readbuffer_.len = 0;
	free(writebuffer_.base);
	writebuffer_.base = nullptr;
	writebuffer_.len = 0;
	close();
}

bool CTcpClient::init()
{
	if (isinit_)return true;
	if (!loop_)return false;
	int iret = uv_tcp_init(loop_, &client_);
	if (iret)return false;
	iret = uv_mutex_init(&write_mutex_handle_);
	if (iret)return false;
	isinit_ = true;
	fprintf(stdout, "客户端(%p) 初始化类型 = %d\n", &client_, client_.type);
	client_.data = this;
	//iret = uv_tcp_keepalive(&client_, 1, 60);//
	//if (iret)return false;
	return true;
}
void CTcpClient::close()
{
	if (!isinit_)return;
	uv_mutex_destroy(&write_mutex_handle_);
	uv_close((uv_handle_t*)&client_, AfterClose);
	isinit_ = false;
}
bool CTcpClient::run(int status)
{
	int iret = uv_run(loop_, (uv_run_mode)status);
	if (iret)return false;
	return true;
}

bool CTcpClient::setNoDelay(bool enable)
{
	//属性设置--服务器与客户端一致
	//http://blog.csdn.net/u011133100/article/details/21485983
	int iret = uv_tcp_nodelay(&client_, enable ? 1 : 0);
	if (iret)return false;
	return true;
}
bool CTcpClient::setKeepAlive(int enable, unsigned int delay)
{
	int iret = uv_tcp_keepalive(&client_, enable, delay);
	if (iret)return false;
	return true;
}

bool CTcpClient::connect(const char* ip, int port)
{
	close();
	init();
	connectip_ = ip;
	connectport_ = port;
	//触发AfterConnect才算真正连接成功,所以用线程
	int iret = uv_thread_create(&connect_threadhanlde_, ConnectThread, this);
	if (iret)return false;
	while (connectstatus_ == CONNECT_DIS) 
	{
		#if defined (WIN32) || defined(_WIN32)
		    Sleep(100);
		#else
		    usleep((100) * 1000)
		#endif
	}
	return connectstatus_ == CONNECT_FINISH;
}
bool CTcpClient::connect6(const char* ip, int port)
{
	close();
	init();
	connectip_ = ip;
	connectport_ = port;
	//触发AfterConnect才算真正连接成功,所以用线程
	int iret = uv_thread_create(&connect_threadhanlde_, ConnectThread6, this);
	if (iret)return false;
	while (connectstatus_ == CONNECT_DIS) 
	{
		//fprintf(stdout,"客户端(%p) 等待, 连接状态 %d\n",this,connectstatus_);
		#if defined (WIN32) || defined(_WIN32)
			Sleep(100);
		#else
			usleep((100) * 1000)
		#endif
	}
	return connectstatus_ == CONNECT_FINISH;
}
void CTcpClient::ConnectThread(void* arg)
{
	CTcpClient *pclient = (CTcpClient*)arg;
	fprintf(stdout, "客户端(%p) 连接线程开始\n", pclient);
	struct sockaddr_in bind_addr;
	int iret = uv_ip4_addr(pclient->connectip_.c_str(), pclient->connectport_, &bind_addr);
	if (iret)return;
	iret = uv_tcp_connect(&pclient->connect_req_, &pclient->client_, (const sockaddr*)&bind_addr, AfterConnect);
	if (iret)return;
	fprintf(stdout, "客户端(%p) 连接线程结束, 连接状态 %d\n", pclient, pclient->connectstatus_);
	pclient->run();
}
void CTcpClient::ConnectThread6(void* arg)
{
	CTcpClient *pclient = (CTcpClient*)arg;
	fprintf(stdout, "客户端(%p) 连接线程启动\n", pclient);
	struct sockaddr_in6 bind_addr;
	int iret = uv_ip6_addr(pclient->connectip_.c_str(), pclient->connectport_, &bind_addr);
	if (iret)return;
	iret = uv_tcp_connect(&pclient->connect_req_, &pclient->client_, (const sockaddr*)&bind_addr, AfterConnect);
	if (iret)return;
	fprintf(stdout, "客户端(%p) 连接线程结束, 连接状态 %d\n", pclient, pclient->connectstatus_);
	pclient->run();
}
int CTcpClient::send(const char* data, std::size_t len)
{
	//客户端向服务器发送数据函数
	//自己控制data的生命周期直到write结束
	//数据为空或者长度小于0
	if (!data || len <= 0)return 0;

	uv_mutex_lock(&write_mutex_handle_);
	if (writebuffer_.len < len) 
	{
		writebuffer_.base = (char*)realloc(writebuffer_.base, len);
		writebuffer_.len = len;
	}
	memcpy(writebuffer_.base, data, len);
	uv_buf_t buf = uv_buf_init((char*)writebuffer_.base, len);
	int iret = uv_write(&write_req_, (uv_stream_t*)&client_, &buf, 1, AfterSend);
	if (iret) 
	{
		uv_mutex_unlock(&write_mutex_handle_);
		errmsg_ = GetUVError(iret);
		return false;
	}
	return true;
}

void CTcpClient::setrecvcb(client_recvcb cb, void* userdata)
{
	//设置 客户端接收数据的回调函数
	recvcb_ = cb;
	userdata_ = userdata;
}
void CTcpClient::onAllocBuffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
	//客户端分配空间函数
	if (!handle->data)return;
	CTcpClient *client = (CTcpClient*)handle->data;
	*buf = client->readbuffer_;
}

void CTcpClient::AfterConnect(uv_connect_t* handle, int status)
{
	fprintf(stdout, "连接处理开始\n");
	CTcpClient *pclient = (CTcpClient*)handle->handle->data;
	if (status)
	{
		pclient->connectstatus_ = CONNECT_ERROR;
		fprintf(stdout, "连接错误:%s\n", GetUVError(status).c_str());
		return;
	}
	//客户端开始接收服务器的数据
	int iret = uv_read_start(handle->handle, onAllocBuffer, AfterClientRecv);
	if (iret)
	{
		fprintf(stdout, "开始读取数据 错误:%s\n", GetUVError(iret).c_str());
		pclient->connectstatus_ = CONNECT_ERROR;
	}
	else {
		pclient->connectstatus_ = CONNECT_FINISH;
	}
	fprintf(stdout, "连接处理结束\n");
}
void CTcpClient::AfterClientRecv(uv_stream_t *handle, ssize_t nread, const uv_buf_t* buf)
{
	if (!handle->data)return;
	CTcpClient *client = (CTcpClient*)handle->data;//服务器的recv带的是TCPClient
	if (nread < 0) 
	{
		if (nread == UV_EOF) 
			fprintf(stdout, "服务器(%p)主动断开\n", handle);
		else if (nread == UV_ECONNRESET) 
			fprintf(stdout, "服务器(%p)异常断开\n", handle);
		else 
			fprintf(stdout, "服务器(%p)异常断开:%s\n", handle, GetUVError(nread).c_str());
		uv_close((uv_handle_t*)handle, AfterClose);
		return;
	}
	if (nread > 0 && client->recvcb_)
		client->recvcb_(buf->base, nread, client->userdata_);
}
void CTcpClient::AfterSend(uv_write_t *req, int status)
{
	CTcpClient *client = (CTcpClient *)req->handle->data;
	uv_mutex_unlock(&client->write_mutex_handle_);
	if (status < 0)
		fprintf(stderr, "Write error %s\n", GetUVError(status).c_str());
}
void CTcpClient::AfterClose(uv_handle_t *handle)
{
	fprintf(stdout, "客户端(%p)已关闭\n", handle);
}

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

#include "tcpClient.h"
#pragma warning(disable:4996)//禁止提示scanf函数为不安全版本函数

void recv_cb(const char* buf, int bufsize, void* userdata)
{
	printf("服务器应答:%s\n",buf);
}
void main()
{
	//创建一个客户端对象
	CTcpClient client;
	//设置接受数据的回调函数
	client.setrecvcb(recv_cb,0);
	//连接服务器端
	int bOk = client.connect("127.0.0.1", 6100);
	if (!bOk)
	{
		//连接服务器端失败
		client.close();
		return;
	}

	int i = 1;
	while (1)
	{
		char msg[100] = { 0 };
		scanf("%s", msg);
		//向服务器发送数据
		bOk = client.send(msg, 100);
		if (!bOk)
		{
			//发送失败,再次输入发送
			printf("发送失败,请重新输入!\n");
			continue;
		}
	}
	client.close();
}
说明:


    客户端不停的循环,在每一次循环中可以输入向服务器发送数据,然后在接受数据的函数中显示接受到的数据。这是一个基本的模型,具备与服务器通信,可以手动输入数据,发送数据和接受数据。

    下面是运行的截图:

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

    结合文章开头提供的服务器端的链接,你可以得到完整的代码,包括测试代码和静态库等,然后服务器端程序先运行,然后再运行客户端程序。服务器端支持很多客户端并发链接,自己测试效果吧。