• windows系统编程2——内存管理和网络


    学习视频链接

    06网络的相关概念_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Fd4y1G7Td?p=6&vd_source=0471cde1c644648fafd07b54e303c905

    目录

    一、内存管理

    1.1 概念

    1.2 内存分区

    二、网络基础

    2.1 IP地址 

    2.2 网络分层

    2.3 网络程序寻址方式

    2.4 TCP协议

    2.5 TCP服务器/客户端创建过程

    2.6 UDP服务器/客户端创建过程

    三、测试 TCP

    3.1 测试连接

    3.2 测试收发数据

    3.3 多线程服务器连接多客户端

    3.4 select服务器


    一、内存管理

    1.1 概念

    1、进程的虚拟地址空间

    对于32位进程来说,这个地址空间是 4GB,因为 32 位指针可以拥有从 0x00000000 至
    0xFFFFFFF 之间的任何一个值。这使得一个指针能够拥有 4,294,967,296 个值中的一个值,它覆盖了一个进程的 4GB 虛拟空间的范围。对于 64 位进程来说,这个地址空间是 16EB (1TB = 1024GB, 1PB = 1024TB, 1EB = 1024PB) ,因为 64 位指针可以拥有从 0x0,000,000,000,000,000 至 0xF,FFF,FFF,FFF,FFF之间的任何值。这使得一个指针可以拥有18446744073709551616 个值中的一个值,它覆盖了一个进程的 16EB 虚拟空间的范围。这是相当大的一个范围

    32 位的 CPU 的寻址空间是 4G,所以虚拟内存的最大值为 4G,而 windows 操作系统把这 4G 分成 2 部分,即 3G 的用户空间和 1G 的系统空间,系统空间是各个进程所共享的,他存放的是操作系统及一些内核对象等,而用户空间是分配给各个进程使用的,用户空间包括用:程序代码和数据,堆,共享库,栈。

    2、分页

    分页的基本方法是,将地址空间分成许多的页。每页的大小由 CPU 决定,然后由操作系统选择页的大小。目前 Inter 系列的 CPU 支持 4KB 或 4MB 的页大小,而 PC 上目前都选择使用 4KB

    1.2 内存分区

    内核空间:

    内核空间表示运行在处理器最高级别的超级用户模式 (supervisor mode) 下的代码或数据,内核空间占用从 0xC0000000 到 0xFFFFFFFF 的 1GB 线性地址空间,内核线性地址空间由所有进程共享,但只有运行在内核态的进程才能访问,用户进程可以通过系统调用切换到内核态访问内核空间,进程运行在内核态时所产生的地址都属于内核空间。

    用户空间:

    用户空间占用从 0x00000000 到 0xFFFF 共 3GB 的线性地址空间,每个进程都有一个独立的 3GB 用户空间,所以用户空间由每个进程独有,但是内核线程没有用户空间,因为它不产生用户空间地址。另外子进程共享 (继承) 父进程的用户空间只是使用与父进程相同的用户线性地址到物理内存地址的映射关系,而不是共享父进程用户空间。运行在用户态和内核态的进程都可以访问用户空间。

    在用户空间内内存被分为:0x08048000 开始

     

    二、网络基础

    详细了解看视频和计算机网络相关内容,这里只列举一些简单的东西

    2.1 IP地址 

    2.2 网络分层

    各层使用的协议 

    2.3 网络程序寻址方式

    1、MAC 地址

    网络通信的最边缘便是 LAN(Local Area Network) 了,我们先来看在 LAN 中如何寻址的

    LAN 主要使用广播通信。在其内部,许多主机连在相同的通信通道上,通信时的关键问题是当争存在时如何决定谁使用通道。解决问题的协议属于链路层的子层,称为 MAC 子层。MAC 子层在 LAN 中特别重要,因为广播通信是由它控制的

    网络中的节点都有链路层地址。事实上,并不是节点有链路层地址,而是节点上的适配器有。链路层地址通常叫做 LAN 地址、物理地址或者 MAC 地址。MAC 地址的长度为 6 字节,共有 2 的 48 次方种可能取值。这个 6 字节地址通常以 16 进制表示,每个字节都用一对 16 进制数表示,如: E6-E9-00-17-BB-4B

    适配器在生产时就被永久性地安排了一个 MAC 地址,它记录在适配器的 ROM 中,是不可改变的。另外,MAC 地址空间是赋 IEEE 管理的,它保证所有适配器的 MAC 地址都不相同

    2、IP地址

    IP 地址就是给每个连接在 Internet 上的主机分配的一个 32bit 地址。 互联网上的 IP 地址统一由一个叫 "IANA" (Internet Assigned Numbers Authority,互联网网络号分配机构) 的组织来管理。

    各个厂家生产的网络系统和设备,如以太网、分组交换网等,它们相互之间不能互通,不能互通的主要原因是因为它们所传送数据的基本单元 (技术上称之为 “帧”) 的格式不同。IP 协议实际上是一套由软件程序组成的协议软件,它把各种不同"帧”统一转换成 "IP数据报" 格式,这种转换是因特网的一个最重要的特点,使所有各种计算机都能在因特网上实现互通,即具有 "开放性” 的特点。

    3、端口号

    按照 OSI 七层模型的描述,传输层提供进程 (应用程序) 通信的能力。为了标识通信实体中进行通信的进程 (应用程序),TCP/IP 协议提出了协议端口 (protocol port,简称端口) 的概念。

    端口是一种抽象的软件结构 (包括一些数据结构和 I/O 缓冲区) 。应用程序通过系统调用与某端口建立连接 (binding) 后,传输层传给该端口的数据都被相应的进程所接收,相应进程发给传输层的数据都通过该端口输出。

    端口用一个整数型标识符来表示,即端口号。端口号跟协议相关,TCP/IP 传输层的两个协议 TCP 和 UDP 是完全独立的两个软件模块,因此各自的端口号也相互独立。

    端口使用一个 16 位的数字来表示,它的范围是 0~65535,1024 以下的端口号保留给预定义的服务。例如:http 使用 80 端口。

    2.4 TCP协议

    三次握手

    1、第一次握手:建立连接。客户端发送连接请求报文段,将 SYN 位置为 1,Sequence Number为 x;然后,客户端进入 SYN _SEND 状态,等待服务器的确认;

    2、第二次握手:服务器收到 SYN 报文段。服务器收到客户端的 SYN 报文段,需要对这个 SYN 报文段进行确认,设置 Acknowledgment Number 为 x+1(Sequence Number+1);同时,自己还要发送 SYN 请求信息,将 SYN 位置为 1, Sequence Number 为 y;服务器端将上述所有信息放到一个报文段 (即 SYN+ ACK 报文段) 中,一并发送给客户端,此时服务器进入 SYN_RECV 状态;

    3、第三次握手:客户端收到服务器的 SYN+ACK 报文段。然后将 Acknowledgment Number 设置为 y+1,向服务器发送 ACK 报文段,这个报文段发送完毕以后,客户端和服务器端都进入 ESTABLISHED 状态,完成 TCP 三次握手。

    完成了三次握手,客户端和服务器端就可以开始传送数据。

    四次分手

    当客户端和服务器通过三次握手建立了 TCP 连接以后,当数据传送完毕,断开 TCP 连接

    1、第 1 次分手:主机1 (可以是客户端,也可以是服务器端),设置 Sequence Number 和 Acknowledgment Number,向主机 2 发送一个 FIN 报文段此时, 主机 1 进入 FIN_WAIT_1 状态;这表示主机 1 没有数据要发送给主机 2 了;

    2、第 2 次分手:主机 2 收到了主机 1 发送的 FIN 报文段,向主机 1 回一个 ACK 报文段,Acknowledgment Number 为 Sequence Number 加 1;主机 1 进入 FIN_WAIT_2 状态;主机 2 告诉主机 1,我也没有数据要发送了,可以进行关闭连接了;

    3、第 3 次分手:主机 2 向主机 1 发送 FIN 报文段,请求关闭连接,同时主机 2 进入 CLOSE_WAIT 状态;

    4、第 4 次分手:主机 1 收到主机 2 发送的 FIN 报文段,向主机 2 发送 ACK 报文段,然后主机 1 进入 TIME_ WAIT 状态;主机 2 收到主机 1 的 ACK 报文段以后,就关闭连接;此时,主机 1 等待 2MSL 后依然没有收到回复,则证明 Server 端已正常关闭,那好,主机 1 也可以关闭连接了

    2.5 TCP服务器/客户端创建过程

    2.6 UDP服务器/客户端创建过程

    三、测试 TCP

    3.1 测试连接

    1、头文件

    1. #ifndef _SOCKET_INIT_H_
    2. #define _SOCKET_INIT_H_
    3. #pragma once
    4. #pragma comment(lib, "ws2_32.lib")
    5. #include
    6. #include
    7. class SocketInit {
    8. public:
    9. SocketInit() {
    10. WORD sockVersion = MAKEWORD(2, 2);
    11. WSADATA wasData;
    12. if (WSAStartup(sockVersion, &wasData) != 0) {
    13. printf("动态链接库加载失败!\n");
    14. }
    15. }
    16. ~SocketInit() {
    17. WSACleanup();
    18. }
    19. };
    20. #endif // !_SOCKET_INIT_H_

    2、服务端

    1. #include "SocketInit.hpp"
    2. using namespace std;
    3. int main()
    4. {
    5. SocketInit socketInit; // 加载动态库
    6. // 创建监听套接字
    7. SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    8. if (sListen == SOCKET_ERROR) {
    9. printf("监听套接字创建失败!\n");
    10. return -1;
    11. }
    12. sockaddr_in sock_in;
    13. sock_in.sin_family = AF_INET; // 协议族
    14. sock_in.sin_port = htons(12306); // 端口号
    15. sock_in.sin_addr.S_un.S_addr = INADDR_ANY; // 获取本机的IP地址
    16. // 绑定套接字
    17. int ret = bind(sListen, (const sockaddr*)&sock_in, sizeof(sockaddr_in));
    18. if (ret == SOCKET_ERROR) {
    19. printf("绑定套接字失败!\n");
    20. closesocket(sListen);
    21. return -1;
    22. }
    23. // 监听
    24. if (listen(sListen, 10) == SOCKET_ERROR) {
    25. printf("监听失败!\n");
    26. closesocket(sListen);
    27. return -1;
    28. }
    29. sockaddr_in clientAddr;
    30. int nlen = sizeof(sockaddr_in);
    31. // 接受客户端的连接
    32. SOCKET sClient = accept(sListen, (sockaddr*)&clientAddr, &nlen);
    33. if (sClient == SOCKET_ERROR) {
    34. printf("接收客户端失败!\n");
    35. closesocket(sListen);
    36. return -1;
    37. }
    38. printf("与客户端建立连接...\n");
    39. getchar();
    40. return 0;
    41. }

    3、客户端

    1. #include "SocketInit.hpp"
    2. using namespace std;
    3. int main()
    4. {
    5. SocketInit socketInit; // 加载动态库
    6. // 创建监听套接字
    7. SOCKET sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    8. if (sClient == SOCKET_ERROR) {
    9. printf("监听套接字创建失败!\n");
    10. return -1;
    11. }
    12. sockaddr_in sock_in;
    13. sock_in.sin_family = AF_INET; // 协议族
    14. sock_in.sin_port = htons(12306); // 端口号
    15. sock_in.sin_addr.S_un.S_addr = inet_addr("192.168.3.165");
    16. if (connect(sClient, (const sockaddr*)&sock_in, sizeof(sock_in)) == SOCKET_ERROR) {
    17. printf("连接服务器失败!\n");
    18. return -1;
    19. }
    20. getchar();
    21. return 0;
    22. }

    过程如果报错,可以使用的解决方法:

    (4条消息) C4996: 'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS_小哈龙的博客-CSDN博客https://blog.csdn.net/qq_22642239/article/details/102456232先打开服务端,然后再打开客户端,运行结果:

    3.2 测试收发数据

    1. #include "SocketInit.hpp"
    2. using namespace std;
    3. int main()
    4. {
    5. SocketInit socketInit; // 加载动态库
    6. // 创建监听套接字
    7. SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    8. if (sListen == SOCKET_ERROR) {
    9. printf("监听套接字创建失败!\n");
    10. return -1;
    11. }
    12. sockaddr_in sock_in;
    13. sock_in.sin_family = AF_INET; // 协议族
    14. sock_in.sin_port = htons(12306); // 端口号
    15. sock_in.sin_addr.S_un.S_addr = INADDR_ANY; // 获取本机的IP地址
    16. // 绑定套接字
    17. int ret = bind(sListen, (const sockaddr*)&sock_in, sizeof(sockaddr_in));
    18. if (ret == SOCKET_ERROR) {
    19. printf("绑定套接字失败!\n");
    20. closesocket(sListen);
    21. return -1;
    22. }
    23. // 监听
    24. if (listen(sListen, 10) == SOCKET_ERROR) {
    25. printf("监听失败!\n");
    26. closesocket(sListen);
    27. return -1;
    28. }
    29. sockaddr_in clientAddr;
    30. int nlen = sizeof(sockaddr_in);
    31. // 接受客户端的连接
    32. SOCKET sClient = accept(sListen, (sockaddr*)&clientAddr, &nlen);
    33. if (sClient == SOCKET_ERROR) {
    34. printf("接收客户端失败!\n");
    35. closesocket(sListen);
    36. return -1;
    37. }
    38. printf("与客户端建立连接...\n");
    39. while (true) {
    40. char buff[1024] = { 0 };
    41. int result = recv(sClient, buff, 1024, 0);
    42. if (result > 0) {
    43. printf("接收到的数据:%s\n", buff);
    44. }
    45. else {
    46. printf("客户端断开连接!\n");
    47. break;
    48. }
    49. }
    50. closesocket(sListen);
    51. getchar();
    52. return 0;
    53. }
    1. #include "SocketInit.hpp"
    2. #define _WINSOCK_DEPRECATED_NO_WARNINGS
    3. using namespace std;
    4. int main()
    5. {
    6. SocketInit socketInit; // 加载动态库
    7. // 创建监听套接字
    8. SOCKET sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    9. if (sClient == SOCKET_ERROR) {
    10. printf("监听套接字创建失败!\n");
    11. return -1;
    12. }
    13. sockaddr_in sock_in;
    14. sock_in.sin_family = AF_INET; // 协议族
    15. sock_in.sin_port = htons(12306); // 端口号
    16. sock_in.sin_addr.S_un.S_addr = inet_addr("192.168.3.165");
    17. if (connect(sClient, (const sockaddr*)&sock_in, sizeof(sock_in)) == SOCKET_ERROR) {
    18. printf("连接服务器失败!\n");
    19. return -1;
    20. }
    21. while (true) {
    22. char buff[1024] = { 0 };
    23. gets_s(buff, 1024);
    24. send(sClient, buff, strlen(buff), 0);
    25. }
    26. closesocket(sClient);
    27. getchar();
    28. return 0;
    29. }

    3.3 多线程服务器连接多客户端

    1. #include "SocketInit.hpp"
    2. using namespace std;
    3. DWORD WINAPI ThreadProc(LPVOID lp) {
    4. SOCKET sClient = *(SOCKET*)lp;
    5. while (true) {
    6. char buff[1024] = { 0 };
    7. int result = recv(sClient, buff, 1024, 0);
    8. if (result > 0) {
    9. printf("接收到的数据:%s\n", buff);
    10. }
    11. else {
    12. printf("客户端断开连接!\n");
    13. break;
    14. }
    15. }
    16. return NULL;
    17. }
    18. int main()
    19. {
    20. SocketInit socketInit; // 加载动态库
    21. // 创建监听套接字
    22. SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    23. if (sListen == SOCKET_ERROR) {
    24. printf("监听套接字创建失败!\n");
    25. return -1;
    26. }
    27. sockaddr_in sock_in;
    28. sock_in.sin_family = AF_INET; // 协议族
    29. sock_in.sin_port = htons(12306); // 端口号
    30. sock_in.sin_addr.S_un.S_addr = INADDR_ANY; // 获取本机的IP地址
    31. // 绑定套接字
    32. int ret = bind(sListen, (const sockaddr*)&sock_in, sizeof(sockaddr_in));
    33. if (ret == SOCKET_ERROR) {
    34. printf("绑定套接字失败!\n");
    35. closesocket(sListen);
    36. return -1;
    37. }
    38. // 监听
    39. if (listen(sListen, 10) == SOCKET_ERROR) {
    40. printf("监听失败!\n");
    41. closesocket(sListen);
    42. return -1;
    43. }
    44. sockaddr_in clientAddr;
    45. int nlen = sizeof(sockaddr_in);
    46. while (true) {
    47. // 接受客户端的连接
    48. SOCKET sClient = accept(sListen, (sockaddr*)&clientAddr, &nlen);
    49. if (sClient == SOCKET_ERROR) {
    50. printf("接收客户端失败!\n");
    51. closesocket(sListen);
    52. return -1;
    53. }
    54. printf("与客户端建立连接...\n");
    55. CreateThread(NULL, NULL, ThreadProc, (LPVOID)&sClient, NULL, NULL);
    56. }
    57. closesocket(sListen);
    58. getchar();
    59. return 0;
    60. }

    3.4 select服务器

    1. #include "SocketInit.hpp"
    2. using namespace std;
    3. int main()
    4. {
    5. SocketInit socketInit; // 加载动态库
    6. // 创建监听套接字
    7. SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    8. if (sListen == SOCKET_ERROR) {
    9. printf("监听套接字创建失败!\n");
    10. return -1;
    11. }
    12. sockaddr_in sock_in;
    13. sock_in.sin_family = AF_INET; // 协议族
    14. sock_in.sin_port = htons(12306); // 端口号
    15. sock_in.sin_addr.S_un.S_addr = INADDR_ANY; // 获取本机的IP地址
    16. // 绑定套接字
    17. int ret = bind(sListen, (const sockaddr*)&sock_in, sizeof(sockaddr_in));
    18. if (ret == SOCKET_ERROR) {
    19. printf("绑定套接字失败!\n");
    20. closesocket(sListen);
    21. return -1;
    22. }
    23. // 监听
    24. if (listen(sListen, 10) == SOCKET_ERROR) {
    25. printf("监听失败!\n");
    26. closesocket(sListen);
    27. return -1;
    28. }
    29. FD_SET fd_read; // 存储SOCKET对象
    30. FD_ZERO(&fd_read); // 初始化
    31. FD_SET(sListen, &fd_read); // 将sListen套接字放到fd_read集合中
    32. while (true) {
    33. FD_SET fd_tmp = fd_read;
    34. const timeval tv = { 1, 0 };
    35. int ret = select(NULL, &fd_tmp, NULL, NULL, &tv);
    36. if (ret == 0) { // 如果没有网络事件
    37. Sleep(1);
    38. continue;
    39. }
    40. for (int i = 0; i < fd_tmp.fd_count; i++) {
    41. if (fd_tmp.fd_array[i] == sListen) { // 如果监听套接字有网络事件,证明有客户端正在连接服务器
    42. sockaddr_in clientAddr;
    43. int nlen = sizeof(sockaddr_in);
    44. // 接受客户端的连接
    45. SOCKET sClient = accept(sListen, (sockaddr*)&clientAddr, &nlen);
    46. if (sClient == SOCKET_ERROR) {
    47. printf("接收客户端失败!\n");
    48. closesocket(sListen);
    49. return -1;
    50. }
    51. printf("与客户端建立连接: %s ...\n", inet_ntoa(clientAddr.sin_addr));
    52. FD_SET(sClient, &fd_read);
    53. }
    54. else { // 如果监听套接字有网络事件,证明客户端正在发送数据,服务端接收数据
    55. char buff[1024] = { 0 };
    56. int result = recv(fd_tmp.fd_array[i], buff, 1024, 0);
    57. if (result > 0) {
    58. printf("接收到的数据:%s\n", buff);
    59. }
    60. else {
    61. // 从fd_read中移除当前SOCKET
    62. FD_CLR(fd_tmp.fd_array[i], &fd_read);
    63. printf("客户端断开连接!\n");
    64. break;
    65. }
    66. }
    67. }
    68. }
    69. closesocket(sListen);
    70. getchar();
    71. return 0;
    72. }
  • 相关阅读:
    用HTTP proxy module配置一个简单反向代理服务器
    使用html+css实现一个静态页面(含源码)
    系统配置与性能评价>性能指标
    解析加密货币:跨境电商支付将迎来颠覆?
    HTX 与 Zebec Protocol 展开深度合作,并将以质押者的身份参与 ZBC Staking
    机器学习教程
    FPGA工程师面试试题集锦121~130
    Apache DolphinScheduler & 亚马逊云科技联合Meetup: 基于云上的最佳实践
    云服务器ECS的简介
    基于人工智能的智能化指挥决策和控制
  • 原文地址:https://blog.csdn.net/HuanBianCheng27/article/details/126861375