Día 2: Programación de red de Windows - TCP

Empecé a aprender programación de red de Windows hoy, y siempre caía en los complicados parámetros de Windows cuando estaba aprendiendo, y me enredaba en estos. A partir de la explicación del docente, estos contenidos pertenecen a la fórmula fija, principalmente el aprendizaje de la lógica de la escritura. Recuérdate a ti mismo, pon tu energía en el lugar correcto y no desperdicies energía sin motivo.

Acerca del modo C/S

C: Cliente cliente:

  • Abra un canal de comunicación y conéctese a un puerto específico en el host del servidor
  • Envíe una solicitud de servicio al servidor, espere y reciba la respuesta y continúe realizando la solicitud
  • Cerrar el canal de comunicación y finalizar después de que finalice la solicitud

S: servidor servidor:

  • Primero, el servidor se inicia primero y proporciona los servicios correspondientes de acuerdo con la solicitud.
  • Abrir un canal de comunicación para recibir solicitudes en una determinada dirección y puerto
  • Espere a que las solicitudes de los clientes lleguen a este puerto
  • Se recibe una solicitud de servicio, se procesa la solicitud y se envía una señal de reconocimiento
  • Regrese al paso 2, esperando la solicitud de otro cliente
  • apagar el servidor

Acerca de TCP y UDP

TCP: sockets orientados a conexión

  • Los datos no se perderán durante la transferencia
  • transferir datos secuencialmente
  • No hay límite de datos en el proceso de transmisión.

UDP: sockets orientados a mensajes

  • Haga hincapié en las transferencias rápidas en lugar de las secuenciales
  • Los datos transmitidos pueden perderse o corromperse
  • Limite el tamaño de los datos transferidos cada vez
  • Los datos transmitidos están limitados por el límite de datos.

Acerca de la PI

Es un protocolo de Internet asignado a los usuarios para navegar por Internet.

Las direcciones IP comunes se dividen en dos categorías: IPv4 e IPv6

Acerca de los puertos

El número de secuencia asignado a los sockets para distinguir los sockets creados en el programa

Diferentes puertos corresponden a diferentes aplicaciones

Hay hasta 65536 puertos

Acerca del tipo de socket y la configuración del protocolo

SOCK_STREAM corresponde al protocolo TCP

SOCK_DGRAM corresponde a UDP

SOCK_RAW puede leer y escribir paquetes IP que no son procesados ​​por el kernel, evitando el mecanismo de procesamiento TCP/IP, y el datagrama transmitido puede transmitirse directamente al programa de aplicación que lo necesita

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

Dos estructuras de datos importantes:

struct sockaddr {
    u_short sa_family; //16 位地址类型 2 字节
    char sa_data[14]; //14 字节地址数据:ip + port
};
struct sockaddr_in {
    short sin_family; //16 位地址类型
    u_short sin_port; //16 位端口号 65535 2 的 16 次方
    struct in_addr sin_addr; //32 位 IP 地址 4 字节
    char sin_zero[8]; //8 字节填充
};

toma TCP

Servidor TCP

el código se muestra a continuación:

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

// error LNK2019: 无法解析的外部符号 __imp__accept@12,函数 _main 中引用了该符号
#pragma comment(lib,"ws2_32.lib")   // 导入库

int main()
{
	printf("TCP Server\n");
	// 0. 初始化网络库 加载套接字库 这是个标准做法,直接拷贝即可
	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;
	}

	// 1. 安装电话机
	// AF_INET指定了使用 IPv4 地址和 TCP/IP 协议栈进行网络通信。
	// 用于指定套接字的类型,表示基于流的传输协议,常用于 TCP 协议的网络通信。
	// 第三个参数由前两个决定。
	// 光标放在socket()函数上,摁下F1,转到官方文档
	SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,0);   //socket 函数创建一个绑定到特定传输服务提供者的套接字。
	if (INVALID_SOCKET == sockSrv)
	{
		printf("socket errorNum = %d\n", GetLastError());
		return -1;
	}
	
	// 2. 分配电话号码,填充参数
	SOCKADDR_IN addSrv;  // SOCKADDR_IN结构指定 AF_INET 地址系列的传输地址和端口。
	// 设置为任何IP
	addSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addSrv.sin_family = AF_INET;
	addSrv.sin_port = htons(6000);
	// 根据官方文档,这里需要强转 
	if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addSrv, sizeof(SOCKADDR))) // 绑定函数将本地地址与套接字相关联。
	{
		printf("bind errorNum = %d\n", GetLastError());
		return -1;
	}
	
	// 3. 监听 listen
	// 将套接字置于侦听传入连接的状态。
	if (SOCKET_ERROR == listen(sockSrv, 5))
	{
		printf("listen errorNum = %d\n", GetLastError());
		return -1;
	}

	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);
	while (true)
	{
		// 4.分配一台分机去处理客户端的连接
		printf("begin accept: \n");
		SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrCli,&len);
		printf("end accept: \n");
		char sendBuf[100] = { 0 };
		sprintf_s(sendBuf, 100, "Welcome %s to bingo!",inet_ntoa(addrCli.sin_addr));
		int iLen = send(sockConn,sendBuf,strlen(sendBuf),0);
		char recvBuf[100] = { 0 };
		iLen = recv(sockConn, recvBuf, 100, 0);
		printf("recvBuf = %s\n", recvBuf);
		// 关闭分机
		closesocket(sockConn);
	}
	// 关闭总机
	closesocket(sockSrv);
	WSACleanup();
	system("pause");
	return 0;
}

Cliente TCP

el código se muestra a continuación:

#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("10.134.142.81");
	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;
}

// 报错:严重性	代码	说明	项目	文件	行	禁止显示状态
// 错误	C4996	'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings	TCP Client	C : \Users\mi\Desktop\Code - C++\MyNetPro\TCP Client\TCP Client.cpp	38
// 我们需要把它 _WINSOCK_DEPRECATED 忽略掉 工程属性 C++预处理器添加即可

El resultado es el siguiente:

Acerca de Escuchar()

listen(sockSrv,5): Esta función especifica la cola de mensajes, puede haber hasta cinco, y el mensaje que llega después no podrá conectarse.

Acerca de los elementos de inicio de VS

Cuál está configurado como el elemento de inicio compilará el archivo C++ de ese proceso

Acerca del error: _WINSOCK_DEPRECATED 

// Error: Código de gravedad Descripción Elemento Línea de archivo Estado prohibido
// Error C4996 'inet_addr': Use inet_pton() o InetPton() en su lugar o defina _WINSOCK_DEPRECATED_NO_WARNINGS para deshabilitar las advertencias obsoletas de la API Cliente TCP C: \Users\mi\Desktop\ Code - C++\MyNetPro\TCP Client\TCP Client.cpp 38
// Necesitamos ignorar su preprocesador C++ de propiedad de proyecto _WINSOCK_DEPRECATED y agregarlo

Optimice la recepción y el envío para resolver el problema de la transmisión de datos de gran tamaño

Extremo de recepción:

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

remitente:

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

Supongo que te gusta

Origin blog.csdn.net/qq_61553520/article/details/130906188
Recomendado
Clasificación