client网络模块的开发和client与server端的部分联动调试

客户端网络模块的开发

我们需要先了解socket通信的流程

socket通信

server端的流程

client端的流程

对于closesocket()函数来说

closesocket()是用来关闭套接字的,将套接字的描述符从内存清除,并不是删除了那个套接字,只是切断了联系,所以我们如果重复调用,不closesocket()就会报错

创建网络模块类

我们依然采用的是单例模式

学会套用server端网络模块类的代码

添加一个ClientSocket类

对于这里面代码的修改

我们修改初始化代码

	bool InitSocket(const std::string& strIPAddress) {if (m_sock != INVALID_SOCKET) CloseSocket();m_sock = socket(PF_INET, SOCK_STREAM, 0);//TODO,校验if (m_sock == -1) return false;sockaddr_in serv_adr; //服务器地址memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(strIPAddress.c_str());serv_adr.sin_port = htons(9527);if (serv_adr.sin_addr.s_addr == INADDR_NONE) {AfxMessageBox("指定的ip地址,不存在");return false;}int ret = connect(m_sock, (sockaddr*)&serv_adr, sizeof(serv_adr));if (ret == -1) {AfxMessageBox("连接失败!!!重新连接");TRACE("连接失败,%d %s\r\n", WSAGetLastError(), GetErrInfo(WSAGetLastError()).c_str());return false;}return true;}

然后我们删除了accept类

然后我们客户端连接失败我们需要打印出连接失败的原因

WSAGetLastError()

使用 WSAGetLastError() 函数 来获得上一次的错误代码

返回值指出了该线程进行的上一次 Windows Sockets API 函数调用时的错误代码

WSAGetLastError()函数返回值表格,在下面文章里面

“WSAGetLastError()使用”讲解

然后我们需要一个可以将错误码格式化的函数

这个函数不用深究,记住这个模板以后直接用

std::string GetErrInfo(int wsaErrCode)
{std::string ret;LPVOID lpMsgBuf = NULL; //这个函数需要自己开辟缓冲区FormatMessage( //系统函数,把错误码格式化的函数FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,NULL,wsaErrCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR)&lpMsgBuf, 0, NULL);ret = (char*)lpMsgBuf;LocalFree(lpMsgBuf); //Free()掉return ret; //把这个缓冲区地址返回出去
}

然后咱们需要编辑client界面

对于这个我们不用添加类,直接双击那个连接测试,就会自动生成一个类在Dlg文件中,因为我们一开始创建这个项目时候就给client图形化界面了

void CRemoteClientDlg::OnBnClickedBtnTest()
{ClientSocket *pClient = ClientSocket::getInstance();bool ret = pClient->InitSocket("127.0.0.1");//后续加返回值的处理if (!ret) {AfxMessageBox("网络初始化失败!!!");return;}CPacket pack(1981,NULL,0);ret = pClient->Send(pack);TRACE("Send ret %d\r\n",ret);int cmd  = pClient->DealCommand();TRACE("ack:%d\r\n",cmd);pClient->CloseSocket();
}

server端和client端联动调试

启动客户端时候会报一个错

添加上那个报错信息

我们需要加上处理包的while循环

            CserverSocket* pserver = CserverSocket::getInstance();int count = 0;if (pserver->InitSocket() == false) {MessageBox(NULL, _T("网络初始化异常未能成功初始化,请检查网络状"), _T("网络初始化异常未能成功初始化,请检查网络状态"), MB_OK | MB_ICONERROR);exit(0);}while (CserverSocket::getInstance() != NULL) { // 相当于while(true)if (pserver->AcceptClient() == false) {if (count >= 3) {MessageBox(NULL, _T("多次无法正常接入程序,结束程序"), _T("接入用户失败!"), MB_OK | MB_ICONERROR);exit(0);}MessageBox(NULL, _T("无法正常接入用户,自动重试"), _T("接入用户失败!"), MB_OK | MB_ICONERROR);count++;}TRACE("AcceptClient true");int ret = pserver->DealCommand(); //获取命令TRACE("DealCommand ret:%d", ret);if (ret > 0) {ret = ExcuteCommand(ret);if (ret != 0) {TRACE("执行命令失败%d ret = %d\r\n", pserver->GetPacket().sCmd, ret);}pserver->CloseClient(); //短连接}}

ExcuteCommand()

int ExcuteCommand(int nCmd)
{int ret;switch (nCmd) {case 1://查看磁盘分区ret = MakeDriveInfo();break;case 2: //查看指定目录下的文件ret = MakeDirectoryInfo();break;case 3: //打开文件ret = RunFile();break;case 4: //下载文件ret = DownloadFile();break;case 5://鼠标操作ret = MouseEvent();break;case 6://发送屏幕内容 ==>发送屏幕的截图ret = SendScreen();break;case 7://锁机上锁(网吧可以用上)ret = LockMC();break;case 8:ret = UnlockMC();//解锁break;case 1981:ret = TestConnect();break;}return ret;
}

case 1981:是我们用来测试包的

int TestConnect()
{CPacket pack(1981, NULL, 0);CserverSocket::getInstance()->Send(pack);return 0;}
//CRemoteClientDlg::OnBnClickedBtnTest()函数	CPacket pack(1981,NULL,0);ret = pClient->Send(pack);TRACE("Send ret %d\r\n",ret);int cmd  = pClient->DealCommand(); //这也仅仅是为了测试TRACE("ack:%d\r\n",cmd);pClient->CloseSocket();

设置双项目调试启动

然后将两个项目的操作地方设置为启动,也可以设置为不调试启动

然后我们使用TRACE来追踪调试信息

我们还需要注意一点 ,就是server端初始化socket(初始化自己的socket等别人连接,基本上是不用改变的),可以等到析构时候在closesocket掉,但是client端不一样,client端可能需要连接不同的server端,所以需要不断的Init,也就是需要不断的将套接字和server端的IP连接

所以server端的m_sock初始化可以放在构造函数里面,client端的m_sock初始化需要放在Init函数里面,同时需要在里面closesocket

你会发现就算终止了,但是这个socket并没有close

证据

再次运行一遍,然后单步

你会发现程序进入了closesocket()函数,代表m_sock并不是INVALID_SOCKET

在遥远的2002年,有程序员碰到了同样的问题

然后就是我们选择持久连接还是非持久连接

client是我们在操控,向服务器发命令很少(间隔几秒),每次都是一个包,所以client端对server端应该采用非持久的连接,也就是

在每次包发完都进行pClient->CloseSocket();关闭socket连接

但是我们client端会向server端请求下载文件,远程桌面之类的命令,我们肯定不止要接收一个包,所以server端对client端要采用长连接

这里面我们需要注意野指针引起的内存泄漏问题

野指针引起的内存泄漏

内存泄漏是指我们在堆中申请(new/malloc)了一块内存,但是没有去手动的释放(delete/free)内存,导致指针已经消失,而指针指向的东西还在,已经不能控制这块内存,

例子

void remodel(std::string &str)
{//创建了一个局部指针变量,函数调用结束后,指针变量消失,但堆中内存仍然被占用,没有被释放,导致内存泄漏std::string *ps = new std::string(str); //内存泄漏了
}

如果发生了内存泄露又没有及时发现,随着程序运行时间的增加,程序越来越大,直到消耗完系统的所有内存,然后系统崩溃

在我们那个DealCommand()函数里面

我们申请了缓冲区去recv由那个套接字收到的数据

但是我们一开始设计时候是为了长连接作准备的,因为我们考虑的是双方都能收到很多包

因为client对server是短连接,所以server端的deal函数只用处理一个包,可以随着过程释放new出来的空间

server端的deal函数

	int DealCommand() { //无限循环if (m_client == -1) return -1;//char buffer[1024] = "";char* buffer = new char[4096]; //if (buffer == NULL) {TRACE("内存不足");return -2;}memset(buffer, 0, 4096);size_t index = 0;while (true) {size_t len = recv(m_client, buffer+index, 4096-index, 0);if (len <= 0) {delete[]buffer;return -1;}TRACE("recv %d\r\n", len);index += len; //可能收到2000个字节的包len = index;m_packet = CPacket ((BYTE*)buffer, len);if (len > 0) {memmove(buffer, buffer + len, 4096-len);//从buffer + len复制4096-len个字节到bufferindex -= len; //可能只用1000个delete[]buffer;return m_packet.sCmd;}}delete[]buffer;return -1;}

client端的deal函数,我们需要处理server端发来的很多包,但是我们又要防止内存泄漏,我们也不知道server端一次性给client端发了多少包,就不知道在这个函数哪个地方释放掉这个内存,但是我们知道的是client端的socket释放时候,我们那个包肯定处理完了,所以我们搞一个成员变量,让其在析构时候自动delete掉,我们想到了vecter,随对象的释放而析构

private: std::vector<char> m_buffer; //也是用的new,内存不需要管理,可以直接取地址用ClientSocket() {if (InitSockEnv() == FALSE) {MessageBox(NULL, _T("无法初始化套接字环境,请检查网络设置"), _T("初始化错误!!!"), MB_OK | MB_ICONERROR);exit(0);}m_buffer.resize(4096); //设置大小}
	int DealCommand() { //无限循环if (m_sock == -1) return -1;//char buffer[1024] = "";char* buffer = m_buffer.data(); //memset(buffer, 0, 4096);size_t index = 0;while (true) {size_t len = recv(m_sock, buffer + index, 4096 - index, 0);if (len <= 0) {return -1;}index += len; //可能收到2000个字节的包len = index;m_packet = CPacket((BYTE*)buffer, len); if (len > 0) {memmove(buffer, buffer + len, 4096 - len);//从buffer + len复制4096-len个字节到bufferindex -= len; //可能只用1000个return m_packet.sCmd;}}return -1;}

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

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

相关文章

安卓主板_MTK联发科主板定制开发|PCBA定制开发

MTK联发科安卓主板&#xff0c;采用MT6762八核平台方案&#xff0c;支持谷歌Android 11.0系统&#xff0c;MT6762采用ARM八核A53内核芯片、主频高达2.0GHz&#xff0c;GPU采用ARM PowerVR GE8329650MHZ&#xff0c;支持主流19201080分辨率&#xff0c;支持硬解H.264&#xff0c…

Win10安装ChatTTS-2024-cuda10.1

0x00 前言 ChatTTS是专门为对话场景设计的文本转语音模型&#xff0c;例如大语言助手对话任务。它支持英文和中文两种语言。最大的模型使用了10万小时以上的中英文数据进行训练。目前在huggingface中的开源版本为4万小时训练且未SFT的版本。 0x01 准备环境 版本操作系统Win1…

鸿蒙内核源码分析(忍者ninja篇) | 都忍者了能不快吗

ninja | 忍者 ninja是一个叫 Evan Martin的谷歌工程师开源的一个自定义的构建系统,最早是用于 chrome的构建,Martin给它取名 ninja(忍者)的原因是因为它strikes quickly(快速出击).这是忍者的特点,可惜Martin不了解中国文化,不然叫小李飞刀更合适些.究竟有多块呢? 用Martin自…

本地Docker部署Navidrome音乐服务器与远程访问听歌详细教程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

StarRocks 存算分离数据回收原理

前言 StarRocks存算分离表中&#xff0c;垃圾回收是为了删除那些无用的历史版本数据&#xff0c;从而节约存储空间。考虑到对象存储按照存储容量收费&#xff0c;因此&#xff0c;节约存储空间对于降本增效尤为必要。 在系统运行过程中&#xff0c;有以下几种情况可能会需要删…

《Cloud Native Data Center Networking》(云原生数据中心网络设计)读书笔记 -- 07数据中心的边缘

本章将帮助你回答以下问题 可以用哪些方式将 Clos 拓扑连接到外部网终?边缘部署路由协议的最佳实践是什么?企业应如何处理混合云中的连接? 连接模型 为什么要连接到外部世界? 数据中心连接到外部世界的原因很多。如果你要对外提供某种服务(例如搜索服务广告推荐系统或内…

35岁程序员转行大模型:如何把握行业机遇与个人发展

对于一位35岁的程序员想要转行到大模型领域&#xff0c;这是一个很好的时机&#xff0c;因为人工智能和大模型技术正在快速发展&#xff0c;并且有着广泛的应用前景。以下是一些具体的步骤和建议&#xff0c;可以帮助您顺利地完成这一转变&#xff1a; 基础知识学习 数学基础&a…

科技在日常生活中的革新

在科技日新月异的今天&#xff0c;‌我们的生活正经历着前所未有的变革。‌从智能家居到可穿戴设备&#xff0c;‌科技已经渗透到我们生活的每一个角落&#xff0c;‌深刻地影响着我们的生活方式和社会经济的发展。‌ 智能家居系统的出现&#xff0c;‌无疑是科技改变生活的典…

[鹏城杯 2022]简单的php

题目源代码 <?phpshow_source(__FILE__); $code $_GET[code]; if(strlen($code) > 80 or preg_match(/[A-Za-z0-9]|\|"||\ |,|\.|-|\||\/|\\|<|>|\$|\?|\^|&|\|/is,$code)){die( Hello); }else if(; preg_replace(/[^\s\(\)]?\((?R)?\)/, , $code…

深度剖析C++string(上篇)

目录 前言 1.C string类 2.string类中的常见构造 3.string类对象的容量操作 4.. string类对象的访问及遍历操作 5. auto和范围for(补充&#xff09; auto关键字 范围for 结束语 前言 C语言我们学习了字符串和字符串的相关函数&#xff0c;在C语言中&#xff0c;字符串是…

10 Java数据结构:包装类、数组(Array工具类)、ArrayList

文章目录 前言一、包装类1、Integer&#xff08;1&#xff09;基本用法&#xff08;2&#xff09;JDK5前的包装类用法&#xff08;了解即可&#xff0c;能更好帮助我们理解下面的自动装箱和自动拆箱机制&#xff09;&#xff08;3&#xff09;自动装箱与自动拆箱机制 --- 导致&…

【学习笔记】Day 21

一、进度概述 1、机器学习常识19-22&#xff0c;以及相关代码复现 二、详情 19、矩阵分解 矩阵分解是一个纯数学问题&#xff0c;但当给矩阵赋予现实意义后&#xff0c;矩阵分解就成为了使用数学应对机器学习问题的一类典型而巧妙的方法。 在线性回归分析中&#xff…

esp32c3 luaos

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、介绍二、相关介绍2.1helloworld——2.2任务框架2.3消息传递 与消息订阅2.4uart2.5二进制数据/c结构体的打包与解析2.6 zbuffer库2.8 uart 485 数据解析2.9 …

缓存实现方式

缓存是一个常见的话题&#xff0c;因为它对于提高应用程序性能至关重要。缓存是一种存储数据的临时地方&#xff0c;以便快速访问数据&#xff0c;减少对原始数据源&#xff08;如数据库或文件系统&#xff09;的访问次数&#xff0c;从而提高应用程序的响应速度和吞吐量。 Jav…

如何应对突发技术故障和危机:开发团队的应急策略

开发团队如何应对突发的技术故障和危机&#xff1f; 在数字化时代&#xff0c;软件服务的稳定性对于企业至关重要。然而&#xff0c;即使是大型平台&#xff0c;如网易云音乐&#xff0c;也可能遇到突发的技术故障。网页端出现502 Bad Gateway 报错&#xff0c;且App也无法正常…

如何在VMware ESXI中创建Linux虚拟机并实现异地SSH远程访问

目录 ⛳️推荐 前言 1. 在VMware ESXI中创建Ubuntu虚拟机 2. Ubuntu开启SSH远程服务 3. 安装Cpolar工具 4. 使用SSH客户端远程访问Ubuntu 5. 固定TCP公网地址 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不…

重塑“我店”平台:绿色积分引领的数字消费新纪元

在数字化转型的洪流中&#xff0c;“我店”平台凭借其创新的绿色积分体系异军突起&#xff0c;成为市场中的璀璨新星。本文将深度剖析“我店”的运营模式、市场效应及其如何通过绿色积分机制开创消费新潮流。 一、崛起之路与市场震撼力 自2021年盛夏在上海启航以来&#xff0c…

ffmpeg读取时长、读取视频格式

ffmpeg读取时长、读取视频格式 ffmpeg读取时长ffmpeg读取视频格式 ffmpeg读取时长 命令命令介绍具体用法ffmpeg -i查看视频时长ffmpeg -i 视频链接 or 视频路径 2>&1 | grep Duration ffmpeg读取视频格式 命令命令介绍具体用法ffmpeg -i查看视频时长ffmpeg -i 视频链接…

Java CompletableFuture:你真的了解它吗?

文章目录 1 什么是 CompletableFuture&#xff1f;2 如何正确使用 CompletableFuture 对象&#xff1f;3 如何结合回调函数处理异步任务结果&#xff1f;4 如何组合并处理多个 CompletableFuture&#xff1f; 1 什么是 CompletableFuture&#xff1f; CompletableFuture 是 Ja…

<数据集>商品条形码识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3748张 标注数量(xml文件个数)&#xff1a;3748 标注数量(txt文件个数)&#xff1a;3748 标注类别数&#xff1a;1 标注类别名称&#xff1a;[Barcode] 序号类别名称图片数框数1Barcode37484086 使用标注工具&am…