我正在编写一个点对点消息队列系统,它必须能够通过UDP运行。我可以任意选择一方或另一方作为“服务器”,但由于两端都是从另一方发送和接收相同类型的数据,所以它似乎不太正确。
是否可以bind()和connect()两端,以便它们只相互发送/接收?这似乎是一种很好的对称方式。
答案 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),而是选择默认接收方。
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
即使我在一台机器上测试过它,我认为一旦你设置了正确的防火墙设置,它也可以在两台不同的机器上运行
以下是对流程的描述:
#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;
}