Principio e implementación paso a paso de la programación de sockets en Windows

Prefacio

Escribí programación de sockets hace unos años, pero no la usé durante mucho tiempo y me olvidé de esta tecnología. Recientemente, estaba estudiando el principio de ejecución de la carga útil de msf y tuve que usar tecnología de programación de sockets, por lo que se creó este artículo.

Este artículo no tiene nada que ver con la tecnología en msf. Es un artículo básico. Después de leer este artículo, continúe leyendo el principio de la carga útil de msf y sentirá una sensación de confusión.


Características de la programación de sockets bajo Windows

En comparación con la programación de sockets basada en Python, la programación de sockets en Windows es muy complicada. Se requieren varios pasos y se deben configurar varias estructuras de datos.
Python solo necesita dos líneas: el
Inserte la descripción de la imagen aquí
lenguaje C requiere muchas, muchas, muchas líneas:
Inserte la descripción de la imagen aquí
pero aquí todavía espero que todos sepan cómo escribir sockets en C, porque es más fácil entender qué es un socket.


Código (servidor)

Establecer el número de versión de la especificación de Windows Sock

Lo primero que necesita para programar un socket en Windows es establecer la versión del socket, que puede entenderse como la versión del protocolo que las dos partes quieren usar para que la conexión del socket se comunique. Este es el primer paso al crear un socket.

void set_editor() {
    
    
	printf("开始设置版本号");
	WSADATA	container; //一个结构体,可以存储我们希望使用‘windows Sock规范’的版本号与ws2_32.dll支持的‘windows Sock规范’的最高的版本号。
	WORD 		version_number; //存储版本号的容器

	version_number = MAKEWORD(2, 2); //设置我们希望使用的‘windows Sock规范’的版本号,从后往前看的,版本号2.2,若为MSKEWORD(1,2),则版本号为2.1

	if (WSAStartup(version_number, &container) < 0) {
    
    // 设置我们希望使用的‘windows Sock规范’的版本号,如果设置失败则返回值不是0,如果成功则返回0
		printf("ws2_32.dll is out of date.\n");
		WSACleanup();//放弃加载ws2_32.dllwin。    备注:socket连接必须加载这个dll
		exit(1);//退出程序,并输出错误码‘1’。
	}
	else
	{
    
    
		printf("win_socket规范版本设置成功\n");
	}
}

Defina los atributos relevantes del socket (ip, protocolo, etc.)

