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`函数
在重连成功后,除了进行之前的一些连接相关的设置外,还需要重新创建并启动接收数据线程。
相关文章:
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下的线程相…...
Linux xargs 命令使用教程
简介 xargs 是一个功能强大的 Linux 命令,用于从标准输入构建和执行命令。它接受一个命令的输出,并将其作为参数提供给另一个命令。它在处理大量输入时特别有用,其含义可以解释为:extended arguments,使用 xargs 允许…...
什么是异步处理
什么是异步处理 if ( conditionA && conditionB )mqSendService.sendMessageAsync(MqTopicConstant.YOUR_TOPIC, ID,JSONObject.toJSONString(CommonMsg.builder().data(ID).msgType(TypeCode).build()));}sendMessageAsync 发送消息的过程不会阻塞当前的执…...

【解决问题】Java2DRenderer生成图片时中文乱码 Linux安装字体
一,问题 在使用Java2DRenderer框架将html生成图片时,html中的中文文本在图片上显示框框,即出现了中文乱码。在确认使用正确的字符编码utf-8之后,并且确认了修改成unicode也同样乱码的情况下,找到了真正的原因…...

WPF 依赖属性和附加属性
除了普通的 CLR 属性, WPF 还有一套自己的属性系统。这个系统中的属性称为依赖属性。 1. 依赖属性 为啥叫依赖属性?不叫阿猫阿狗属性? 通常我们定义一个普通 CLR 属性,其实就是获取和设置一个私有字段的值。假设声明了 100 个 …...
leetcode hot100 删除链表的第n个节点
19. 删除链表的倒数第 N 个结点 已解答 中等 相关标签 相关企业 提示 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点 # Definition for singly-linked list. # class ListNode(object): # def __init__(self, val0, nextNon…...
MyBatis-Plus分页拦截器,源码的重构(重构total总数的计算逻辑)
1.1创建ThreadLocal工具类(作为业务逻辑结果存放类) package org.springblade.sample.utils;public class QueryContext {private static final ThreadLocal<Long> totalInThreadLocal new ThreadLocal<>();public static void setTotalIn…...
记一MySQL连接速度慢的问题
某一个程序启动速度超级慢,查看日志得知是是在Init DruidDataSource ~ {dataSource-1} inited 这一段耗时最长,这一段是Druid 数据源初始化,进行连接的创建等,使用mysql命令行连接发现连接超级慢,可见是在创建连接的时…...
asp.net core webapi项目中 在生产环境中 进不去swagger
builder.WebHost.UseUrls 是 ASP.NET Core 中配置应用程序监听 URL 或端口的方法。通过使用这个方法,你可以指定应用程序应该在哪些 URL 上运行,以便接收 HTTP 请求。 1.在appsetting.json中 添加 "LaunchUrl": "http://*:327"2.在…...

逆向攻防世界CTF系列63-secret-string-400
逆向攻防世界CTF系列63-secret-string-400 丢入exeinfo,查得zip,解压得四个文件 点进Task,查看源码:Test your luck! Enter valid string and you will know flag 顺理成章地看js 定位check函数 调用了machine的loadcode 跟进…...

Datawhale AI 冬令营学习笔记-零编程基础制作井字棋小游戏
井字棋小游戏是通过豆包MarsCode实现的,没有改动任何的代码,全部是通过对话让AI进行优化和改进。 开始进入正题:进入豆包MarsCode在线IDE,直接点击上方蓝字,或复制链接打开: 豆包 MarsCode - 编程助手。 IDE界面&…...

分布式专题(10)之ShardingSphere分库分表实战指南
一、ShardingSphere产品介绍 Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。Apache ShardingSphere 设计哲学为 Database Plus,旨在…...

clickhouse解决suspiciously many的异常
1. 问题背景 clickhouse安装在虚拟机上,持续写入日志时,突然关机,然后重启,会出现clickhouse可以正常启动,但是查询sql语句,提示suspiciously many异常,如图所示 2. 问题修复 touch /data/cl…...
计算机的错误计算(一百九十)
摘要 用两个大模型计算cot(1.234). 其中,1.234是以弧度为单位的角度。结果保留10位有效数字。实验表明,两个的计算公式虽然不同,但是都是正确的。然而,数值计算则是有问题的---包括每一个中间运算与结果。 例1. 计算cot(1.234)…...

STM32-笔记12-实现SysTick模拟多线程流水灯
1、前言 正常STM32实现多线程,需要移植一个操作系统FreeRTOS。但是在这里不移植FreeRTOS怎么实现多线程呢?使用SysTick,那么怎么使用SysTick来模拟多线程呢?前面我们知道SysTick就是一个定时器,它不是在主函数的while循…...

牛客网刷题 ——C语言初阶——BC114 小乐乐排电梯
1.牛客网 :BC114 小乐乐排电梯 题目描述: 小乐乐学校教学楼的电梯前排了很多人,他的前面有n个人在等电梯。电梯每次可以乘坐12人,每次上下需要的时间为4分钟(上需要2分钟,下需要2分钟)。请帮助…...

web三、 window对象,延时器,定时器,时间戳,location对象(地址),本地存储-localStorage,数组去重new Set
一、window对象 window对象 是一个全局对象,也可以说是JavaScript中的 顶级对象 像document、alert()、console.log()这些都是window的属性,基本BOM的属性和方法都是window的 所有通过 var定义 在全局作用域中的 变量 、 函数 都会变成window对象的属…...

【EthIf-13】EthIfGeneral容器配置-01
1.EthIfGeneral类图结构 下面是EthIfGeneral配置参数的类图,比较重要的参数就是配置: 接收中断是否打开发送确认中断是否打开EthIf轮询周期 1.EthIfGeneral参数的含义...

‘pnpm’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
‘pnpm’ 不是内部或外部命令,也不是可运行的程序或批处理文件。 1.情况: npm -v 和 node -v的都正常就是 pnpm-v 无效 检查环境变量也没看出问题 2.分析 没有正确添加环境变量 3.解决 找到npm的全局安装目录 npm list -g --depth 0这里出现了npm的全局安装…...
ECMAScript 6-11 概述
1. ECMA 介绍 ECMA(European Computer Manufacturers Association)是欧洲计算机制造商协会,目标是评估、开发和认可电信和计算机标准。1994年后改名为Ecma国际。 2. ECMAScript 是什么 ECMAScript 是由Ecma国际通过ECMA-262标准化的脚本程…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...