Можете ли вы связать () и подключить () оба конца UDP-соединения - PullRequest
32 голосов
/ 16 марта 2012

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

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

Ответы [ 10 ]

26 голосов
/ 16 марта 2012

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

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

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

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

17 голосов
/ 12 июля 2018

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

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

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

Однако, если connect() вызывается заранее с IP-адресом и портом конкретного удаленного получателя, ядро ​​операционной системы сможет записать ссылку на маршрут и назначить ее сокету , сделав егозначительно быстрее отправлять сообщение, если последующие 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 и не используете сокет для нескольких удаленных обработчиков, попробуйте.

14 голосов
/ 13 мая 2014

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

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

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

В терминале 1 работает

./<program_name> 127.0.0.1 5555 5556

В терминале 2 работает

./<program_name> 127.0.0.1 5556 5555

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

Вотописание потока:

  1. Подсказки по настройке указали тип адреса назначения как тип соединения UDP
  2. Используйте getaddrinfo для получения структуры информации об адресе dstinfo на основена аргументе 1, который является адресом назначения, и аргументе 2, который является портом назначения
  3. Создайте сокет с первой действительной записью в dstinfo
  4. Используйте getaddrinfo для получения адресаинформационная структура srcinfo в основном для сведений о порте источника
  5. Используйте srcinfo для привязки к полученному сокету
  6. Теперь подключитесь к первой действительной записи dstinfo
  7. Если все в порядке, войдите в цикл
  8. Цикл использует select для блокировки в списке дескрипторов чтения, который состоит из созданного сокета STDIN и sockfd
  9. Если вход STDIN имеет вход, он отправляется в UDP-соединение назначения с помощью функции sendall
  10. Если получен EOM, цикл завершается.
  11. Если у sockfd есть некоторые данные, они читаются через recv
  12. Если recv возвращает -1, это ошибка, мы пытаемся декодировать ее с помощью perror
  13. Если recv возвращает 0, это означает, что удаленный узел закрыл соединение.Но я полагаю, что это не имеет никакого отношения к 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;
}
5 голосов
/ 16 марта 2012

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

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

1 голос
/ 03 июля 2017

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

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, ипричина, почему это не работает.

1 голос
/ 28 мая 2017

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

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

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

1 голос
/ 05 марта 2017

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

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

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

0 голосов
/ 10 апреля 2019

ДА, вы можете. Я тоже это делаю.

И ваш вариант использования - тот, где это полезно: обе стороны действуют как клиент и сервер, и с обеих сторон только один процесс.

0 голосов
/ 21 июля 2018

Если вы любитель c / 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;
}
0 голосов
/ 13 мая 2014

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...