Tengo entendido que para crear un socket debe dividirse en dos pasos, primero use la función getaddrinfo para definir los atributos específicos del socket, como la compatibilidad con la versión ip, la conexión tcp o la conexión udp, el tipo de sock, etc. Luego use la función socket para crear un socket completo. El código central específico es el siguiente:

	struct addrinfo* result = NULL; //我的理解是result存储的是hints的地址,getaddrinfo函数会将hints的地址传给result。
	struct addrinfo hints; //存储着服务端的socket的相关详细数据。
	ZeroMemory(&hints, sizeof(hints)); //初始化hints

	hints.ai_family = AF_INET;//IPV4
	hints.ai_socktype = SOCK_STREAM;//tcp模式
	hints.ai_protocol = IPPROTO_TCP;//基于tcp协议


	printf("开始执行getaddrinfo\n");
	int Result = getaddrinfo("172.16.99.235", "55555", &hints, &result); //设置服务端的相关信息
	if (Result != 0)
	{
    
    
		printf("getaddrinfo失败,错误码为%ld\n", WSAGetLastError());
		safeexit();
	}

	printf("开始创建socket\n");
	SOCKET sockett;
	sockett = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//创建一个socket
	if (sockett == INVALID_SOCKET) {
    
    
		printf("socket创建失败 %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		safeexit();//这是我自己写等函数,用于安全退出socket。
	}

Enlazar puerto e ip

El puerto de enlace y el uso de ip son las funciones de enlace. El enlace puede entenderse como el enlace del socket que creamos a un puerto determinado. En este momento, otros hosts pueden acceder a este puerto y pueden acceder a nuestro puerto, lo que equivale a aplicar a nosotros Establezca una conexión de socket
Debido a que hemos definido la información de socket a través de getaddrinfo en el primer paso, utilícela directamente en este momento.

    printf("开始bind端口\n");
	Result = bind(sockett, result->ai_addr, (int)result->ai_addrlen);//将服务端的信息与socket进行绑定,相当于给socket指定一些详细信息,比如监听端口,使用协议等。
	if (Result == SOCKET_ERROR) {
    
    
		printf("绑定失败: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(sockett);
		safeexit();
	}
	freeaddrinfo(result);//释放result使用的内存

En cuanto a cómo provienen los parámetros en el enlace, puede consultar la documentación oficial de Microsoft. Solo proporciono una respuesta directa aquí.

Apéndice:
estructura de función getaddrinfo estructura
addrinfo estructura de
función de enlace estructura
sockaddr


Puerto de escucha

Después de vincular la información, es hora de abrir el puerto para escuchar:

	printf("开始listen\n");
	Result = listen(sockett,SOMAXCONN);//开始监听端口,并将允许创建的连接数设置到最大
	if (Result == SOCKET_ERROR) {
    
    
		printf("监听失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}

El conjunto es relativamente simple y nada especial.


Esperando la conexión

Cuando un host quiere conectarse con nosotros, necesitamos llamar a la función accept para aceptar la solicitud de conexión. El código específico es el siguiente:

    char* recbuf = NULL;
	char* sendbuf = NULL;
	recbuf = "welcome !!!\n";
	char* sockConnName = "Client";
	SOCKET another_socket = accept(sockett,NULL, NULL);//等待连接,连接成功后创建指定当前socket链接为another_socket,过去的sockett可以关闭。
	if (another_socket == INVALID_SOCKET) {
    
    
		printf("接受链接失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}
	closesocket(sockett);
	printf("连接建立成功,关闭socket,准备传输数据\n");

Apéndice: aceptar estructura de función

Déjame hablar al respecto. El significado de los dos parámetros después de accept es el mismo que el significado de los tres parámetros de bind. Es solo que usamos NULL aquí para imponer restricciones en el host al que queremos vincular. Si quieres restringirlo, puedes La definición de un objeto de estructura addrinfo puede restringir el host con condiciones específicas para conectarse a este socket.

Recuerde: después de usar la función de aceptar, se generará una nueva conexión de socket y el socket creado antes se puede descartar usando la función closesocket.

enviar datos

Una vez completados todos los pasos anteriores, se ha establecido la conexión. En este momento, puede elegir enviar datos. El código específico es el siguiente:

	char* recbuf = NULL;
	char* sendbuf = NULL;
	recbuf = "welcome !!!\n";
	char* sockConnName = "Client";
	SOCKET another_socket = accept(sockett,NULL, NULL);//等待连接,连接成功后创建指定当前socket链接为another_socket,过去的sockett可以关闭。
	if (another_socket == INVALID_SOCKET) {
    
    
		printf("接受链接失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}
	printf("连接建立成功,关闭socket,准备传输数据\n");
	send(another_socket, recbuf, strlen(recbuf) + 1, 0);  // 发送显示欢迎信息
	int n = 20;
	char recvBuf[11] = "1234567890";
	int zonghe = 0;
	while (n--) {
    
        // 不断等待客户端请求的到来
		char* wel = NULL;
		wel = "Please enter the data:";
		printf("一共传输20次数据,还剩%d次:\n", n + 1);
		int num = recv(another_socket, recvBuf, 6, 0);
		printf("%s : %s\n", sockConnName, recvBuf);     // 接收信息
		printf("当前轮次接收数据量为:%d Byte\n", num);
		zonghe = zonghe + num;
		printf("一共接受数据量为:%d Byte\n", zonghe);
		send(another_socket, wel, strlen(wel) + 1, 0);  // 发送显示欢迎信息
		if (num == SOCKET_ERROR)
		{
    
    
			safeexit();
		}
	}


	printf("数据接收完毕,退出程序");
	shutdown(sockett, SD_SEND);
	safeexit();

Cabe mencionar aquí que las dos funciones envían y recv, los parámetros de estas dos funciones son iguales, son el descriptor de la conexión de socket establecida, el búfer que almacena los datos, la longitud de los datos enviados o recibidos, y el último Para el uso específico del bit de bandera, consulte este artículo . Generalmente, es mejor usar 0.


El código completo del servidor se proporciona a continuación. Cuando lo usa, puede usar el comando nc para conectarse directamente al número de puerto correspondiente:

#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"


void safeexit()
{
    
    
	WSACleanup();
	exit(1);
}
/* 设置windows Sock规范版本号 */
void set_editor() {
    
    
	printf("开始设置版本号");
	WSADATA	container; //一个结构体,可以存储我们希望使用‘windows Sock规范’的版本号与ws2_32.dll支持的‘windows Sock规范’的最高的版本号。
	WORD 		version_number; //存储版本号的容器

	version_number = MAKEWORD(2, 2); //设置我们希望使用的‘windows Sock规范’的版本号,从后往前看的,版本号2.2,若为MSKEWORD(1,2),则版本号为2.1

	if (WSAStartup(version_number, &container) < 0) {
    
    // 设置我们希望使用的‘windows Sock规范’的版本号,如果设置失败则返回值不是0,如果成功则返回0
		printf("ws2_32.dll is out of date.\n");
		WSACleanup();//放弃加载ws2_32.dllwin。    备注:socket连接必须加载这个dll
		exit(1);//退出程序,并输出错误码‘1’。
	}
	else
	{
    
    
		printf("win_socket规范版本设置成功\n");
	}
}

void llisten()
{
    
    
	struct addrinfo* result = NULL; //我的理解是result存储的是hints的地址,getaddrinfo函数会将hints的地址传给result。
	struct addrinfo hints; //存储着服务端的socket的相关详细数据。
	ZeroMemory(&hints, sizeof(hints)); //初始化hints

	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;


	printf("开始执行getaddrinfo\n");
	int Result = getaddrinfo("172.16.99.235", "55555", &hints, &result); //设置服务端的相关信息
	if (Result != 0)
	{
    
    
		printf("getaddrinfo失败,错误码为%ld\n", WSAGetLastError());
		safeexit();
	}

	printf("开始创建socket\n");
	SOCKET sockett;
	sockett = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//创建一个socket
	if (sockett == INVALID_SOCKET) {
    
    
		printf("socket创建失败 %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		safeexit();
	}
	printf("开始bind端口\n");
	Result = bind(sockett, result->ai_addr, (int)result->ai_addrlen);//将服务端的信息与socket进行绑定,相当于给socket指定一些详细信息,比如监听端口,使用协议等。
	if (Result == SOCKET_ERROR) {
    
    
		printf("绑定失败: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(sockett);
		safeexit();
	}
	freeaddrinfo(result);//释放result使用的内存

	printf("开始listen\n");
	Result = listen(sockett, SOMAXCONN);//开始监听端口,并将允许创建的连接数设置到最大
	if (Result == SOCKET_ERROR) {
    
    
		printf("监听失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}


	char* recbuf = NULL;
	char* sendbuf = NULL;
	recbuf = "welcome !!!\n";
	char* sockConnName = "Client";
	SOCKET another_socket = accept(sockett,NULL, NULL);//等待连接,连接成功后创建指定当前socket链接为another_socket,过去的sockett可以关闭。
	if (another_socket == INVALID_SOCKET) {
    
    
		printf("接受链接失败 %d\n", WSAGetLastError());
		closesocket(sockett);
		safeexit();
	}
	closesocket(sockett);
	printf("连接建立成功,关闭socket,准备传输数据\n");
	send(another_socket, recbuf, strlen(recbuf) + 1, 0);  // 发送显示欢迎信息
	int n = 20;
	char recvBuf[11] = "1234567890";
	int zonghe = 0;
	while (n--) {
    
        // 不断等待客户端请求的到来
		char* wel = NULL;
		wel = "Please enter the data:";
		printf("一共传输20次数据,还剩%d次:\n", n + 1);
		int num = recv(another_socket, recvBuf, 6, 0);
		printf("%s : %s\n", sockConnName, recvBuf);     // 接收信息
		printf("当前轮次接收数据量为:%d Byte\n", num);
		zonghe = zonghe + num;
		printf("一共接受数据量为:%d Byte\n", zonghe);
		send(another_socket, wel, strlen(wel) + 1, 0);  // 发送显示欢迎信息
		if (num == SOCKET_ERROR)
		{
    
    
			safeexit();
		}
	}


	printf("数据接收完毕,退出程序");
	shutdown(sockett, SD_SEND);
	safeexit();
}



int main()
{
    
    
	set_editor();
	llisten();
}


resultado de la operación:

servidor en windows
Inserte la descripción de la imagen aquí

Conéctese con nc en linux:
Inserte la descripción de la imagen aquí

Puede ingresar datos para su transmisión en linux:
Inserte la descripción de la imagen aquí

Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_41874930/article/details/108070886
Recomendado
Clasificación