01. rsa 操作

windows简单安装openssl:http://slproweb.com/products/Win32OpenSSL.html
Win32OpenSSL-1_1_1c.zip
rsa需要openssl库函数,直接安装这个程序(下一步下一步不需要自己改动)即可
安装完查看版本:
image.png
image.png

  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <iostream>
  3. #include <windows.h>
  4. #include <openssl\rsa.h>
  5. #include <openssl\pem.h>
  6. #include <openssl\rand.h>
  7. #include <openssl\applink.c>
  8. #pragma comment(lib, "libcrypto.lib")
  9. #define PUB_KEY_FILE "pubkey.pem" // 公钥路径
  10. #define PRI_KEY_FILE "prikey.pem" // 私钥路径
  11. // 函数方法生成密钥对
  12. void generate_rsa_key()
  13. {
  14. // 生成 rsa 密钥对, 参数一密钥长度,参数二公钥指数 e,参数三四可以不指定
  15. RSA* keypair = RSA_generate_key(1024, RSA_F4, NULL, NULL);
  16. // 从生成的密钥对中读取私钥到内存对象
  17. BIO* pri = BIO_new(BIO_s_mem());
  18. PEM_write_bio_RSAPrivateKey(pri, keypair, NULL, NULL, 0, NULL, NULL);
  19. // 获取密钥长度并且申请空间进行保存
  20. size_t pri_len = BIO_pending(pri);
  21. char* pri_key = (char*)calloc(pri_len + 1, sizeof(char));
  22. BIO_read(pri, pri_key, pri_len);
  23. // 将生成的私钥写入到指定的文件中
  24. FILE* private_file = nullptr;
  25. if (fopen_s(&private_file, PRI_KEY_FILE, "w") == NULL)
  26. {
  27. if (pri_key && private_file)
  28. {
  29. fputs(pri_key, private_file);
  30. fclose(private_file);
  31. }
  32. }
  33. BIO* pub = BIO_new(BIO_s_mem());
  34. PEM_write_bio_RSAPublicKey(pub, keypair);
  35. size_t pub_len = BIO_pending(pub);
  36. char* pub_key = (char*)calloc(pub_len + 1, sizeof(char));
  37. BIO_read(pub, pub_key, pub_len);
  38. FILE* public_file = nullptr;
  39. if (fopen_s(&public_file, PUB_KEY_FILE, "w") == NULL)
  40. {
  41. if (pub_key && public_file)
  42. {
  43. fputs(pub_key, public_file);
  44. fclose(public_file);
  45. }
  46. }
  47. // 释放对应的资源,防止泄露
  48. RSA_free(keypair);
  49. BIO_free_all(pub);
  50. BIO_free_all(pri);
  51. free(pri_key);
  52. free(pub_key);
  53. }
  54. /*加密最大长度为加密长度-41*/
  55. RSA* get_public_key()
  56. {
  57. // 打开公钥文件
  58. FILE* public_file = nullptr;
  59. if (fopen_s(&public_file, PUB_KEY_FILE, "r") == NULL)
  60. {
  61. // 从指定文件中读取公钥
  62. RSA* rsa = PEM_read_RSAPublicKey(public_file, NULL, NULL, NULL);
  63. if (public_file) fclose(public_file);
  64. return rsa;
  65. }
  66. return nullptr;
  67. }
  68. RSA* get_private_key()
  69. {
  70. // 打开私钥文件
  71. FILE* private_file = nullptr;
  72. if (fopen_s(&private_file, PRI_KEY_FILE, "r") == NULL)
  73. {
  74. // 从指定文件中读取公钥
  75. RSA* rsa = PEM_read_RSAPrivateKey(private_file, NULL, NULL, NULL);
  76. if (private_file) fclose(private_file);
  77. return rsa;
  78. }
  79. return nullptr;
  80. }
  81. BYTE* rsa_encrypt(BYTE* data, int data_len, RSA* rsa)
  82. {
  83. int rsa_len = RSA_size(rsa);
  84. BYTE* encrypt = (BYTE*)malloc(rsa_len);
  85. // RSA 1024 加密的明文分组长度不能超过 117, 生成的密文长度是 128
  86. // 且 RSA 1024 不支持分组加密,意味着只能自己使用循环进行加密。
  87. if (data_len > 117) return nullptr;
  88. // RSA_PKCS1_PADDING 填充方式,随机添加一些数据进行加密和解密
  89. RSA_public_encrypt(data_len, data, encrypt, rsa, RSA_PKCS1_PADDING);
  90. return encrypt;
  91. }
  92. // 解密数据,
  93. BYTE* rsa_decrypt(BYTE* data, RSA* rsa)
  94. {
  95. int rsa_len = RSA_size(rsa);
  96. BYTE* decrypt = (BYTE*)malloc(rsa_len);
  97. RSA_private_decrypt(rsa_len, data, decrypt, rsa, RSA_PKCS1_PADDING);
  98. return decrypt;
  99. }
  100. int main()
  101. {
  102. // 生成 rsa 密钥对并读取到 rsa 对象中
  103. generate_rsa_key();
  104. RSA* public_key = get_public_key();
  105. RSA* private_key = get_private_key();
  106. // 构建需要加密的文件
  107. BYTE plaintext[117] = { 0 };
  108. memset(plaintext, 'a', 117);
  109. // 加密和解密
  110. BYTE* encrypt = rsa_encrypt(plaintext, 117, public_key);
  111. BYTE* decrypt = rsa_decrypt(encrypt, private_key);
  112. return 0;
  113. }

