代码运行环境: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();
}
说明:
客户端不停的循环,在每一次循环中可以输入向服务器发送数据,然后在接受数据的函数中显示接受到的数据。这是一个基本的模型,具备与服务器通信,可以手动输入数据,发送数据和接受数据。
下面是运行的截图:
结合文章开头提供的服务器端的链接,你可以得到完整的代码,包括测试代码和静态库等,然后服务器端程序先运行,然后再运行客户端程序。服务器端支持很多客户端并发链接,自己测试效果吧。