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

news/2024/9/18 14:52:42/ 标签: 网络

客户端网络模块的开发

我们需要先了解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;}

添加IP地址和端口控件

这个控件在下面添加

然后分别右击两个控件,添加上变量m_server_address,m_nPort

然后我们需要在程序里面使用这个变量

UpdateData(默认是true)

对话框数据交换

如果使用 DDX 机制,则可设置对话框对象的成员变量的初始值(通常在 OnInitDialog 处理程序或对话框构造函数中)。 就在显示对话框之前,框架的 DDX 机制会将成员变量的值传输到对话框中的控件,当对话框本身为响应 DoModalCreate 而出现时,这些控件将会显示在对话框中。 OnInitDialog 中的 CDialog 的默认实现调用 UpdateData 类的 CWnd 成员函数以在对话框中初始化控件

当用户单击“确定”按钮时(或在你每次使用 TRUE 自变量调用 UpdateData 成员函数时),同一个机制都会将值从控件传输到成员变量。 对话框数据验证机制将验证为其指定了验证规则的所有数据项

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

说人话就是TRUE参数时候,会将控件里面的值赋值给成员变量

然后我们需要将端口调整为默认的9527

添加上如下代码:

然后改变入口点的代码

int CRemoteClientDlg::SendCommandPacket(int nCmd, bool bAutoClose,BYTE* pData, size_t nLength)
{UpdateData();ClientSocket* pClient = ClientSocket::getInstance();bool ret = pClient->InitSocket(m_server_address, atoi((LPCTSTR)m_nPort));//后续加返回值的处理``````
}

因为m_nPort是编辑框来的,所以默认是string,需要将其转化为int

bool InitSocket(int nIP,int nPort) {```serv_adr.sin_addr.s_addr = htonl(nIP); //bug,主机字节序转成网络字节序serv_adr.sin_port = htons(nPort);```
}

htonl是将主机字节序转为网络字节序


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

相关文章

20240828 每日AI必读资讯

8岁女孩玩转AI编程&#xff0c;45分钟打造聊天机器人&#xff0c;Karpathy都看呆了 - 新晋顶流AI代码编辑器——Cursor&#xff0c;已经进化到了“0手工代码”阶段。 - 提供了多个AI模型&#xff0c;包括GPT-4、GPT-4o和Claude 3.5 Sonnet等&#xff0c;可以通过跟大模型聊天…

微服务——远程调用

为什么需要远程调用&#xff1f; 在微服务架构中&#xff0c;每个服务都是独立部署和运行的&#xff0c;它们之间需要相互协作以完成复杂的业务逻辑。因此&#xff0c;远程调用成为微服务之间通信的主要方式。通过远程调用&#xff0c;一个服务可以请求另一个服务执行某些操作或…

【Python机器学习】NLP概述——词序和语法

词的顺序很重要&#xff0c;那些在词序列&#xff08;如句子&#xff09;中控制词序的规则被称为语言的语法&#xff08;也被称为文法&#xff09;。这是之前的词袋或词向量例子中所丢弃的信息。在大多数简短的短语甚至许多完整的句子中&#xff0c;上述词向量近似方法都可以奏…

设计模式 7 桥接模式

设计模式 7 创建型模式&#xff08;5&#xff09;&#xff1a;工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式结构型模式&#xff08;7&#xff09;&#xff1a;适配器模式、桥接模式、组合模式、装饰者模式、外观模式、享元模式、代理模式行为型模式&#xff0…

整合sentinel遇到的小问题

1.运行jar包 &#xff0c;端口为默认8080 正确命令 java -Dserver.port8090 -Dcsp.sentinel.dashboard.server127.0.0.1:8090 -Dproject.namesentinel-dashboard -jar sentinel-dashboard-1.8.6.jar -D这些指令要在 -jar前面 在宝塔部署时&#xff0c;直接复制到运行命令后…

vue事件监听

我们可以使用 v-on 指令 (简写为 ) 来监听 DOM 事件&#xff0c;并在事件触发时执行对应的 1.回车事件&#xff08;点击回车触发&#xff09; confirm 适用uni-app keyup.enter 适用vue3 运用场景&#xff1a;通常在文本框输入的时候使用 2.点击事件&#xff08;鼠标左键…

Cubase操作:如何修改每个音频块的名字 写歌习惯

如何修改每个音频块的名字 我对命名比较注重&#xff0c;之前用Cubase12&#xff0c;导入我手机中编辑过的Cubasis的工程时&#xff0c;发现中文部分有乱码…… 而且好像改名改得很费劲…… 后面通过多方咨询和探索思考&#xff0c;终于找到方法了&#xff01; 可以先把信息…

【C#】【EXCEL】BumblebeeComponentsAnalysisGH_Ex_Ana_CondTopCount

这段代码定义了一个名为 GH_Ex_Ana_CondTopCount 的 Grasshopper 组件。它的主要功能是为 Excel 中的一个范围添加条件格式&#xff0c;具体是根据数值的大小高亮显示前N个&#xff08;或后N个&#xff09;数值。以下是该组件的详细介绍&#xff1a; 功能概述&#xff1a; 组件…

