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

news2024/12/12 13:41:30

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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

风控大讲堂|游戏黑产情报挖掘与治理

您的产品有没有遇到过被薅羊毛了&#xff1f;网络游戏行业的繁荣&#xff0c;催生了一批围绕游戏而生的职业玩家&#xff0c;他们利用多开、修改器等手段&#xff0c;疯狂薅游戏资源&#xff0c;破坏游戏经济平衡&#xff0c;给游戏公司带来了难以估量的巨大损失。那么针对此类…

最近邻搜索 - 经典树型结构 M-Tree

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 最近邻搜索的目标是从 N N N 个对象中&#xff0c;快速找到距离查询点最近的对象。根据需求的不同&#xff0c;该任务又分…

Jmeter进阶篇(30)深入探索 JMeter 监听器

前言 在性能测试领域里,Apache JMeter 是一款经典而强大的工具,而其中的监听器(Listeners)组件更是发挥着不可或缺的关键作用。 监听器就像敏锐的观察者,默默记录测试执行过程中的各种数据,作为系统性能分析的数据依据。 本文将带你全方位走进 JMeter 监听器的奇妙世界,…

uni-app 个人课程表页面

uni-app 个人课程表页面 插件参考地址 大部分代码都是参考了上述代码&#xff0c;只对代码做出了优化 1. 页面模板 在 schedule.vue 文件中&#xff0c;编写页面结构&#xff1a; <template><view><u-navbar title"个人中心"><view class&q…

ElementEye,网页分析器

介绍 我们经常使用Python写爬虫&#xff0c;爬到网页数据之后&#xff0c;就需要用beautifulSoup进行解析。因为写爬虫并不是我的主营工作&#xff0c;大多数只是用来分析一下想要的数据而已&#xff0c;所以经常会忘记beautifulSoup的用法。 同时&#xff0c;我们总是分析页面…

【Compose multiplatform教程】01 创建你的多平台项目 <官网搬运>

这是 “创建带有共享逻辑和用户界面的 Compose 多平台应用” 教程的第一部分。 第一步&#xff1a;创建你的多平台项目 第二步&#xff1a;探究可组合代码 第三步&#xff1a;修改项目 第四步&#xff1a;创建你自己的应用程序 在这里&#xff0c;你将学习如何使用 Kotlin 多平…

使用OpenTK展示3D点云图像(C#)

最近在研究3D显示&#xff0c;找到一款在winform上展示3D点云的控件&#xff0c;并且实现了点线面的展示&#xff0c;及光照渲染纹理贴图等功能&#xff0c;如下面几张图所展示。 一些基础知识可以在LearnOpenTK - OpenTK 这个网站上学习到。 我这边使用的是openTK3.3.3版本&a…

李宏毅机器学习-批次 (batch)和动量(momentum)

一.batch&#xff08;批次&#xff09; 在计算微分时&#xff0c;不是对所有的数据算出来的Loss值做微分&#xff0c;而是将所有的数据分成一个一个的batch。一个batch是一个B&#xff0c;在更新参数时&#xff0c;拿B的资料计算Loss&#xff0c;计算gradient&#xff0c;再更新…

洗鞋小程序(源码+文档+部署+讲解)

本文将深入解析“洗鞋小程序”的项目&#xff0c;探究其架构、功能以及技术栈&#xff0c;并分享获取完整源码的途径。 系统概述 为洗鞋提供服务&#xff0c;包含小程序和管理端。 本项目名称为洗鞋小程序&#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个结点的完…

交换排序(Swap Sort)详解

交换排序Swap Sort详解 冒泡排序冒泡算法代码实现冒泡分析 快速排序快排算法代码实现快排分析 交换类排序主要是通过两两比较待排元素的关键字&#xff0c;若发现与排序要求相逆&#xff0c;则交换之。在这类排序方法中最常见的是起泡排序&#xff08;冒泡排序&#xff09;和快…

MySQL追梦旅途之性能优化

1、索引优化 索引可以显著加速查询操作&#xff0c;但过多或不适当的索引也会带来负面影响&#xff08;如增加写入开销&#xff09;。因此&#xff0c;选择合适的索引至关重要。 创建索引&#xff1a; 为经常用于WHERE子句、JOIN条件和ORDER BY排序的列创建索引。 CREATE I…

小程序IOS安全区域优化:safe-area-inset-bottom

ios下边有一个小黑线&#xff0c;位于底部的元素会被黑线阻挡 safe-area-inset-bottom 一 用法及作用&#xff1a; IOS全面屏底部有小黑线&#xff0c;位于底部的元素会被黑线阻挡&#xff0c;可以使用以下样式&#xff1a; .model{padding-bottom: constant(safe-area-ins…

矩阵的乘(包括乘方)和除

矩阵的乘分为两种&#xff1a; 一种是高等代数中对矩阵的乘的定义&#xff1a;可以去这里看看包含矩阵的乘。总的来说&#xff0c;若矩阵 A s ∗ n A_{s*n} As∗n​列数和矩阵 B n ∗ t B_{n*t} Bn∗t​的行数相等&#xff0c;则 A A A和 B B B可相乘&#xff0c;得到一个矩阵 …

解决阿里云轻量级服务器 Ubuntu 24.04.1 LTS 没网也 ping 不通 8.8.8.8 以及 route -n 没有输出任何转发信息

事情发生在两天前&#xff0c;位于公网的阿里云轻量级服务器&#xff08;Ubuntu 24.04.1 LTS&#xff09;忽然没网。主要是上次上服务器进行配置已经是一个多月前&#xff0c;最近也没有做什么事情&#xff0c;就忽然没网了&#xff0c;让人纳闷。更主要的是&#xff0c;上次备…

Cesium中实现仿ArcGIS三维的动态图层加载方式

Cesium 加载 ArcGIS 动态图层的方式 如果你在 Cesium 中加载过 ArcGIS 的动态图层&#xff0c;你会发现&#xff0c;Cesium 对于动态图层仍然采用类似切片图层的逻辑进行加载。也就是每个固定的瓦片 export 一张图片。 这样会造成一些问题&#xff1a; 请求量大&#xff0c;…

Tablesaw封装Plot.ly实现数据可视化

上文介绍tablesaw的数据处理功能&#xff0c;本文向你展示其数据可视化功能&#xff0c;并通过几个常用图表示例进行说明。 Plot.ly包装 可视化是数据分析的重要组成部分&#xff0c;无论你只是“查看”新数据集还是验证机器学习算法的结果。Tablesaw是一个开源、高性能的Java…

智慧商城项目2(vue核心技术与实战)

页面访问拦截了解 router/index.js import Vue from vue import VueRouter from vue-router import Login from /views/login import Layout from /views/layout import Search from /views/search import SearchList from /views/search/list import Prodetail from /views/…

第一个C++程序--(蓝桥杯备考版)

第一个C程序 基础程序 #include <iostream>//头⽂件 using namespace std;//使⽤std的名字空间 int main()//main函数 {cout << "hello world!" << endl; //输出&#xff1a;在屏幕打印"hello world!" return 0;}main函数 main 函数是…

华为云域名网站,域名切换到Cloudflare CDN出现访问报错:DNS 重定向次过多

网站域名切换到Cloudflare出现访问报错&#xff1a;重定向次过多&#xff0c;应该如何处理&#xff1f; 最近我自己已经遇到很多次这个情况了&#xff0c;将网站域名DNS切换到Cloudflare之后&#xff0c;网站会打不开&#xff0c;出现重定向次数过多报错。 网站域名切换到Clo…