C++组播聊天程序异常:接收未激活且跨客户端数据无法传输
C++组播聊天程序问题修复
核心错误点
- 无限创建线程导致逻辑混乱:主函数的
while(true)循环会不断生成新的收发线程,造成资源泄漏,旧线程未被正常管理,接收逻辑完全混乱。 - 发送目标地址错误:发送时使用的
maddr被初始化为INADDR_ANY,而非组播IP,导致数据根本没发送到组播组,对方自然接收不到。 - recvfrom参数未重置:接收线程中
namelen未在每次recvfrom调用前重置为sizeof(paddr),不符合Socket API要求,可能导致接收失败。 - 主线程未等待线程结束:主线程无限创建线程,永远执行不到关闭Socket和清理WSA的代码,资源无法正常释放。
修复后的核心代码
// 头文件与错误输出函数省略 #define MAXBUF 80 SOCKADDR_IN maddr; unsigned __stdcall sendf(LPVOID arg); unsigned __stdcall recvf(LPVOID arg); int main(int argc, char* argv[]) { int port; cout << "input port number" << endl; cin >> port; cout << "use port : " << port << endl; WSADATA wsa; if (WSAStartup(MAKEWORD(2, 2), &wsa)) { err_display("WSAStartup"); return -1; } // 创建接收Socket(绑定并加入组播) SOCKET r_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (r_sock == INVALID_SOCKET) { err_display("recv socket"); return -1; } // 创建发送Socket SOCKET s_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s_sock == INVALID_SOCKET) { err_display("send socket"); return -1; } // 配置接收Socket绑定地址 SOCKADDR_IN bind_addr; bind_addr.sin_family = AF_INET; bind_addr.sin_port = htons(port); bind_addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(r_sock, (SOCKADDR*)&bind_addr, sizeof(bind_addr))) { err_display("bind"); return -1; } // 加入组播组 const char* mip = "236.0.0.1"; IP_MREQ mreq; mreq.imr_interface.s_addr = htonl(INADDR_ANY); if (!(inet_pton(AF_INET, mip, &mreq.imr_multiaddr))) { err_display("inet_pton"); return -1; } if (setsockopt(r_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq))) { err_display("setsockopt"); return -1; } // 配置发送目标地址(组播IP+端口) maddr.sin_family = AF_INET; maddr.sin_port = htons(port); if (!(inet_pton(AF_INET, mip, &maddr.sin_addr))) { err_display("inet_pton for send addr"); return -1; } // 仅创建一次收发线程 HANDLE h1 = (HANDLE)_beginthreadex(NULL, 0, &sendf, (LPVOID)s_sock, 0, NULL); HANDLE h2 = (HANDLE)_beginthreadex(NULL, 0, &recvf, (LPVOID)r_sock, 0, NULL); // 等待线程结束 WaitForSingleObject(h1, INFINITE); WaitForSingleObject(h2, INFINITE); // 清理资源 CloseHandle(h1); CloseHandle(h2); closesocket(r_sock); closesocket(s_sock); WSACleanup(); return 0; } unsigned __stdcall sendf(LPVOID arg) { SOCKET s_sock = (SOCKET)arg; char mesbuf[MAXBUF]; int sendlen; while (1) { if (fgets(mesbuf, MAXBUF - 1, stdin) == NULL) break; cout << "send Thread: " << mesbuf; sendlen = strlen(mesbuf); if (sendto(s_sock, mesbuf, sendlen, 0, (SOCKADDR*)&maddr, sizeof(maddr)) == SOCKET_ERROR) { err_display("sendto"); break; } } return 0; } unsigned __stdcall recvf(LPVOID arg) { SOCKET r_sock = (SOCKET)arg; char mesbuf[MAXBUF]; SOCKADDR_IN paddr; int recvlen; while (1) { int namelen = sizeof(paddr); // 每次调用前重置长度参数 recvlen = recvfrom(r_sock, mesbuf, MAXBUF - 1, 0, (SOCKADDR*)&paddr, &namelen); if (recvlen == SOCKET_ERROR) { err_display("recvfrom"); closesocket(r_sock); break; } if (recvlen == 0) { cout << "connection closed" << endl; closesocket(r_sock); break; } mesbuf[recvlen] = '\0'; char peer_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &paddr.sin_addr, peer_ip, INET_ADDRSTRLEN); cout << "from " << peer_ip << ": " << mesbuf; } return 0; }
修复说明
- 移除线程无限创建循环:主函数仅初始化一次收发线程,通过
WaitForSingleObject等待线程结束,避免资源泄漏。 - 修正发送目标地址:单独配置发送用的
maddr为组播IP,确保数据发送到组播组。 - 重置recvfrom长度参数:每次调用
recvfrom前重置namelen,符合Windows Socket API规范。 - 添加发送错误检查:对
sendto返回值做错误判断,便于排查发送问题。 - 优化接收端输出:显示发送方IP地址,提升调试体验。
内容的提问来源于stack exchange,提问作者Hys
相关产品推荐
相关产品推荐

