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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9741392/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:14:03  来源:igfitidea点击:

Can you bind() and connect() both ends of a UDP connection

clinuxudp

提问by gct

I'm writing a point-to-point message queue system, and it has to be able to operate over UDP. I could arbitrarily pick one side or the other to be the "server" but it doesn't seem quite right since both ends are sending and receiving the same type of data from the other.

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

Is it possible to bind() and connect() both ends so that they send/receive only from each other? That seems like a nicely symmetric way to do it.

是否可以绑定()和连接()两端以便它们仅从彼此发送/接收?这似乎是一种很好的对称方式。

回答by datenwolf

UDP is connectionless, so there's little sense for the OS in actually making some sort of connection.

UDP 是无连接的,因此操作系统实际上建立某种连接几乎没有意义。

In BSD sockets one can do a connecton a UDP socket, but this basically just sets the default destination address for send(instead giving explicitly to send_to).

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

Bind on a UDP socket tells the OS for which incoming address to actually accept packets (all packets to other addresses are dropped), regardless the kind of socket.

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

Upon receiving you must use recvfromto identify which source the packet comes from. Note that if you want some sort of authentication, then using just the addresses involved is as insecure as no lock at all. TCP connections can be hiHymaned and naked UDP literally has IP spoofing written all over its head. You must add some sort of HMAC

收到后,您必须使用recvfrom来识别数据包来自哪个来源。请注意,如果您想要某种身份验证,那么仅使用所涉及的地址与根本没有锁定一样不安全。TCP 连接可以被劫持,而裸 UDP 字面上写满了 IP 欺骗。您必须添加某种 HMAC

回答by Geoff Reedy

Really the key is connect():

真正的关键是connect()

If the socket sockfd is of type SOCK_DGRAM then addr is the address to which datagrams are sent by default, and the only address from which datagrams are received.

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

回答by Clarus

I'd look at it more from the idea of what UDP is providing. UDP is an 8 byte header which adds 2 byte send and receive ports (4 bytes total). These ports interact with Berkeley Sockets to provide your traditional socket interface. I.e. you can't bind to an address without a port or vice-versa.

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

Typically when you send a UDP packet the receive side port (source) is ephemeral and the send side port (destination) is your destination port on the remote computer. You can defeat this default behavior by binding first and then connecting. Now your source port and destination port would be the same so long as the same ports are free on both computers.

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

In general this behavior (let's call it port hiHymaning) is frowned upon. This is because you have just limited your send side to only being able to send from one process, as opposed to working within the ephemeral model which dynamically allocates send side source ports.

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

Incidentally, the other four bytes of an eight byte UDP payload, length and CRC are pretty much totally useless as they are already provided in the IP packet and a UDP header is fixed length. Like come on people, computers are pretty good at doing a little subtraction.

顺便提一下,八字节 UDP 有效载荷的其他四个字节、长度和 CRC 几乎完全没用,因为它们已经在 IP 数据包中提供,并且 UDP 标头是固定长度的。就像来吧人们一样,计算机非常擅长做一点减法。

回答by Conrad Gomes

Here's a program that demonstrates how to bind() and connect() on the same UDP socket to a specific set of source and destination ports respectively. The program can be compiled on any Linux machine and has the following usage:

这是一个程序,它演示了如何将同一 UDP 套接字上的 bind() 和 connect() 分别连接到一组特定的源端口和目标端口。该程序可以在任何Linux机器上编译,具有以下用途:

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

I tested this code opening two terminals. You should be able to send a message to the destination node and receive messages from it.

我测试了打开两个终端的这段代码。您应该能够向目标节点发送消息并从中接收消息。

In terminal 1 run

在终端 1 中运行

./<program_name> 127.0.0.1 5555 5556

In terminal 2 run

在终端 2 中运行

./<program_name> 127.0.0.1 5556 5555

Even though I've tested it on a single machine I think it should also work on two different machines once you've setup the correct firewall settings

即使我已经在一台机器上测试过它,我认为一旦你设置了正确的防火墙设置,它也应该可以在两台不同的机器上运行

Here's a description of the flow:

下面是对流程的描述:

  1. Setup hints indicated the type of destination address as that of a UDP connection
  2. Use getaddrinfo to obtain the address info structure dstinfobased on argument 1 which is the destination address and argument 2 which is the destination port
  3. Create a socket with the first valid entry in dstinfo
  4. Use getaddrinfo to obtain the address info structure srcinfoprimarily for the source port details
  5. Use srcinfoto bind to the socket obtained
  6. Now connect to the first valid entry of dstinfo
  7. If all is well enter the loop
  8. The loop uses a select to block on a read descriptor list which consists of the STDIN and sockfd socket created
  9. If STDIN has an input it is sent to the destination UDP connection using sendall function
  10. If EOM is received the loop is exited.
  11. If sockfd has some data it is read through recv
  12. If recv returns -1 it is an error we try to decode it with perror
  13. If recv returns 0 it means the remote node has closed the connection. But I believe has no consequence with UDP a which is connectionless.
  1. 设置提示将目标地址的类型指示为 UDP 连接的类型
  2. 使用getaddrinfo根据参数1是目的地址和参数2是目的端口获取地址信息结构dstinfo
  3. 使用dstinfo 中的第一个有效条目创建一个套接字
  4. 使用 getaddrinfo 获取地址信息结构srcinfo主要用于源端口详细信息
  5. 使用srcinfo绑定到获得的socket
  6. 现在连接到dstinfo的第一个有效条目
  7. 如果一切顺利进入循环
  8. 该循环使用一个选择来阻止读取描述符列表,该列表由创建的 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\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;
}

