You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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里别忘了关闭打开的文件,否则会导致资源泄漏。

测试建议

  1. 先测试HTTP请求:访问你的服务器,看HTML文件是否能正常加载,检查浏览器开发者工具的Network面板,确认Content-length和实际文件大小一致。
  2. 再测试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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:35:38