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

Win32 Socket编程出现奇怪的400 Bad Request错误求助

解决Win32 Socket发送明文HTTP到HTTPS端口的400错误

问题原因

你连接的是443端口(标准HTTPS端口),但直接向该端口发送了明文HTTP请求。HTTPS协议要求在发送HTTP数据前,必须先通过TLS/SSL握手建立加密连接,否则服务器会拒绝明文请求,返回HTTP/1.1 400 Bad Request及the plain HTTP request was sent to HTTPS port错误。

对比你的curl命令:

  • curl -v https://reqres.in/api/users:443:curl识别https://前缀后,会自动完成TLS握手,再加密发送HTTP请求,因此能正常获取数据。
  • curl -v reqres.in/api/users:443:curl默认使用HTTP协议,直接发送明文请求到443端口,和你的代码行为一致,因此触发相同错误。

解决方案

要解决这个问题,你需要在建立TCP连接后,通过Schannel API完成TLS握手,建立加密通道,再通过加密通道发送HTTP请求。以下是修改后的完整代码(恢复了必要的错误检查,添加了TLS处理逻辑):

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <schannel.h>
#include <secur32.h>

#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "secur32.lib")

#define DEFAULT_BUFLEN 1024
#define DEFAULT_PORT "443"

int main() {
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo* result = NULL, * ptr = NULL, hints;
    const char* sendbuf = "GET /api/users HTTP/1.1\r\nHost: reqres.in\r\nConnection: close\r\n\r\n";
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;

    // 初始化Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // 解析服务器地址和端口
    iResult = getaddrinfo("reqres.in", DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // 尝试连接到服务器
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    }

    // -------------------------- 初始化TLS上下文 --------------------------
    CtxtHandle hContext = { 0 };
    SECURITY_STATUS secStatus;
    SecPkgContext_StreamSizes streamSizes = { 0 };
    SecBufferDesc sendBufferDesc, recvBufferDesc;
    SecBuffer sendBuffer, recvBuffer;
    char tlsSendBuf[DEFAULT_BUFLEN], tlsRecvBuf[DEFAULT_BUFLEN];

    // 初始化安全上下文
    SECURITY_ATTRIBUTES secAttr = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
    secStatus = InitializeSecurityContext(NULL, NULL, (LPWSTR)L"reqres.in",
        ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY,
        0, SECURITY_NATIVE_DREP, NULL, 0, &hContext, &sendBufferDesc, NULL, NULL);

    if (secStatus != SEC_E_OK && secStatus != SEC_I_CONTINUE_NEEDED) {
        printf("InitializeSecurityContext failed: 0x%08x\n", secStatus);
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // 发送TLS握手数据
    if (secStatus == SEC_I_CONTINUE_NEEDED) {
        sendBufferDesc.ulVersion = SECBUFFER_VERSION;
        sendBufferDesc.cBuffers = 1;
        sendBufferDesc.pBuffers = &sendBuffer;
        sendBuffer.BufferType = SECBUFFER_TOKEN;
        sendBuffer.pvBuffer = tlsSendBuf;
        sendBuffer.cbBuffer = sizeof(tlsSendBuf);

        do {
            secStatus = InitializeSecurityContext(NULL, &hContext, (LPWSTR)L"reqres.in",
                ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY,
                0, SECURITY_NATIVE_DREP, &recvBufferDesc, 0, &hContext, &sendBufferDesc, NULL, NULL);

            if (sendBufferDesc.cBuffers > 0 && sendBufferDesc.pBuffers[0].cbBuffer > 0) {
                iResult = send(ConnectSocket, (char*)sendBufferDesc.pBuffers[0].pvBuffer,
                    sendBufferDesc.pBuffers[0].cbBuffer, 0);
                if (iResult == SOCKET_ERROR) {
                    printf("send TLS data failed: %d\n", WSAGetLastError());
                    DeleteSecurityContext(&hContext);
                    closesocket(ConnectSocket);
                    WSACleanup();
                    return 1;
                }
            }

            if (secStatus == SEC_I_CONTINUE_NEEDED) {
                // 接收服务器的TLS响应
                recvBufferDesc.ulVersion = SECBUFFER_VERSION;
                recvBufferDesc.cBuffers = 1;
                recvBufferDesc.pBuffers = &recvBuffer;
                recvBuffer.BufferType = SECBUFFER_TOKEN;
                recvBuffer.pvBuffer = tlsRecvBuf;
                recvBuffer.cbBuffer = sizeof(tlsRecvBuf);

                iResult = recv(ConnectSocket, tlsRecvBuf, sizeof(tlsRecvBuf), 0);
                if (iResult <= 0) {
                    printf("recv TLS data failed: %d\n", WSAGetLastError());
                    DeleteSecurityContext(&hContext);
                    closesocket(ConnectSocket);
                    WSACleanup();
                    return 1;
                }
                recvBuffer.cbBuffer = iResult;
            }
        } while (secStatus == SEC_I_CONTINUE_NEEDED);
    }

    if (secStatus != SEC_E_OK) {
        printf("TLS handshake failed: 0x%08x\n", secStatus);
        DeleteSecurityContext(&hContext);
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // 获取流大小信息,用于加密/解密数据
    secStatus = QueryContextAttributes(&hContext, SECPKG_ATTR_STREAM_SIZES, &streamSizes);
    if (secStatus != SEC_E_OK) {
        printf("QueryContextAttributes failed: 0x%08x\n", secStatus);
        DeleteSecurityContext(&hContext);
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // -------------------------- 加密并发送HTTP请求 --------------------------
    char encryptedBuf[DEFAULT_BUFLEN + streamSizes.cbHeader + streamSizes.cbTrailer];
    SecBuffer sendBuffers[4];
    SecBufferDesc sendDesc;

    sendDesc.ulVersion = SECBUFFER_VERSION;
    sendDesc.cBuffers = 3;
    sendDesc.pBuffers = sendBuffers;

    sendBuffers[0].BufferType = SECBUFFER_STREAM_HEADER;
    sendBuffers[0].pvBuffer = encryptedBuf;
    sendBuffers[0].cbBuffer = streamSizes.cbHeader;

    sendBuffers[1].BufferType = SECBUFFER_DATA;
    sendBuffers[1].pvBuffer = (char*)sendbuf;
    sendBuffers[1].cbBuffer = strlen(sendbuf);

    sendBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
    sendBuffers[2].pvBuffer = encryptedBuf + streamSizes.cbHeader + sendBuffers[1].cbBuffer;
    sendBuffers[2].cbBuffer = streamSizes.cbTrailer;

    secStatus = EncryptMessage(&hContext, 0, &sendDesc, 0);
    if (secStatus != SEC_E_OK) {
        printf("EncryptMessage failed: 0x%08x\n", secStatus);
        DeleteSecurityContext(&hContext);
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // 发送加密后的HTTP请求
    iResult = send(ConnectSocket, encryptedBuf, streamSizes.cbHeader + strlen(sendbuf) + streamSizes.cbTrailer, 0);
    if (iResult == SOCKET_ERROR) {
        printf("send encrypted HTTP data failed: %d\n", WSAGetLastError());
        DeleteSecurityContext(&hContext);
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // -------------------------- 接收并解密服务器响应 --------------------------
    char decryptedBuf[DEFAULT_BUFLEN];
    SecBuffer recvBuffers[4];
    SecBufferDesc recvDesc;
    int totalDecrypted = 0;

    recvDesc.ulVersion = SECBUFFER_VERSION;
    recvDesc.cBuffers = 4;
    recvDesc.pBuffers = recvBuffers;

    recvBuffers[0].BufferType = SECBUFFER_STREAM_HEADER;
    recvBuffers[0].pvBuffer = tlsRecvBuf;
    recvBuffers[0].cbBuffer = streamSizes.cbHeader;

    recvBuffers[1].BufferType = SECBUFFER_DATA;
    recvBuffers[1].pvBuffer = tlsRecvBuf + streamSizes.cbHeader;
    recvBuffers[1].cbBuffer = DEFAULT_BUFLEN - streamSizes.cbHeader - streamSizes.cbTrailer;

    recvBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
    recvBuffers[2].pvBuffer = tlsRecvBuf + streamSizes.cbHeader + recvBuffers[1].cbBuffer;
    recvBuffers[2].cbBuffer = streamSizes.cbTrailer;

    recvBuffers[3].BufferType = SECBUFFER_EMPTY;
    recvBuffers[3].pvBuffer = NULL;
    recvBuffers[3].cbBuffer = 0;

    printf("Received response:\n");
    do {
        iResult = recv(ConnectSocket, tlsRecvBuf, sizeof(tlsRecvBuf), 0);
        if (iResult <= 0) {
            if (iResult < 0) {
                printf("recv failed with error: %d\n", WSAGetLastError());
            }
            break;
        }

        recvBuffers[1].cbBuffer = iResult - streamSizes.cbHeader - streamSizes.cbTrailer;
        secStatus = DecryptMessage(&hContext, &recvDesc, 0, NULL);
        if (secStatus != SEC_E_OK) {
            printf("DecryptMessage failed: 0x%08x\n", secStatus);
            break;
        }

        // 输出解密后的响应内容
        memcpy(decryptedBuf + totalDecrypted, recvBuffers[1].pvBuffer, recvBuffers[1].cbBuffer);
        totalDecrypted += recvBuffers[1].cbBuffer;
        printf("%.*s", recvBuffers[1].cbBuffer, (char*)recvBuffers[1].pvBuffer);

    } while (iResult > 0);

    // 清理资源
    DeleteSecurityContext(&hContext);
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;
}

关键修改说明

  1. 添加Schannel依赖:包含<schannel.h>和<secur32.h>头文件,链接secur32.lib库,用于TLS握手和数据加密/解密。
  2. TLS握手流程:通过InitializeSecurityContext完成TLS握手,与服务器交换加密密钥,建立安全通道。
  3. 加密发送HTTP请求:使用EncryptMessage对明文HTTP请求加密后,再通过Socket发送。
  4. 解密响应数据:接收服务器的加密响应后,用DecryptMessage解密,得到明文HTTP响应内容。

内容的提问来源于stack exchange,提问作者Marco Vinicio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 11:05:53