Protocolo UDP y programación de comunicaciones.

1. Información general

La capa de transporte de Internet tiene dos protocolos principales, uno es TCP y el otro es UDP:
Protocolo TCP: Protocolo de control de transmisión (TCP, Transmission Control Protocol);
Protocolo UDP: Protocolo de datagrama de usuario (UDP, User Datagram Protocol).
La principal diferencia entre los dos:

  1. Conexión: TCP está orientado a la conexión, es decir, primero debe establecer una conexión con la otra parte antes de enviar y recibir datos; mientras que UDP puede enviar paquetes de datos IP encapsulados sin establecer una conexión.
  2. Fiabilidad: TCP es un protocolo de comunicación de capa de transporte orientado a conexión, fiable y basado en flujo de bytes; mientras que UDP proporciona comunicación sin conexión y no garantiza la fiabilidad de los paquetes de datos transmitidos. Es adecuado para transmitir una pequeña cantidad de datos a una UDP La confiabilidad de la transmisión es responsabilidad de la capa de aplicación. Los paquetes UDP no tienen campos de garantía de confiabilidad, garantía de secuencia y control de flujo, etc., y su confiabilidad es deficiente. Sin embargo, debido a que el protocolo UDP tiene menos opciones de control, la demora en el proceso de transmisión de datos es pequeña y la eficiencia de transmisión de datos es alta.Es adecuado para aplicaciones que no requieren alta confiabilidad o aplicaciones que pueden garantizar la confiabilidad, como DNS, TFTP, SNMP en espera.
  3. Formato de datos: TCP transmite flujos de datos sin el concepto de paquetes, mientras que UDP es un formato de mensaje, es decir, en forma de paquetes uno por uno.

2 Introducción a la programación UDP

2.1 Interfaz de programación

Hay muchos métodos e interfaces para la programación UDP, aquí solo presentamos la programación bajo Windows, y otros sistemas son similares.
La API de Windows proporciona la biblioteca de funciones de programación SOCKET, que se puede utilizar para la programación UDP.

2.2 Tipo de programación UDP

Cualquiera que haya hecho programación TCP debe saber que la programación TCP se puede dividir en dos categorías: servidor y cliente.
El servidor y el cliente son una definición de los roles de ambas partes para una transacción de comunicación. La parte que abre el puerto y espera a que otros programas se conecten (escuchen) es el servidor, y la parte que se conecta (conecta) activamente a un IP y puerto específicos Una parte es el cliente. Si un programa es un servidor o un cliente, o tanto un servidor como un cliente, debe determinarse de acuerdo con los requisitos funcionales de su propio programa.
Del mismo modo, la programación UDP también se divide en dos tipos: servidor y cliente, que también es la regla: la parte que abre el puerto de escucha y espera a que otros programas envíen datos a este puerto es el servidor, y la parte que envía datos activamente a una IP y puerto específico es el cliente.

2.3 Flujo del programa UDP

En general, el flujo de un programa UDP es el siguiente:

2.3.1 Flujo del programa del servidor UDP

  1. Cree un socket de comunicación SOCKET: utilice la función socket();
  2. Configure el puerto de escucha y la IP: utilice la función bind();
  3. Recibir datos del cliente: utilice la función recvfrom();
  4. Enviar datos al cliente: utilice la función sendto();
  5. Una vez finalizada la comunicación, cierre el socket: utilice la función closesocket();

2.3.2 Flujo del programa del cliente UDP

  1. Cree un socket de comunicación SOCKET: utilice la función socket();
  2. Envía datos al servidor: usa la función sendto();
  3. Recibir datos del servidor: utilice la función recvfrom();
  4. Una vez finalizada la comunicación, cierre el socket: utilice la función closesocket();

3 Precauciones y ejemplos de programación UDP

Ejemplo: dos computadoras se comunican con UDP, la IP del servidor es 192.168.1.100, el puerto de escucha UDP es 2000 y la IP del cliente es 192.168.1.200.

3.1 Programa de servidor

1) Crear un socket de comunicación SOCKET

SOCKET m_sockUdpRecv;
int m_nError;
if(m_sockUdpRecv==INVALID_SOCKET)
{
    
    
	m_sockUdpRecv = socket(AF_INET, SOCK_DGRAM, 0);
	if (m_sockUdpRecv == INVALID_SOCKET)
	{
    
    
		m_nError = WSAGetLastError();
		return -1;
	}
}

El segundo parámetro de la función de socket es SOCK_DGRAM, lo que significa crear un socket para la comunicación UDP.

2) Configure el puerto de escucha y la IP

SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(2000); //UDP监听端口为2000
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);	//INADDR_ANY表示允许任何IP进入
//如果希望仅允许特定IP进入,如192.168.1.200,那么需要修改为:
//addrSrv.sin_addr.S_un.S_addr = htonl(inet_ntoa(“192.168.1.200”));
int retVal = bind((SOCKET)m_sockUdpRecv, (const sockaddr*)(LPSOCKADDR)&addrSrv,
	(int)sizeof(SOCKADDR_IN));
