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

gct*_*gct 34 c linux udp

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

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

dat*_*olf 27

UDP是无连接的,因此对于实际进行某种连接的操作系统几乎没有意义.

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

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

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

  • SOCK_DGRAM套接字上的connect()设置默认/发送接收地址,因此您可以使用send和recv.我正在编写它以通过TCP工作,因此最终会使两个协议共用一些其他代码. (10认同)
  • @nhed:不,我的意思是,Linux确实支持带有几个扩展的*super*BSD套接字集.我不得不查找一个虚假的参考页面以确保,我不是在撰写特定于Linux的扩展. (2认同)

xim*_*ica 23

你好,从遥远的未来,即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..]
}
Run Code Online (Sandbox Code Playgroud)

在Linux内核4.18之前,此功能主要仅限于IPv4地址系列.但是,自4.18-rc4(以及希望Linux内核版本4.18)以来,它也完全适用于IPv6套接字.

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

  • @SectoKia不,那只是一个哈希表查找。哈希表保留在RAM中,因此只是CPU周期和RAM查找。 (2认同)
  • 我回来看一个类似的问题,所以你好来自 2019 年的遥远未来! (2认同)

小智 14

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

usage: ./<program_name> dst-hostname dst-udpport src-udpport
Run Code Online (Sandbox Code Playgroud)

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

在终端1运行

./<program_name> 127.0.0.1 5555 5556
Run Code Online (Sandbox Code Playgroud)

在终端2运行

./<program_name> 127.0.0.1 5556 5555
Run Code Online (Sandbox Code Playgroud)

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

这是流程的描述:

  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无连接的后果.

#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;
}
Run Code Online (Sandbox Code Playgroud)


Geo*_*edy 5

真的关键是connect():

如果套接字sockfd的类型为SOCK_DGRAM,则addr是默认情况下发送数据报的地址,以及接收数据报的唯一地址.

  • 根据我的理解,'服务器'必须绑定(),以便实际连接到端口,以便客户端可以有一个真实的位置来发送和接收数据...... (4认同)
  • @gct:嗯,你可以绑定双方. (3认同)