tcp 指示流结束

tcp indicating end of stream

我无法结束 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] = '[=11=]';
    readn(sock, s_welcome_msg, 20); //Blocks here if shutdown() is not called in server 

readn()writen() 函数改编自 Stevens 的 "The Socket Networking API",可在此处找到:http://www.informit.com/articles/article.aspx?p=169505&seqNum=9

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

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

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

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

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

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

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

如果发生select()超时, 然后假设所有数据都已读取 并在 select/read 循环

之后继续执行下一个代码语句

还记得始终 'refresh' 超时值 在 select() 语句参数之前 执行 select()

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

为了防止它阻塞,您可以在套接字上执行正常的 read()(或 recv())。在这种情况下,这很可能会如您所愿。

一般来说,您不能指望能够将 write()s 和 read()s 配对用于 TCP 连接。字符串 "bar" 的单个 write() 可以任意拆分数据。作为一个极端的例子,三个连续的 read() 可能 return "b"、"a" 和 "r"。这个特定的例子不太可能,但对于较大的 write()s 和 read()s 你必须考虑到这一点(如果你想绝对安全的话,对于较小的传输也是如此)。

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

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

更新:

顺便说一句,你的空终止逻辑有一个错误。将二十个字节读入 s_welcome_msg 会将 s_welcome_msg[19] 设置为最后读取的字节,覆盖您的空终止符。如果要将 20 字节的非空终止字符串读入 s_welcome_msg 并对其进行空终止,s_welcome_msg 将需要 21 个字节长,并且您需要执行 s_welcome_msg[20] = '[=29= ]'.