01. rsa 操作
windows简单安装openssl:http://slproweb.com/products/Win32OpenSSL.html
Win32OpenSSL-1_1_1c.zip
rsa需要openssl库函数,直接安装这个程序(下一步下一步不需要自己改动)即可
安装完查看版本:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <windows.h>
#include <openssl\rsa.h>
#include <openssl\pem.h>
#include <openssl\rand.h>
#include <openssl\applink.c>
#pragma comment(lib, "libcrypto.lib")
#define PUB_KEY_FILE "pubkey.pem" // 公钥路径
#define PRI_KEY_FILE "prikey.pem" // 私钥路径
// 函数方法生成密钥对
void generate_rsa_key()
{
// 生成 rsa 密钥对, 参数一密钥长度,参数二公钥指数 e,参数三四可以不指定
RSA* keypair = RSA_generate_key(1024, RSA_F4, NULL, NULL);
// 从生成的密钥对中读取私钥到内存对象
BIO* pri = BIO_new(BIO_s_mem());
PEM_write_bio_RSAPrivateKey(pri, keypair, NULL, NULL, 0, NULL, NULL);
// 获取密钥长度并且申请空间进行保存
size_t pri_len = BIO_pending(pri);
char* pri_key = (char*)calloc(pri_len + 1, sizeof(char));
BIO_read(pri, pri_key, pri_len);
// 将生成的私钥写入到指定的文件中
FILE* private_file = nullptr;
if (fopen_s(&private_file, PRI_KEY_FILE, "w") == NULL)
{
if (pri_key && private_file)
{
fputs(pri_key, private_file);
fclose(private_file);
}
}
BIO* pub = BIO_new(BIO_s_mem());
PEM_write_bio_RSAPublicKey(pub, keypair);
size_t pub_len = BIO_pending(pub);
char* pub_key = (char*)calloc(pub_len + 1, sizeof(char));
BIO_read(pub, pub_key, pub_len);
FILE* public_file = nullptr;
if (fopen_s(&public_file, PUB_KEY_FILE, "w") == NULL)
{
if (pub_key && public_file)
{
fputs(pub_key, public_file);
fclose(public_file);
}
}
// 释放对应的资源,防止泄露
RSA_free(keypair);
BIO_free_all(pub);
BIO_free_all(pri);
free(pri_key);
free(pub_key);
}
/*加密最大长度为加密长度-41*/
RSA* get_public_key()
{
// 打开公钥文件
FILE* public_file = nullptr;
if (fopen_s(&public_file, PUB_KEY_FILE, "r") == NULL)
{
// 从指定文件中读取公钥
RSA* rsa = PEM_read_RSAPublicKey(public_file, NULL, NULL, NULL);
if (public_file) fclose(public_file);
return rsa;
}
return nullptr;
}
RSA* get_private_key()
{
// 打开私钥文件
FILE* private_file = nullptr;
if (fopen_s(&private_file, PRI_KEY_FILE, "r") == NULL)
{
// 从指定文件中读取公钥
RSA* rsa = PEM_read_RSAPrivateKey(private_file, NULL, NULL, NULL);
if (private_file) fclose(private_file);
return rsa;
}
return nullptr;
}
BYTE* rsa_encrypt(BYTE* data, int data_len, RSA* rsa)
{
int rsa_len = RSA_size(rsa);
BYTE* encrypt = (BYTE*)malloc(rsa_len);
// RSA 1024 加密的明文分组长度不能超过 117, 生成的密文长度是 128
// 且 RSA 1024 不支持分组加密,意味着只能自己使用循环进行加密。
if (data_len > 117) return nullptr;
// RSA_PKCS1_PADDING 填充方式,随机添加一些数据进行加密和解密
RSA_public_encrypt(data_len, data, encrypt, rsa, RSA_PKCS1_PADDING);
return encrypt;
}
// 解密数据,
BYTE* rsa_decrypt(BYTE* data, RSA* rsa)
{
int rsa_len = RSA_size(rsa);
BYTE* decrypt = (BYTE*)malloc(rsa_len);
RSA_private_decrypt(rsa_len, data, decrypt, rsa, RSA_PKCS1_PADDING);
return decrypt;
}
int main()
{
// 生成 rsa 密钥对并读取到 rsa 对象中
generate_rsa_key();
RSA* public_key = get_public_key();
RSA* private_key = get_private_key();
// 构建需要加密的文件
BYTE plaintext[117] = { 0 };
memset(plaintext, 'a', 117);
// 加密和解密
BYTE* encrypt = rsa_encrypt(plaintext, 117, public_key);
BYTE* decrypt = rsa_decrypt(encrypt, private_key);
return 0;
}
02. tcp 客户端
#include <iostream>
#include <ws2tcpip.h>
// 0. 进行套接字编程必须用到的头文件和库
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#define CLIENT_COUNT 100
// 根据传入的布尔值进行相应的输出
void check_result(bool b, LPCSTR msg)
{
// 参数一因该是一个表达式,如果表达式为 true 就输出
if (b)
{
printf("error: %s\n", msg);
ExitProcess(0);
}
}
// 创建一个客户端进行连接
DWORD CALLBACK create_client(LPVOID param)
{
// 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
// - AF_INET: 表示使用网络传输协议
// - SOCK_STREAM: 表示使用流式套接字(TCP)
// - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
check_result(client == INVALID_SOCKET, "socket()");
// 作为客户端来讲,每一个客户端套接字都会默认的分配端口
// 连接到服务器,打电话(需要知道打给谁)
sockaddr_in serveraddr = { AF_INET };
serveraddr.sin_port = htons(0x1234);
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
int result = connect(client, (sockaddr*)&serveraddr, sizeof(serveraddr));
check_result(result == SOCKET_ERROR, "accept()");
// 收发消息 send\recv,打电话
while (true)
{
// 发送当前是第几个客户端到服务器
send(client, (char*)¶m, 4, 0);
Sleep(500);
}
}
int main()
{
// 初始化网络环境,搜索信号(3G? 4G?)
WSAData wsadata = { 0 };
int result = WSAStartup(0x0202, &wsadata);
check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
// 创建指定个数的 客户端
for (int i = 0; i < CLIENT_COUNT; ++i)
CreateThread(NULL, 0, create_client, (LPVOID)i, 0, NULL);
// WaitForMultipleObjects 能够等待的句柄最多 64 个
system("pause");
// 清理网络环境,销户
WSACleanup();
return 0;
}
03. tcp 服务端
// TCP 是什么? 传输层的协议,主要用于数据的传输。TCP 是
// 相对安全的,面向有连接的。确保数据的完整性。类似于打电
// 话,会有 响铃 + 接通 + 交流 的过程。
// UDP 是什么? 传输层的协议,主要用于数据的传输。UDP 是
// 相对不稳定的,面向无连接的。类似于 BB 机,只会将消息发
// 送出去,并不能保证消息一定会被接收到。
// 如何确保消息发送给当前局域网内的某一个机器? 当计算机连
// 接到网络中后,路由器会分配一个唯一的 ip 地址给具体的机,
// 器,当信息传递给路由器的时候,会根据相应的 ip 地址传递给
// 对应的设备。
// 如何确保消息发送给当前系统的某一个程序? 每一个独立套接字
// 都会拥有独一无二的端口,当系统接受到网络信息之后,会根据对
// 应的端口将信息发送给指定的程序。
// ip 和 端口 的组成是什么? ipv4 的地址实际上是一个四字节的
// 整数数据,组成通常 x.x.x.x 的形式,假设存在 1.1.1.127 这个
// ip 对应的四字节实际上是 0x0101017F。端口在计算机中通常有
// 65535 个,实际可以由一个 WORD 来保存。
// 数据在网络传输的过程中是如何存储的? 通常在个人计算机中,数据
// 都是以小端方式存储的,在网络数据的传输过程中,规定应该使用大端
// 的方式保存所有的数据。
#include <iostream>
#include <ws2tcpip.h>
// 0. 进行套接字编程必须用到的头文件和库
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
// 根据传入的布尔值进行相应的输出
void check_result(bool b, LPCSTR msg)
{
// 参数一因该是一个表达式,如果表达式为 true 就输出
if (b)
{
printf("error: %s\n", msg);
ExitProcess(0);
}
}
// 创建一个互斥体,用于互斥输出
HANDLE Mutex = CreateMutex(NULL, FALSE, NULL);
// 专门用于接受指定套接字的数据
DWORD CALLBACK RecvThread(LPVOID param)
{
// 从参数获取到套接字
SOCKET client = (SOCKET)param;
INT number = { 0 };
// 循环接受套接字传入的数据,recv 的返回值应该是接受
// 到的数据长度,如果返回值 <= 0 就表示断开了连接
while (recv(client, (char*)&number, 4, 0) > 0)
{
WaitForSingleObject(Mutex, INFINITE);
printf("[%08X]: %d\n", GetCurrentThreadId(), number);
ReleaseMutex(Mutex);
}
return 0;
}
int main()
{
// 1. 初始化网络环境,搜索信号(3G? 4G?)
WSAData wsadata = { 0 };
int result = WSAStartup(0x0202, &wsadata);
check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
// 2. 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
// - AF_INET: 表示使用网络传输协议
// - SOCK_STREAM: 表示使用流式套接字(TCP)
// - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
check_result(server == INVALID_SOCKET, "socket()");
// 3. 绑定套接字到对应的 ip:port,类似于选择手机号
sockaddr_in serveraddr = { AF_INET };
serveraddr.sin_port = htons(0x1234);
// - 127.0.0.1 在大多数设备上指向的都是当前的主机
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
result = bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
check_result(result == SOCKET_ERROR, "bind()");
// 4. 开启监听模式,设置监听的最大数量,把卡装到手机开机
result = listen(server, SOMAXCONN);
check_result(result == SOCKET_ERROR, "listen()");
// 编写一个死循环,在循环内不断的接收客户端
while (true)
{
// 5. 等待客户端的连接,接电话
// - 等待哪一个套接字的连接、连接客户端的地址信息,结构体大小
sockaddr_in clientaddr = { 0 };
int length = sizeof(clientaddr);
SOCKET client = accept(server, (sockaddr*)&clientaddr, &length);
check_result(client == INVALID_SOCKET, "accept()");
// 6. 为[每一个客户端]创建[单独]的线程,进行消息的接收 recv
// - 如果没有创建线程,那么程序就会阻塞在 recv 函数,导致无法接受
CreateThread(NULL, 0, RecvThread, (LPVOID)client, 0, NULL);
}
// 7. 关闭套接字,挂断电话
closesocket(server);
// 8. 清理网络环境,销户
WSACleanup();
system("pause");
return 0;
}
04. iocp 服务端
#include <iostream>
#include <ws2tcpip.h>
// 0. 进行套接字编程必须用到的头文件和库
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
// 用于保存异步信息的结构体
struct MYOVERLAPPED
{
OVERLAPPED overlapped;
WSABUF wsabuf;
};
// 根据传入的布尔值进行相应的输出
void check_result(bool b, LPCSTR msg)
{
// 参数一因该是一个表达式,如果表达式为 true 就输出
if (b)
{
printf("error: %s\n", msg);
ExitProcess(0);
}
}
// 创建一个互斥体,用于互斥输出
HANDLE Mutex = CreateMutex(NULL, FALSE, NULL);
// 专门用于接受指定套接字的数据
DWORD CALLBACK RecvThread(LPVOID param)
{
// 首先获取到参数传入的 iocp 对象
HANDLE iocp = (HANDLE)param;
DWORD read = 0;
ULONG_PTR completion_key = NULL;
MYOVERLAPPED* overlapped = nullptr;
// 不断的接受队列中传入的消息
while (true)
{
// 从完成端口队列获取信息
BOOL result = GetQueuedCompletionStatus(
iocp, // 从哪一个 iocp 获取
&read, // 实际接收的数量
&completion_key, // 在这里是产生消息的套接字
// 接收的 OVERLAPPED** ,但实际刚才传入的是 OVERLAPPED*
(LPOVERLAPPED*)&overlapped, // 重叠IO结构
INFINITE); // 等待时长
// 如果消息接收成功就输出
if (result == TRUE && read > 0)
{
WaitForSingleObject(Mutex, INFINITE);
printf("[%08X]: %d\n", GetCurrentThreadId(),
*(int*)overlapped->wsabuf.buf);
ReleaseMutex(Mutex);
// 需要再次投递一个请求
DWORD flags = 0;
WSARecv((SOCKET)completion_key, // 接受谁的信息
&overlapped->wsabuf, // 消息保存到哪里
1, // wsabuf 结构的数量
NULL, // 对于异步IO,可以填写0
&flags, // 对应 recv 的最后一个参数
(LPWSAOVERLAPPED)overlapped,// 重叠 IO 结构体
NULL); // 回调函数
}
}
return 0;
}
int main()
{
// [1]. IOCP 是用有限的线程处理多个异步操作,线程数量通常是CPU核心 * 2
SYSTEM_INFO system_info = { 0 };
GetSystemInfo(&system_info);
// [2]. 创建一个 IOCP 对象,维护所有的线程
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
// [3]. 根据获取到的核心数量,创建 iocp 工作线程,传入 iocp 对象
for (DWORD i = 0; i < system_info.dwNumberOfProcessors * 2; ++i)
CreateThread(NULL, 0, RecvThread, (LPVOID)iocp, 0, NULL);
// 1. 初始化网络环境,搜索信号(3G? 4G?)
WSAData wsadata = { 0 };
int result = WSAStartup(0x0202, &wsadata);
check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
// 2. 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
// - AF_INET: 表示使用网络传输协议
// - SOCK_STREAM: 表示使用流式套接字(TCP)
// - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
check_result(server == INVALID_SOCKET, "socket()");
// 3. 绑定套接字到对应的 ip:port,类似于选择手机号
sockaddr_in serveraddr = { AF_INET };
serveraddr.sin_port = htons(0x1234);
// - 127.0.0.1 在大多数设备上指向的都是当前的主机
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
result = bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
check_result(result == SOCKET_ERROR, "bind()");
// 4. 开启监听模式,设置监听的最大数量,把卡装到手机开机
result = listen(server, SOMAXCONN);
check_result(result == SOCKET_ERROR, "listen()");
// 编写一个死循环,在循环内不断的接收客户端
while (true)
{
// 5. 等待客户端的连接,接电话
// - 等待哪一个套接字的连接、连接客户端的地址信息,结构体大小
sockaddr_in clientaddr = { 0 };
int length = sizeof(clientaddr);
SOCKET client = accept(server, (sockaddr*)&clientaddr, &length);
check_result(client == INVALID_SOCKET, "accept()");
// [4]. 将每一个接收到的套接字都绑定到 iocp 上
// - 参数三:完成键,通常用于保存产生消息的句柄(客户端句柄)
CreateIoCompletionPort((HANDLE)client, iocp, client, 0);
// WSARecv 对需要使用单独的 MYOVERLAPPED 结构体。
MYOVERLAPPED* overlapped = new MYOVERLAPPED{ 0 };
overlapped->wsabuf.len = 0x10;
overlapped->wsabuf.buf = new CHAR[0x10]{ 0 };
// [5]. 投递一个接收客户端数据的请求,使用 WSARecv,对于每一个
DWORD flags = 0;
WSARecv(client, // 接受谁的信息
&overlapped->wsabuf, // 消息保存到哪里
1, // wsabuf 结构的数量
NULL, // 对于异步IO,可以填写0
&flags, // 对应 recv 的最后一个参数
(LPWSAOVERLAPPED)overlapped,// 重叠 IO 结构体
NULL); // 回调函数
// 将请求投递到 IOCP 的队列中,一旦请求执行完毕,GetQueuedCompletionStatus
// 就会从完成请求的队列中获取到这个投递的请求,在这个地方,主线程会被切换到工
// 作线程,执行 GetQueuedCompletionStatus,错误的真正地址是在这里。
}
// 7. 关闭套接字,挂断电话
closesocket(server);
// 8. 清理网络环境,销户
WSACleanup();
system("pause");
return 0;
}
// 使用 IOCP 的步骤: 主线程
// 1. 创建一个 iocp 对象
// 2. 根据 cpu 核心数量创建线程
// 3. 接收客户端,将接收的客户端绑定到 iocp,即添加到对象队列中
// 4. 投递一个 IO 请求,WSARecv 请求,即接收目标的信息
// 每一个请求都应该对应有不同的 OVERLAPPED 结构体,且应该在堆空间
// 使用 IOCP 的步骤: 工作线程
// 1. GetQueuedCompletionStatus 从完成队列中取出消息
// 2. 函数如果返回 FALSE 或者实际操作的字节 <=0 就失败
// 3. 可以从 OVERLAPPED 中获取到想要的信息并输出
// 4. 为了能够继续的获取下一次信息,需要再次投递请求
05. tcp 文件发送
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <ws2tcpip.h>
// 0. 进行套接字编程必须用到的头文件和库
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
// windows 中检查了一些重复包含
#include <windows.h>
#include "function.h"
#define SECTION_SIZE 10240
// 发送文件的时候分成两个步骤:1 发送文件的请求头(基本信息)
typedef struct _FILE_HEADER
{
CHAR filename[MAX_PATH] = { 0 }; // 文件的名称
SIZE_T filesize = 0; // 文件的大小
DWORD section_count = 0; // 接收整个文件需要的次数
CHAR sig[128] = { 0 }; // 签名
} FILE_HEADER, *PFILE_HEADER;
// 发送文件的时候分成两个步骤:2 发送文件的请求体(n个,第几个区块,以及内容)
typedef struct _FILE_SECTION
{
int index = 0; // 文件的大小
DWORD size = 0; // 一次发送的大小
CHAR data[SECTION_SIZE] = { 0 }; // 发送的具体数据
} FILE_SECTION, * PFILE_SECTION;
// 根据传入的布尔值进行相应的输出
void check_result(bool b, LPCSTR msg)
{
// 参数一因该是一个表达式,如果表达式为 true 就输出
if (b)
{
printf("error: %s\n", msg);
ExitProcess(0);
}
}
int main()
{
// 1. 初始化网络环境,搜索信号(3G? 4G?)
WSAData wsadata = { 0 };
int result = WSAStartup(0x0202, &wsadata);
check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
// 2. 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
// - AF_INET: 表示使用网络传输协议
// - SOCK_STREAM: 表示使用流式套接字(TCP)
// - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
SOCKET sender = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
check_result(sender == INVALID_SOCKET, "socket()");
// 3. 绑定套接字到对应的 ip:port,类似于选择手机号
sockaddr_in serveraddr = { AF_INET };
serveraddr.sin_port = htons(0x1234);
// - 127.0.0.1 在大多数设备上指向的都是当前的主机
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
result = bind(sender, (sockaddr*)&serveraddr, sizeof(serveraddr));
check_result(result == SOCKET_ERROR, "bind()");
// 4. 开启监听模式,设置监听的最大数量,把卡装到手机开机
result = listen(sender, SOMAXCONN);
check_result(result == SOCKET_ERROR, "listen()");
// 5. 等待客户端的连接,接电话
// - 等待哪一个套接字的连接、连接客户端的地址信息,结构体大小
sockaddr_in clientaddr = { 0 };
int length = sizeof(clientaddr);
SOCKET reciver = accept(sender, (sockaddr*)&clientaddr, &length);
check_result(reciver == INVALID_SOCKET, "accept()");
// 6. 打开需要发送的文件,获取相关的信息填充到结构体中
HANDLE file = CreateFileA("demo.exe", GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
check_result(file == INVALID_HANDLE_VALUE, "CreateFileA()");
SIZE_T filesize = GetFileSize(file, NULL);
DWORD sectioncount = filesize % SECTION_SIZE == 0 ?
filesize / SECTION_SIZE : (filesize / SECTION_SIZE) + 1;
FILE_HEADER file_header = { "demo.exe", filesize, sectioncount };
calc_file_sig("demo.exe", file_header.sig);
send(reciver, (CHAR*)&file_header, sizeof(file_header), 0);
// 7. 根据计算出的区块数量,循环发送每一个区块
for (DWORD i = 0; i < sectioncount; ++i)
{
// 7.1 创建缓冲区用于保存需要发送的区块
FILE_SECTION section = { i };
// 7.2 修改文件指针指向每一个区块的首地址
SetFilePointer(file, i * SECTION_SIZE, 0, FILE_BEGIN);
// 7.3 从指定的位置读取数据保存并发送
ReadFile(file, section.data, SECTION_SIZE, §ion.size, NULL);
//printf("%d:%d\n", i, section.size);
send(reciver, (CHAR*)§ion, sizeof(section), 0);
}
// bug 产生的原因是过早的关闭了服务器(发送端)的套接字,如果已经关闭了
// 但是客户端还没有接收完数据,就会直接导致丢包
CloseHandle(file);
system("pause");
return 0;
}
06. tcp 文件接收
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <ws2tcpip.h>
// 0. 进行套接字编程必须用到的头文件和库
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <windows.h>
#include "function.h"
#define SECTION_SIZE 10240
// 发送文件的时候分成两个步骤:1 发送文件的请求头(基本信息)
typedef struct _FILE_HEADER
{
CHAR filename[MAX_PATH] = { 0 }; // 文件的名称
SIZE_T filesize = 0; // 文件的大小
DWORD section_count = 0; // 接收整个文件需要的次数
CHAR sig[128] = { 0 }; // 签名
} FILE_HEADER, * PFILE_HEADER;
// 发送文件的时候分成两个步骤:2 发送文件的请求体(n个,第几个区块,以及内容)
typedef struct _FILE_SECTION
{
int index = 0; // 文件的大小
DWORD size = 0; // 一次发送的大小
CHAR data[SECTION_SIZE] = { 0 }; // 发送的具体数据
} FILE_SECTION, * PFILE_SECTION;
// 根据传入的布尔值进行相应的输出
void check_result(bool b, LPCSTR msg)
{
// 参数一因该是一个表达式,如果表达式为 true 就输出
if (b)
{
printf("error: %s\n", msg);
ExitProcess(0);
}
}
int main()
{
// 初始化网络环境,搜索信号(3G? 4G?)
WSAData wsadata = { 0 };
int result = WSAStartup(0x0202, &wsadata);
check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
// 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
// - AF_INET: 表示使用网络传输协议
// - SOCK_STREAM: 表示使用流式套接字(TCP)
// - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
SOCKET reciver = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
check_result(reciver == INVALID_SOCKET, "socket()");
// 作为客户端来讲,每一个客户端套接字都会默认的分配端口
// 连接到服务器,打电话(需要知道打给谁)
sockaddr_in serveraddr = { AF_INET };
serveraddr.sin_port = htons(0x1234);
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
result = connect(reciver, (sockaddr*)&serveraddr, sizeof(serveraddr));
check_result(result == SOCKET_ERROR, "accept()");
// 直接接收服务器发送过来的文件头信息
FILE_HEADER file_header = { 0 };
recv(reciver, (CHAR*)&file_header, sizeof(file_header), 0);
// 使用接收到的名称创建文件
HANDLE file = CreateFileA("demo1.exe", GENERIC_WRITE, NULL,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
check_result(file == INVALID_HANDLE_VALUE, "CreateFileA()");
// 根据接收到的轮数,循环接收整个文件
for (DWORD i = 0; i < file_header.section_count; ++i)
{
// 7.1 创建缓冲区用于接收块的信息
FILE_SECTION section = { 0 };
int n = recv(reciver, (CHAR*)§ion, sizeof(section), 0);
// 7.2 修改文件指针指向接收到的数据对应文职
SetFilePointer(file, section.index * SECTION_SIZE, 0, FILE_BEGIN);
// 7.3 将数据写到指定的位置
DWORD aaa;
if (section.size == 0)
break;
//printf("%d:%d\n", section.index, section.size);
WriteFile(file, section.data, section.size, &aaa, NULL);
}
CloseHandle(file);
if (verify_file_sig("demo1.exe", file_header.sig))
printf("签名校验失败\n");
else
printf("签名校验成功\n");
system("pause");
// 清理网络环境,销户
WSACleanup();
return 0;
}