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  1000

class 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个线程,分别进行接收链接  接收数据 发送数据 发送心跳包
        // 创建线程,传入当前对象指针作为参数,线程启动函数为 SendHeartbeat
    HANDLE 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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ansible自动化运维(一)简介及清单,模块

相关文章ansible自动化运维&#xff08;二&#xff09;playbook模式详解-CSDN博客ansible自动化运维&#xff08;三&#xff09;jinja2模板&&roles角色管理-CSDN博客ansible自动化运维&#xff08;四&#xff09;运维实战-CSDN博客 ansible自动化运维工具 1.什么是自…

MATLAB四种逻辑运算

MATLAB中的四种逻辑运算包括逻辑与用&或 a n d 表示 ( 全为 1 时才为 1 &#xff0c;否则为 0 ) and表示(全为1时才为1&#xff0c;否则为0) and表示(全为1时才为1&#xff0c;否则为0)&#xff0c;逻辑或用|或 o r 表示 ( 有 1 就为 1 &#xff0c;都为 0 才为 0 ) or表示…

基于Spring Boot + Vue的摄影师分享交流社区的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有实…

鸿蒙NEXT开发案例:世界时间表

【引言】 本案例将展示如何使用鸿蒙NEXT框架开发一个简单的世界时钟应用程序。该应用程序能够展示多个城市的当前时间&#xff0c;并支持搜索功能&#xff0c;方便用户快速查找所需城市的时间信息。在本文中&#xff0c;我们将详细介绍应用程序的实现思路&#xff0c;包括如何…

Windows如何安装Php 7.4

一、进入官网&#xff0c;选择其他版本 https://windows.php.net/download/ 二、配置环境变量 将解压后的php 路径在系统环境变量中配置一下 cmd 后输入 php-v

yosys内部数据结构

一、参考链接 1.Yosys内部结构doxygen文件 yosys-master: RTLIL Namespace Reference 2.yosys内部结构介绍 https://yosyshq.readthedocs.io/projects/yosys/en/docs-preview-cellhelp/yosys_internals/formats/rtlil_rep.html 二、概览 图 1 网表核心数据结构 如图 1所示…

Java性能调优 - 多线程性能调优

锁优化 Synchronized 在JDK1.6中引入了分级锁机制来优化Synchronized。当一个线程获取锁时 首先对象锁将成为一个偏向锁&#xff0c;这样做是为了优化同一线程重复获取锁&#xff0c;导致的用户态与内核态的切换问题&#xff1b;其次如果有多个线程竞争锁资源&#xff0c;锁…

window的conda环境下espeak not installed on your system问题解决

1 问题描述 windows的conda环境下运行VITS2模型预处理&#xff0c;报出如下错误&#xff1a; Traceback (most recent call last):File "D:\ml\vits2\filelists.py", line 63, in <module>text_norm tokenizer(text_norm, Vocab, cleaners, languagehps.dat…

wine的使用方法

wine版本 所有分支&#xff0c;新的主要版本&#xff1a; wine-x.0 All branches, release candidates:各分支、候选版本&#xff1a; wine-x.0-rcn Stable branch updates: 稳定分支更新&#xff1a; wine-x.0.z Development branch updates: wine-x.y wine *.exe “更改目…

深度学习笔记26_糖尿病预测模型优化探索

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、我的环境 1.语言环境&#xff1a;Python 3.9 2.编译器&#xff1a;Pycharm 3.深度学习环境&#xff1a;TensorFlow 2.10.0 二、GPU设置…

深度学习的unfold操作

unfold&#xff08;展开&#xff09;是深度学习框架中常见的数据操作。与我们熟悉的卷积类似&#xff0c;unfold也是使用一个特定大小的窗口和步长自左至右、自上至下滑动&#xff0c;不同的是&#xff0c;卷积是滑动后与核求乘积&#xff08;所以取名为卷积&#xff09;&#…

Redis篇-2--原理篇1--I/O多路复用机制(5种I/O模型,I/O多路复用)

I/O多路复用机制&#xff1a; Redis 是通过I/O多路复用机制来管理大量客户端连接。这使得redis可以实现通过单线程来处理多个客户端连接的请求&#xff0c;避免了为每个客户端创建独立的线程&#xff0c;从而减少了上下文切换的开销&#xff0c;提高了系统的并发性和性能。 理解…

计算机毕设-基于springboot的某学院兼职平台的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…

Promise详解-1:初识Promise

最近在回顾ES6的知识&#xff0c;想整理下跟Promise相关的内容。我准备整一个Promise解读的系列&#xff0c;看看能深入到什么程度吧。由浅入深&#xff0c;先认识下Promise。 痛苦的回忆&#xff1a;回调地狱 假如现在让你维护一个“古老”的项目&#xff0c;缺少脚手架的加…

【蓝桥杯备战】Day 1

1.基础题目 LCR 018.验证回文串 给定一个字符串 s &#xff0c;验证 s 是否是 回文串 &#xff0c;只考虑字母和数字字符&#xff0c;可以忽略字母的大小写。 本题中&#xff0c;将空字符串定义为有效的 回文串 。 示例 1: 输入: s "A man, a plan, a canal: Panama…

让文案生成更具灵活性/chatGPT新功能canvas画布编辑

​ ​ OpenAI最近在2024年12月发布了canvas画布编辑功能&#xff0c;这是一项用途广泛的创新工具&#xff0c;专为需要高效创作文案的用户设计。 无论是职场人士、学生还是创作者&#xff0c;这项功能都能帮助快速生成、优化和编辑文案&#xff0c;提升效率的同时提高内容质量…

使用秘钥登录服务器

在我们测试或生产环境中&#xff0c;为了服务器安全性&#xff0c;有时可能需要以 SSH 密钥的方式登录服务器&#xff0c;接下来&#xff0c;将演示如何通过 SSH 私钥的方式来远程服务器。 一、远程服务器生成密钥对 1、首先在目标远程服务器下生成 SSH 密钥对 ssh-keygen然…

linux-16 关于shell(十五)date,clock,hwclock,man,时间管理,命令帮助

想显示一下当前系统上的时间该怎么显示&#xff1f;有一个命令叫做date&#xff0c;来看date命令&#xff0c;如下图&#xff0c; 第一个星期几对吧&#xff1f;然后是月日小时分钟秒&#xff0c;最后一个是年对吧&#xff1f;CST指的是它的时间格式&#xff0c;我这个可以先姑…

番外篇 | Hyper-YOLO:超图计算与YOLO架构相结合成为目标检测新的SOTA !

前言:Hello大家好,我是小哥谈。Hyper-YOLO,该方法融合了超图计算以捕捉视觉特征之间复杂的高阶关联。传统的YOLO模型虽然功能强大,但其颈部设计存在局限性,限制了跨层特征的融合以及高阶特征关系的利用。Hyper-YOLO在骨干和颈部的联合增强下,成为一个突破性的架构。在COC…

在 Ubuntu 中 make 是否是系统自带的?怎么样查看Linux系统中是否有make?

make 命令 并不是所有 Ubuntu 系统都默认安装的&#xff0c;但它通常是开发工具链的一部分&#xff0c;许多开发者会在安装系统后配置它。make 是一个非常重要的构建工具&#xff0c;用于自动化编译和构建过程&#xff0c;特别是在编译软件或内核时。 make 的来源 make 是一个…