你能 bind() 和 connect() UDP 连接的两端吗

2024-11-07 08:55:00
admin
原创
32
摘要:问题描述:我正在编写一个点对点消息队列系统,它必须能够通过 UDP 运行。我可以任意选择一端或另一端作为“服务器”,但这似乎不太合适,因为两端都在向另一端发送和接收相同类型的数据。是否可以在两端都 bind() 和 connect(),以便它们仅相互发送/接收?这似乎是一种很好的对称方法。解决方案 1:从遥远...

问题描述:

我正在编写一个点对点消息队列系统,它必须能够通过 UDP 运行。我可以任意选择一端或另一端作为“服务器”,但这似乎不太合适,因为两端都在向另一端发送和接收相同类型的数据。

是否可以在两端都 bind() 和 connect(),以便它们仅相互发送/接收?这似乎是一种很好的对称方法。


解决方案 1:

从遥远的未来,也就是 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,并且不使用套接字进行多个远程处理,那么您应该尝试一下。

解决方案 2:

UDP 是无连接的,因此对于操作系统来说,实际建立某种连接没有什么意义。

在 BSD 套接字中,可以在 UDP 套接字上执行connect,但这基本上只是设置了 的默认目标地址send(而不是明确给予send_to)。

UDP 套接字上的绑定会告诉操作系统哪个传入的本地接口地址实际接受数据包(所有发往其他地址的数据包都将被丢弃),而不管套接字的类型。

接收时,您必须使用recvfrom来识别数据包来自哪个源。请注意,如果您需要某种身份验证,那么仅使用所涉及的地址与根本没有锁定一样不安全。TCP 连接可能被劫持,而裸露的 UDP 实际上在其头部到处都写着 IP 欺骗。您必须添加某种 HMAC

解决方案 3:

以下程序演示了如何将同一 UDP 套接字上的一组特定源端口和目标端口分别 bind() 和 connect()。该程序可以在任何 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根据参数 1(目标地址)和参数 2(目标端口)获取地址信息结构dstinfo

  3. 使用dstinfo中的第一个有效条目创建一个套接字

  4. 使用 getaddrinfo 获取地址信息结构srcinfo主要用于源端口详细信息

  5. 使用srcinfo绑定到获取的套接字

  6. 现在连接到dstinfo的第一个有效条目

  7. 如果一切顺利,进入循环

  8. 循环使用 select 来阻塞读取描述符列表,该列表由创建的 STDIN 和 sockfd 套接字组成

  9. 如果 STDIN 有输入,则使用 sendall 函数将其发送到目标 UDP 连接

  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
",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
");
      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
", 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
");
      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
", 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
", 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
", 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!
");
         if(0 >= (len = read(STDIN, buffer, sizeof(buffer))))
         {
            perror("read STDIN");
            ret = 4;
            goto LBL_RET;
         }

         fprintf(stdout, ">>%s
", buffer);

         /*EOM
 implies user wants to exit*/
         if(!strcmp(buffer,"EOM
")){
            printf("Received EOM closing
");
            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!
", 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!
");
         /*recv will use receive to connected sockfd */
         numbytes = recv(sockfd, buffer, sizeof(buffer), 0);
         if(0 == numbytes){
            printf("Destination closed
");
            break;
         }else if(-1 == numbytes){
            /*Could be an ICMP error from remote end*/
            perror("recv");
            printf("Receive error check your firewall settings
");
            ret = 5;
            goto LBL_RET;
         }
         fprintf(stdout, "<<Number of bytes %d Message: %s
", numbytes, buffer);
      }

      /*Heartbeat*/
      printf(".
");
   }

   ret = 0;
LBL_RET:

   if(dstinfo)
      freeaddrinfo(dstinfo);

   if(srcinfo)
      freeaddrinfo(srcinfo);

   close(sockfd);

   return ret;
}

解决方案 4:

真正关键的是connect()

如果套接字 sockfd 的类型为 SOCK_DGRAM,则 addr 是默认发送数据报的地址,也是接收数据报的唯一地址。

解决方案 5:

您的代码存在问题:

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,您可以获得所有可能地址的列表。因此,当您调用 socket 时,您使用的地址可能不是您预期的 UDP 地址。您应该使用

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

而是要确保您检索的 addrinfo 是您想要的。

换句话说,您创建的套接字可能不是 UDP 套接字,这就是它不起作用的原因。

解决方案 6:

此页面包含有关连接套接字与未连接套接字的一些重要信息:
http ://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html

这句话回答了你的问题:

通常情况下,调用 connect 的是 UDP 客户端,但在某些应用程序中,UDP 服务器与单个客户端进行长时间通信(例如,TFTP);在这种情况下,客户端和服务器都可以调用 connect。

解决方案 7:

我会更多地从 UDP 提供的内容的角度来看待它。UDP 是一个 8 字节的标头,它添加了 2 字节的发送和接收端口(总共 4 字节)。这些端口与 Berkeley 套接字交互以提供您的传统套接字接口。即您不能绑定到没有端口的地址,反之亦然。

通常,当您发送 UDP 数据包时,接收端端口(源)是临时的,而发送端端口(目标)是远程计算机上的目标端口。您可以通过先绑定然后连接来取消此默认行为。现在,只要两台计算机上的相同端口都是空闲的,您的源端口和目标端口就会相同。

一般来说,这种行为(我们称之为端口劫持)是令人反感的。这是因为您刚刚将发送端限制为只能从一个进程发送,而不是在动态分配发送端源端口的临时模型中工作。

顺便说一句,八字节 UDP 有效负载中的其他四个字节、长度和 CRC 几乎完全没用,因为它们已在 IP 数据包中提供,并且 UDP 报头的长度是固定的。拜托,计算机非常擅长做一点减法。

解决方案 8:

我没有在 UDP 下使用过 connect()。我觉得 connect() 在 UDP 和 TCP 下的设计目的完全不同。

手册页对 UDP 下 connect() 的使用做了一些简要介绍:

通常,基于连接的协议(例如 TCP)套接字可能仅成功 connect() 一次;无连接协议(例如 UDP)套接字可能多次使用 connect() 来改变它们的关联。

解决方案 9:

是的,你可以。我也这么做。

您的用例是其中有用的:双方都充当客户端和服务器,并且双方都只有一个进程。

解决方案 10:

如果你是 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
", 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
", 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;
}
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用