puerto - protocolo udp ejemplos
¿Puedes vincular() y conectar() ambos extremos de una conexión UDP? (7)
Realmente la clave es connect()
:
Si el socket sockfd es del tipo SOCK_DGRAM, addr es la dirección a la que se envían los datagramas por defecto, y la única dirección desde la que se reciben los datagramas.
Estoy escribiendo un sistema de cola de mensajes punto a punto, y debe poder operar a través de UDP. Podría elegir arbitrariamente un lado o el otro para ser el "servidor", pero no parece del todo correcto, ya que ambos extremos envían y reciben el mismo tipo de datos del otro.
¿Es posible enlazar () y conectar () ambos extremos para que se envíen / reciban solo el uno del otro? Parece una forma muy simétrica de hacerlo.
UDP no tiene conexión, por lo que tiene poco sentido que el sistema operativo realmente establezca algún tipo de conexión.
En los zócalos BSD, uno puede hacer una conexión en un socket UDP, pero esto básicamente solo establece la dirección de destino predeterminada para el send
(en lugar de ello, explícitamente a send_to
).
La vinculación en un socket UDP le dice al sistema operativo para el que la dirección entrante realmente acepta paquetes (todos los paquetes a otras direcciones se eliminan), independientemente del tipo de socket.
Al recibirlo, debe usar recvfrom para identificar de qué fuente proviene el paquete. Tenga en cuenta que si desea algún tipo de autenticación, usar solo las direcciones involucradas es tan inseguro como no tener ningún bloqueo. Las conexiones TCP pueden ser secuestradas y el UDP desnudo literalmente tiene suplantación de IP escrita por completo. Debe agregar algún tipo de HMAC
Me gustaría ver más desde la idea de lo que proporciona UDP. UDP es un encabezado de 8 bytes que agrega puertos de envío y recepción de 2 bytes (4 bytes en total). Estos puertos interactúan con Berkeley Sockets para proporcionar su interfaz de socket tradicional. Es decir, no puede vincularse a una dirección sin un puerto o viceversa.
Normalmente, cuando envía un paquete UDP, el puerto del lado de recepción (fuente) es efímero y el puerto del lado de envío (destino) es su puerto de destino en la computadora remota. Puede vencer este comportamiento predeterminado vinculando primero y luego conectándose. Ahora su puerto de origen y de destino sería el mismo siempre que los mismos puertos estén libres en ambas computadoras.
En general, este comportamiento (llamémoslo puerto de secuestro) está mal visto. Esto se debe a que acaba de limitar su lado de envío para que solo pueda enviar desde un proceso, en lugar de trabajar dentro del modelo efímero que asigna dinámicamente puertos de origen de envío.
Incidentalmente, los otros cuatro bytes de una carga útil UDP de ocho bytes, longitud y CRC son bastante inútiles, ya que ya se proporcionan en el paquete IP y un encabezado UDP es de longitud fija. Al igual que las personas, las computadoras son bastante buenas para hacer una pequeña resta.
Aquí hay un programa que muestra cómo enlazar () y conectar () en el mismo socket UDP a un conjunto específico de puertos de origen y destino, respectivamente. El programa se puede compilar en cualquier máquina Linux y tiene el siguiente uso:
usage: ./<program_name> dst-hostname dst-udpport src-udpport
Probé este código abriendo dos terminales. Debería poder enviar un mensaje al nodo de destino y recibir mensajes de él.
En la terminal 1 ejecutar
./<program_name> 127.0.0.1 5555 5556
En la terminal 2 ejecutar
./<program_name> 127.0.0.1 5556 5555
Aunque lo probé en una sola máquina, creo que también debería funcionar en dos máquinas diferentes una vez que haya configurado las configuraciones correctas del firewall.
Aquí hay una descripción del flujo:
- Las sugerencias de configuración indicaban el tipo de dirección de destino como la de una conexión UDP
- Utilice getaddrinfo para obtener la estructura de información de la dirección dstinfo basada en el argumento 1, que es la dirección de destino y el argumento 2, que es el puerto de destino
- Cree un socket con la primera entrada válida en dstinfo
- Utilice getaddrinfo para obtener la estructura de información de direcciones srcinfo principalmente para los detalles del puerto de origen
- Use srcinfo para enlazar al socket obtenido
- Ahora conéctese a la primera entrada válida de dstinfo
- Si todo está bien, ingrese el ciclo
- El ciclo usa una selección para bloquear en una lista de descriptores de lectura que consiste en el socket STDIN y sockfd creado
- Si STDIN tiene una entrada, se envía a la conexión UDP de destino usando la función sendall
- Si se recibe MOE, se sale del ciclo.
- Si sockfd tiene algunos datos, se lee a través de recv
- Si recv devuelve -1, es un error que tratamos de decodificar con perror
- Si recv devuelve 0 significa que el nodo remoto ha cerrado la conexión. Pero creo que no tiene ninguna consecuencia con UDP a, que no tiene conexión.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define STDIN 0
int sendall(int s, char *buf, int *len)
{
int total = 0; // how many bytes we''ve sent
int bytesleft = *len; // how many we have left to send
int n;
while(total < *len) {
n = send(s, buf+total, bytesleft, 0);
fprintf(stdout,"Sendall: %s/n",buf+total);
if (n == -1) { break; }
total += n;
bytesleft -= n;
}
*len = total; // return number actually sent here
return n==-1?-1:0; // return -1 on failure, 0 on success
}
int main(int argc, char *argv[])
{
int sockfd;
struct addrinfo hints, *dstinfo = NULL, *srcinfo = NULL, *p = NULL;
int rv = -1, ret = -1, len = -1, numbytes = 0;
struct timeval tv;
char buffer[256] = {0};
fd_set readfds;
// don''t care about writefds and exceptfds:
// select(STDIN+1, &readfds, NULL, NULL, &tv);
if (argc != 4) {
fprintf(stderr,"usage: %s dst-hostname dst-udpport src-udpport/n");
ret = -1;
goto LBL_RET;
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM; //UDP communication
/*For destination address*/
if ((rv = getaddrinfo(argv[1], argv[2], &hints, &dstinfo)) != 0) {
fprintf(stderr, "getaddrinfo for dest address: %s/n", gai_strerror(rv));
ret = 1;
goto LBL_RET;
}
// loop through all the results and make a socket
for(p = dstinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1) {
perror("socket");
continue;
}
/*Taking first entry from getaddrinfo*/
break;
}
/*Failed to get socket to all entries*/
if (p == NULL) {
fprintf(stderr, "%s: Failed to get socket/n");
ret = 2;
goto LBL_RET;
}
/*For source address*/
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM; //UDP communication
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
/*For source address*/
if ((rv = getaddrinfo(NULL, argv[3], &hints, &srcinfo)) != 0) {
fprintf(stderr, "getaddrinfo for src address: %s/n", gai_strerror(rv));
ret = 3;
goto LBL_RET;
}
/*Bind this datagram socket to source address info */
if((rv = bind(sockfd, srcinfo->ai_addr, srcinfo->ai_addrlen)) != 0) {
fprintf(stderr, "bind: %s/n", gai_strerror(rv));
ret = 3;
goto LBL_RET;
}
/*Connect this datagram socket to destination address info */
if((rv= connect(sockfd, p->ai_addr, p->ai_addrlen)) != 0) {
fprintf(stderr, "connect: %s/n", gai_strerror(rv));
ret = 3;
goto LBL_RET;
}
while(1){
FD_ZERO(&readfds);
FD_SET(STDIN, &readfds);
FD_SET(sockfd, &readfds);
/*Select timeout at 10s*/
tv.tv_sec = 10;
tv.tv_usec = 0;
select(sockfd + 1, &readfds, NULL, NULL, &tv);
/*Obey your user, take his inputs*/
if (FD_ISSET(STDIN, &readfds))
{
memset(buffer, 0, sizeof(buffer));
len = 0;
printf("A key was pressed!/n");
if(0 >= (len = read(STDIN, buffer, sizeof(buffer))))
{
perror("read STDIN");
ret = 4;
goto LBL_RET;
}
fprintf(stdout, ">>%s/n", buffer);
/*EOM/n implies user wants to exit*/
if(!strcmp(buffer,"EOM/n")){
printf("Received EOM closing/n");
break;
}
/*Sendall will use send to transfer to bound sockfd*/
if (sendall(sockfd, buffer, &len) == -1) {
perror("sendall");
fprintf(stderr,"%s: We only sent %d bytes because of the error!/n", argv[0], len);
ret = 5;
goto LBL_RET;
}
}
/*We''ve got something on our socket to read */
if(FD_ISSET(sockfd, &readfds))
{
memset(buffer, 0, sizeof(buffer));
printf("Received something!/n");
/*recv will use receive to connected sockfd */
numbytes = recv(sockfd, buffer, sizeof(buffer), 0);
if(0 == numbytes){
printf("Destination closed/n");
break;
}else if(-1 == numbytes){
/*Could be an ICMP error from remote end*/
perror("recv");
printf("Receive error check your firewall settings/n");
ret = 5;
goto LBL_RET;
}
fprintf(stdout, "<<Number of bytes %d Message: %s/n", numbytes, buffer);
}
/*Heartbeat*/
printf("./n");
}
ret = 0;
LBL_RET:
if(dstinfo)
freeaddrinfo(dstinfo);
if(srcinfo)
freeaddrinfo(srcinfo);
close(sockfd);
return ret;
}
Esta página contiene una gran información sobre enchufes conectados y no conectados: http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html
Esta cita responde a su pregunta:
Normalmente, es un cliente UDP que llama a connect, pero hay aplicaciones en las que el servidor UDP se comunica con un único cliente durante un período prolongado (por ejemplo, TFTP); en este caso, tanto el cliente como el servidor pueden llamar a connect.
No he usado connect () bajo UDP. Siento que connect () fue diseñado para dos propósitos totalmente diferentes bajo UDP vs TCP.
La página man contiene algunos detalles breves sobre el uso de connect () bajo UDP:
Generalmente, los zócalos del protocolo basado en conexión (como TCP) pueden conectarse () con éxito solo una vez; Los sockets de protocolo sin conexión (como UDP) pueden usar connect () varias veces para cambiar su asociación.
Hay un problema en tu código:
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM; //UDP communication
/*For destination address*/
if ((rv = getaddrinfo(argv[1], argv[2], &hints, &dstinfo))
Al usar solo AF_UNSPEC y SOCK_DGRAM, obtiene una lista de todos los posibles nombres. Entonces, cuando llamas al socket, la dirección que estás utilizando podría no ser tu UDP esperado. Deberías usar
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE;
en su lugar, para asegurarse de que el addrinfo que está recuperando es lo que quería.
En otras palabras, el socket que ha creado puede no ser un socket UDP, y esa es la razón por la que no funciona.