[C++]服务器与客户端建立连接与检测断开的demo

news/2024/2/21 3:45:54

该程序在IP127.0.0.1以及端口5000环境下测试

有一段时间没有在Windows下用C++进行网络编程了,这段日子都在做QT的网络编程和OpenCV的图像识别。
今天重新写个Windows下C++的,基于TCP的双端连接建立与断开检测的demo,巩固下自己Windows下的网络编程知识点。

在下面的代码共有四个类,一个内部结构体,以下是他们的介绍。

WebException类可以忽略,是一个异常类,用于反馈意外情况。
WebBase类是服务端和客户端的基类,用于初始化共同的基本数据。
Server类是服务端类,用于接收客户端连接。
Client类是客户端类,用于连接服务端。

Server类的内部结构体ClientSocket,用以保存已经连接到服务端的客户端Socket。

由于只是做个简单的相互检测连接与断开的demo,所以整个程序就全在这一个cpp中了。

该demo的主要功能是:

  • 服务器能被连接十次,服务器可以检测客户端断开与否。
  • 客户端连接上服务器后,客户端可以检测到与服务器失联与否。

服务端的整体思路是:

主线程负责检测客户端的连接请求,服务端同意连接获取到客户Socket后,以clock()获取到的值当作客户id,以id为key,将客户Socket保存到cliSockets这个map中;
保存好客户Socket后,开启一条线程用以检测连接是否丢失,如果丢失了(暂不考虑重连),则回收客户的Socket与相应线程资源;
此外,为了了解服务端关闭后,客户端的失联处理,服务端被设置成只能连接十次;
十次后关闭服务端,服务端这次的主动关闭将回收所有存活的客户端Socket和相应的线程资源。
安全回收后,服务端正式关闭。

客户端的整体思路是:

主线程负责检测连接上服务端,每秒尝试一次连接,连接成功获取到服务端Socket后开启一条线程用以检测连接是否丢失,如果丢失了(暂不考虑重连),则回收服务端的Socket与相应线程资源;
如果客户端发现在主动断开与服务端的连接前就已经无法联系服务端,那么将回收服务端Socket和相应的线程资源。
安全回收后,客户端正式关闭。

