当前位置: 首页 > news >正文

TcpServer 服务器优化之后,加了多线程,对心跳包进行优化

TcpServer 服务器优化之后,加了多线程,对心跳包进行优化

TcpServer.h

#ifndef TCPSERVER_H
#define TCPSERVER_H#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <vector>
#include <map>
#include <string>
#include <ctime>// 引入静态链接库
#pragma comment(lib, "ws2_32.lib")
#define HEARTBEATTIME  1000class TcpServer {
public:TcpServer();~TcpServer();// 启动服务器,监听指定端口bool start(int port);// 停止服务器void stop();// 发送数据给指定客户端int sendData(SOCKET clientSocket, const char* data, int dataLength);// 处理服务器业务逻辑,通常在循环中调用void handle();//链接static DWORD WINAPI ThreadAccept(LPVOID lpParam);//接收数据static DWORD WINAPI ThreadRecvData(LPVOID lpParam);//心跳包static DWORD WINAPI ThreadHeartBeat(LPVOID lpParam);
public:std::vector<SOCKET> socketsToRemove;BOOL  m_bExit;//程序是否关闭BOOL m_bHeartBeat;//是否启用心跳包int heartbeatInterval;  // 心跳包间隔时间(秒)
private:SOCKET listenSocket;std::vector<SOCKET> clientSockets;std::map<SOCKET, std::time_t> clientLastHeartbeatTime;// 设置套接字为非阻塞模式bool setSocketNonBlocking(SOCKET socket);// 接受新的客户端连接void acceptNewClients();// 接收客户端数据void receiveClientData();// 发送心跳包给客户端,并检测客户端响应void sendHeartbeatsAndCheck();// 移除已断开连接的客户端void removeDisconnectedClients(std::vector<SOCKET> &socketsToRemove);
};#endif

TcpServer.cpp

