【问题标题】:tcp indicating end of streamtcp 指示流结束
【发布时间】:2015-05-25 05:41:35
【问题描述】:

我无法结束 tcp 流。我正在编写一个简单的服务器和客户端,其中客户端连接到服务器,服务器显示一条欢迎消息,要求客户端输入用户名。

问题是,当服务器写入消息时,客户端的 read() 被阻塞。只有当我调用 shutdown() 时它才会被解除阻塞。

服务器:

           if (FD_ISSET(tcp_listenfd, &rset)) {
                  len = sizeof(cliaddr);
                  if ((new_confd = accept(tcp_listenfd, (struct sockaddr *) &cliaddr, &len)) < 0) {
                            perror("accept");
                            exit(1);
                  }
                  /* Send connection message asking for handle */
                  writen(new_confd, handle_msg, strlen(handle_msg));
                  /* Fork here or shutdown fd is inherited */
                  shutdown(new_confd, SHUT_WR);

客户:

    if ((connect(sock, (struct sockaddr *) server, sizeof(struct sockaddr_in))) < 0) {
            perror("inet_wstream:connect");
            exit(1);
    }

    s_welcome_msg[19] = '\0';
    readn(sock, s_welcome_msg, 20); //Blocks here if shutdown() is not called in server 

readn()writen() 函数改编自 Stevens 的“套接字网络 API”,可在此处找到:http://www.informit.com/articles/article.aspx?p=169505&seqNum=9

如何在不调用shutdown() 且没有客户端阻止的情况下从服务器编写欢迎消息?如果需要更多上下文,我将发布更多代码。

【问题讨论】:

  • readn 函数在实际读取 20 个字节之前不会返回(或者它得到 EOF,这是您通过关闭发送端引起的)。这可能不是你想要的。您需要决定客户端在看到消息结尾时如何知道。

标签: c tcp stream


【解决方案1】:

请注意,readn() 设计为循环中的read(),直到读取 20 个字节或出现 EOF 或套接字上的错误。如果服务器发送的消息长度小于 20 字节,客户端将阻塞等待更多数据。

为了防止它阻塞,您可以在套接字上执行普通的read()(或recv())。在这种情况下,这很可能会满足您的需求。

一般来说,您不能依赖将write()s 和read()s 配对来进行TCP 连接。字符串“bar”的单个write() 可以任意拆分数据。作为一个极端的例子,三个连续的read()s 可能会返回“b”、“a”和“r”。该特定示例不太可能发生,但对于较大的 write()s 和 read()s,您必须考虑到这一点(对于较小的传输,如果您想要完全安全的话)。

要解决此问题,您必须在接收端进行自己的缓冲。在这种情况下,最简单的解决方案是 read() 一次一个字符(或者使用 readn() 与您期望的数据量完全相同,如果已知的话)。一个更通用的解决方案是将read() 与当前可用的数据一样多(确保检查read() 的返回值以查看您返回了多少数据!)到缓冲区中,并且仅在您“已经收集到足够的了。只要有一些数据可供读取,普通的read() 就不会阻塞,但您返回的数据可能比您请求的要少。

“够了”通常是您协议中的完整“信息”。您将需要一些方法来确定消息边界。两种选择是长度字段(通常是我的经验中最好的解决方案)或消息终止符。两者都将与其余数据一起发送。

更新

顺便说一句,您的空终止逻辑中有一个错误。将 20 个字节读入 s_welcome_msg 会将 s_welcome_msg[19] 设置为读取的最后一个字节,从而覆盖您的空终止符。如果你想将一个 20 字节的非空终止字符串读入 s_welcome_msg 并以空终止它,s_welcome_msg 需要有 21 个字节长,你需要做 s_welcome_msg[20] = '\0'

【讨论】:

    【解决方案2】:

    read(n) 将阻塞直到它收到请求的字节数

    (并且接收字段只有 19 个字节,所以它确实读取了 20 个字节 这将是缓冲区溢出,这是未定义的行为,并且可能/将导致段错误事件)

    作为一种可能的解决方法,我建议使用带有超时的 select() 语句的循环 当 select() 指示一些可用数据时, 只读取一个字节 将该字节附加到 s_welcome_msg[] 缓冲区

    (同时始终检查缓冲区是否溢出 这通常意味着最多只能读取 18 个字节 所以读取的值将是一个有效的字符串)

    您的代码应该使 read() 成为非阻塞的 所以它不会挂起。

    读完字节后, 如果输入缓冲区未满(读取 18 个字节) 然后循环回到 select() 语句

    如果出现 select() 超时, 然后假设所有数据都已读取 并在选择/读取循环之后继续执行下一个代码语句

    还要记住始终“刷新”超时值 在 select() 语句参数之前 执行 select()

    【讨论】:

    • read() 阻塞,直到它传输至少一个字节,或流结束或发生错误。 OP 所要做的就是指定正确的缓冲区长度:一次读取一个字节是一个荒谬的建议,并且性能会很差。并且不要对不是代码的文本使用代码格式。
    猜你喜欢
    • 2021-09-20
    • 2018-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-10
    • 1970-01-01
    • 2013-10-23
    • 1970-01-01
    相关资源
    最近更新 更多