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

我正在写一个点对点的消息队列系统,它必须能够通过UDP进行操作。 我可以任意select一方或另一方作为“服务器”,但是由于两端正在发送和接收来自另一端的相同types的数据,所以它看起来不太正确。

bind()和connect()两端是否可以相互发送/接收? 这似乎是一个很好的对称的方式来做到这一点。

Solutions Collecting From Web of "你可以绑定()和连接()两端的UDP连接"

UDP是无连接的,所以对于操作系统来说,实际上做一些连接是没有意义的。

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

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

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

下面是一个程序,演示如何将同一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根据作为目标端口的目的地址和参数2的自变量1来获得地址信息结构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\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; } 

真的关键是connect()

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

本页面包含一些关于连接和未连接套接字的详细信息: http : //www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html

这个引用回答你的问题:

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

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

该手册页在UDP下使用connect()有一些简要的细节:

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

你的代码有一个问题:

 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套接字,这就是为什么它不起作用的原因。

我想从UDP的提供的角度来看它。 UDP是一个8字节的头部,增加了2个字节的发送和接收端口(总共4个字节)。 这些端口与Berkeley套接字交互以提供传统的套接字接口。 也就是说,你不能绑定到没有端口的地址,反之亦然。

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

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

顺便说一下,八字节UDP有效载荷,长度和CRC的其他四个字节几乎完全没有用,因为它们已经在IP数据包中提供了,并且UDP标头是固定长度的。 就像来吧人们一样,电脑在做一些减法上相当不错。