UDP套接字编程问题:C代码控制信号接收器阻塞于recvfrom
排查你的UDP通信C代码问题
看起来你已经通过netcat验证了UDP通信是可行的,那我们一步步找出C代码里的问题:
1. UDP的connect误区
首先要明确:UDP是无连接协议,调用connect并不会像TCP那样和设备建立真正的连接,它只是给当前socket绑定了默认的目标地址,让后续的send/recv可以不用重复指定地址。所以你看到的"Connected"提示其实是误导性的——这个connect成功只说明系统能解析目标IP和端口,不代表设备真的"连接"上了。
2. recvfrom的致命参数错误
你的recvfrom调用有两个关键问题:
- 接收长度参数用了
strlen(recepbuff):你用memset(recepbuff, '0', sizeof(recepbuff))把缓冲区填的是ASCII的'0'(不是字符串终止符'\0'),所以strlen(recepbuff)会返回BUFFLEN,这虽然数值上是对的,但正确的做法应该直接用sizeof(recepbuff),因为我们要接收的是字节流,不是依赖字符串长度。 - 最后一个参数传错了:
recvfrom的第6个参数需要是socklen_t*类型的指针,用来返回发送方的地址长度。你直接传了sizeof(serv_addr),这会导致未定义行为,大概率是recvfrom无法正确接收数据,一直阻塞。
修正后的recvfrom调用应该是这样:
socklen_t addr_len = sizeof(serv_addr); if ((recvfrom(socket_fd, recepbuff, sizeof(recepbuff)-1, 0, (struct sockaddr *)&serv_addr, &addr_len)) < 0)
注意这里用sizeof(recepbuff)-1是为了留一个字节放'\0',方便后续打印字符串。
3. 不安全的gets函数
gets函数已经被废弃了,它不会检查缓冲区大小,很容易导致缓冲区溢出。建议换成fgets,并且要处理掉fgets读取到的换行符(因为你输入命令后按回车,fgets会把\n也读进cmd里,而设备可能只识别不带换行的命令):
printf("Enter command : "); fgets(cmd, BUFFLEN, stdin); // 去掉换行符 cmd[strcspn(cmd, "\n")] = '\0';
4. 错误处理不完善
当socket创建失败时,你只打印了错误信息,但程序会继续执行后续代码,这会导致更严重的错误。应该在错误后直接退出,并且用perror代替printf,它能显示系统错误码对应的描述,方便排查问题:
if ( (socket_fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0 ) { perror("ERROR opening socket"); exit(EXIT_FAILURE); }
5. 发送数据的小细节
你的sendto调用是正确的,但可以考虑用send代替(因为已经connect过了,默认目标地址就是设备),不过这不是必须的。另外要确保发送的命令格式和netcat一致——比如netcat输入命令后回车,是否会发送换行符?如果设备不需要换行,那用fgets处理掉换行符就很重要。
修正后的完整代码(关键部分修改)
/* Standard Linux headers */ #include <stdio.h> //printf #include <string.h> //memset #include <stdlib.h> //exit(0); #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <errno.h> /* DEFINE */ #define SERVER "192.168.0.10" #define BUFFLEN 1024 //Max length of buffer #define PORT 3000 //The port on which to send data int main (void) { struct sockaddr_in serv_addr; char recepbuff[BUFFLEN]; char cmd[BUFFLEN]; int socket_fd; memset(recepbuff, 0 ,sizeof(recepbuff)); // 用0初始化,方便后续字符串处理 if ( (socket_fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0 ) { perror("ERROR opening socket"); exit(EXIT_FAILURE); } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); if(inet_pton(AF_INET, SERVER, &serv_addr.sin_addr)<=0) { perror("Invalid address/ Address not supported"); exit(EXIT_FAILURE); } // UDP的connect只是绑定默认目标地址,不是真正的连接 if (connect(socket_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { perror("Failed to set default target address"); exit(EXIT_FAILURE); } printf("Default target address set to %s:%d\n", SERVER, PORT); while(1) { printf("Enter command : "); if (!fgets(cmd, BUFFLEN, stdin)) { perror("Failed to read command"); break; } // 去掉换行符 cmd[strcspn(cmd, "\n")] = '\0'; // 用send代替sendto,因为已经connect过了 if (send(socket_fd, cmd, strlen(cmd), 0) < 0 ) { perror("Failed sending cmd"); } else { printf("CMD * %s * SENT\n", cmd); } memset(recepbuff, 0 ,sizeof(recepbuff)); socklen_t addr_len = sizeof(serv_addr); ssize_t recv_len = recvfrom(socket_fd, recepbuff, sizeof(recepbuff)-1, 0, (struct sockaddr *)&serv_addr, &addr_len); if (recv_len < 0) { perror("Failed to receive response"); } else { recepbuff[recv_len] = '\0'; // 手动添加字符串终止符 printf("Received response: %s\n", recepbuff); } } close(socket_fd); return 0; }
额外排查建议
- 确认设备是否会对每个命令返回响应:有些UDP设备可能只在特定命令下返回数据,或者需要特定的命令格式(比如结尾加'\n'或'\r\n')。如果设备不返回响应,
recvfrom会一直阻塞,这时候可以给recvfrom设置超时:用setsockopt设置SO_RCVTIMEO选项。 - 用
tcpdump或wireshark抓包,对比netcat发送的数据包和你的C程序发送的数据包,看是否有差异(比如命令内容、端口、IP等)。
内容的提问来源于stack exchange,提问作者HacHac
相关产品推荐
相关产品推荐

