【问题标题】:Winsock2 error 10014 on split TCP stream拆分 TCP 流上的 Winsock2 错误 10014
【发布时间】:2013-01-02 13:43:16
【问题描述】:

代码优先

DWORD WINAPI tcp_t(LPVOID lpParam)
{
    SOCKET tcp_client_s = (SOCKET)lpParam;
    struct sockaddr_in tcp_client;
    int tcp_client_len = sizeof(tcp_client), length;
    char req[4096], resp[4096];

    getpeername(tcp_client_s, (struct sockaddr *)&tcp_client, &tcp_client_len);

    cli_log(PROTO_TCP, LOG_SYS, "(%s:%d) TCP thread spawned\n", inet_ntoa(tcp_client.sin_addr), ntohs(tcp_client.sin_port));

    length = get_req_tcp(tcp_client_s, req, tcp_client);

    if(strci(req, "GET /syachi2ds/web/", 0))
    {
        while(!strstr(req, "Connection: close\r\n\r\n"))
            length += get_req_tcp(tcp_client_s, req + length, tcp_client);

        length = check_req(req, resp);

        if(length > 0)
            send_resp_tcp(tcp_client_s, resp, length, tcp_client);
    }

    closesocket(tcp_client_s);
    cli_log(PROTO_TCP, LOG_SYS, "(%s:%d) socket closed, closing thread\n", inet_ntoa(tcp_client.sin_addr), ntohs(tcp_client.sin_port));
    ExitThread(0);
}

int get_req_tcp(SOCKET in_s, char *buf, struct sockaddr_in in)
{
    int retval;

    cli_log(PROTO_TCP, LOG_COMM, "(%s:%d) waiting for incoming request...\n", inet_ntoa(in.sin_addr), ntohs(in.sin_port));
    if ( (retval = recv(in_s, buf, 4096, 0)) == SOCKET_ERROR)
        cli_log(PROTO_TCP, LOG_ERROR, "(%d) recv() failed\n", WSAGetLastError());
    cli_log(PROTO_TCP, LOG_COMM, "(%s:%d) data received\n", inet_ntoa(in.sin_addr), ntohs(in.sin_port));
    return retval;
}

这是我正在编写的一个更大程序的一部分,该程序执行某种服务器仿真。它适用于未拆分为多个数据包的 TCP 流,否则它会在 while 循环中调用的第一个后续 recv() 上给出 winsock 错误 10014。

PS:strci() 是自定义的不区分大小写的strstr()

PS2:我知道没有检查 req 数组上的缓冲区溢出。

【问题讨论】:

  • 如果失败:第一个get_req_tcp() 调用(在循环之前)为length 返回哪个值?
  • 此时 cli_log() 调用 exit(0) 如果 LOG_ERROR 作为第二个参数传递(我已经计划改变这种行为)
  • 从socket流中读取的数据不是nul终止的,所以如果你想使用strstr,你需要在每个recv之后手动添加一个'\0'

标签: c tcp winsock2


【解决方案1】:

10014 是WSAEFAULT,这意味着recv() 正在检测“buf 参数未完全包含在用户地址空间的有效部分中。”。这是有道理的,因为您的代码中有缓冲区溢出错误。您在调用堆栈上为 req 缓冲区分配了 4096 个字节。每次调用 get_req_tcp() 时,您都在告诉它读取 4096 个字节,即使 req 实际上没有 4096 个字节可供读取。

每次循环运行时,您都在告诉recv() 将字节读入缓冲区内的新开始位置,但您并没有告诉recv() 在该位置之后剩余多少字节,因此循环会溢出缓冲区并且最终会访问一个不在调用栈上的内存地址,导致WSAEFAULT错误。

您需要向get_req_tcp() 添加一个额外参数,告诉它要读取多少字节。

试试这个:

DWORD WINAPI tcp_t(LPVOID lpParam)
{
    SOCKET tcp_client_s = (SOCKET)lpParam;
    struct sockaddr_in tcp_client;
    int tcp_client_len = sizeof(tcp_client), length;
    char req[4096], resp[4096];

    getpeername(tcp_client_s, (struct sockaddr *)&tcp_client, &tcp_client_len);

    cli_log(PROTO_TCP, LOG_SYS, "(%s:%d) TCP thread spawned\n", inet_ntoa(tcp_client.sin_addr), ntohs(tcp_client.sin_port));

    length = get_req_tcp(tcp_client_s, req, sizeof(req), tcp_client);
    if (length > 0)
    {
        while (!strstr(req, "\r\n\r\n"))
        {
            retval = get_req_tcp(tcp_client_s, req + length, sizeof(req) - length, tcp_client);
            if (retval < 1)
            {
                length = 0;
                break;
            }
            length += retval;
        }

        if ((length > 0) && (strci(req, "GET /syachi2ds/web/", 0)))
        {
            length = check_req(req, resp);
            if (length > 0)
                send_resp_tcp(tcp_client_s, resp, length, tcp_client);
        }
    }

    closesocket(tcp_client_s);
    cli_log(PROTO_TCP, LOG_SYS, "(%s:%d) socket closed, closing thread\n", inet_ntoa(tcp_client.sin_addr), ntohs(tcp_client.sin_port));

    return 0;
}

int get_req_tcp(SOCKET in_s, char *buf, int buflen, struct sockaddr_in in)
{
    cli_log(PROTO_TCP, LOG_COMM, "(%s:%d) waiting for incoming request...\n", inet_ntoa(in.sin_addr), ntohs(in.sin_port));

    if ((!buf) || (buflen < 1))
    {
        cli_log(PROTO_TCP, LOG_ERROR, "invalid buffer passed for recv()\n");
        return -1;
    }

    int retval = recv(in_s, buf, buflen, 0);

    if (retval == SOCKET_ERROR)
    {
        cli_log(PROTO_TCP, LOG_ERROR, "(%d) recv() failed\n", WSAGetLastError());
        return -1;
    }

    if (retval == 0)
    {
        cli_log(PROTO_TCP, LOG_ERROR, "client disconnected\n");
        return 0;
    }

    cli_log(PROTO_TCP, LOG_COMM, "(%s:%d) %d bytes received\n", retval, inet_ntoa(in.sin_addr), ntohs(in.sin_port));

    return retval;
}

【讨论】:

  • 仅供参考,HTTP 响应很容易大于 4096 字节,因此您可能需要重新考虑您的方法。请改用动态大小的缓冲区。将原始数据读入缓冲区,根据需要进行增长,然后从缓冲区中逐行读取,而不是使用strstr()
  • +1;这既提供了信息,也解决了我的(相当相似的)问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-20
  • 2013-06-08
  • 2017-02-01
  • 2013-06-12
  • 2016-01-23
相关资源
最近更新 更多