灵办AI搜索引擎和文档总结工具

前言—— 在信息爆炸的时代&#xff0c;如何高效地获取和处理知识成为了每个人面临的挑战。随着人工智能技术的迅猛发展&#xff0c;本文将深入探讨这一创新工具的功能与优势&#xff0c;以及如何在日常生活和工作中充分利用它&#xff0c;开启智能化的信息获取新篇章。 点击…

Part3-DOM学习笔记-操作元素

5.操作多个元素 5.1 排他思想 前面所述均为操作一个元素&#xff0c;给一个元素注册事件&#xff0c;如果是一组元素&#xff0c;就需要用循环的方式给元素注册事件。 我们想要给当前的元素实现某种样式&#xff0c;而其他的元素没有这个样式&#xff0c;这就需要用到排他思…

P2709 小B的询问

*原题链接* 非常简单的莫队板子题&#xff0c;让我们求出区间[l,r]中每个数出现次数的平方和&#xff0c;设枚举到,原来答案是res&#xff0c;如果加上后&#xff0c;则原来的变为&#xff0c;即res相比原来加上&#xff0c;删除同理。知道如何维护一个数的添加和删除后&#…

WIFI 配网

配网:指的是外部向WiFi模块提供SSID和密码&#xff0c;以便Wi-Fi模块可以连接指定的热点 常见的配网方式有:-键配网smart config、SoftAP配网、蓝牙配网、屏幕配网。 1.0 一键配网 2.0 蓝牙配网 一键配网的模式对应的厂加模式 3.0 状态机WIFI模组物联网 4.0 创建枚举结构体 ty…

Vue3.0项目实战(二)——大事件管理系统登录注册功能实现

目录 1. 登录注册页面 [element-plus 表单 & 表单校验] 1.1 注册登录 静态结构 & 基本切换 2. 注册功能 2.1 实现注册校验 2.2 注册前的预校验 2.3 封装 api 实现注册功能 3. 登录功能 3.1 实现登录校验 3.2 登录前的预校验 & 登录成功 1. 登录注册页面 […

Go反射四讲---第三讲:如何使用反射操作函数,获取函数的相关信息?

反射-函数 这是我们反射四讲的第三讲&#xff0c;本次给大家讲解如何使用反射处理函数相关的操作。 在这一部分&#xff0c;向大家展示如何输出方法的信息并执行调用。 输出信息&#xff0c;包含方法名&#xff0c;方法参数&#xff0c;返回值。 最后&#xff0c;如何使用反…

【小趴菜前端实习日记4】

el-table数据更新视图不更新的问题、el-dialog居中展示、el-form表单验证之对象属性验证、vue2过滤器 一、el-table数据更新视图不更新的问题二、el-dialog居中展示三、el-form表单验证之对象属性验证四、vue2过滤器 一、el-table数据更新视图不更新的问题 手动触发元素更新&a…

Linux:Socket网络编程

目录 1. 理解源 IP 地址和目的 IP 地址 2&#xff1a;认识端口号 3&#xff1a;端口号范围划分 4&#xff1a;理解源端口号和目的端口号 5&#xff1a;理解Socket(套接字) 6&#xff1a;两个传输协议 &#xff08;TCP/UDP&#xff09; 6.1&#xff1a;User Datagram Prot…

打卡55天------图论(并查集)

图论这里我学的不是很好&#xff0c;作为一名JavaScript前端开发工程师&#xff0c;我能说我基本上在工作中都没用到过吗&#xff1f; 一、并查集理论基础 这个说句实话&#xff0c;我平常工作很少用到&#xff0c;上学的时候好像也没学过&#xff0c;可能我只是本科生吧&…

USB3.2 摘录(10)

系列文章目录 USB3.2 摘录&#xff08;一&#xff09; USB3.2 摘录&#xff08;二&#xff09; USB3.2 摘录&#xff08;三&#xff09; USB3.2 摘录&#xff08;四&#xff09; USB3.2 摘录&#xff08;五&#xff09; USB3.2 摘录&#xff08;六&#xff09; USB3.2 摘录&…

如何使用双重IP代理实现更安全的网络访问

在进行网络爬虫或其他需要隐匿真实IP的操作时&#xff0c;单一的代理IP有时并不能完全满足我们的需求。为了进一步提高安全性和隐私保护&#xff0c;我们可以使用双重IP代理。本文将详细介绍如何使用Java实现双重IP代理&#xff0c;帮助你在网络环境中更加游刃有余。 什么是双重…

神经网络—卷积层

1.讲解 Conv2d out_channels 参数为2时&#xff0c;会生成两个卷积核&#xff0c;分别与输入进行卷积。得到的两个输出为输出 新生成的卷积核和原来的卷积核不一定相同 in_channels (int) – Number of channels in the input image out_channels (int) – Number of channels…