Можете ли вы связать() и подключить() оба конца UDP-соединения

Я пишу систему сообщений "точка-точка", и она должна работать над UDP. Я мог бы произвольно выбрать одну или несколько сторон, чтобы быть "сервером", но это не кажется совершенно правильным, поскольку оба конца отправляют и получают один и тот же тип данных от другого.

Возможно ли связать() и connect() оба конца, чтобы они отправляли/получали только друг от друга? Это похоже на хорошо симметричный способ сделать это.

Ответ 1

UDP не имеет соединения, поэтому ОС практически не имеет смысла устанавливать какое-либо соединение.

В BSD-сокетах можно выполнить connect через UDP-сокет, но это в основном просто устанавливает адрес назначения по умолчанию для send (вместо этого явно send_to).

Привязка к UDP-сокету сообщает ОС, для какого входящего адреса фактически принимать пакеты (все пакеты на другие адреса отбрасываются), независимо от типа сокета.

После получения вы должны использовать recvfrom чтобы определить, из какого источника поступает пакет. Обратите внимание, что если вам нужна какая-то аутентификация, то использование только соответствующих адресов небезопасно, поскольку никакой блокировки вообще нет. TCP-соединения могут быть перехвачены, и в обнаженном протоколе UDP буквально написано подделывание IP-адреса. Вы должны добавить какой-то HMAC

Ответ 2

Здесь представлена ​​программа, которая демонстрирует, как связывать() и connect() в одном и том же сокете UDP с определенным набором портов источника и получателя соответственно. Программа может быть скомпилирована на любой машине Linux и имеет следующее использование:

usage: ./<program_name> dst-hostname dst-udpport src-udpport

Я проверил этот код, открыв два терминала. Вы должны иметь возможность отправить сообщение в пункт назначения node и получать от него сообщения.

В терминале 1 запустите

./<program_name> 127.0.0.1 5555 5556

При запуске терминала 2

./<program_name> 127.0.0.1 5556 5555

Несмотря на то, что я тестировал его на одной машине, я думаю, что он также должен работать на двух разных компьютерах, как только вы настроите правильные настройки брандмауэра

Здесь описание потока:

  • В подсказках настройки указывался тип адреса назначения, как адрес UDP-соединения
  • Используйте getaddrinfo для получения структуры данных адреса dstinfo на основе аргумента 1, который является адресом назначения и аргументом 2, который является портом назначения
  • Создайте сокет с первой допустимой записью в dstinfo
  • Используйте getaddrinfo для получения структуры информации адреса srcinfo в основном для деталей порта источника.
  • Использовать srcinfo для привязки к полученному сокету
  • Теперь подключитесь к первой допустимой записи dstinfo
  • Если все хорошо вписывается в цикл
  • В цикле используется блок выбора для блокировки в списке дескрипторов чтения, который состоит из созданного сокета STDIN и sockfd.
  • Если STDIN имеет вход, он отправляется в UDP-соединение назначения с помощью функции sendall
  • Если EOM получен, цикл завершается.
  • Если sockfd имеет некоторые данные, он считывается через recv
  • Если recv возвращает -1, это ошибка, которую мы пытаемся декодировать с помощью perror
  • Если recv возвращает 0, это означает, что удаленный node закрыл соединение. Но я считаю, что не имеет никакого отношения к UDP a, который является бесконтактным.

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

Ответ 3

Привет из далекого будущего, которое является 2018 годом, до 2012 года.

На самом деле, есть причина, по которой connect() использует UDP-сокет на практике (хотя, по идее, благословенный POSIX этого не требует).

Обычный сокет UDP ничего не знает о своих будущих местах назначения, поэтому он выполняет поиск маршрута каждый раз, когда вызывается sendmsg().

Однако, если connect() вызывается заранее с определенным IP-адресом и портом удаленного получателя, ядро операционной системы сможет записать ссылку на маршрут и назначить ее сокету, что значительно ускоряет отправку сообщения при последующих sendmsg() вызовах sendmsg() не указывается получательпротивном случае предыдущая настройка будет игнорироваться), вместо этого выбирается настройка по умолчанию.

Посмотрите на строки с 1070 по 1171:

if (connected)
    rt = (struct rtable *)sk_dst_check(sk, 0);

if (!rt) {
    [..skip..]

    rt = ip_route_output_flow(net, fl4, sk);

    [..skip..]
}

