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

news2024/9/22 1:22:34

客户端网络模块的开发

我们需要先了解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个字节到buffer
				index -= 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个字节到buffer
				index -= 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.coloradmin.cn/o/2068983.html

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

相关文章

合合信息文档解析Coze插件发布,PDF转Markdown功能便捷集成

近日&#xff0c;TextIn开发的PDF转Markdown插件正式上架Coze平台。 在扣子搜索“pdf转markdown”&#xff0c;或在Coze平台搜索“pdf2markdown”&#xff0c;即可找到插件&#xff0c;在你的专属智能体中便捷使用文档解析功能。 如果想测试解析插件在你需要的场景下表现如何&…

R语言VAR模型的多行业关联与溢出效应可视化分析

全文链接&#xff1a;https://tecdat.cn/?p37397 摘要&#xff1a;本文对医疗卫生、通信、金融、房地产和零售等行业的数据展开深入研究。通过读取数据、计算收益率、构建 VAR 模型并进行估计&#xff0c;帮助客户进一步分析各行业变量的影响及残差的协方差与相关矩阵&#xf…

xml打印模板解析-SAAS本地化及未来之窗行业应用跨平台架构

一、为何要自己设置打印模板系统 1.确保自由知识产权 2.支持跨平台&#xff1a;物联网&#xff0c;自助终端&#xff0c;电脑&#xff0c;web&#xff0c;C#&#xff0c;jsp,android,java,php 等多种语言 二、xml 代码解析 package CyberWinPHP.Cyber_Plus;import java.io.…

2024下半年软考有哪些科目开考?该怎么选?

近年来&#xff0c;软考&#xff08;软件水平考试&#xff09;的难度逐渐攀升&#xff0c;这并非源于题目本身的复杂化&#xff0c;而是官方对通过率的调控策略所致。整体通过率维持在13%左右&#xff0c;高级别考试更是低至10%以下&#xff0c;考生需慎重对待。以湖南2024年上…

数据仓库建模的步骤-从需求分析到模型优化的全面指南

想象一下,你正站在一座巨大的图书馆前。这座图书馆里存放着你公司所有的数据。但是,书籍杂乱无章,没有分类,没有索引。你如何才能快速找到所需的信息?这就是数据仓库建模要解决的问题。本文将带你深入了解数据仓库建模的主要步骤,让你掌握如何将杂乱的数据转化为有序、高效、易…

Java | Leetcode Java题解之第367题有效的完全平方数

题目&#xff1a; 题解&#xff1a; class Solution {public boolean isPerfectSquare(int num) {double x0 num;while (true) {double x1 (x0 num / x0) / 2;if (x0 - x1 < 1e-6) {break;}x0 x1;}int x (int) x0;return x * x num;} }

监控摄像头能看到电脑屏幕内容吗?监控摄像头VS电脑屏幕监控软件!告别盲区,让您的企业管理无死角!

在企业管理中&#xff0c;监控摄像头和电脑屏幕监控软件都是重要的工具&#xff0c;但它们在监控电脑屏幕内容方面存在显著差异。本文将深入探讨监控摄像头在捕捉电脑屏幕内容时的局限性&#xff0c;并对比介绍安企神——电脑屏幕监控软件的优势及其功能策略&#xff0c;帮助企…

在Windows下安装设置Node.js 20.16.0

文章目录 一、下载Node.js二、安装Node.js三、设置Node.js四、更换npm源 一、下载Node.js 官网地址&#xff08;中文网&#xff09; 或者使用直链下载 二、安装Node.js 打开下载好的安装包&#xff0c;点击下一步 同意用户协议后下一步 选择安装目录后下一步 可以根据自…

02 tkinter有趣项目-头像制作-界面设计

头像制界面设计 **avatar.png** **界面** 界面分析 背景图片: 顶部中央位置显示一个小孩背着书包的图片。这个图片是程序的背景&#xff0c;占据了大部分的窗口空间。 标题和按钮: 在图片上方&#xff0c;有一个标题栏&#xff0c;显示文本“在线姓氏头像制作”&#xff0c;使…

vue+fastadmin跨域请求问题

记录一个 vuefastadmin项目api 访问跨域问题 前端页面使用的是 axios 发起请求&#xff0c;api 是 fastadmin 写的&#xff0c;遇到跨域错误&#xff1a; 解决办法&#xff1a; 控制器代码中加入check_cors_request()实现跨域检测 fa 官网对跨域的说明 &#xff1a;跨域配置 -…

