0 为什么要实现这个功能?
任何代码都是从需求出发,把抽象的需求转换为技术点,技术点再转换为一个个功能点,再通过c++语法去实现这个功能点
首先我们有一个需求就是文件需求
-
打开文件
-
删除文件
-
下载文件
这些需求最基本的就是要获取文件所处的驱动
客户端发来一个命令获取驱动信息,我们需要把这个命令从缓冲区里面解析出来(参见上节)
我们去实现获取驱动信息这个功能,这个功能实现完了后,需要发送数据包,将结果返回给服务器,这个时候问题来了怎么创建一个数据包呢?怎么发送一个CPacket对象呢?
怎么创建一个数据包呢?
创建一个数据包我们可以创建一个空的数据包,然后一个一个手动赋值,但是这样很麻烦,我们可以重载构造函数,让创建数据包更加简单
怎么发送一个CPacket对象呢?
我们知道send函数有这样的两个参数,指向缓冲区的指针,缓冲区数据的长度,通过这个我们可知,我们需要将这个CPacket对象中的数据,赋值到一个缓冲区里面,这个缓冲区我们可以在CPacket里面定义
1 实现了获取磁盘驱动信息功能
#include <direct.h> int MakeDrivers() { std::string Drivers; // 指定当前工作驱动器的 1 到 26 的整数(1=A,2=B 等)。 // 更改当前驱动器,返回0表示成功, for (int i = 1; i <= 26; i++) { if (_chdrive(i) == 0) { if (Drivers.size() > 0) { Drivers += ","; } Drivers += 'A' + i - 1; } } CPacket packet = CPacket(1, (BYTE*)Drivers.c_str(), Drivers.size()); CServerSocket::getInstance()->Send(packet); }
2 重载了CPacket方法--实现发送数据打包
为了方便数据包的创建,不然得一个一个的赋值
// 发送数据打包 CPacket(WORD nCmd, const BYTE* pData, size_t nSize) { sHead = 0xFEFF; nLength = nSize + 4; sCmd = nCmd; strData.resize(nSize); memcpy((void*)strData.c_str(), pData, nSize); sSum = 0; for (int i = 0; i < strData.size(); i++) { sSum += BYTE(strData[i]) & 0xFF; } }
3 重载了Send方法
为了方便数据包的发送,以及使用逻辑上的简便
代码:
CPacket packet = CPacket(1, (BYTE*)Drivers.c_str(), Drivers.size()); CServerSocket::getInstance()->Send(packet);
4 实现了CPacket对象变成缓冲区的的方法
4.1 为什么要将CPacket对象变成缓冲区?
利用socket发送数据包的最终是调用send函数,send函数的参数是
-
socket,连接的套接字描述符
-
buf,要发送的数据包的缓冲区指针
-
len, 缓冲区中数据的长度
-
flags
所以需要有一个缓冲区指针指向CPacket对象,len非常容易算出来就等于就等于发送数据的数据长度加6,参见上一节数据包的封装,数据包的解包和封包逻辑很重要,服务器的本质无非就是收发数据,然后对接收的数据做出处理,只要能准确识别出数据,剩下的就是根据数据做出对应的回应了
// 包数据的值 const char* Data() { strOut.resize(nLength + 6); BYTE* pData = (BYTE*)strOut.c_str(); *(WORD*)pData = sHead; pData += 2; *(DWORD*)pData = nLength; pData += 4; *(WORD*)pData = sCmd; pData += 2; memcpy(pData, strData.c_str(), strData.size()); pData += strData.size(); *(WORD*)pData = sSum; return strOut.c_str(); }
5 包对象的对齐方法处理
需要将CPacket对象的对齐方式改为1字节,就是不需要操作系统进行填充,原本的数据是怎么样就是怎么样,不然操作系统会填充一些数据导致解包异常
// 保存当前字节对齐的状态 #pragma pack(push) // 改成一字节对齐 #pragma pack(1) class CPacket { }; // 还原字节对齐状态 #pragma pack(pop)
6 实现了以16进制打印缓冲区内容
可以用来测试数据包的封装是否正确,把一个packet转成缓冲区(参见标题4),利用这个函数打印出来
void Dump(BYTE* pData, size_t nSize) { std::string StrOut; for (size_t i = 0; i < nSize; i++) { if (i > 0 && i % 16 == 0) { StrOut += "\n"; } char buf[8] = ""; // 将格式化的数据写入字符串。 snprintf(buf, sizeof(buf), "%02X ", pData[i] & 0xFF); StrOut += buf; } StrOut += "\n"; OutputDebugStringA(StrOut.c_str()); }