До ядра Linux 4.18 эта функция в основном ограничивалась только семейством адресов IPv4. Тем не менее, начиная с 4.18-rc4 (и, будем надеяться, также и на ядре Linux версии 4.18), он также полностью функционирует с сокетами IPv6.

Это может быть источником серьезного выигрыша в производительности, хотя это будет сильно зависеть от используемой ОС. По крайней мере, если вы используете Linux и не используете сокет для нескольких удаленных обработчиков, вы должны попробовать.

Ответ 4

На самом деле ключ connect():

Если сокет sockfd имеет тип SOCK_DGRAM, то addr - это адрес, по которому по умолчанию отправляются датаграммы, и единственный адрес, из которого получены датаграммы.

Ответ 5

На этой странице содержится подробная информация о подключенных или несвязанных сокетах: http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html

Эта цитата отвечает на ваш вопрос:

Обычно клиент UDP вызывает соединение, но есть приложения, в которых UDP-сервер взаимодействует с одним клиентом в течение длительного времени (например, TFTP); в этом случае клиент и сервер могут вызвать соединение.

Ответ 6

Я не использовал connect() в UDP. Я считаю, что connect() был разработан для двух совершенно разных целей в UDP и TCP.

В справочной странице приведены краткие сведения об использовании connect() в UDP:

Как правило, сокеты на основе протокола (например, TCP) могут соединяться() успешно только один раз; (например, UDP) сокеты могут использовать connect() несколько раз, чтобы изменить их связь.

Ответ 7

В вашем коде есть проблема:

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

Используя только AF_UNSPEC и SOCK_DGRAM, вы получаете список всех возможных адронов. Таким образом, при вызове сокета адрес, который вы используете, может быть не вашим ожидаемым UDP. Вы должны использовать

hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE;

вместо этого убедитесь, что добавление, которое вы извлекаете, - это то, что вы хотели.

В другом слове сокет, который вы создали, может быть не сокет UDP, и именно по этой причине он не работает.

Ответ 8

Я бы посмотрел на это больше, чем идея UDP. UDP - это 8-байтовый заголовок, который добавляет 2-байтовые порты отправки и получения (всего 4 байта). Эти порты взаимодействуют с Berkeley Sockets для обеспечения традиционного интерфейса сокетов. То есть вы не можете привязываться к адресу без порта или наоборот.

Обычно, когда вы отправляете пакет UDP, принимающий боковой порт (источник) является эфемерным, а порт стороны отправки (получатель) - это порт назначения на удаленном компьютере. Вы можете победить это поведение по умолчанию, связав сначала, а затем подключившись. Теперь ваш исходный порт и порт назначения будут такими же, пока одни и те же порты свободны на обоих компьютерах.

В общем, это поведение (позвольте назвать его захват порта) нахмурилось. Это связано с тем, что вы просто ограничили свою сторону отправки только возможностью отправлять из одного процесса, а не работать в эфемерной модели, которая динамически распределяет исходные порты сторон отправки.

Кстати, остальные четыре байта полезной нагрузки, длины и CRC с 8 байтами UDP в значительной степени бесполезны, поскольку они уже предоставлены в IP-пакете, а UDP-заголовок фиксированной длины. Как и люди, компьютеры очень хорошо делают небольшое вычитание.

Ответ 9

Если вы любите с /c++, вы можете попробовать route_io

Это просто в использовании, создайте экземпляр, чтобы принять другую маршрутизацию порта для вашей функции.

Пример:

  void read_data(rio_request_t *req);
  void read_data(rio_request_t *req) {
  char *a = "CAUSE ERROR FREE INVALID";

  if (strncmp( (char*)req->in_buff->start, "ERROR", 5) == 0) {
    free(a);
  }
  // printf("%d,  %.*s\n", i++, (int) (req->in_buff->end - req->in_buff->start), req->in_buff->start);
  rio_write_output_buffer_l(req, req->in_buff->start, (req->in_buff->end - req->in_buff->start));
  // printf("%d,  %.*s\n", i++, (int) (req->out_buff->end - req->out_buff->start), req->out_buff->start);
}

int main(void) {

  rio_instance_t * instance = rio_create_routing_instance(24, NULL, NULL);
  rio_add_udp_fd(instance, 12345, read_data, 1024, NULL);
  rio_add_tcp_fd(instance, 3232, read_data, 64, NULL);

  rio_start(instance);

  return 0;
}