windows TCP Server demo

embedded/2024/12/26 18:31:44/

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")class TcpServer {
public:TcpServer();~TcpServer();// 启动服务器,监听指定端口bool start(int port);// 停止服务器void stop();// 发送数据给指定客户端int sendData(SOCKET clientSocket, const char* data, int dataLength);// 处理服务器业务逻辑,通常在循环中调用void handle();private:SOCKET listenSocket;std::vector<SOCKET> clientSockets;std::map<SOCKET, std::time_t> clientLastHeartbeatTime;int heartbeatInterval;  // 心跳包间隔时间(秒)// 设置套接字为非阻塞模式bool setSocketNonBlocking(SOCKET socket);// 接受新的客户端连接void acceptNewClients();// 接收客户端数据void receiveClientData();// 发送心跳包给客户端,并检测客户端响应void sendHeartbeatsAndCheck();// 移除已断开连接的客户端void removeDisconnectedClients();
};#endif

TcpServer.cpp

#include "TcpServer.h"// 构造函数,初始化相关成员变量
TcpServer::TcpServer() : listenSocket(INVALID_SOCKET), heartbeatInterval(5) {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;}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()
{std::vector<SOCKET> socketsToRemove;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;}socketsToRemove.push_back(clientSocket);}else if (bytesReceived == 0){// 客户端关闭连接socketsToRemove.push_back(clientSocket);}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 = "recv ok:";int sentBytes = sendData(clientSocket, heartbeatData.c_str(), heartbeatData.length());}}// 移除已断开连接的客户端(此处调用下面定义的 removeDisconnectedClients 函数,无参数传递)removeDisconnectedClients();
}// 发送心跳包给客户端,并检测客户端响应
void TcpServer::sendHeartbeatsAndCheck()
{const char* heartbeatData = "HEARTBEAT";  // 简单的心跳包内容,可自定义int dataLength = strlen(heartbeatData);std::vector<SOCKET> socketsToRemove;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;}char responseBuffer[1024];int receivedBytes = ::recv(clientSocket, responseBuffer, sizeof(responseBuffer), 0);if (receivedBytes == SOCKET_ERROR){if (WSAGetLastError() == WSAEWOULDBLOCK){// 暂时无数据可读,等待下次检测continue;}socketsToRemove.push_back(clientSocket);continue;}else if (receivedBytes == 0){// 客户端关闭连接socketsToRemove.push_back(clientSocket);continue;}responseBuffer[receivedBytes] = '\0';std::string responseData(responseBuffer);// 在这里可以根据客户端对心跳包的响应进行进一步判断和处理,比如验证响应内容是否正确等if (responseData != "HEARTBEAT_RESPONSE"){  // 假设正确响应内容为 HEARTBEAT_RESPONSEsocketsToRemove.push_back(clientSocket);continue;}lastHeartbeatTime = currentTime;}// 移除已断开连接或异常的客户端(此处调用下面定义的 removeDisconnectedClients 函数,无参数传递)removeDisconnectedClients();
}// 移除已断开连接的客户端(更新函数定义,无参数)
void TcpServer::removeDisconnectedClients() 
{std::vector<SOCKET> socketsToRemove;for (size_t i = 0; i < clientSockets.size(); ++i){SOCKET clientSocket = clientSockets[i];// 检查是否已经判定为需要移除(比如在前面接收数据或心跳检测环节标记为要移除)if (clientLastHeartbeatTime.find(clientSocket) == clientLastHeartbeatTime.end()) {socketsToRemove.push_back(clientSocket);}}for (SOCKET socketToRemove : socketsToRemove){auto it = std::find(clientSockets.begin(), clientSockets.end(), socketToRemove);if (it != clientSockets.end()) {clientSockets.erase(it);clientLastHeartbeatTime.erase(socketToRemove);}}
}// 处理服务器业务逻辑,通常在循环中调用
void TcpServer::handle() 
{acceptNewClients();receiveClientData();sendHeartbeatsAndCheck();removeDisconnectedClients();
}

main.cpp

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

http://www.ppmy.cn/embedded/145403.html

相关文章

TimesFM(Time Series Foundation Model)安装(2)

TimesFM&#xff08;Time Series Foundation Model&#xff09;安装简介 readme TimesFM&#xff08;Time Series Foundation Model&#xff09;安装简介&#xff08;1&#xff09;-CSDN博客https://blog.csdn.net/chenchihwen/article/details/144359861?spm1001.2014.3001…

在 Ubuntu 20.04 上离线安装和配置 Redis

下面是从零开始配置 Redis 的完整步骤&#xff0c;包括从安装 Redis 到离线安装 Redis 包的步骤。本文将覆盖如何从下载 Redis 安装包、手动安装 Redis、创建所需目录、配置 Redis、以及如何配置 Redis 为系统服务&#xff0c;确保服务可以在启动时自动运行。 步骤 1: 准备环境…

RK3568(一)——SDK环境搭建

虚拟机安装 搭建文件互传方法 网络配置 虚拟机网络配置 开发板网络设置 临时设置ip地址&#xff08;重启失效&#xff09; ifconfig eth0 192.168.137.10 netmask 255.255.255.0 route add default gw 192.168.137.1设置动态ip vi etc/network/interfaces auto eth0 if…

基于单片机智能控制的饮水机控制系统

基于单片机智能控制的饮水机控制系统&#xff0c;以STC89C52单片机为核心&#xff0c;利用防水型DS18B20温度传感器对饮水机内的水温做出检测&#xff0c;其次利用水位传感器对饮水机内的水量做出检测&#xff0c;并显示在OLED液晶显示屏上。用户在使用饮水机时&#xff0c;通过…

大模型qiming面试内容整理-编码能力评估

编码能力评估是大模型相关岗位面试中非常关键的一环,面试官通常希望通过这个环节了解候选人对编程语言、算法与数据结构的掌握情况,以及其在实践中解决实际问题的能力。以下是编码能力评估的常见内容和类型,特别是针对机器学习、大模型和深度学习方向: 编程语言熟练度 ● P…

ios 开发配置蓝牙

如果使用了蓝牙功能, 又没有配置, 会出现以下错误: This app has crashed because it attempted to access privacy-sensitive data without a usage description. The apps Info.plist must contain an NSBluetoothAlwaysUsageDescription key with a string value explaini…

酷柚易汛进销存系统PHP+Uniapp

移动端订货通、商品管理、库存管理、订单管理、客户管理、供应商、财务管理、经营分析 版本更新V1.6.4 1、新增供应商分类不可添加重复类别2、新增客户分类不可添加重复类别3、新增商品分类不可添加重复类别4、新增支出类别不可添加重复类别5、新增收入类别不可添加重复类别6…

Linux下网卡实现NAT转发

目标 在嵌入式Linux设备下&#xff0c;使用单一的网卡&#xff08;前提支持STA&#xff0b;AP共存&#xff09;&#xff0c;使用NAT&#xff08;网络地址转换&#xff09;实现软路由&#xff0c;以自身为热点&#xff0c;将接收到的流量数据全部转发出去。 一&#xff0c;STA…