02. tcp 客户端

  1. #include <iostream>
  2. #include <ws2tcpip.h>
  3. // 0. 进行套接字编程必须用到的头文件和库
  4. #include <winsock2.h>
  5. #pragma comment(lib,"ws2_32.lib")
  6. #define CLIENT_COUNT 100
  7. // 根据传入的布尔值进行相应的输出
  8. void check_result(bool b, LPCSTR msg)
  9. {
  10. // 参数一因该是一个表达式,如果表达式为 true 就输出
  11. if (b)
  12. {
  13. printf("error: %s\n", msg);
  14. ExitProcess(0);
  15. }
  16. }
  17. // 创建一个客户端进行连接
  18. DWORD CALLBACK create_client(LPVOID param)
  19. {
  20. // 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
  21. // - AF_INET: 表示使用网络传输协议
  22. // - SOCK_STREAM: 表示使用流式套接字(TCP)
  23. // - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
  24. SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  25. check_result(client == INVALID_SOCKET, "socket()");
  26. // 作为客户端来讲,每一个客户端套接字都会默认的分配端口
  27. // 连接到服务器,打电话(需要知道打给谁)
  28. sockaddr_in serveraddr = { AF_INET };
  29. serveraddr.sin_port = htons(0x1234);
  30. inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
  31. int result = connect(client, (sockaddr*)&serveraddr, sizeof(serveraddr));
  32. check_result(result == SOCKET_ERROR, "accept()");
  33. // 收发消息 send\recv,打电话
  34. while (true)
  35. {
  36. // 发送当前是第几个客户端到服务器
  37. send(client, (char*)&param, 4, 0);
  38. Sleep(500);
  39. }
  40. }
  41. int main()
  42. {
  43. // 初始化网络环境,搜索信号(3G? 4G?)
  44. WSAData wsadata = { 0 };
  45. int result = WSAStartup(0x0202, &wsadata);
  46. check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
  47. // 创建指定个数的 客户端
  48. for (int i = 0; i < CLIENT_COUNT; ++i)
  49. CreateThread(NULL, 0, create_client, (LPVOID)i, 0, NULL);
  50. // WaitForMultipleObjects 能够等待的句柄最多 64 个
  51. system("pause");
  52. // 清理网络环境,销户
  53. WSACleanup();
  54. return 0;
  55. }

