我无法结束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的“The Socket Networking API”:http://www.informit.com/articles/article.aspx?p=169505&seqNum=9
如何在不调用shutdown()
且没有客户端阻止的情况下从服务器编写欢迎消息?如果需要更多上下文,我将发布更多代码。
答案 0 :(得分:1)
请注意,readn()
在循环中设计为read()
,直到读取20个字节或EOF或套接字上的错误为止。如果服务器发送的消息长度少于20个字节,则客户端将阻止等待更多数据。
为防止阻止,您可以在套接字上执行正常read()
(或recv()
)。在这种情况下,这可能会做你想要的。
通常,您不能依赖于能够为write()
和read()
配对TCP连接。单个write()
字符串&#34; bar&#34;可以随意拆分数据。作为一个极端的例子,连续三个read()
可以返回&#34; b&#34;,&#34; a&#34;和&#34; r&#34;。这个特定的例子不太可能,但是对于较大的write()
和read()
,您必须考虑到这一点(对于较小的传输,如果您想要完全安全的话)。
要解决此问题,您必须在接收端进行自己的缓冲。在这种情况下,最简单的解决方案是read()
一次一个字符(或者使用readn()
与您期望的数据量完全相同(如果已知)。一个更通用的解决方案是read()
尽可能多的数据(当前可用(确保检查read()
的返回值以查看您获得的数据!)到缓冲区中并且仅作用于数据,只要你收集到足够的数据。只要有某些数据可供读取,普通read()
就不会阻止,但您可能会收到的数据少于您请求的数据。
&#34;足够了&#34;通常是一个完整的消息&#34;在您的协议中。您需要一些方法来确定消息边界。两个备选方案是长度字段(通常是我的经验中的最佳解决方案)或消息终止符。两者都将与其他数据一起发送。
<强>更新强>:
顺便提一下,你的空终止逻辑中有一个错误。将20个字节读入s_welcome_msg
会将s_welcome_msg[19]
设置为读取的最后一个字节,覆盖空终止符。如果要将一个20字节的非空终止字符串读入s_welcome_msg
并将其终止,则s_welcome_msg
需要长度为21个字节,您需要执行s_welcome_msg[20] = '\0'
}。
答案 1 :(得分:-2)
read(n)将阻塞,直到收到请求的字节数
(接收字段只有19个字节,所以它确实读取了20个字节 这将是一个缓冲区溢出,它是未定义的行为,可能/将导致seg错误事件)
作为一种可能的解决方案,我建议使用带有超时的select()语句的循环 当select()表示某些数据可用时, 只读一个字节 将该字节附加到s_welcome_msg []缓冲区
(同时始终检查缓冲区是否溢出 通常,这意味着只读取最多18个字节 所以读取值将是一个有效的字符串)
您的代码应该使read()成为非阻塞 所以它不会挂起。
读完字节后, 如果输入缓冲区未满(读取18个字节) 然后循环回到select()语句
如果发生select()超时, 然后假设已经读取了所有数据 并在选择/读取循环
之后继续执行下一个代码语句还记得永远刷新&#39;超时值 在select()语句参数之前 执行select()