C实现WebSocket服务器:客户端连接成功但无法接收数据求助
分析你的C语言Web服务器数据发送问题
看起来你遇到了两个核心问题:HTTP响应的Content-length硬编码导致客户端等待,以及WebSocket完全没有实现握手流程,这两个都会让客户端看起来收不到数据。让我一步步帮你修复:
1. HTTP请求的Content-length硬编码问题
你的SendData函数里把Content-length固定写死为2048,这会导致客户端认为要接收2048字节的数据,但如果你的文件实际大小小于这个值,客户端会一直等待剩余数据,表现为"收不到数据"。
修复方法:动态计算文件大小,生成正确的Content-length头:
void SendData(SOCKET sock, char* ct, char* fileName) { char protocol[] = "HTTP/1.0 200 OK\r\n"; char servName[] = "Server:simple web server\r\n"; char cntType[BUF_SMALL]; char buf[BUF_SIZE]; FILE* sendFile; char cntLen[BUF_SMALL]; // 改为动态生成 sprintf_s(cntType, sizeof(cntType), "Content-type:%s; charset=UTF-8\r\n\r\n", ct); if (fopen_s(&sendFile, fileName, "r") != 0) { SendErrorMSG(sock); return; } // 计算文件实际大小 fseek(sendFile, 0, SEEK_END); long fileSize = ftell(sendFile); fseek(sendFile, 0, SEEK_SET); // 生成正确的Content-length sprintf_s(cntLen, sizeof(cntLen), "Content-length:%ld\r\n", fileSize); /* 用send_all确保数据全部发送(后面会实现这个函数) */ send_all(sock, protocol, strlen(protocol)); send_all(sock, servName, strlen(servName)); send_all(sock, cntLen, strlen(cntLen)); send_all(sock, cntType, strlen(cntType)); /* 发送文件内容 */ while (fgets(buf, BUF_SIZE, sendFile) != NULL) { send_all(sock, buf, strlen(buf)); } fclose(sendFile); // 别忘了关闭文件! closesocket(sock); }
2. WebSocket完全缺失握手流程
WebSocket不是普通的TCP连接,它需要先完成HTTP握手:客户端发送包含Sec-WebSocket-Key的请求,服务器必须返回正确的Sec-WebSocket-Accept响应后,才能开始发送数据。你的代码只是检测到"websocket"就开线程,完全跳过了握手,客户端根本不会认为连接成功,自然收不到任何数据。
修复WebSocket握手和帧发送:
首先,实现一个send_all函数确保数据完整发送:
int send_all(SOCKET sock, const char* buf, int len) { int total_sent = 0; while (total_sent < len) { int sent = send(sock, buf + total_sent, len - total_sent, 0); if (sent == SOCKET_ERROR) { printf("Send error: %d\n", WSAGetLastError()); return SOCKET_ERROR; } total_sent += sent; } return total_sent; }
然后修改RequestHandler里的WebSocket分支,添加握手逻辑:
else if (strstr(buf, "websocket")) { printf("WebSocket handshake request received\n"); // 提取Sec-WebSocket-Key char ws_key[BUF_SMALL] = {0}; char* key_start = strstr(buf, "Sec-WebSocket-Key: "); if (!key_start) { SendErrorMSG(hClntSock); return 1; } key_start += strlen("Sec-WebSocket-Key: "); char* key_end = strstr(key_start, "\r\n"); if (key_end) { strncpy_s(ws_key, sizeof(ws_key), key_start, key_end - key_start); } // 生成响应需要的Accept字符串(使用Windows加密API) char combined[BUF_SIZE] = {0}; strcat_s(combined, sizeof(combined), ws_key); strcat_s(combined, sizeof(combined), "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); // 固定协议字符串 unsigned char sha1_hash[20]; HCRYPTPROV h_prov; HCRYPTHASH h_hash; if (!CryptAcquireContext(&h_prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { printf("CryptAcquireContext failed: %d\n", GetLastError()); SendErrorMSG(hClntSock); return 1; } if (!CryptCreateHash(h_prov, CALG_SHA1, 0, 0, &h_hash)) { printf("CryptCreateHash failed: %d\n", GetLastError()); CryptReleaseContext(h_prov, 0); SendErrorMSG(hClntSock); return 1; } CryptHashData(h_hash, (BYTE*)combined, strlen(combined), 0); CryptGetHashParam(h_hash, HP_HASHVAL, sha1_hash, (DWORD*)sizeof(sha1_hash), 0); CryptDestroyHash(h_hash); CryptReleaseContext(h_prov, 0); // 把SHA1哈希转成Base64 char accept_key[BUF_SMALL] = {0}; DWORD accept_len = 0; CryptBinaryToString(sha1_hash, sizeof(sha1_hash), CRYPT_STRING_BASE64, NULL, &accept_len); CryptBinaryToString(sha1_hash, sizeof(sha1_hash), CRYPT_STRING_BASE64, accept_key, &accept_len); // 发送握手响应 char handshake_response[BUF_SIZE] = {0}; sprintf_s(handshake_response, sizeof(handshake_response), "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: %s\r\n\r\n", accept_key); send_all(hClntSock, handshake_response, strlen(handshake_response)); // 启动线程发送WebSocket帧(注意:WebSocket数据必须封装成帧) _beginthreadex(NULL, 0, updateFiles, (void*)hClntSock, 0, NULL); return 0; // 不要关闭socket,WebSocket需要保持长连接 }
接下来,WebSocket不能直接发送字符串,必须封装成帧格式,实现一个发送文本帧的函数:
void SendWebSocketTextFrame(SOCKET sock, const char* text) { int text_len = strlen(text); char frame[BUF_SIZE] = {0}; int frame_pos = 0; // 帧头第一个字节:FIN(1) + 文本帧(0x01) frame[frame_pos++] = 0x81; // 帧头第二个字节:无掩码(0) + 数据长度 if (text_len <= 125) { frame[frame_pos++] = text_len; } else if (text_len <= 65535) { frame[frame_pos++] = 126; frame[frame_pos++] = (text_len >> 8) & 0xFF; frame[frame_pos++] = text_len & 0xFF; } else { // 大于65535的长度暂时不处理 printf("Text too long for WebSocket frame\n"); return; } // 复制文本数据到帧 memcpy(frame + frame_pos, text, text_len); frame_pos += text_len; // 发送完整帧 send_all(sock, frame, frame_pos); }
然后实现你的updateFiles函数,用上面的函数发送数据:
unsigned WINAPI updateFiles(void* arg) { SOCKET hClntSock = (SOCKET)arg; char update_msg[] = "File updated!"; // 示例消息 // 每隔一段时间发送一次消息(示例) while (1) { SendWebSocketTextFrame(hClntSock, update_msg); Sleep(3000); // 3秒一次 // 可以在这里添加文件检测逻辑,发送实际的文件更新信息 } closesocket(hClntSock); return 0; }
3. 其他需要修复的小问题
- ContentType函数不完整:补全扩展名判断逻辑:
char* ContentType(char* file) { char extension[BUF_SMALL]; char fileName[BUF_SMALL]; char* context; strcpy_s(fileName, sizeof(fileName), file); // 分割文件名和扩展名 strtok_s(fileName, ".", &context); char* ext = strtok_s(NULL, ".", &context); if (!ext) return "text/plain"; if (!strcmp(ext, "html") || !strcmp(ext, "htm")) return "text/html"; else if (!strcmp(ext, "css")) return "text/css"; else if (!strcmp(ext, "js")) return "application/javascript"; else if (!strcmp(ext, "png")) return "image/png"; else if (!strcmp(ext, "jpg") || !strcmp(ext, "jpeg")) return "image/jpeg"; else return "text/plain"; }
- 内存泄漏:在RequestHandler结束时释放malloc的内存:
// 在RequestHandler的末尾,比如HTTP处理完之后 free(((LPClnt)arg)->clntAdr); free((LPClnt)arg);
- 关闭文件:SendData里别忘了关闭打开的文件,否则会导致资源泄漏。
测试建议
- 先测试HTTP请求:访问你的服务器,看HTML文件是否能正常加载,检查浏览器开发者工具的Network面板,确认Content-length和实际文件大小一致。
- 再测试WebSocket:用JavaScript写一个简单的客户端,比如:
const ws = new WebSocket('ws://localhost:你的端口'); ws.onopen = () => console.log('WebSocket connected'); ws.onmessage = (event) => console.log('Received:', event.data); ws.onerror = (err) => console.error('Error:', err);
运行后看控制台是否能收到服务器发送的消息。
内容的提问来源于stack exchange,提问作者Hyunsoo
相关产品推荐
相关产品推荐