#include "TcpServer.h"// 构造函数,初始化相关成员变量
TcpServer::TcpServer() : listenSocket(INVALID_SOCKET),
heartbeatInterval(5), m_bExit(false), m_bHeartBeat(false)
{WSADATA wsaData;int result = WSAStartup(MAKEWORD(2, 2), &wsaData);if (result != 0) {std::cerr << "WSAStartup failed: " << result << std::endl;}
}// 析构函数,关闭套接字并清理WinSock环境
TcpServer::~TcpServer() 
{stop();WSACleanup();
}// 启动服务器,监听指定端口
bool TcpServer::start(int port)
{listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (listenSocket == INVALID_SOCKET){std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl;return false;}if (!setSocketNonBlocking(listenSocket)) {std::cerr << "Failed to set listen socket non-blocking" << std::endl;closesocket(listenSocket);return false;}sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = INADDR_ANY;serverAddr.sin_port = htons(port);int result = bind(listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));if (result == SOCKET_ERROR){std::cerr << "Bind failed: " << WSAGetLastError() << std::endl;closesocket(listenSocket);return false;}result = listen(listenSocket, SOMAXCONN);if (result == SOCKET_ERROR){std::cerr << "Listen failed: " << WSAGetLastError() << std::endl;closesocket(listenSocket);return false;}return true;
}// 停止服务器
void TcpServer::stop()
{if (listenSocket != INVALID_SOCKET) {closesocket(listenSocket);listenSocket = INVALID_SOCKET;}for (SOCKET clientSocket : clientSockets) {closesocket(clientSocket);}clientSockets.clear();clientLastHeartbeatTime.clear();
}// 设置套接字为非阻塞模式
bool TcpServer::setSocketNonBlocking(SOCKET socket) 
{u_long iMode = 1;int result = ioctlsocket(socket, FIONBIO, &iMode);if (result == SOCKET_ERROR){std::cerr << "ioctlsocket failed: " << WSAGetLastError() << std::endl;return false;}return true;
}// 发送数据给指定客户端
int TcpServer::sendData(SOCKET clientSocket, const char* data, int dataLength) 
{if (clientSocket == INVALID_SOCKET) {std::cerr << "Invalid client socket, cannot send data" << std::endl;return SOCKET_ERROR;}int totalBytesSent = 0;while (totalBytesSent < dataLength) {int bytesSent = ::send(clientSocket, data + totalBytesSent, dataLength - totalBytesSent, 0);if (bytesSent == SOCKET_ERROR){if (WSAGetLastError() == WSAEWOULDBLOCK) {// 暂时无法发送,等待下次尝试continue;}return SOCKET_ERROR;}totalBytesSent += bytesSent;}return totalBytesSent;
}// 接受新的客户端连接
void TcpServer::acceptNewClients()
{SOCKET newClientSocket = accept(listenSocket, NULL, NULL);if (newClientSocket == INVALID_SOCKET){if (WSAGetLastError() != WSAEWOULDBLOCK) {std::cerr << "Accept failed: " << WSAGetLastError() << std::endl;}return;}else{std::cout << "Accept success: " << newClientSocket << std::endl;}if (!setSocketNonBlocking(newClientSocket)) {std::cerr << "Failed to set client socket non-blocking" << std::endl;closesocket(newClientSocket);return;}clientSockets.push_back(newClientSocket);clientLastHeartbeatTime[newClientSocket] = std::time(nullptr);
}// 接收客户端数据
void TcpServer::receiveClientData()
{for (size_t i = 0; i < clientSockets.size(); ++i){SOCKET clientSocket = clientSockets[i];char buffer[1024];int bytesReceived = ::recv(clientSocket, buffer, sizeof(buffer), 0);if (bytesReceived == SOCKET_ERROR) {if (WSAGetLastError() == WSAEWOULDBLOCK) {// 暂时无数据可读,继续检查下一个客户端continue;}}else{buffer[bytesReceived] = '\0';std::string receivedData(buffer);// 在这里可以根据接收到的数据进行具体业务逻辑处理,比如解析命令等std::cout << "Received from client " << clientSocket << ": " << receivedData << std::endl;clientLastHeartbeatTime[clientSocket] = std::time(nullptr);std::string heartbeatData = buffer;heartbeatData+="    recvok:";int sentBytes = sendData(clientSocket, heartbeatData.c_str(), heartbeatData.length());}}
}// 发送心跳包给客户端,并检测客户端响应
void TcpServer::sendHeartbeatsAndCheck()
{const char* heartbeatData = "HEARTBEAT";  // 简单的心跳包内容,可自定义int dataLength = strlen(heartbeatData);for (auto& clientPair : clientLastHeartbeatTime){SOCKET clientSocket = clientPair.first;std::time_t& lastHeartbeatTime = clientPair.second;std::time_t currentTime = std::time(nullptr);if (currentTime - lastHeartbeatTime > heartbeatInterval) {// 超过心跳间隔时间没收到心跳响应,认为客户端连接异常socketsToRemove.push_back(clientSocket);continue;}int sentBytes = sendData(clientSocket, heartbeatData, dataLength);if (sentBytes == SOCKET_ERROR){// 发送心跳包失败,认为客户端连接可能有问题socketsToRemove.push_back(clientSocket);continue;}}
}// 移除已断开连接的客户端(更新函数定义,无参数)
void TcpServer::removeDisconnectedClients(std::vector<SOCKET>&socketsToRemove)
{for (SOCKET socketToRemove : socketsToRemove){auto it = std::find(clientSockets.begin(), clientSockets.end(), socketToRemove);if (it != clientSockets.end()) {std::cout << "Remove :"<< * it << std::endl;clientSockets.erase(it);clientLastHeartbeatTime.erase(socketToRemove);}}
}//接收链接线程
DWORD WINAPI TcpServer::ThreadAccept(LPVOID lpParam)
{TcpServer* t_Server = static_cast<TcpServer*>(lpParam);while (t_Server->m_bExit==false){t_Server->acceptNewClients();}return 0;
}
//接收数据
DWORD WINAPI TcpServer::ThreadRecvData(LPVOID lpParam)
{TcpServer* t_Server = static_cast<TcpServer*>(lpParam);while (t_Server->m_bExit == false){t_Server->receiveClientData();}return 0;
}//心跳包
DWORD WINAPI TcpServer::ThreadHeartBeat(LPVOID lpParam)
{TcpServer* t_Server = static_cast<TcpServer*>(lpParam);while (t_Server->m_bExit == false){Sleep(HEARTBEATTIME);t_Server->sendHeartbeatsAndCheck();if (t_Server->heartbeatInterval > 0){t_Server->removeDisconnectedClients(t_Server->socketsToRemove);}}return 0;
}
// 处理服务器业务逻辑,通常在循环中调用
void TcpServer::handle() 
{//创建4个线程,分别进行接收链接  接收数据 发送数据 发送心跳包// 创建线程,传入当前对象指针作为参数,线程启动函数为 SendHeartbeatHANDLE acceptThreadHandle = CreateThread(NULL, 0, ThreadAccept, this, 0, NULL);if (acceptThreadHandle == NULL){std::cerr << "Create accept thread failed: " << GetLastError() << std::endl;}else{std::cerr << "Create accept thread success: " << acceptThreadHandle << std::endl;}HANDLE recvDatatThreadHandle = CreateThread(NULL, 0, ThreadRecvData, this, 0, NULL);if (acceptThreadHandle == NULL){std::cerr << "Create recvData thread failed: " << GetLastError() << std::endl;}else{std::cerr << "Create recvData thread success: " << recvDatatThreadHandle << std::endl;}if (m_bHeartBeat == true){HANDLE heartBeatThreadHandle = CreateThread(NULL, 0, ThreadHeartBeat, this, 0, NULL);if (acceptThreadHandle == NULL){std::cerr << "Create heartBeat thread failed: " << GetLastError() << std::endl;}else{std::cerr << "Create heartBeat thread success: " << heartBeatThreadHandle << std::endl;}}}

main.cpp

#include "TcpServer.h"int main()
{TcpServer server;server.heartbeatInterval = 30;server.m_bHeartBeat = true;if (server.start(8080)) {while (true) {server.handle();// 可以在这里添加适当的延时,避免过于频繁地循环处理,消耗过多CPU资源Sleep(100);break;}}else{std::cout << "server initiatefail" << std::endl;}Sleep(1000000);return 0;
}

在这里插入图片描述
在这里插入图片描述

相关文章:

TcpServer 服务器优化之后,加了多线程,对心跳包进行优化

TcpServer 服务器优化之后&#xff0c;加了多线程&#xff0c;对心跳包进行优化 TcpServer.h #ifndef TCPSERVER_H #define TCPSERVER_H#include <iostream> #include <winsock2.h> #include <ws2tcpip.h> #include <vector> #include <map> #…...

黑马程序员Java项目实战《苍穹外卖》Day12

苍穹外卖-day12 课程内容 工作台Apache POI导出运营数据Excel报表 功能实现&#xff1a;工作台、数据导出 工作台效果图&#xff1a; 数据导出效果图&#xff1a; 在数据统计页面点击数据导出&#xff1a;生成Excel报表 1. 工作台 1.1 需求分析和设计 1.1.1 产品原…...

经纬度解析到省市区【开源】

现在业务中有需要解析经纬度到省市区。 按理说可以直接使用高德&#xff0c;百度之类的。 但是老板太抠。于是去找开源项目。找了一圈&#xff0c;数据都太老了&#xff0c;而且有时候编码还不匹配。 所以诞生了这个项目&#xff0c;提供完整的一套省市区编码和定位反解析。…...

bug:uniapp运行到微信开发者工具 白屏 页面空白

1、没有报错信息 2、预览和真机调试都能正常显示&#xff0c;说明代码没错 3、微信开发者工具版本已经是win7能装的最高版本了&#xff0c;1.05版 链接 不打算回滚旧版本 4、解决&#xff1a;最后改调试基础库为2.25.4解决了&#xff0c;使用更高版本的都会报错&#xff0c;所…...

旧版本 MySQL 处理字符表情写入问题

报错信息 新增数据 java.sql.SQLException: Incorrect string value: \xF0\x9F\x91\x8D\xE5\x8F... for column解决方案 老项目&#xff0c;而且是旧版本&#xff0c;且表情不影响业务&#xff0c;直接简单粗暴的过滤掉即可&#xff0c;有还原的需求也可以 toUnicode 转为字…...

vue使用v-if和:class完成条件渲染

1.使用v-if 和v-else 完成主body和暂无数据两个<tbody>标签的条件渲染(注意与v-show效果的区别) 2.v-for完成列表渲染 3.:class完成分数标红的条件控制 删哪个就传哪个的id&#xff0c;基于这个id去过滤掉相同id的项&#xff0c;把剩下的项返回 <td><a click.p…...

Docker:WARNING: Published ports are discarded when using host network mode 解决方法

在Docker中&#xff0c;使用主机网络模式&#xff08;host network mode&#xff09;时&#xff0c;容器将共享主机的网络命名空间&#xff0c;这意味着容器将直接使用主机的网络接口和端口。因此&#xff0c;当你尝试通过Docker的发布端口功能&#xff08;publish a port&…...

音视频入门基础:MPEG2-TS专题(12)—— FFmpeg源码中,把各个transport packet组合成一个Section的实现

一、引言 从《音视频入门基础&#xff1a;MPEG2-TS专题&#xff08;9&#xff09;——FFmpeg源码中&#xff0c;解码TS Header的实现》可以知道&#xff1a;FFmpeg源码中使用handle_packet函数来处理一个transport packet&#xff08;TS包&#xff09;&#xff0c;该函数的前半…...

【数据结构】二叉树的性质和存储结构

性质 在二叉树的第i层上至多有2^{i-1}个结点,至少有1个结点 深度为k的二叉树至多有2^{k-1}个结点&#xff08;k≥1&#xff09;&#xff0c;至少有k个结点 对任何一棵二叉树T&#xff0c;如果其叶子数为n0&#xff0c;度为2的结点数为n2&#xff0c;则n0n21 具有n个结点的完…...

gbase8s之查看锁表的sql

#只能看当前锁表的sql&#xff0c;看不到历史的。 #使用方法&#xff1a;sh 脚本文件名 库名 表名 database$1 table$2 hexoncheck -pt $database:$table|grep -i partnum|awk {printf ("%x|",$3)} #echo $hex #echo ${hex%?} #ownonstat -k |grep -iE ${he…...

URI 未注册(设置 语言和框架 架构和 DTD)

一、问题描述&#xff1a;在springboot项目中的resources中新建mybatis-config.xml文件时&#xff0c;从mybatis文档中复制的代码报错&#xff1a;URI 未注册(设置 | 语言和框架 | 架构和 DTD) 二、解决&#xff1a;在Springboot项目的设置->架构和DTD中添加 红色的网址&…...

Ubuntu上使用system()函数运行不需要输入密码

使用system()运行一些终端命令的时候&#xff0c;需要sudo权限&#xff0c;也就是必须输入密码&#xff0c;那么在程序自启动的时候就无法成功启动。如果设置Ubuntu下所有操作都不需要密码&#xff0c;安全性太低&#xff0c;所以我们可以将需要用到的终端指令给予无需输入密码…...

【MySQL】数据库必备知识:全面整合表的约束与深度解析

前言&#xff1a;本节内容讲述表的约束的相关内容。 表的约束博主将会通过两篇文章进行讲解&#xff0c; 这是第一篇上半部分。 讲到了约束概念。 以及几种常见约束。下面友友们开始学习吧&#xff01; ps:友友们使用了mysql就可以放心观看喽&#xff01; 目录 表的约束概念 …...

Windows下Docker快速安装使用教程

在当今软件开发和部署的世界中&#xff0c;Docker 已经成为一个不可或缺的工具。这里不对Docker进行详细阐述&#xff0c;需要系统学习Docker的伙伴可寻求更专业详细的教程或书籍学习。本文主要讲解Windows系统下Docker安装及使用。 一、环境准备 1.1检查电脑是否开启虚拟化 …...

PTA DS 6-2 另类堆栈 (C补全函数)

6-2 另类堆栈 分数 15 全屏浏览 切换布局 作者 DS课程组 单位 浙江大学 在栈的顺序存储实现中&#xff0c;另有一种方法是将Top定义为栈顶的上一个位置。请编写程序实现这种定义下堆栈的入栈、出栈操作。如何判断堆栈为空或者满&#xff1f; 函数接口定义&#xff1a; …...

rk3568之mpp开发笔记mpp移植到开发板

前言&#xff1a; 大家好&#xff0c;今天给大家介绍的内容是rk平台的mpp编解码这块的内容&#xff0c;在rk目前看到有三套框架涉及到编解码内容&#xff1a; 1、rkmedia 2、rockit 3、mpp 这三种不同形式的编解码方式&#xff0c;后面再做详细的框架对比&#xff0c;今天我…...

Vue解决跨域问题

要解决 Vue 项目的跨域问题并通过 vue.config.js 配置代理&#xff0c;可以按照以下步骤修改 vue.config.js 文件。你提供的代码大部分已经正确&#xff0c;只需要做一些格式上的调整。以下是正确的 vue.config.js 配置&#xff1a; // vue.config.jsmodule.exports {devServ…...

Kubernetes Nginx-Ingress | 禁用HSTS/禁止重定向到https

目录 前言禁用HSTS禁止重定向到https关闭 HSTS 和设置 ssl-redirect 为 false 的区别 前言 客户请求经过ingress到服务后&#xff0c;默认加上了strict-transport-security&#xff0c;导致客户服务跨域请求失败&#xff0c;具体Response Headers信息如下&#xff1b; 分析 n…...

TortoiseGit的下载、安装和配置

一、TortoiseGit的简介 tortoiseGit是一个开放的git版本控制系统的源客户端&#xff0c;支持Winxp/vista/win7.该软件功能和git一样 不同的是&#xff1a;git是命令行操作模式&#xff0c;tortoiseGit界面化操作模式&#xff0c;不用记git相关命令就可以直接操作&#xff0c;读…...

如何绕过IP禁令

网站、游戏和应用程序可以屏蔽特定IP地址&#xff0c;从而阻止使用该IP地址的任何人访问其服务。这称为IP禁令。管理员可以出于多种原因&#xff08;例如发出过多请求或可疑活动&#xff09;屏蔽IP地址。但是&#xff0c;这些禁令会使收集数据或访问在线内容变得更加困难。 一…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

动态 Web 开发技术入门篇

一、HTTP 协议核心 1.1 HTTP 基础 协议全称 &#xff1a;HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09; 默认端口 &#xff1a;HTTP 使用 80 端口&#xff0c;HTTPS 使用 443 端口。 请求方法 &#xff1a; GET &#xff1a;用于获取资源&#xff0c;…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

Spring Security 认证流程——补充

一、认证流程概述 Spring Security 的认证流程基于 过滤器链&#xff08;Filter Chain&#xff09;&#xff0c;核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤&#xff1a; 用户提交登录请求拦…...

华硕电脑,全新的超频方式,无需进入BIOS

想要追求更佳性能释放 或探索更多可玩性的小伙伴&#xff0c; 可能会需要为你的电脑超频。 但我们常用的不论是BIOS里的超频&#xff0c; 还是Armoury Crate奥创智控中心超频&#xff0c; 每次调节都要重启&#xff0c;有点麻烦。 TurboV Core 全新的超频方案来了 4不规…...