你能绑定()和连接()UDP连接的两端

时间:2012-03-16 16:57:24

标签: c linux udp

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

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

10 个答案:

答案 0 :(得分:26)

UDP是无连接的,因此操作系统实际上没有任何意义上的连接。

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

在UDP套接字上绑定告诉操作系统实际接受数据包的传入地址(丢弃到其他地址的所有数据包),无论套接字的类型如何。

收到后,您必须使用recvfrom来确定数据包来自哪个来源。请注意,如果您需要某种身份验证,那么仅使用所涉及的地址就像没有锁定一样不安全。 TCP连接可能被劫持,裸体UDP确实在其头部写入了IP欺骗。您必须添加某种HMAC

答案 1 :(得分:17)

你好,从遥远的未来(即2018年)到2012年。

实际上,connect()在实践中使用UDP套接字是有原因的(尽管从理论上讲,有福的POSIX并不需要您这样做)。

普通的UDP套接字对其未来的目的地一无所知,所以it performs a route lookup each time sendmsg() is called

但是,如果事先使用特定远程接收者的IP和端口调用connect(),则操作系统内核将能够write down the reference to the route and assign it to the socket,如果随后的{{1 }}呼叫未指定接收方otherwise the previous setting would be ignored),而是选择默认接收方。

看看lines 1070 through 1171

sendmsg()

在Linux内核4.18之前,此功能大部分仅限于IPv4地址系列。但是,从4.18-rc4(以及希望的Linux内核版本4.18)开始,it's fully functional with IPv6 sockets as well

它可能是a serious performance benefit的来源,尽管它在很大程度上取决于您使用的操作系统。至少,如果您使用的是Linux并且不将套接字用于多个远程处理程序,则应尝试一下。

答案 2 :(得分:14)

这是一个程序,演示了如何将同一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. 中的第一个有效条目创建套接字
  5. 使用getaddrinfo获取地址信息结构 srcinfo 主要用于源端口详细信息
  6. 使用 srcinfo 绑定到获得的套接字
  7. 现在连接到 dstinfo
  8. 的第一个有效条目
  9. 如果一切顺利,请进入循环
  10. 循环使用select来阻止读取描述符列表,该列表由创建的STDIN和sockfd套接字组成
  11. 如果STDIN有输入,则使用sendall功能将其发送到目标UDP连接
  12. 如果收到EOM,则退出循环。
  13. 如果sockfd有一些数据,则通过recv
  14. 读取
  15. 如果recv返回-1,我们尝试用perror解码它是错误的
  16. 如果recv返回0,则表示远程节点已关闭连接。但我相信UDP a无连接。

  17. #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 :(得分:5)

关键是connect()

  

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

答案 4 :(得分:1)

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

这句话回答了你的问题:

  

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

答案 5 :(得分:1)

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

The man page详细介绍了UDP下的connect()用法:

  

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

答案 6 :(得分:1)

您的代码中存在问题:

<loading></loading>
<div>
<router-outlet></router-outlet>
</div>

仅使用AF_UNSPEC和SOCK_DGRAM,您将获得所有可能的地址列表。因此,当您调用socket时,您使用的地址可能不是您期望的UDP地址。你应该使用

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

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

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

答案 7 :(得分:0)

是的,可以。我也是。

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

答案 8 :(得分:-1)

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

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

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

顺便说一句,8字节UDP有效载荷,长度和CRC的其他四个字节几乎完全没用,因为它们已经在IP数据包中提供,UDP报头是固定长度的。就像人们一样,计算机相当擅长做一些减法。

答案 9 :(得分:-1)

如果您是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;
}