if(retVal == SOCKET_ERROR)
{
    
    
	m_nError = WSAGetLastError();
	closesocket(m_sockUdpRecv);
	m_sockUdpRecv=INVALID_SOCKET;
	return -1;
}

De esta forma, el programa del lado del servidor abre una escucha UDP en el puerto 2000, que el cliente utiliza para enviar datos a este puerto.

3) Recibir datos del cliente

char pBuf[1024];	//接收缓冲区
int nLen=1024;	//希望接收的最大数据长度
struct sockaddr_in m_addrUdpFrom;	//用来接收对方信息
int addrlen = sizeof( struct sockaddr_in);
CString sREmoteInfo;
int ret;
ret = recvfrom(m_sockUdpRecv,(char*)pBuf,nLen,0,
	(struct sockaddr*)&m_addrUdpFrom,&addrlen);
if(ret>=0)
{
    
    
	//此时sRemoteInfo中记录了客户端的IP及使用端口,我们可以如下所示来查看
	sRemoteInfo.Format("%s:%d",
		inet_ntoa(m_addrUdpFrom.sin_addr),ntohs(m_addrUdpFrom.sin_port));
}

Cabe señalar aquí que, en circunstancias normales, la función recvfrom está bloqueada, es decir, a menos que se reciban los datos enviados por el cliente o se produzca un error de red, el programa siempre se quedará atascado en la sentencia recvfrom.
Si no desea que recvfrom se bloquee, una forma es establecer el tiempo de espera de recepción, es decir: antes de llamar a recvfrom, ejecute la siguiente instrucción

struct timeval tv_out;
tv_out.tv_sec = 1;//设置recvfrom超时时间为1秒
tv_out.tv_usec = 0;
setsockopt(m_sockUdpRecv,SOL_SOCKET,SO_RCVTIMEO,(const char*)&tv_out, sizeof(tv_out));

De esta forma, si recvfrom no espera la llegada de datos o un error de red dentro del tiempo de espera establecido, la función también finalizará y devolverá un error de red.
La función setsockopt utilizada anteriormente es una función que se utiliza para configurar opciones y parámetros de comunicación. Por ejemplo, queremos recibir una gran cantidad de datos, que pueden exceder el tamaño del búfer predeterminado de UDP. En este momento, podemos usar esta función para configurar la recepción. antes de recibir el tamaño del búfer

int nSendBuf=32*1024;//设置为32K
setsockopt(m_sockUdpRecv,SOL_SOCKET,SO_RCVBUF,(const char*)&nSendBuf,sizeof(int));

Cabe señalar que la cantidad máxima de bytes en un paquete de comunicación UDP es de 64K. Excluyendo la información de varios encabezados, los datos máximos disponibles son aproximadamente 65507 (busque artículos relacionados usted mismo si está interesado).

4) Enviar datos al cliente

Si el programa necesita enviar datos al cliente, entonces hemos obtenido la IP y el puerto de la otra parte (registrados en m_addrUdpFrom) a través de recvfrom ahora mismo, podemos enviar datos directamente a esta IP y puerto, es decir, usar directamente el m_addrUdpFrom parámetro para llamar a sendto, aquí lo registramos como modo de envío uno.
Otro punto es: el SOCKET usado para enviar puede usar el m_sockUdpRecv anterior, o puede usar socket() para crear uno, como el siguiente m_sockUdpSend, que es muy simple, y el código para crear un socket no se dará aquí.

char* pData;	//要发送的数据
int nLen=123;	//要发送的数据长度
int addrlen = sizeof( struct sockaddr_in);
int ret = sendto(m_sockUdpSend, (char*)pData, nLen, 0, (struct sockaddr*)&m_addrUdpFrom, addrlen);

Si el cliente no desea que el servidor envíe datos a su puerto de origen de datos, sino que los devuelva a otro puerto especificado (como el 3000), lo registramos como modo de envío 2, por lo que:

SOCKADDR_IN addrTo;
addrTo.sin_family = AF_INET;
addrTo.sin_port = htons(3000);
addrTo.sin_addr.s_addr = inet_addr( inet_ntoa(m_addrUdpFrom.sin_addr) );
int addrlen = sizeof( struct sockaddr_in);
int ret = sendto(m_sockUdpSend, (char*)pData, nLen, 0, (struct sockaddr*)&addrTo, addrlen);

De manera similar, si la cantidad de datos enviados es relativamente grande, puede establecer el tamaño del búfer de envío por adelantado:

int nSendBuf=32*1024;//设置为32K
setsockopt(m_sockUdpSend,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

5) Una vez finalizada la comunicación, cierre el enchufe.

Una vez finalizada toda la comunicación, el socket debe cerrarse para liberar recursos.

if(m_sockUdpRecv!=INVALID_SOCKET)
	closesocket(m_sockUdpRecv);
if(m_sockUdpSend!=INVALID_SOCKET)
	closesocket(m_sockUdpSend);

3.2 Programa cliente

1) Crear un socket de comunicación SOCKET

Igual que el anterior, no hay más discusión aquí.

2) Enviar datos al servidor

