windows C++ TCP客户端

demo有一下功能

1、心跳包
2、断开重连
3、非阻塞
4、接受数据单独线程处理


#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <string>
#include <process.h>  // 用于Windows下的线程相关操作#pragma comment(lib, "ws2_32.lib")#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 6000
#define RECV_BUF_SIZE 1024
#define HEARTBEAT_INTERVAL 5000  // 心跳包发送间隔,单位:毫秒
#define HEARTBEAT_TIMEOUT 10000  // 心跳包超时时间,单位:毫秒
#define MAX_RECONNECT_ATTEMPTS 10 // 最大重连尝试次数
#define RECONNECT_INTERVAL_SECONDS 2  // 重连间隔时间(秒)class TCPClient
{
public:TCPClient();~TCPClient();bool connectToServer();void disconnect();int sendData(const std::string& data);private:SOCKET m_socket;sockaddr_in m_serverAddr;bool m_connected;// 心跳包相关变量和函数DWORD m_lastHeartbeatTime;bool m_heartbeatSent;HANDLE m_heartbeatThreadHandle;bool m_heartbeatThreadRunning;static unsigned int __stdcall HeartbeatThread(void* param);bool sendHeartbeat();bool checkHeartbeatResponse();// 用于设置套接字为非阻塞模式bool setSocketNonBlocking();// 尝试重连服务器bool reconnect();// 初始化Winsock库bool initializeWinsock();// 关闭套接字并清理相关资源void closeSocket();// 接收数据线程相关函数和变量static unsigned int __stdcall ReceiveDataThread(void* param);HANDLE m_receiveThreadHandle;bool m_receiveThreadRunning;
};// 构造函数,初始化成员变量并初始化Winsock库
TCPClient::TCPClient() : m_socket(INVALID_SOCKET), m_connected(false),m_lastHeartbeatTime(0), m_heartbeatSent(false),m_heartbeatThreadHandle(NULL), m_heartbeatThreadRunning(false),m_receiveThreadHandle(NULL), m_receiveThreadRunning(false) 
{if (!initializeWinsock()) {std::cerr << "初始化Winsock库失败" << std::endl;}m_serverAddr.sin_family = AF_INET;m_serverAddr.sin_port = htons(SERVER_PORT);if (inet_pton(AF_INET, SERVER_IP, &(m_serverAddr.sin_addr)) <= 0) {std::cerr << "inet_pton转换IP地址错误" << std::endl;}
}// 析构函数,断开连接并清理Winsock库,同时关闭心跳包线程和接收数据线程
TCPClient::~TCPClient()
{disconnect();if (m_heartbeatThreadHandle!= NULL) {m_heartbeatThreadRunning = false;// 等待心跳包线程结束WaitForSingleObject(m_heartbeatThreadHandle, INFINITE);CloseHandle(m_heartbeatThreadHandle);}if (m_receiveThreadHandle!= NULL) {m_receiveThreadRunning = false;// 等待接收数据线程结束WaitForSingleObject(m_receiveThreadHandle, INFINITE);CloseHandle(m_receiveThreadHandle);}WSACleanup();
}// 连接服务器的函数
bool TCPClient::connectToServer()
{m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (m_socket == INVALID_SOCKET){std::cerr << "创建套接字失败,错误码: " << WSAGetLastError() << std::endl;return false;}// 设置套接字为非阻塞模式if (!setSocketNonBlocking()){std::cerr << "设置套接字为非阻塞模式失败" << std::endl;closeSocket();return false;}int ret = connect(m_socket, (struct sockaddr*)&m_serverAddr, sizeof(m_serverAddr));if (ret == SOCKET_ERROR){int errCode = WSAGetLastError();if (errCode!= WSAEWOULDBLOCK) {std::cerr << "连接服务器失败,错误码: " << errCode << std::endl;closeSocket();return false;}}// 等待连接真正建立(非阻塞模式下需要轮询检查)timeval timeout;timeout.tv_sec = 5;  // 设置超时时间为5秒timeout.tv_usec = 0;fd_set writefds;FD_ZERO(&writefds);FD_SET(m_socket, &writefds);ret = select(0, NULL, &writefds, NULL, &timeout);if (ret == SOCKET_ERROR){std::cerr << "select函数出错,错误码: " << WSAGetLastError() << std::endl;closeSocket();return false;} else if (ret == 0){std::cerr << "连接超时" << std::endl;closeSocket();return false;}if (FD_ISSET(m_socket, &writefds)){m_connected = true;// 创建并启动接收数据线程m_receiveThreadHandle = (HANDLE)_beginthreadex(NULL, 0, ReceiveDataThread, this, 0, NULL);if (m_receiveThreadHandle == NULL) {std::cerr << "创建接收数据线程失败" << std::endl;closeSocket();return false;}m_receiveThreadRunning = true;// 创建并启动心跳包线程m_heartbeatThreadHandle = (HANDLE)_beginthreadex(NULL, 0, HeartbeatThread, this, 0, NULL);if (m_heartbeatThreadHandle == NULL) {std::cerr << "创建心跳包线程失败" << std::endl;closeSocket();return false;}m_heartbeatThreadRunning = true;std::cout << "成功连接到服务器" << std::endl;return true;}return false;
}// 断开与服务器连接的函数
void TCPClient::disconnect()
{if (m_connected) {closesocket(m_socket);m_connected = false;std::cout << "已断开与服务器的连接" << std::endl;}m_receiveThreadRunning = false;m_heartbeatThreadRunning = false;
}// 发送数据到服务器的函数
int TCPClient::sendData(const std::string& data) 
{if (!m_connected) {if (reconnect()){}else{std::cerr << "未连接到服务器,无法发送数据" << std::endl;return SOCKET_ERROR;}}int ret = send(m_socket, data.c_str(), data.size(), 0);if (ret == SOCKET_ERROR) {int errCode = WSAGetLastError();if (errCode == WSAEWOULDBLOCK) {// 在非阻塞模式下,缓冲区满等情况会返回此错误,可根据需要处理return 0;} else{std::cerr << "发送数据失败,错误码: " << errCode << std::endl;// 如果是连接断开相关错误,尝试重连if (errCode == WSAECONNRESET || errCode == WSAENETRESET){if (reconnect()){// 重连成功后再次发送数据return sendData(data);}}return SOCKET_ERROR;}}return ret;
}// 发送心跳包的函数
bool TCPClient::sendHeartbeat() 
{if (!m_connected){return false;}const std::string heartbeatData = "HEARTBEAT_CLIENT";int ret = send(m_socket, heartbeatData.c_str(), heartbeatData.size(), 0);if (ret == SOCKET_ERROR){int errCode = WSAGetLastError();if (errCode == WSAEWOULDBLOCK){return false;}else {std::cerr << "发送心跳包失败,错误码: " << errCode << std::endl;return false;}}m_heartbeatSent = true;return true;
}// 检查心跳包响应的函数
bool TCPClient::checkHeartbeatResponse()
{if (!m_connected){return false;}char buffer[RECV_BUF_SIZE];int ret = recv(m_socket, buffer, RECV_BUF_SIZE, 0);if (ret == SOCKET_ERROR){int errCode = WSAGetLastError();if (errCode == WSAEWOULDBLOCK) {return false;} else{std::cerr << "接收心跳包响应失败,错误码: " << errCode << std::endl;return false;}}else if (ret == 0){// 对方关闭了连接std::cerr << "服务器关闭了连接" << std::endl;disconnect();return false;} else{std::string response(buffer, ret);if (response == "HEARTBEAT_ACK"){return true;}}return false;
}// 设置套接字为非阻塞模式的函数
bool TCPClient::setSocketNonBlocking()
{u_long mode = 1;int ret = ioctlsocket(m_socket, FIONBIO, &mode);return ret!= SOCKET_ERROR;
}// 尝试重连服务器的函数
bool TCPClient::reconnect()
{int attempt = 0;while (attempt < MAX_RECONNECT_ATTEMPTS){attempt++;closeSocket();Sleep(RECONNECT_INTERVAL_SECONDS * 1000);  // 等待一段时间后重连m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (m_socket == INVALID_SOCKET){std::cerr << "重连时创建套接字失败,错误码: " << WSAGetLastError() << std::endl;continue;}// 设置套接字为非阻塞模式if (!setSocketNonBlocking()){std::cerr << "重连时设置套接字为非阻塞模式失败" << std::endl;closeSocket();continue;}int ret = connect(m_socket, (struct sockaddr*)&m_serverAddr, sizeof(m_serverAddr));if (ret == SOCKET_ERROR){int errCode = WSAGetLastError();if (errCode!= WSAEWOULDBLOCK){std::cerr << "重连失败,错误码: " << errCode << std::endl;continue;}}// 等待连接真正建立(非阻塞模式下需要轮询检查)timeval timeout;timeout.tv_sec = 5;  // 设置超时时间为5秒timeout.tv_usec = 0;fd_set writefds;FD_ZERO(&writefds);FD_SET(m_socket, &writefds);ret = select(0, NULL, &writefds, NULL, &timeout);if (ret == SOCKET_ERROR){std::cerr << "重连时select函数出错,错误码: " << WSAGetLastError() << std::endl;closeSocket();continue;} else if (ret == 0) {std::cerr << "重连超时" << std::endl;closeSocket();continue;}if (FD_ISSET(m_socket, &writefds)){m_connected = true;// 重新创建并启动接收数据线程if (m_receiveThreadHandle!= NULL){m_receiveThreadRunning = false;WaitForSingleObject(m_receiveThreadHandle, INFINITE);CloseHandle(m_receiveThreadHandle);}m_receiveThreadHandle = (HANDLE)_beginthreadex(NULL, 0, ReceiveDataThread, this, 0, NULL);if (m_receiveThreadHandle == NULL) {std::cerr << "重连后创建接收数据线程失败" << std::endl;closeSocket();return false;}m_receiveThreadRunning = true;// 重新创建并启动心跳包线程if (m_heartbeatThreadHandle!= NULL) {m_heartbeatThreadRunning = false;WaitForSingleObject(m_heartbeatThreadHandle, INFINITE);CloseHandle(m_heartbeatThreadHandle);}m_heartbeatThreadHandle = (HANDLE)_beginthreadex(NULL, 0, HeartbeatThread, this, 0, NULL);if (m_heartbeatThreadHandle == NULL) {std::cerr << "重连后创建心跳包线程失败" << std::endl;closeSocket();return false;}m_heartbeatThreadRunning = true;std::cout << "重连成功" << std::endl;return true;}}std::cerr << "达到最大重连尝试次数,重连失败" << std::endl;return false;
}// 初始化Winsock库的函数
bool TCPClient::initializeWinsock() 
{WSADATA wsaData;return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0;
}// 关闭套接字并清理相关资源的函数
void TCPClient::closeSocket() 
{if (m_socket!= INVALID_SOCKET) {closesocket(m_socket);m_socket = INVALID_SOCKET;}
}// 心跳包线程函数
unsigned int __stdcall TCPClient::HeartbeatThread(void* param)
{TCPClient* client = static_cast<TCPClient*>(param);while (client->m_heartbeatThreadRunning && client->m_connected) {DWORD currentTime = GetTickCount();if (currentTime - client->m_lastHeartbeatTime >= HEARTBEAT_INTERVAL){if (client->sendHeartbeat()){client->m_lastHeartbeatTime = currentTime;}}if (currentTime - client->m_lastHeartbeatTime > HEARTBEAT_TIMEOUT){std::cerr << "心跳包超时,通知主线程尝试重连" << std::endl;client->m_connected = false;break;}Sleep(100);  // 适当休眠,避免过于频繁循环检查}return 0;
}// 接收数据线程函数
unsigned int __stdcall TCPClient::ReceiveDataThread(void* param) 
{TCPClient* client = static_cast<TCPClient*>(param);std::string receivedData;while (client->m_receiveThreadRunning && client->m_connected) {char recvBuf[RECV_BUF_SIZE];int ret = recv(client->m_socket, recvBuf, RECV_BUF_SIZE, 0);if (ret == SOCKET_ERROR){int errCode = WSAGetLastError();if (errCode == WSAEWOULDBLOCK){// 在非阻塞模式下,无数据可读时会返回此错误,可根据需要处理continue;}else {std::cerr << "接收数据线程中接收数据失败,错误码: " << errCode << std::endl;// 如果是连接断开相关错误,通知主线程尝试重连if (errCode == WSAECONNRESET || errCode == WSAENETRESET) {client->m_connected = false;break;}}} else if (ret == 0){// 对方关闭了连接std::cerr << "服务器关闭了连接(接收数据线程中)" << std::endl;client->m_connected = false;break;} else{receivedData.assign(recvBuf, ret);std::cout << "接收数据线程从服务器接收到数据: " << receivedData << std::endl;}}return 0;
}int main() 
{TCPClient client;if (client.connectToServer()){while (true){// 发送数据示例std::string sendDataStr = "Hello, server!\n";client.sendData(sendDataStr);// 简单的休眠,避免过于频繁循环Sleep(100);}}return 0;
}

1. 接收数据线程相关的成员变量

  • m_receiveThreadHandle:用于存储接收数据线程的句柄,通过_beginthreadex函数创建线程时获取,用于后续对线程的操作,比如等待线程结束、关闭线程句柄等。
  • m_receiveThreadRunning:布尔类型变量,用于标记接收数据线程是否正在运行,在启动线程时设置为true,当需要停止线程(比如断开连接或者程序结束时)设置为false,线程函数内部会根据这个变量来判断是否继续循环接收数据。

2. connectToServer函数

在成功连接到服务器后,不仅将m_connected标记设置为true,还会创建并启动接收数据线程。通过_beginthreadex函数创建线程,传入ReceiveDataThread函数作为线程执行的入口点,并将当前TCPClient对象指针this作为参数传递进去,以便在线程函数中能够访问对象的成员变量和函数。如果线程创建失败,会关闭套接字并返回false,表示连接失败;若线程创建成功,则将m_receiveThreadRunning设置为true,表示接收数据线程开始运行。

3. disconnect函数

除了关闭套接字并将m_connected标记设置为false外,还会将m_receiveThreadRunning设置为false,通知接收数据线程停止运行。这样线程函数在下次循环判断时就会退出循环,结束线程的执行。

4. reconnect`函数

在重连成功后,除了进行之前的一些连接相关的设置外,还需要重新创建并启动接收数据线程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/495600.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024-12-24 NO1. XR Interaction ToolKit 环境配置

文章目录 1 软件配置2 安装 XRToolKit3 配置 OpenXR4 安装示例场景5 运行测试 1 软件配置 Unity 版本&#xff1a;Unity6000.0.26 ​ 2 安装 XRToolKit 创建新项目&#xff08;URP 3D&#xff09;&#xff0c;点击进入 Asset Store。 进入“Unity Registry”页签&#xff0…

C语言基础——指针(4)

一&#xff0e; 字符指针变量 字符指针变量的使用和整型指针变量的使用方法相似&#xff0c;以下是其基本使用方法的例子&#xff1a; &#xff08;1&#xff09;字符指针变量还有一种使用方法&#xff1a; const char* p "abcd" 需…

week 11 - BCNF

1. More on functional dependencies (功能依赖的更多内容) Lossless decomposition (无损分解) 研究如何在分解表的过程中不丢失信息&#xff0c;也就是说&#xff0c;通过分解后的表可以无损地重建原始表。 2. BCNF (Boyce-Codd Normal Form, BCNF范式) &#xff08;1&…

嵌入式学习-QT-Day06

嵌入式学习-QT-Day06 六、多窗口编程 1、QMessageBox 消息对话框 2、QWidget类 3、parent参数 4、堆栈窗口&#xff08;QStackedWidget&#xff09; 5、新建自定义窗口类 6、对象传值 6.1 父对象 → 子对象 6.2 子对象 → 父对象 7、事件机制 8、QMainWindow主窗口类 8.1 QMenu…

《战神:诸神黄昏》游戏运行时提示找不到gamede.dll文件怎么办?gamede.dll丢失的修复指南

在沉浸于《战神&#xff1a;诸神黄昏》的壮阔世界时&#xff0c;突然弹出的“找不到gamede.dll文件”错误提示可能会让玩家措手不及。作为一名经验丰富的软件开发从业者&#xff0c;我深知这类问题对游戏体验的影响。今天&#xff0c;我将为大家详细解析gamede.dll文件丢失的原…

1.系统学习-线性回归

系统学习-线性回归 前言线性回归介绍误差函数梯度下降梯度下降示例 回归问题常见的评价函数1. MAE, mean absolutely error2. MSE, mean squared error3. R square &#xff08;决定系数或R方&#xff09; 机器学习建模流程模型正则化拓展阅读作业 链接: 2.系统学习-逻辑回归 …

基于微信小程序的校园访客登记系统

基于微信小程序的校园访客登记系统 功能列表 用户端功能 注册与登录 &#xff1a;支持用户通过手机号短信验证码注册和登录。个人资料管理 &#xff1a;允许用户编辑和更新个人信息及其密码。站内信消息通知&#xff1a;通知公告。来访预约&#xff1a;提交来访预约支持车牌…

H3C MPLS跨域optionB

实验拓扑 实验需求 如图,VPN1 和 VPN2 分别通过运营商 MPLS VPN 连接各自分支机构按照图示配置 IP 地址,VPN1 和 VPN2 连接同一个 PE 设备的私网 IP 网段存在地址复用,使用多 VRF 技术来防止 IP 冲突AS 100 和 AS 200 内部的公共网络中各自运行 OSPF 使 AS 内各设备的 Loo…

【项目管理】根据业务流程进行函数结构设计和模块化设计

在开发一个复杂的系统时&#xff0c;根据业务流程进行函数结构设计和模块化设计是一个非常重要的步骤。通过这种方式&#xff0c;能够将复杂的业务逻辑拆分成多个功能模块和函数&#xff0c;使代码更清晰、易维护、易扩展。我们在写代码的时候需要基于对于业务的理解来编程&…

VMware虚拟机中CentOS系统/dev/mapper/centos-home分区扩容指南

要将VMware上新扩展的磁盘添加到CentOS虚拟机,并将其扩容到/dev/mapper/centos-home下,你可以按照以下步骤操作: 一、在VMware中扩展虚拟机磁盘 关闭CentOS虚拟机:确保在扩展磁盘之前,CentOS虚拟机已经关闭。 编辑虚拟机设置:在VMware中,右键点击CentOS虚拟机,选择“设…

GPUStack v0.4.1 单节点与多节点安装与部署指南 Docker PowerShell

Introduce GPUStack 是一个开源的 GPU 集群管理器&#xff0c;专为运行 AI 模型而设计。它以其广泛的硬件兼容性而闻名&#xff0c;支持多种品牌的 GPU&#xff0c;并能在 Apple MacBook、Windows PC 和 Linux 服务器上运行。 GPUStack支持各种AI模型&#xff0c;包括大型语言…

【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

Hiヽ(゜▽゜ )&#xff0d;欢迎来到蓝染Aizen的CSDN博客~ &#x1f525; 博客主页&#xff1a; 【✨蓝染 の Blog&#x1f618;】 &#x1f496;感谢大家点赞&#x1f44d; 收藏⭐ 评论✍ 文章目录 行为型模式1、模板方法模式&#xff08;1&#xff09;概述&#xff08;2&…

【解决报错】AttributeError: ‘NoneType‘ object has no attribute ‘group‘

学习爬虫时&#xff0c;遇到如下报错&#xff1a; 报错原因&#xff1a; 正则表达式的 search 或 finditer 方法没有找到任何匹配项&#xff0c;可能是换行符处理不当等。 解决方法如下&#xff1a; 在正则表达式末尾加上re.S即可&#xff0c;re.S是一个编译标志&#xff0c…

一款5k star的 Redis 客户端!!简洁高效!

作为一名热爱编程的程序员&#xff0c;对于高效的工具总是格外追求。在日常的开发中&#xff0c;Redis 作为一款优秀的内存数据库&#xff0c;是我们不可或缺的利器之一。了不起之前也推荐过一些出色的 Redis 客户端&#xff0c;它们在提升我们的开发效率和便利性方面发挥了巨大…

关于科研中使用linux服务器的集锦

文章目录 常用的linux命令下载COCO2017数据集 常用的linux命令 一个文件移动到另一个目录下的命令是&#xff1a;mv -v ./old_name ./new_name 如果目标文件夹中已经有同名文件或文件夹&#xff0c;mv 会覆盖它们&#xff08;除非使用了 -i 选项来提示确认&#xff09;。 使用…

Vue开发环境搭建上篇:安装NVM和NPM(cpnm、pnpm)

文章目录 引言I 安装NVM1.1 Windows系统安装NVM,实现Node.js多版本管理1.2 配置下载镜像1.3 NVM常用操作命令II NPM永久使用淘宝源安装 cnpm安装pnpm【推荐】see also: vscode常用插件引言 淘宝镜像:http://npm.taobao.org 和 http://registry.npm.taobao.org 已在 2022.06.3…

x86_64 Ubuntu 编译安装英伟达GPU版本的OpenCV

手把手带你在Linux上安装带GPU加速的opencv库&#xff08;C版本&#xff09;_opencv linux-CSDN博客 cmake \-D CMAKE_BUILD_TYPERELEASE \-D OPENCV_GENERATE_PKGCONFIGON \-D CMAKE_INSTALL_PREFIX/usr/local \-D OPENCV_EXTRA_MODULES_PATH/home/hwj/opencv/opencv_contrib…

计算机毕业设计Python+卷积神经网络租房推荐系统 租房大屏可视化 租房爬虫 hadoop spark 58同城租房爬虫 房源推荐系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

bypy上传配置

bypy upload 上传文件权限不足时 使用此方法,改变权限保存 : w ! sudo tee % 好使但是没有进度条 CMAKE 的配置方法

三维动画的常用“视觉特效”有哪些?

在当今的视觉盛宴中&#xff0c;三维动画技术宛如一位神奇的魔法师&#xff0c;为视觉特效&#xff08;VFX&#xff09;领域施下了变革的咒语。从大荧幕上的震撼电影&#xff0c;到让人沉浸其中的视频游戏&#xff0c;再到夺人眼球的广告以及精细的模拟场景&#xff0c;三维动画…