我正在尝试使用UDP编写的程序设置一个简单的通信方案。程序将以非阻塞方式设置UDP套接字,然后使用select()从套接字读取非阻塞。以下是该计划的内容:
#include <stdio.h> /* Standard input/output definitions */
#include <stdlib.h> /* UNIX standard function definitions */
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <fcntl.h> /* File control definitions */
#include <signal.h>
#include <time.h>
#include <stdint.h> /* Standard integer types */
#include <string.h> /* String function definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
int setupUdpSocketN(int *fd, uint16_t port_no)
{
struct sockaddr_in my_addr;
// Create UDP socket:
if ( (*fd = socket(AF_INET,SOCK_DGRAM,0)) < 0 )
return -1;
fcntl(*fd, F_SETFL, O_NONBLOCK);
// Bind socket:
memset((char *)&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
my_addr.sin_port = htons(port_no);
if ( bind(*fd, (struct sockaddr*)&my_addr, sizeof(my_addr)) == -1 ) {
close(*fd);
return -2;
}
return EXIT_SUCCESS;
}
int main(void) {
int fd, maxfd, nready;
size_t recvlen;
uint8_t buf[256];
fd_set rset;
setupUdpSocketN(&fd, 60000);
maxfd = fd + 1;
while(1) {
FD_ZERO(&rset);
FD_SET(fd, &rset);
if( (nready = select(maxfd, &rset, NULL, NULL, NULL)) < 0 ) {
printf("Error in select: %s\n", strerror(errno));
}
if( FD_ISSET(fd, &rset) ) {
recvlen = recv(fd, buf, 256, 0);
//recvlen = recvfrom(fd, buf, 256, 0, NULL, NULL);
for(int i = 0; i < recvlen; i++) {
printf("%02x ", buf[i]);
}
printf("\n");
}
}
return 0;
}
然后我尝试使用echo,xxd和netcat发送此程序二进制数据:
echo -ne "\xfe\x64\x32\x1a\xb0" | xxd -rp | nc -u localhost 60000
然而,这个程序在运行时只是无休止地阻塞。我对套接字编程很新,不确定如何继续。
答案 0 :(得分:1)
我认为主要问题不在于代码,而在于如何调用netcat。当我运行服务器并使用nc -u localhost 60000
时,我可以重现您的问题:服务器无限期地阻塞而没有接收数据。但是,当我强制netcat使用IPv4时,我可以将数据成功发送到服务器:
nc -4 -u localhost 60000
或者,将代码更改为使用IPv6并使用不带-4
标志的netcat也可以:
struct sockaddr_in6 my_addr;
...
if ( (*fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0 )
return -1;
...
my_addr.sin6_family = AF_INET6;
my_addr.sin6_addr = in6addr_any;
my_addr.sin6_port = htons(port_no);
向您展示如何调试问题:我首先使用netcat启动UDP服务器,然后使用第二个netcat实例将数据发送到该服务器。这按预期工作。然后,我使用strace
检查了netcat服务器使用的系统调用。 strace
输出显示使用IPv6的netcat:
...
socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
bind(3, {sa_family=AF_INET6, sin6_port=htons(60000), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_scope_id=0}, 28) = 0
...
除此之外,还有两个其他问题:
recv
函数返回接收到的字节数,如果发生错误,则返回-1
。因此,返回值的类型为ssize_t
,而不是size_t
。由于您的代码声明了size_t recvlen
,因此当recv
返回-1
时,将发生整数下溢,并且recv
将是类型size_t
的最大可能值(例如{{ 1}}。这样会导致缓冲区溢出,因为ULLONG_MAX = 18446744073709551615
小得多。
您的buf
/ echo
调用仅对我产生零。我不确定这是否打算。 xxd
的{{1}}标志启用“反向”模式,其中-r
将尝试解析十六进制转储(例如,由xxd
产生而没有xxd
标志)然后再现原始字节序列,该序列是原始十六进制转储的输入。但是在您的情况下,xxd
输出原始二进制数据,而不是编码的十六进制转储,因此-r
无法以反向模式解析此数据。