Cuando el cliente quiere enviar datos al servidor, debe conocer la IP del servidor y el puerto de escucha UDP del servidor de antemano, como 192.168.1.100:2000 en nuestro ejemplo:

SOCKADDR_IN addrTo;
addrTo.sin_family = AF_INET;
addrTo.sin_port = htons(2000);	//服务器UDP监听端口
addrTo.sin_addr.s_addr = inet_addr( inet_ntoa("192.168.1.100") );	//服务器IP
int addrlen = sizeof( struct sockaddr_in);
int ret = sendto(m_sockUdpSend, (char*)pData, nLen, 0, (struct sockaddr*)&addrTo, addrlen);

3) Recibir datos del servidor

Cuando el servidor envía datos como se mencionó anteriormente, recordamos dos modos. El programa de recepción del cliente correspondiente a estos dos modos es el siguiente:
Modo 1, el servidor envía directamente al puerto de envío del cliente, y el programa de recepción del cliente es:

char pBuf[1024];	//接收缓冲区
int nLen=1024;	//希望接收的最大数据长度
struct sockaddr_in m_addrUdpFrom;	//用来接收对方信息
int addrlen = sizeof( struct sockaddr_in);
CString sREmoteInfo;
int ret;
ret = recvfrom(m_sockUdpSend,(char*)pBuf,nLen,0,
	(struct sockaddr*)&m_addrUdpFrom,&addrlen);
if(ret>=0)
{
    
    
	//此时sRemoteInfo中记录了服务器端回送的IP及端口,我们可以如下所示来查看
	sRemoteInfo.Format("%s:%d",
		inet_ntoa(m_addrUdpFrom.sin_addr),ntohs(m_addrUdpFrom.sin_port));
}

Modo 2, el servidor envía datos a un puerto específico del cliente, luego, en este modo, nuestro cliente es el servidor, y el servidor anterior se puede usar para abrir el puerto de escucha UDP y esperar a que se envíen los datos.
Igual que el anterior, no hay más discusión aquí.

4) Una vez finalizada la comunicación, cierre el enchufe.

Igual que el anterior, no hay más discusión aquí.

4 Discusión sobre las medidas de confiabilidad de la capa de aplicación de la comunicación UDP

Como se mencionó anteriormente, UDP es una transmisión de datos no conectada y poco confiable. La confiabilidad de la transmisión UDP es responsable de la capa de aplicación. Es decir, el remitente solo es responsable de enviar datos, independientemente de si el receptor puede recibirlos. Esto nos obliga a Esta fiabilidad está asegurada en la propia aplicación.
En términos generales, si se envía un paquete pequeño de vez en cuando, la recepción y el envío de UDP son relativamente confiables. Por ejemplo, los programas de chat que usan UDP son muy adecuados. La mayor parte de la falta de fiabilidad de la transmisión UDP se produce cuando es necesario enviar y recibir varios paquetes de forma continua. Por ejemplo, ahora tenemos 1 Mbyte de datos (si decido, no usaré UDP para enviar 1 M bytes, elegiré TCP, pero a veces el usuario puede solicitar que UDP) debe enviarse a la otra parte. De acuerdo con lo anterior, un paquete UDP solo puede enviarse como máximo 60K, es decir, los datos de 1M byte, al menos deben dividirse en aproximadamente 18 paquetes Si los datos enviados por UDP son 1K cada vez, entonces debe dividirse en más paquetes, como 1024 paquetes para enviar.
Según la experiencia personal, la aplicación debe garantizar una transmisión de datos UDP confiable y se debe prestar atención a los siguientes puntos:

  1. Si el remitente envía varios paquetes UDP consecutivos, se debe agregar un retraso en el medio, de lo contrario, la otra parte puede perder paquetes. Por ejemplo, generalmente agrego Sleep(2) en medio de varias transmisiones UDP consecutivas. Este retraso es para el otra parte Un tiempo de procesamiento después de recibir un paquete, en cuanto a cuánto es apropiado este retraso, aún debe explorarlo usted mismo;
  2. Después de recibir un paquete, el extremo receptor debe procesarlo lo antes posible o entregarlo a otros subprocesos para que lo procesen lo antes posible. Si el tiempo de procesamiento después de recibir el paquete es demasiado largo, los paquetes UDP posteriores no se recibirán en tiempo, y el paquete se perderá.

Por supuesto, para garantizar una transmisión confiable, existe un método obvio, que es usar un modo de pregunta y respuesta, es decir, después del envío, la otra parte responderá un paquete dentro de un cierto período de tiempo si la recepción es exitoso, y luego enviar el siguiente paquete si es exitoso. Sin embargo, esto no se recomienda, porque esto perderá la alta eficiencia de transmisión del protocolo UDP. Es mejor usar TCP para la transmisión directamente, que es confiable y más eficiente que este UDP de preguntas y respuestas. Por lo tanto, el uso de "TCP confiable" o "programa UDP+ no confiable para garantizar la confiabilidad" depende completamente de los requisitos del usuario o de las ideas de diseño de su programa.

Supongo que te gusta

Origin blog.csdn.net/hangl_ciom/article/details/106969920
Recomendado
Clasificación