【问题标题】:Winsock: recv() on Server is blocking, but client has already moved past send()Winsock:服务器上的 recv() 阻塞,但客户端已经移过 send()
【发布时间】:2019-10-11 21:52:42
【问题描述】:

我正在为学校做一个项目,但遇到了以下问题。尽管我的客户端已经发送了完整的消息,但我的服务器在 recv() 上被阻塞了。

这就是我想要发生的事情:

Server        Client
recv() <----  send()
send() ---->  recv()

这是正在发生的事情:

Server        Client
recv() <----  send()
recv() -----  recv()

一些背景

2 周前,我使用已编码的服务器应用程序自行创建了客户端。当我对客户端进行编码时,它与提供的服务器一起正常运行,所以我想说客户端是错误的,但我不知道如何让我编码的服务器认识到不会有更多数据进入。

代码

这是我认为相关的代码:

客户:

    bytesSent = 0;
    retVal = send(sock, phrase, msgLen, 0);
    bytesSent = retVal;
    while (bytesSent < msgLen) {
        retVal = send(sock, phrase + bytesSent, msgLen - bytesSent, 0);
        if (retVal == SOCKET_ERROR) {
            DisplayFatalErr("send() function failed.");
            exit(1);
        }
        bytesSent += retVal;
        // May need to re-call send in order to keep sending the data.

    }

...

    bytesRead = 0;
    while (bytesRead < msgLen) {
        retVal = recv(sock, rcvBuffer, RCVBUFSIZ - 1, 0);
        if (retVal <= 0) {
            DisplayFatalErr("recv() function failed.");
            exit(1);
        }
        bytesRead += retVal;
        for (int i = 0; i < retVal; i++) {
            printf("%c", rcvBuffer[i]);
        }
    }

服务器:

    char* rcvBuffer[RCVBUFSIZ]; // RCVBUFSIZ = 50
    char* msg = "";
    int bytesRead = 0;
    do {
        if ((bytesRead = recv(clientSock, rcvBuffer, RCVBUFSIZ - 1, 0)) == 0) {
            break;
        }
        if (bytesRead < 0) {
            return -1;
        }
        char* msgConcatenated;
        int msgLen = strlen(msg);
        msgConcatenated = malloc(msgLen + bytesRead);
        if (msgConcatenated != NULL) {
            int newMsgLen = strlen(msgConcatenated);
            strncpy_s(msgConcatenated, newMsgLen, msg, msgLen);
            strncat_s(msgConcatenated, newMsgLen, rcvBuffer, bytesRead);
            msg = msgConcatenated;
        }
    } while (bytesRead != 0);

如果我需要提供额外信息,请告诉我。

【问题讨论】:

  • 关于:int msgLen = strlen(msg); 因为 char 数组 msg 没有被设置为任何东西,尤其是任何以 NUL 终止字节结尾的东西,结果是未定义的行为,它可能导致任何东西,包括段故障事件
  • 关于:msgConcatenated = malloc(msgLen + bytesRead); 这会导致内存泄漏,因为原始指针被覆盖(因此永远丢失)强烈建议使用realloc()
  • 请发minimal reproducible example,以便我们重现问题并帮助您调试。
  • 关于:bytesRead += retVal; for (int i = 0; i &lt; retVal; i++) { printf("%c", rcvBuffer[i]); 为什么不断重复输出部分消息?建议全部接收后才输出数据
  • @user3629249:我没有看到 int msgLen = strlen(msg) 的未定义行为,因为在第一次迭代中,msg 指向一个空字符串,在进一步的迭代中,它指向最后一个连接的字符串。但是,您是对的,代码包含memory leak。但是你提到的那一行并没有发生内存泄漏,因为msgConcatenated已经保存在msg中。相反,内存泄漏发生在msg = msgConcatenated 行中,因为msg 之前指向的内存不再被任何变量引用。

标签: c winsock2


【解决方案1】:

当使用TCP,向套接字的另一端发出不再发送数据的信号时,必须发送一个设置了 FIN 标志的数据包。这在 Winsock 中通过调用函数shutdown 以 SD_SEND 作为第二个参数来完成。这将导致套接字另一端的程序在调用recv 时不再阻塞。相反,recv 将返回 0,表示连接已正常关闭(除非还有尚未读取的数据)。有关详细信息,请参阅Microsoft documentation on the shutdown functionThis documentation page 还包含一些关于优雅套接字关闭的有用信息。

此外,正如 cmets 中所指出的,您的代码在以下行中包含 memory leak

msg = msgConcatenated

在该行中,您重新分配 msg 而不首先释放 msg 指向的内存。不幸的是,修复内存泄漏并不容易,因为在重新分配之前不能简单地在msg 上调用free。这是因为,在循环的第一次迭代中,msg 还可以指向动态分配内存之外的其他内容。因此,要修复泄漏,您还必须跟踪 msg 指向的内存类型,或者使其始终指向动态分配的内存,即使字符串为空(即它只包含终止空字符)。

【讨论】:

  • OP 发布的代码有很多错误,你真的应该提到所有其他问题。讨论关闭服务器上的套接字的方法不会帮助 OP 解决其他问题
  • @user3629249:感谢您指出内存泄漏,我在答案中添加了对它的引用。