回答by konrad

This page contains some great info about connected versus unconnected sockets: http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html

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

This quote answers your question:

这句话回答了你的问题:

Normally, it is a UDP client that calls connect, but there are applications in which the UDP server communicates with a single client for a long duration (e.g., TFTP); in this case, both the client and server can call connect.

通常是UDP客户端调用connect,但也有UDP服务器与单个客户端长时间通信的应用(如TFTP);在这种情况下,客户端和服务器都可以调用连接。

回答by Bin TAN - Victor

I have not used connect() under UDP. I feel connect() was designed for two totally different purposes under UDP vs TCP.

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

The man pagehas some brief details on the usage of connect() under UDP:

手册页有一些关于 UDP 下 connect() 用法的简要细节:

Generally, connection-based protocol (like TCP) sockets may connect() successfully only once; connectionless protocol (like UDP) sockets may use connect() multiple times to change their association.

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

回答by Hyman

There is a problem in your code:

你的代码有问题:

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

By using AF_UNSPEC and SOCK_DGRAM only, you gets a list of all the possible addrs. So, when you call socket, the address you are using might not be your expected UDP one. You should use

通过仅使用 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;

instead to make sure the addrinfo you are retrieving is what you wanted.

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

In another word, the socket you created may not be an UDP socket, and that is the reason why it does not work.

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

回答by ximaera

Hello from the distant future which is the year 2018, to the year 2012.

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

There's, in fact, a reason behind connect()ing an UDP socket in practice (though blessed POSIX and its implementationsdon't in theory require you to).

实际上,connect()在实践中使用 UDP 套接字是有原因的(尽管幸运的 POSIX及其实现在理论上不需要您这样做)。

An ordinary UDP socket doesn't know anything about its future destinations, so it performs a route lookup each time sendmsg()is called.

普通的 UDP 套接字对其未来的目的地一无所知,因此每次sendmsg()调用时它都会执行路由查找

However, if connect()is called beforehand with a particular remote receiver's IP and port, the operating system kernel will be able to write down the reference to the route and assign it to the socket, making it significantly faster to send a message if subsequent sendmsg()calls do not specify a receiver(otherwise the previous setting would be ignored), choosing the default one instead.

但是,如果connect()事先使用特定的远程接收器的 IP 和端口调用,操作系统内核将能够记下对路由的引用并将其分配给套接字,如果后续sendmsg()调用没有,则发送消息的速度会显着加快指定一个接收器否则之前的设置将被忽略),而是选择默认的。

Look at the lines 1070through 1171:

看看1070通过1171

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

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

    rt = ip_route_output_flow(net, fl4, sk);

    [..skip..]
}

Until Linux kernel 4.18, this feature had been mostly limited to the IPv4 address family only. However, since 4.18-rc4 (and hopefully Linux kernel release 4.18 as well), it's fully functional with IPv6 sockets as well.

在 Linux 内核 4.18 之前,此功能主要仅限于 IPv4 地址系列。但是,从 4.18-rc4(希望 Linux 内核也能发布 4.18 版)开始,它也完全可以使用 IPv6 套接字

It may be a source of a serious performance benefit, though it will heavily depend on the OS you're using. At least, if you're using Linux and don't use the socket for multiple remote handlers, you should give it a try.

它可能是一个重要的性能优势的来源,尽管它在很大程度上取决于您使用的操作系统。至少,如果您使用的是 Linux 并且不将套接字用于多个远程处理程序,那么您应该尝试一下。

回答by woon minika

If you are c/c++ lover, you may try route_io

如果你是 c/c++ 爱好者,你可以试试route_io

It is simple to use, create a instance to accept different port routing to your function.

使用起来很简单,创建一个实例来接受不同的端口路由到你的函数。

Example :

例子 :

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

回答by Droopycom

YES, you can. I do it too.

是的你可以。我也这样做。

And your use case is the one where this is useful: both side act as both client & server, and there is only one process on both side.

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