乐凡三防高亮屏工业平板电脑的应用场景

随着科技的发展&#xff0c;三防高亮屏工业平板电脑在越来越多的领域中得到广泛应用。下面探讨一下三防高亮屏工业平板电脑的应用场景及其在不同领域中的优势。 物流行业 在物流行业中&#xff0c;三防高亮屏工业平板电脑可以用于仓库管理、货物跟踪、运输调度等多个方面。在阳…

在Ubuntu22.04使用PySide6或PyQt5的文件选择框时,无法显示文件夹中的文件问题的解决方案

摘要&#xff1a;在使用PySide6或PyQt5开发图形用户界面&#xff08;GUI&#xff09;应用程序时&#xff0c;我们经常会使用 QFileDialog 来让用户选择文件或文件夹。然而&#xff0c;有时候会遇到一个奇怪的问题&#xff0c;即在打开文件选择对话框时&#xff0c;某些文件类型…

Vue3的三种样式控制及实现原理

你好&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏和关注。个人知乎 Vue3中一共有三种样式控制&#xff0c;分别是全局样式控制、局部作用域样式控制和深度样式控制&#xff0c;今天我们一起看下这三种样式控制的使用&#xff0c;以及实现的原理是什么。 一、全局样式控制…

数据恢复技术-手动修复MBR-/NTFS分区

前言 本文只作为本人学习笔记&#xff0c;不做他用&#xff0c;转载请注明原处谢谢&#xff01; 本文教大家如何手工修复MBR引导分区&#xff0c;找回丢失的数据&#xff0c;先附加题目镜像&#xff08;右键我的电脑或win标&#xff0c;选择管理/磁盘管理---操作---附加VHD&…

笔记整理—uboot启动过程(2)BL1低级初始化

lowlevel_init看名字就知道是关于初级方面的初始化&#xff0c;其中可用将其干的事情分为11个步骤&#xff1a; &#xff08;1&#xff09;push {lr} 也就是lr压栈。 &#xff08;2&#xff09;检测复位状态&#xff1a;如冷上电、热启动、睡眠等。冷上电要初始化DDR后才能使用…

MS2232/MS2232T——±20kV ESD 保护、3V-5.5V 供电、真 RS-232 收发器

MS2232/MS2232T 芯片是集成电荷泵、具有 20kV ESD 保护的 RS-232 收发器&#xff0c;包括两路接收器、两路发送器。芯 片满足 TIA/EIA-232 标准&#xff0c;为异步通信控制器和串口连接器 提供通信接口。 芯片采用 3V-5.5V 供电&#xff0c;电荷泵仅用 4 个 0.1-0.47μF 小…

GUI / GitOps / API: 用 Bytebase 实现 SQL 审核

修改数据库中的数据时&#xff0c;确保安全准确至关重要。Bytebase 提供 SQL 审核功能&#xff1a;将 SQL 变更应用到数据库之前&#xff0c;可对其进行评估。SQL 审核可通过 Bytebase GUI、GitOps 工作流或 API 触发。 本教程将使用 Bytebase 的 SQL 审核来改进数据库 Schema …

对数据治理和云采用的思考:过去和现在

组织在向云服务转变的过程中面临数据治理复杂性和挑战。 如今&#xff0c;到了 2024 年&#xff0c;形势已经发生了变化&#xff0c;但根本问题却愈演愈烈。 数据的增长以及网络攻击的频率和复杂性不断增加&#xff0c;使得对强大的数据治理和安全的需求比以往任何时候都更加…

Educational Codeforces Round 169 (Rated for Div. 2)

前言 电脑显示屏一闪一闪地感觉要拿去修了&#xff0c;比赛时重启了好几次。 手速场&#xff0c;E 题没学过 Sprague-Grundy 吃了亏&#xff0c;好在前四题都一发过才不至于掉分。 Standings&#xff1a;1214 题目链接&#xff1a;Dashboard - Educational Codeforces Round 16…

shell脚本中$0 $1 $# $@ $* $? $$ 的各种符号意义详解

文章目录 一、概述1.1、普通字符1.2、元字符 二、转义字符$2.1、实例12.2、实例22.3、实例32.4、实例42.5、实例5 三、linux命令执行返回值$?说明 一、概述 shell中有两类字符&#xff1a;普通字符、元字符。 1.1、普通字符 在Shell中除了本身的字面意思外没有其他特殊意义…