基础

  1. TCP 是传输层协议,主要用于数据的传输,TCP 是相对安全,面向连接,确保数据的完整性
  2. UDP 是传输层协议,主要用于数据的传输,UDP 是相对不稳定,面向无连接,只会将消息发送出去,并不能保证消息一定会被接收到
  3. 当计算机连接到网络后,路由器会分配一个唯一的 IP 地址给具体的机器,当信息传递给路由器时,会根据相应的 IP 地址传递给对应的设备
  4. 每个独立的套接字都会有独一无二的端口,系统接收到网络信息后,会根据对应的端口将消息发送给指定的程序
  5. IPv4实际上是一个4字节的数据,端口在计算机中通常有65535个,可由一个 WORD 来保存
  6. 通常在个人计算机中,数据都是以小端方式存储的,在网络数据的传输过程中,规定使用大端方式保存所有的数据
  7. TCP网络编程中的服务器流程:
    WSAStartup()初始化网络环境
    socket()创建套接字 SOCK_STREAM IPPROTO_TCP
    bind()绑定套接字到当前窗口
    listen()监听端口
    accept()等待客户端的连接
    send()/recv()发送/接收数据
    closesocket()关闭套接字
    WSACleanup()清理网络环境
    TCP网络编程中的客户器流程:
    WSAStartup() -> socket() -> connect() -> send()/recv() -> closesocket() -> WSACleanup()
    客户端不需要绑定端口设置监听,需要使用connect()连接目标服务器
  8. UDP的服务器和客户端的编写流程:
    WSAStartup() -> socket() -> bind() -> sendto()/recvfrom()() -> closesocket() -> WSACleanup()
  9. 因为UDP传输基于无连接,所以没有等待连接和连接的部分

TCP服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<stdio.h>
#include<WS2tcpip.h>
//进行套接字编程必须用到的头文件和库
#include<winsock2.h>
#pragma comment(lib, "ws2_32.lib")
//根据传入的布尔值进行相应的输出
void check_result(bool b, LPCSTR msg) {
if (b) {//若为true就输出
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()
{
//初始化网络环境,搜索信号
WSAData wsadata = { 0 };
int result = WSAStartup(0x0202, &wsadata);
//判断是否符合
check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");

//创建套接字 IP:PORT,选择服务商
SOCKET server = socket(
AF_INET, //使用网络传输协议
SOCK_STREAM, //使用流式套接字(TCP)
IPPROTO_TCP //TCP,IPPROTO_UDP表示UDP
);
check_result(server == INVALID_SOCKET, "socket()");
//绑定套接字到对应的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()");
//开启监听模式,设置监听的最大数量
result = listen(server, SOMAXCONN);
check_result(result == SOCKET_ERROR, "listen()");
while (true) {//不断接收客户端
//等待客户端的连接
//等待哪一个套接字的连接,连接客户端的地址信息,结构体大小
sockaddr_in clientaddr = { 0 };
int length = sizeof(clientaddr);
SOCKET client = accept(server, (sockaddr*)&clientaddr, &length);
check_result(client == INVALID_SOCKET, "accept()");
//为每一个客户端创建单独的线程,进行消息的接收recv
//若没创建线程,程序就会阻塞在recv函数
CreateThread(NULL, 0, RecvThread, (LPVOID)client, 0, NULL);
}
//关闭套接字
closesocket(server);
//清理网络环境
WSACleanup();
system("pause");
return 0;
}

TCP客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<stdio.h>
#include<WS2tcpip.h>
#include<winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#define CLIENT_COUNT 10
// 根据传入的布尔值进行相应的输出
void check_result(bool b, LPCSTR msg)
{
// 参数一因该是一个表达式,如果表达式为 true 就输出
if (b)
{
printf("error: %s\n", msg);
//ExitProcess(0);
}
}
//创建一个客户端进行连接
DWORD CALLBACK create_client(LPVOID param) {
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, "connect()");
//收发消息
while (true) {
//发送当前是第几个客户端到服务器
send(client, (char*)&param, 4, 0);
Sleep(500);
}
}
int main()
{
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;
}

IOCP服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#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. 初始化网络环境
WSAData wsadata = { 0 };
int result = WSAStartup(0x0202, &wsadata);
check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
// 2. 创建套接字[IP:PORT],选择服务商
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
check_result(server == INVALID_SOCKET, "socket()");
// 3. 绑定套接字到对应的
sockaddr_in serveraddr = { AF_INET };
serveraddr.sin_port = htons(0x1234);
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)
{
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;
}

文件发送