03. tcp 服务端

  1. // TCP 是什么? 传输层的协议,主要用于数据的传输。TCP 是
  2. // 相对安全的,面向有连接的。确保数据的完整性。类似于打电
  3. // 话,会有 响铃 + 接通 + 交流 的过程。
  4. // UDP 是什么? 传输层的协议,主要用于数据的传输。UDP 是
  5. // 相对不稳定的,面向无连接的。类似于 BB 机,只会将消息发
  6. // 送出去,并不能保证消息一定会被接收到。
  7. // 如何确保消息发送给当前局域网内的某一个机器? 当计算机连
  8. // 接到网络中后,路由器会分配一个唯一的 ip 地址给具体的机,
  9. // 器,当信息传递给路由器的时候,会根据相应的 ip 地址传递给
  10. // 对应的设备。
  11. // 如何确保消息发送给当前系统的某一个程序? 每一个独立套接字
  12. // 都会拥有独一无二的端口,当系统接受到网络信息之后,会根据对
  13. // 应的端口将信息发送给指定的程序。
  14. // ip 和 端口 的组成是什么? ipv4 的地址实际上是一个四字节的
  15. // 整数数据,组成通常 x.x.x.x 的形式,假设存在 1.1.1.127 这个
  16. // ip 对应的四字节实际上是 0x0101017F。端口在计算机中通常有
  17. // 65535 个,实际可以由一个 WORD 来保存。
  18. // 数据在网络传输的过程中是如何存储的? 通常在个人计算机中,数据
  19. // 都是以小端方式存储的,在网络数据的传输过程中,规定应该使用大端
  20. // 的方式保存所有的数据。
  21. #include <iostream>
  22. #include <ws2tcpip.h>
  23. // 0. 进行套接字编程必须用到的头文件和库
  24. #include <winsock2.h>
  25. #pragma comment(lib,"ws2_32.lib")
  26. // 根据传入的布尔值进行相应的输出
  27. void check_result(bool b, LPCSTR msg)
  28. {
  29. // 参数一因该是一个表达式,如果表达式为 true 就输出
  30. if (b)
  31. {
  32. printf("error: %s\n", msg);
  33. ExitProcess(0);
  34. }
  35. }
  36. // 创建一个互斥体,用于互斥输出
  37. HANDLE Mutex = CreateMutex(NULL, FALSE, NULL);
  38. // 专门用于接受指定套接字的数据
  39. DWORD CALLBACK RecvThread(LPVOID param)
  40. {
  41. // 从参数获取到套接字
  42. SOCKET client = (SOCKET)param;
  43. INT number = { 0 };
  44. // 循环接受套接字传入的数据,recv 的返回值应该是接受
  45. // 到的数据长度,如果返回值 <= 0 就表示断开了连接
  46. while (recv(client, (char*)&number, 4, 0) > 0)
  47. {
  48. WaitForSingleObject(Mutex, INFINITE);
  49. printf("[%08X]: %d\n", GetCurrentThreadId(), number);
  50. ReleaseMutex(Mutex);
  51. }
  52. return 0;
  53. }
  54. int main()
  55. {
  56. // 1. 初始化网络环境,搜索信号(3G? 4G?)
  57. WSAData wsadata = { 0 };
  58. int result = WSAStartup(0x0202, &wsadata);
  59. check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
  60. // 2. 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
  61. // - AF_INET: 表示使用网络传输协议
  62. // - SOCK_STREAM: 表示使用流式套接字(TCP)
  63. // - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
  64. SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  65. check_result(server == INVALID_SOCKET, "socket()");
  66. // 3. 绑定套接字到对应的 ip:port,类似于选择手机号
  67. sockaddr_in serveraddr = { AF_INET };
  68. serveraddr.sin_port = htons(0x1234);
  69. // - 127.0.0.1 在大多数设备上指向的都是当前的主机
  70. inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
  71. result = bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
  72. check_result(result == SOCKET_ERROR, "bind()");
  73. // 4. 开启监听模式,设置监听的最大数量,把卡装到手机开机
  74. result = listen(server, SOMAXCONN);
  75. check_result(result == SOCKET_ERROR, "listen()");
  76. // 编写一个死循环,在循环内不断的接收客户端
  77. while (true)
  78. {
  79. // 5. 等待客户端的连接,接电话
  80. // - 等待哪一个套接字的连接、连接客户端的地址信息,结构体大小
  81. sockaddr_in clientaddr = { 0 };
  82. int length = sizeof(clientaddr);
  83. SOCKET client = accept(server, (sockaddr*)&clientaddr, &length);
  84. check_result(client == INVALID_SOCKET, "accept()");
  85. // 6. 为[每一个客户端]创建[单独]的线程,进行消息的接收 recv
  86. // - 如果没有创建线程,那么程序就会阻塞在 recv 函数,导致无法接受
  87. CreateThread(NULL, 0, RecvThread, (LPVOID)client, 0, NULL);
  88. }
  89. // 7. 关闭套接字,挂断电话
  90. closesocket(server);
  91. // 8. 清理网络环境,销户
  92. WSACleanup();
  93. system("pause");
  94. return 0;
  95. }