#pragma comment(lib,"Ws2_32.lib")
#include <iostream>
#include <Windows.h>
#include <thread>
#include <conio.h>
#include <map>
using namespace std;
//自定义的异常类,用来反馈网络异常
class WebException {
public:WebException(int error):error(error), errorMsg("未知异常") {}WebException(int error,string errorMsg) :error(error), errorMsg(errorMsg) {}virtual void what()const {switch (error) {default:cout << errorMsg << endl;}}
private:int error;string errorMsg;
};
class WebBase {
protected://设定监听端口WebBase():port(5000){}virtual ~WebBase() {closesocket(_socket);//关闭套接字cout << "套接字关闭完成..." << endl;WSACleanup();//清理资源cout << "DLL资源已清理..." << endl;system("pause");}
public:virtual void init() {wVersionRequested = MAKEWORD(2, 2);//计算版本号if (0 != WSAStartup(wVersionRequested, &ws)) throw WebException(0, "初始化DLL失败");cout << "初始化DLL完成..." << endl;_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);memset(&addr, 0, sizeof(addr));//初始化地址结构为0addr.sin_family = AF_INET;//赋值addr.sin_port = htons(port);//赋值端口信息addr.sin_addr.S_un.S_addr = INADDR_ANY;//表示32位IPv4地址,以网络字节序保存}WORD wVersionRequested;//版本号WSADATA ws;//记录WinSock DLL信息SOCKET _socket;//创建套接字const u_short port;//端口号struct sockaddr_in addr;//地址信息
};
class Server:public WebBase {
public:~Server() {cout << "当前存活的客户端连接数:" << cliSockets.size() << endl;//非强迫地要求所有线程停止活动map<clock_t, pair<bool, thread>>::iterator itr = cliThreads.begin();while (itr != cliThreads.end()) {cliThreads[itr->first].first = false;++itr;}//要求完之后,等待所有线程结束,此处不用资源释放,资源释放在线程中完成itr = cliThreads.begin();while (itr != cliThreads.end()) {cliThreads[itr->first].second.join();cliThreads.erase(itr->first);itr = cliThreads.begin();}cout << "服务器已关闭所有客户端连接" << endl;}void init() {cout << "正在部署服务器..." << endl;WebBase::init();if (SOCKET_ERROR == bind(_socket, (const sockaddr*)(&addr), sizeof(addr)))throw WebException(0, "绑定失败");cout << "套接字绑定成功..." << endl;if(SOCKET_ERROR  == listen(_socket, 5))//设置服务端网络监听,队列为5throw WebException(0, " 监听设置失败");cout << "正在监听..." << endl;char buff[128];gethostname(buff, sizeof(buff)); cout << "服务器IP:" << inet_ntoa(*(in_addr*)*(gethostbyname(buff)->h_addr_list)) << endl;cout << "服务器等待连接请求中..." << endl;int live_num = 10;while (live_num--) {ClientSocket cliSocket;int len = sizeof(cliSocket.addr);cliSocket._socket = accept(_socket, (struct sockaddr*)&cliSocket.addr, &len);//接受连接请求Sleep(1);clock_t id = clock();cliSockets[id] = cliSocket;cliThreads[id] = pair<bool,thread>(true,thread(&Server::testConRun, this, id));cout << "客户(" << id << ")建立起与服务器的连接" << endl;}shutdown(_socket, 3);cout << "已完成十次连接,服务器自动关闭" << endl;}
private:void testConRun(clock_t id) {//每两秒1次连接检测int num = 0;while (cliThreads[id].first) {++num;if (num >= 10) {num = 0;if (SOCKET_ERROR == send(cliSockets[id]._socket, "t", sizeof("t"), 0)) {cout << "客户(" << id << ")断开了与服务器的连接" << endl;closesocket(cliSockets[id]._socket);cout << "关闭了客户(" << id << ")的Socket" << endl;cliSockets.erase(id);//这里让线程与thread类分离,使得erase掉thread类不影响线程//return后,分离了的线程会自己自动销毁cliThreads[id].second.detach();cliThreads.erase(id);return;}}Sleep(200);}if (!cliThreads[id].first) {cout << "服务器断开了与客户(" << id << ")的连接" << endl;closesocket(cliSockets[id]._socket);cout << "关闭了客户(" << id << ")的Socket" << endl;cliSockets.erase(id);}}struct ClientSocket {struct sockaddr_in addr;//地址信息SOCKET _socket;//创建套接字};map<clock_t, Server::ClientSocket> cliSockets;map<clock_t, pair<bool, thread>> cliThreads;
};
class Client:public WebBase {
public:Client():testCon(nullptr),isTestConRun(true){}~Client() {isTestConRun = false;testCon->join();closesocket(_socket);cout << "关闭了客户端的Socket" << endl;testCon.release();}void init() {cout << "正在部署客户端..." << endl;WebBase::init();addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//表示32位IPv4地址,以网络字节序保存while (SOCKET_ERROR == connect(_socket, (const sockaddr*)&addr, sizeof(addr))) {cout << "连接服务器失败" << endl;Sleep(1000);}cout << "连接服务器成功,输入任意键以断开链接" << endl;testCon.reset(new thread(&Client::testConRun,this));_getch();}
private:void testConRun() {//每两秒1次连接检测int num = 0;while (isTestConRun) {++num;if (num >= 10) {num = 0;if (SOCKET_ERROR == send(_socket, "t", sizeof("t"), 0)) {cout << "服务器断开了与客户端的链接" << endl;return;}}Sleep(200);}}unique_ptr<thread> testCon;volatile bool isTestConRun;
};
//启动服务端
void startServer() {Server server;try {server.init();}catch (WebException& e) {e.what();}catch (...) {cout << "未知其他异常";}
}
//启动客户端
void startClient() {Client client;try {client.init();}catch (WebException& e) {e.what();}catch (...) {cout << "未知其他异常";}
}
int main()
{while (true) {system("cls");cout << "请选择端的类型:" << endl;cout << "1.服务端" << endl;cout << "2.客户端" << endl;cout << "0.退出" << endl;cout << "请输入:" << endl;switch (_getch()) {case '1':system("cls"); startServer();break;case '2':system("cls"); startClient();break;case '0':return 0;}}
}

http://www.ppmy.cn/news/25809.html

相关文章

举个栗子~Tableau 技巧(251):统一多个工作表的坐标轴范围

在工作汇报场景&#xff0c;有一个很常见、很多数据粉反馈的需求&#xff1a;同一看板上的两个图表&#xff0c;因为轴范围不一致&#xff08;如下图&#xff09;&#xff0c;很难直观比较。有什么办法可以统一它们的坐标轴范围呢&#xff1f; 类似需求&#xff0c;不论两个还是…

Hudi系列19:Hudi写入模式

一. Changelog 模式 如果希望 Hoodie 保留消息的所有变更(I/-U/U/D), 之后接上 Flink 引擎的有状态计算实现全链路近实时数仓(增量计算)&#xff0c; Hoodie 的 MOR 表通过行存 原生支持 保留消息的所有变更(format 层面的集成)&#xff0c; 通过流读MOR 表可以消费到所有的变…

【Unity资源下载】POLYGON Dungeon Realms - Low Poly 3D Art by Synty

$149.99 Synty Studios 一个史诗般的低多边形资产包&#xff0c;包括人物、道具、武器和环境资产&#xff0c;用于创建一个以奇幻为主题的多边形风格游戏。 模块化的部分很容易在各种组合中拼凑起来。 包包含超过1,118个详细预制件。 主要特点 ◼ ◼ 完全模块化的地下城!包…

【Ctfshow_Web】信息收集和爆破

0x00 信息收集 web1 直接查看源码 web2 查看不了源码&#xff0c;抓包即可看到&#xff08;JS拦截了F12&#xff09; web3 抓包&#xff0c;发送repeater&#xff0c;在响应包中有Flag字段 web4 题目提示后台地址在robots&#xff0c;访问/robots.txt看到Disallow: /fl…

Springboot 我随手封装了一个万能的导出excel工具,传什么都能导出

前言 如题&#xff0c;这个小玩意&#xff0c;就是不限制你查的是哪张表&#xff0c;用的是什么类。 我直接一把梭&#xff0c;嘎嘎给你一顿导出。 我知道&#xff0c;这是很多人都想过的&#xff0c; 至少我就收到很多人问过我这个类似的问题。 我也跟他们说了&#xff0c;但…

备战蓝桥杯【二维前缀和】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

Linux驱动学习笔记

驱动学习笔记 1、字符设备驱动 Linux 驱动有两种运行方式 第一种就是将驱动编译进 Linux 内核中&#xff0c;这样当 Linux 内核启 动的时候就会自动运行驱动程序。 第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko)&#xff0c;在 Linux 内核启动以后使用“insmod”命…

科技云报道:2023,云计算的风向变了

科技云报道原创。 2022&#xff0c;是云计算的“分水岭”之年。 与前两年的火热相比&#xff0c;2022年云计算行业实属不太好过&#xff1a;阿里云一季度营收增速创出历史新低&#xff0c;腾讯云的市场份额也被后来者华为云反超&#xff0c;沦为第三。 在此情形下&#xff0c…

【C++】C/C++内存管理

文章目录1. C/C内存分布2. C语言当中的动态内存管理3. C 内存管理方式3.1 new/delete操作内置类型3.2 new和delete操作自定义类型4. operator new 和operator delete 函数5. new和delete的实现原理5.1 内置类型5.2 自定义类型6. 定位new表达式(placement-new)7. 常见面试题7.1 …

07 react+echart+大屏

reactechart大屏大屏ECharts 图表实际步骤React Typescript搭建大屏项目&#xff0c;并实现屏幕适配flexible rem实现适配1. 安装插件对echarts进行的React封装&#xff0c;可以用于React项目中&#xff0c;支持JS、TS如何使用完整例子官网参考大屏 ECharts 图表 ECharts 图…

14. QT_OPenGL中引入顶点着色器和片段着色器

1. 说明: 着色器是OPenGL中非常重要的一部分&#xff0c;在有了模型后&#xff0c;如果未给模型添加着色器&#xff0c;那么渲染效果会折扣很多。着色器中使用到的语言是GLSL(OPenGL Shader Language)&#xff0c;可以通过这篇文章GLSL基本语法进行了解。 效果展示&#xff1a…

新建一个完整的react项目和完善初始项目

一&#xff1a;新建一个完整的react项目 1.环境准备 目前我的环境是 node&#xff1a;16.17.1 npm&#xff1a; 8.15.0 查看环境&#xff1a;1)&#xff1a;打开命令提示符工具&#xff0c;利用node -v和npm -v 查看一下自己的环境&#xff0c;如果觉得重新卸载、安装node比较…

搭建Hexo博客-第1章-Git和GitHub以及Coding的简单用法

搭建Hexo博客-第1章-Git和GitHub以及Coding的简单用法 搭建Hexo博客-第1章-Git和GitHub以及Coding的简单用法 Coding GitHub Hexo Markdown 搭建博客 大家好&#xff0c;这是我第一次写博客。使用 GitHub Hexo 创建最基本的博客很容易&#xff0c;网上有很多现成的教程。…

try catch finally执行顺序

try catch finally&#xff0c;try里有return&#xff0c;finally还执行么&#xff1f;答案&#xff1a; 执行&#xff0c;并且返回return时&#xff0c;finally的执行早于try。try-catch-finally的执行顺序无return当try中的t()没有抛出异常public static void main(String[] …

20基于主从博弈的智能小区代理商定价策略及电动汽车充电管理MATLAB程序

参考文档&#xff1a;《基于主从博弈的智能小区代理商定价策略及电动汽车充电管理》基本复现仿真平台&#xff1a;MATLABCPLEX/gurobi平台优势&#xff1a;代码具有一定的深度和创新性&#xff0c;注释清晰&#xff0c;非烂大街的代码&#xff0c;非常精品&#xff01;主要内容…

多源 复制

使复制从属服务器能够同时从多个主服务器接收事务至少需要两个主服务器和一个从属服务器设备从属服务器为每个主服务器创建一个 复制通道从属服务器必须使用基于表的资料档案库多源复制与基于文件的资料档案库不兼容不尝试检测或解决冲突如果需要此功能&#xff0c;则由应用程序…

MySQL 服务正在启动.MySQL 服务无法启动.服务没有报告任何错误。请键入 NET HELPMSG 3534 以获得更多的帮助。总结较全 (已解决)

输入以下命令启动mysql&#xff1a; net start mysql出现以下错误提示&#xff1a; MySQL 服务正在启动 .MySQL 服务无法启动。服务没有报告任何错误。请键入 NET HELPMSG 3534 以获得更多的帮助。 出现这个问题的话&#xff0c;一般有几个情况&#xff1a; 一、MySQL安装文…

C++类和对象补充

目录 前言&#xff1a; 1. 构造函数->初始化列表 1.1 初始化列表出现原因 1.2 初始化列表写法 2. explicit关键字 2.1 explict的出现 2.2 explict的写法 3. static成员 4. 友元 4.1 友元函数 4.2 友元类 5. 内部类和匿名对象 5.1 内部类 5.2 匿名对象 前言&a…

Python3 数据结构

列表 Python中列表是可变的&#xff0c;这是它区别于字符串和元组的最重要的特点&#xff0c;一句话概括即&#xff1a;列表可以修改&#xff0c;而字符串和元组不能。 以下是 Python 中列表的方法&#xff1a; 方法 描述 list.append(x) 把一个元素添加到列表的结尾&#xf…

MAC 系统安装多版本 JDK 并任意切换

1、背景 在进行 Java 开发的过程中&#xff0c;我们可能需要使用不同版本的 JDK。例如&#xff1a;一些旧的 Java 应用程序只能在旧版本的 JDK 上运行&#xff0c;而一些新的 Java 应用程序需要较新的 JDK 才能运行。 在 MAC 系统上&#xff0c;如何安装多个版本的 JDK 并配置…
最新文章