C语言Daytime协议UDP扩展:sendto()报‘非套接字操作’求助
问题诊断与修复方案
核心错误原因
- UDP与TCP的socket逻辑混淆:
- TCP中
accept()返回新的客户端socket描述符,用于后续通信;但UDP是无连接协议,recvfrom()返回的是接收的字节数,不是socket描述符。你把这个字节数赋值给fd,然后传给sendto(),自然会触发"非socket上的操作"错误。
- TCP中
- UDP的sendto参数错误:
sendto()需要明确知道目标客户端的地址,你在sending_datetime函数内新建了空的client结构体,没有传入实际获取到的客户端地址,同时用了错误的fd(字节数)。
- UDP不必要的socket关闭:
- UDP全程复用服务器的监听socket,不需要像TCP那样关闭客户端socket,你原来的逻辑中错误地尝试关闭一个非socket的
fd。
- UDP全程复用服务器的监听socket,不需要像TCP那样关闭客户端socket,你原来的逻辑中错误地尝试关闭一个非socket的
具体修改步骤
1. 调整主函数的UDP处理逻辑
- 把存储
recvfrom返回值的变量从fd改为recv_len(避免与socket描述符混淆); - 在
recvfrom成功接收数据后,直接调用发送函数,使用服务器的监听socket(cfg->sockfd),并传入获取到的客户端地址; - 移除UDP模式下的
close(fd)操作。
修改后主函数的UDP分支:
else if (cfg->proto == UDP) { char buffer[MSGLENGTH]; ssize_t recv_len; // 存储接收的字节数,而非socket描述符 if ((recv_len = recvfrom(cfg->sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client, &client_len)) == -1) { if (errno != EWOULDBLOCK && errno != EAGAIN) ERROR("Error receiving message"); } else { buffer[recv_len] = '\0'; printf("Received message from %s:%d: %s\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), buffer); LOG("Successful incoming datagram from %s:%d.\n", \ inet_ntoa(client.sin_addr),ntohs(client.sin_port)); // 调用修改后的sending_datetime,传入服务器socket、客户端地址 if (sending_datetime(cfg->sockfd, UDP, &client, client_len) == -1) ERROR("Error sending datetime via UDP"); LOG("Successfully sent datetime to %s:%d.\n", \ inet_ntoa(client.sin_addr),ntohs(client.sin_port)); } }
同时,主函数中原有的if (fd != -1)分支仅保留TCP模式的处理,调整为:
// 仅TCP模式处理该分支 if (cfg->proto == TCP && fd != -1) { LOG("Successful incoming connection from %s:%d.\n", \ inet_ntoa(client.sin_addr),ntohs(client.sin_port)); if (sending_datetime(fd, TCP, NULL, 0) == -1) ERROR("Error sending datetime"); LOG("Successfully sent datetime to %s:%d.\n", \ inet_ntoa(client.sin_addr),ntohs(client.sin_port)); if (close(fd) == -1) ERROR("Error on closing socket"); LOG("Successfully closing connection from %s:%d.\n", \ inet_ntoa(client.sin_addr),ntohs(client.sin_port)); }
2. 修改sending_datetime函数
- 添加客户端地址和长度参数,用于UDP模式的
sendto()调用; - 移除函数内部空的
client结构体定义,使用传入的客户端地址。
修改后的函数声明:
int sending_datetime(int fd, int port, struct sockaddr_in *client_addr, socklen_t client_len);
修改后的函数实现:
int sending_datetime(int fd, int port, struct sockaddr_in *client_addr, socklen_t client_len) { time_t curr_time; char buffer[MSGLENGTH]; curr_time = time(NULL); snprintf(buffer, sizeof(buffer), "%.24s\r\n", ctime(&curr_time)); if (port == TCP) { return write(fd, buffer, strlen(buffer)); } else if (port == UDP) { ssize_t n; n = sendto(fd, buffer, strlen(buffer), 0, (struct sockaddr *)client_addr, client_len); if (n < 0) { ERROR("ERROR IN SENDTO()"); } return n; } return -1; // 默认返回错误 }
额外注意事项
- 确保
client_len在UDP模式下始终是socklen_t类型(原代码中是int,虽然多数系统兼容,但标准要求用socklen_t); - 非阻塞socket的处理逻辑保持不变,注意
EWOULDBLOCK/EAGAIN的判断。
内容的提问来源于stack exchange,提问作者trying_to_learn
相关产品推荐
相关产品推荐