04. iocp 服务端

  1. #include <iostream>
  2. #include <ws2tcpip.h>
  3. // 0. 进行套接字编程必须用到的头文件和库
  4. #include <winsock2.h>
  5. #pragma comment(lib,"ws2_32.lib")
  6. // 用于保存异步信息的结构体
  7. struct MYOVERLAPPED
  8. {
  9. OVERLAPPED overlapped;
  10. WSABUF wsabuf;
  11. };
  12. // 根据传入的布尔值进行相应的输出
  13. void check_result(bool b, LPCSTR msg)
  14. {
  15. // 参数一因该是一个表达式,如果表达式为 true 就输出
  16. if (b)
  17. {
  18. printf("error: %s\n", msg);
  19. ExitProcess(0);
  20. }
  21. }
  22. // 创建一个互斥体,用于互斥输出
  23. HANDLE Mutex = CreateMutex(NULL, FALSE, NULL);
  24. // 专门用于接受指定套接字的数据
  25. DWORD CALLBACK RecvThread(LPVOID param)
  26. {
  27. // 首先获取到参数传入的 iocp 对象
  28. HANDLE iocp = (HANDLE)param;
  29. DWORD read = 0;
  30. ULONG_PTR completion_key = NULL;
  31. MYOVERLAPPED* overlapped = nullptr;
  32. // 不断的接受队列中传入的消息
  33. while (true)
  34. {
  35. // 从完成端口队列获取信息
  36. BOOL result = GetQueuedCompletionStatus(
  37. iocp, // 从哪一个 iocp 获取
  38. &read, // 实际接收的数量
  39. &completion_key, // 在这里是产生消息的套接字
  40. // 接收的 OVERLAPPED** ,但实际刚才传入的是 OVERLAPPED*
  41. (LPOVERLAPPED*)&overlapped, // 重叠IO结构
  42. INFINITE); // 等待时长
  43. // 如果消息接收成功就输出
  44. if (result == TRUE && read > 0)
  45. {
  46. WaitForSingleObject(Mutex, INFINITE);
  47. printf("[%08X]: %d\n", GetCurrentThreadId(),
  48. *(int*)overlapped->wsabuf.buf);
  49. ReleaseMutex(Mutex);
  50. // 需要再次投递一个请求
  51. DWORD flags = 0;
  52. WSARecv((SOCKET)completion_key, // 接受谁的信息
  53. &overlapped->wsabuf, // 消息保存到哪里
  54. 1, // wsabuf 结构的数量
  55. NULL, // 对于异步IO,可以填写0
  56. &flags, // 对应 recv 的最后一个参数
  57. (LPWSAOVERLAPPED)overlapped,// 重叠 IO 结构体
  58. NULL); // 回调函数
  59. }
  60. }
  61. return 0;
  62. }
  63. int main()
  64. {
  65. // [1]. IOCP 是用有限的线程处理多个异步操作,线程数量通常是CPU核心 * 2
  66. SYSTEM_INFO system_info = { 0 };
  67. GetSystemInfo(&system_info);
  68. // [2]. 创建一个 IOCP 对象,维护所有的线程
  69. HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
  70. // [3]. 根据获取到的核心数量,创建 iocp 工作线程,传入 iocp 对象
  71. for (DWORD i = 0; i < system_info.dwNumberOfProcessors * 2; ++i)
  72. CreateThread(NULL, 0, RecvThread, (LPVOID)iocp, 0, NULL);
  73. // 1. 初始化网络环境,搜索信号(3G? 4G?)
  74. WSAData wsadata = { 0 };
  75. int result = WSAStartup(0x0202, &wsadata);
  76. check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
  77. // 2. 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
  78. // - AF_INET: 表示使用网络传输协议
  79. // - SOCK_STREAM: 表示使用流式套接字(TCP)
  80. // - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
  81. SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  82. check_result(server == INVALID_SOCKET, "socket()");
  83. // 3. 绑定套接字到对应的 ip:port,类似于选择手机号
  84. sockaddr_in serveraddr = { AF_INET };
  85. serveraddr.sin_port = htons(0x1234);
  86. // - 127.0.0.1 在大多数设备上指向的都是当前的主机
  87. inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
  88. result = bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
  89. check_result(result == SOCKET_ERROR, "bind()");
  90. // 4. 开启监听模式,设置监听的最大数量,把卡装到手机开机
  91. result = listen(server, SOMAXCONN);
  92. check_result(result == SOCKET_ERROR, "listen()");
  93. // 编写一个死循环,在循环内不断的接收客户端
  94. while (true)
  95. {
  96. // 5. 等待客户端的连接,接电话
  97. // - 等待哪一个套接字的连接、连接客户端的地址信息,结构体大小
  98. sockaddr_in clientaddr = { 0 };
  99. int length = sizeof(clientaddr);
  100. SOCKET client = accept(server, (sockaddr*)&clientaddr, &length);
  101. check_result(client == INVALID_SOCKET, "accept()");
  102. // [4]. 将每一个接收到的套接字都绑定到 iocp 上
  103. // - 参数三:完成键,通常用于保存产生消息的句柄(客户端句柄)
  104. CreateIoCompletionPort((HANDLE)client, iocp, client, 0);
  105. // WSARecv 对需要使用单独的 MYOVERLAPPED 结构体。
  106. MYOVERLAPPED* overlapped = new MYOVERLAPPED{ 0 };
  107. overlapped->wsabuf.len = 0x10;
  108. overlapped->wsabuf.buf = new CHAR[0x10]{ 0 };
  109. // [5]. 投递一个接收客户端数据的请求,使用 WSARecv,对于每一个
  110. DWORD flags = 0;
  111. WSARecv(client, // 接受谁的信息
  112. &overlapped->wsabuf, // 消息保存到哪里
  113. 1, // wsabuf 结构的数量
  114. NULL, // 对于异步IO,可以填写0
  115. &flags, // 对应 recv 的最后一个参数
  116. (LPWSAOVERLAPPED)overlapped,// 重叠 IO 结构体
  117. NULL); // 回调函数
  118. // 将请求投递到 IOCP 的队列中,一旦请求执行完毕,GetQueuedCompletionStatus
  119. // 就会从完成请求的队列中获取到这个投递的请求,在这个地方,主线程会被切换到工
  120. // 作线程,执行 GetQueuedCompletionStatus,错误的真正地址是在这里。
  121. }
  122. // 7. 关闭套接字,挂断电话
  123. closesocket(server);
  124. // 8. 清理网络环境,销户
  125. WSACleanup();
  126. system("pause");
  127. return 0;
  128. }
  129. // 使用 IOCP 的步骤: 主线程
  130. // 1. 创建一个 iocp 对象
  131. // 2. 根据 cpu 核心数量创建线程
  132. // 3. 接收客户端,将接收的客户端绑定到 iocp,即添加到对象队列中
  133. // 4. 投递一个 IO 请求,WSARecv 请求,即接收目标的信息
  134. // 每一个请求都应该对应有不同的 OVERLAPPED 结构体,且应该在堆空间
  135. // 使用 IOCP 的步骤: 工作线程
  136. // 1. GetQueuedCompletionStatus 从完成队列中取出消息
  137. // 2. 函数如果返回 FALSE 或者实际操作的字节 <=0 就失败
  138. // 3. 可以从 OVERLAPPED 中获取到想要的信息并输出
  139. // 4. 为了能够继续的获取下一次信息,需要再次投递请求