.h 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#pragma once
#include <iostream>
#include <windows.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/buffer.h>
#include <openssl/applink.c>
#pragma comment(lib, "libcrypto.lib")
#define PUB_KEY_FILE "pubkey.pem" // 公钥路径
#define PRI_KEY_FILE "prikey.pem" // 私钥路径
int md5_encrypt(const void* data, size_t len, unsigned char* md5)
{
// 初始化保存 md5 信息的结构体
MD5_CTX ctx = { 0 };
MD5_Init(&ctx);
// 将需要计算的数据传入到对应的结构中
MD5_Update(&ctx, data, len);
// 从结构中获取计算后的结果
MD5_Final(md5, &ctx);
return 0;
}
// 要求传入一个需要加密的串,以及串的长度,参数三是否需要换行,返回编码后的数据
char* Base64Encode(const char* input, int length, bool with_new_line)
{
// 创建一个 base64 对象,对象的特点就是使用 write 写入的
// 数据会被自动编码,使用 read 读取的数据会自动解码
BIO* b64 = BIO_new(BIO_f_base64());
// 默认编码之后存在换行符,通常不需要换行符
if (!with_new_line)
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// 再次创建了一个内存对象,对象的特点就是使用 write 写入的
// 数据会自动保存到某一个缓冲区。BIO_push 将两个对象进行关
// 联,也就是说传入的数据首先会进行 base64 编码,然后保存到
// 缓冲区。
b64 = BIO_push(b64, BIO_new(BIO_s_mem()));
// 将传入的数据进行编码,BIO_flush 将操作刷新到对象
BIO_write(b64, input, length);
BIO_flush(b64);
// 从 base64 对象中获取到相应的编码后的内容
BUF_MEM* bptr = NULL;
BIO_get_mem_ptr(b64, &bptr);
// 将编码后的数据拷贝到指定的位置
char* b64encode = new char[bptr->max]{};
memcpy(b64encode, bptr->data, bptr->max);
// 清理 BIO 对象,并返回结果
BIO_free_all(b64);
return b64encode;
}
char* Base64Decode(char* input, int length, bool with_new_line)
{
BIO* b64 = BIO_new(BIO_f_base64());
if (!with_new_line)
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
// 编码后的长度和原文大概比例是 4:3,使用编码后的长度
// 进行解码,空间是绝对足够的
char* buffer = (char*)malloc(length);
if (buffer) memset(buffer, 0, length);
// 创建一个内存对象,存入编码后的内容,并关联到 base64 对象
BIO* bmem = BIO_push(b64, BIO_new_mem_buf(input, length));
// 对 base64 read 就是解码数据
BIO_read(bmem, buffer, length);
// 清理并返回原文
BIO_free_all(bmem);
return buffer;
}
/*加密最大长度为加密长度-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, RSA* rsa)
{
int rsa_len = RSA_size(rsa);
BYTE* encrypt = (BYTE*)malloc(rsa_len);
RSA_public_encrypt(16, 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;
}
// 函数方法生成密钥对
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);
}
void calc_file_sig(LPCSTR filename, LPSTR out)
{
HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
int size = GetFileSize(file, NULL);
int count = size % 256 == 0 ? size / 256 : size / 256 + 1;
DWORD read = 0;
BYTE* file_buffer = new BYTE[size]{ 0 };
ReadFile(file, file_buffer, size, &read, NULL);
UCHAR md5[17] = { 0 };
md5_encrypt(file_buffer, size, md5);
RSA* pub_key = get_public_key();
BYTE* sig = rsa_encrypt(md5, pub_key);
memcpy(out, sig, 128);
}
bool verify_file_sig(LPCSTR filename, LPSTR sig)
{
HANDLE file = CreateFileA(filename, GENERIC_READ, NULL, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
int size = GetFileSize(file, NULL);
int count = size % 256 == 0 ? size / 256 : size / 256 + 1;
DWORD read = 0;
BYTE* file_buffer = new BYTE[size]{ 0 };
ReadFile(file, file_buffer, size, &read, NULL);
UCHAR md5[17] = { 0 };
md5_encrypt(file_buffer, size, md5);
RSA* pri_key = get_private_key();
BYTE* sig_md5 = rsa_decrypt((BYTE*)sig, pri_key);
return !memcmp(sig_md5, md5, 128);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <ws2tcpip.h>
#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. 初始化网络环境
WSAData wsadata = { 0 };
int result = WSAStartup(0x0202, &wsadata);
check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
// 2. 创建套接字[IP:PORT]
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);
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, &section.size, NULL);
//printf("%d:%d\n", i, section.size);
send(reciver, (CHAR*)&section, sizeof(section), 0);
}
// bug 产生的原因是过早的关闭了服务器(发送端)的套接字,如果已经关闭了
// 但是客户端还没有接收完数据,就会直接导致丢包
CloseHandle(file);
system("pause");
return 0;
}

文件接收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <ws2tcpip.h>
#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()
{
// 初始化网络环境
WSAData wsadata = { 0 };
int result = WSAStartup(0x0202, &wsadata);
check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");

// 创建套接字[IP:PORT],选择服务商
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*)&section, 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;
}