05. tcp 文件发送

  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <iostream>
  3. #include <ws2tcpip.h>
  4. // 0. 进行套接字编程必须用到的头文件和库
  5. #include <winsock2.h>
  6. #pragma comment(lib,"ws2_32.lib")
  7. // windows 中检查了一些重复包含
  8. #include <windows.h>
  9. #include "function.h"
  10. #define SECTION_SIZE 10240
  11. // 发送文件的时候分成两个步骤:1 发送文件的请求头(基本信息)
  12. typedef struct _FILE_HEADER
  13. {
  14. CHAR filename[MAX_PATH] = { 0 }; // 文件的名称
  15. SIZE_T filesize = 0; // 文件的大小
  16. DWORD section_count = 0; // 接收整个文件需要的次数
  17. CHAR sig[128] = { 0 }; // 签名
  18. } FILE_HEADER, *PFILE_HEADER;
  19. // 发送文件的时候分成两个步骤:2 发送文件的请求体(n个,第几个区块,以及内容)
  20. typedef struct _FILE_SECTION
  21. {
  22. int index = 0; // 文件的大小
  23. DWORD size = 0; // 一次发送的大小
  24. CHAR data[SECTION_SIZE] = { 0 }; // 发送的具体数据
  25. } FILE_SECTION, * PFILE_SECTION;
  26. // 根据传入的布尔值进行相应的输出
  27. void check_result(bool b, LPCSTR msg)
  28. {
  29. // 参数一因该是一个表达式,如果表达式为 true 就输出
  30. if (b)
  31. {
  32. printf("error: %s\n", msg);
  33. ExitProcess(0);
  34. }
  35. }
  36. int main()
  37. {
  38. // 1. 初始化网络环境,搜索信号(3G? 4G?)
  39. WSAData wsadata = { 0 };
  40. int result = WSAStartup(0x0202, &wsadata);
  41. check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
  42. // 2. 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
  43. // - AF_INET: 表示使用网络传输协议
  44. // - SOCK_STREAM: 表示使用流式套接字(TCP)
  45. // - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
  46. SOCKET sender = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  47. check_result(sender == INVALID_SOCKET, "socket()");
  48. // 3. 绑定套接字到对应的 ip:port,类似于选择手机号
  49. sockaddr_in serveraddr = { AF_INET };
  50. serveraddr.sin_port = htons(0x1234);
  51. // - 127.0.0.1 在大多数设备上指向的都是当前的主机
  52. inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
  53. result = bind(sender, (sockaddr*)&serveraddr, sizeof(serveraddr));
  54. check_result(result == SOCKET_ERROR, "bind()");
  55. // 4. 开启监听模式,设置监听的最大数量,把卡装到手机开机
  56. result = listen(sender, SOMAXCONN);
  57. check_result(result == SOCKET_ERROR, "listen()");
  58. // 5. 等待客户端的连接,接电话
  59. // - 等待哪一个套接字的连接、连接客户端的地址信息,结构体大小
  60. sockaddr_in clientaddr = { 0 };
  61. int length = sizeof(clientaddr);
  62. SOCKET reciver = accept(sender, (sockaddr*)&clientaddr, &length);
  63. check_result(reciver == INVALID_SOCKET, "accept()");
  64. // 6. 打开需要发送的文件,获取相关的信息填充到结构体中
  65. HANDLE file = CreateFileA("demo.exe", GENERIC_READ, FILE_SHARE_READ,
  66. NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  67. check_result(file == INVALID_HANDLE_VALUE, "CreateFileA()");
  68. SIZE_T filesize = GetFileSize(file, NULL);
  69. DWORD sectioncount = filesize % SECTION_SIZE == 0 ?
  70. filesize / SECTION_SIZE : (filesize / SECTION_SIZE) + 1;
  71. FILE_HEADER file_header = { "demo.exe", filesize, sectioncount };
  72. calc_file_sig("demo.exe", file_header.sig);
  73. send(reciver, (CHAR*)&file_header, sizeof(file_header), 0);
  74. // 7. 根据计算出的区块数量,循环发送每一个区块
  75. for (DWORD i = 0; i < sectioncount; ++i)
  76. {
  77. // 7.1 创建缓冲区用于保存需要发送的区块
  78. FILE_SECTION section = { i };
  79. // 7.2 修改文件指针指向每一个区块的首地址
  80. SetFilePointer(file, i * SECTION_SIZE, 0, FILE_BEGIN);
  81. // 7.3 从指定的位置读取数据保存并发送
  82. ReadFile(file, section.data, SECTION_SIZE, &section.size, NULL);
  83. //printf("%d:%d\n", i, section.size);
  84. send(reciver, (CHAR*)&section, sizeof(section), 0);
  85. }
  86. // bug 产生的原因是过早的关闭了服务器(发送端)的套接字,如果已经关闭了
  87. // 但是客户端还没有接收完数据,就会直接导致丢包
  88. CloseHandle(file);
  89. system("pause");
  90. return 0;
  91. }

06. tcp 文件接收

  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <iostream>
  3. #include <ws2tcpip.h>
  4. // 0. 进行套接字编程必须用到的头文件和库
  5. #include <winsock2.h>
  6. #pragma comment(lib,"ws2_32.lib")
  7. #include <windows.h>
  8. #include "function.h"
  9. #define SECTION_SIZE 10240
  10. // 发送文件的时候分成两个步骤:1 发送文件的请求头(基本信息)
  11. typedef struct _FILE_HEADER
  12. {
  13. CHAR filename[MAX_PATH] = { 0 }; // 文件的名称
  14. SIZE_T filesize = 0; // 文件的大小
  15. DWORD section_count = 0; // 接收整个文件需要的次数
  16. CHAR sig[128] = { 0 }; // 签名
  17. } FILE_HEADER, * PFILE_HEADER;
  18. // 发送文件的时候分成两个步骤:2 发送文件的请求体(n个,第几个区块,以及内容)
  19. typedef struct _FILE_SECTION
  20. {
  21. int index = 0; // 文件的大小
  22. DWORD size = 0; // 一次发送的大小
  23. CHAR data[SECTION_SIZE] = { 0 }; // 发送的具体数据
  24. } FILE_SECTION, * PFILE_SECTION;
  25. // 根据传入的布尔值进行相应的输出
  26. void check_result(bool b, LPCSTR msg)
  27. {
  28. // 参数一因该是一个表达式,如果表达式为 true 就输出
  29. if (b)
  30. {
  31. printf("error: %s\n", msg);
  32. ExitProcess(0);
  33. }
  34. }
  35. int main()
  36. {
  37. // 初始化网络环境,搜索信号(3G? 4G?)
  38. WSAData wsadata = { 0 };
  39. int result = WSAStartup(0x0202, &wsadata);
  40. check_result(result != 0 || wsadata.wVersion != 0x0202, "WSAStartup()");
  41. // 创建套接字[IP:PORT],选择服务商(联通? 电信? 移动)
  42. // - AF_INET: 表示使用网络传输协议
  43. // - SOCK_STREAM: 表示使用流式套接字(TCP)
  44. // - IPPROTO_TCP 表示 TCP, IPPROTO_UDP 表示 UDP
  45. SOCKET reciver = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  46. check_result(reciver == INVALID_SOCKET, "socket()");
  47. // 作为客户端来讲,每一个客户端套接字都会默认的分配端口
  48. // 连接到服务器,打电话(需要知道打给谁)
  49. sockaddr_in serveraddr = { AF_INET };
  50. serveraddr.sin_port = htons(0x1234);
  51. inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
  52. result = connect(reciver, (sockaddr*)&serveraddr, sizeof(serveraddr));
  53. check_result(result == SOCKET_ERROR, "accept()");
  54. // 直接接收服务器发送过来的文件头信息
  55. FILE_HEADER file_header = { 0 };
  56. recv(reciver, (CHAR*)&file_header, sizeof(file_header), 0);
  57. // 使用接收到的名称创建文件
  58. HANDLE file = CreateFileA("demo1.exe", GENERIC_WRITE, NULL,
  59. NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  60. check_result(file == INVALID_HANDLE_VALUE, "CreateFileA()");
  61. // 根据接收到的轮数,循环接收整个文件
  62. for (DWORD i = 0; i < file_header.section_count; ++i)
  63. {
  64. // 7.1 创建缓冲区用于接收块的信息
  65. FILE_SECTION section = { 0 };
  66. int n = recv(reciver, (CHAR*)&section, sizeof(section), 0);
  67. // 7.2 修改文件指针指向接收到的数据对应文职
  68. SetFilePointer(file, section.index * SECTION_SIZE, 0, FILE_BEGIN);
  69. // 7.3 将数据写到指定的位置
  70. DWORD aaa;
  71. if (section.size == 0)
  72. break;
  73. //printf("%d:%d\n", section.index, section.size);
  74. WriteFile(file, section.data, section.size, &aaa, NULL);
  75. }
  76. CloseHandle(file);
  77. if (verify_file_sig("demo1.exe", file_header.sig))
  78. printf("签名校验失败\n");
  79. else
  80. printf("签名校验成功\n");
  81. system("pause");
  82. // 清理网络环境,销户
  83. WSACleanup();
  84. return 0;
  85. }