对于练习,我们将实现一个服务器,该服务器具有一个侦听连接的线程,接受它们并将套接字抛出到BlockingQueue中。然后,池中的一组工作线程将通过队列并处理通过套接字进入的请求。
每个客户端连接到服务器,发送大量请求(在发送下一个请求之前等待响应),并在完成后最终断开连接。
我当前的方法是让每个工作线程在队列上等待,获取套接字,然后处理一个请求,最后将(仍然打开的)套接字放回队列中,然后再处理另一个请求,可能来自不同的客户端。客户端线程比工作线程多得多,因此很多连接都会排队。
此方法存在的问题:即使客户端没有发送任何内容,客户也会阻止该线程。可能的伪解决方案,都不尽如人意:
<input onChange={this.handleChange} />
上调用available()
,如果连接返回0,则将连接重新置于队列中。问题:无法检测客户端是否仍然连接。inputStream
或socket.isClosed()
来确定客户端是否仍处于连接状态。问题:两种方法都没有检测到客户端挂断,正如EJP在Java socket API: How to tell if a connection has been closed?中所描述的那样有没有办法解决这个问题?即是否可以在不阻止或发送内容的情况下区分断开连接的客户端和被动客户端?
答案 0 :(得分:3)
简答:不。如需更长的答案,请参阅EJP的答案。
这就是为什么你可能根本不应该将套接字放回队列,而是处理来自套接字的所有请求,然后关闭它。将连接传递给不同的工作线程以单独处理请求不会给您带来任何好处。
如果您的客户端性能不佳,则可以在套接字上使用读取超时,因此只有在超时发生时才会阻止读取。然后你可以关闭那个套接字,因为你的服务器没有时间来满足那些表现不佳的客户端。
答案 1 :(得分:2)
有没有办法解决这个问题?即是否可以在不阻止或发送内容的情况下区分断开连接的客户端和被动客户端?
使用阻止IO时不是真的。
您可以查看非阻塞(NIO)程序包,该程序包处理的内容略有不同。
本质上你有一个可以用“选择器”注册的套接字。如果为“数据准备好读取”注册套接字,则可以确定要读取的套接字而无需单独轮询。
同样的写作。
答案 2 :(得分:1)
事实证明,这个问题可以通过一些技巧来解决。经过与几个人的长时间讨论,我结合他们的想法,在合理的时间内完成工作:
socket.setSoTimeout(100);
System.currentTimeMillis()
available()
。如果返回0,则将连接重新放回队列,因为没有什么可读的。available()
的情况:如果时间戳太旧(例如,超过1秒),请使用read()
实际阻止连接。这不会超过您为套接字设置的SoTimeout
。如果遇到TimeoutException,请将连接放回队列中。如果读取-1,则将远程端关闭的连接丢弃。使用此策略,大多数读取尝试会立即终止,要么返回一些数据,要么因为没有available()
而被跳过。如果另一端关闭了它的连接,我们将在一秒钟内检测到这一点,因为上一次成功读取的时间戳太旧了。在这种情况下,我们执行将返回-1的实际读取,并相应地更新套接字isClosed()
。在套接字仍然打开但队列太长以至于我们有超过一秒的延迟的情况下,我们需要100毫秒才能发现连接仍在那里但尚未就绪。
编辑:对此的改进是改变&#34;上次成功阅读&#34;到&#34;最后阻止阅读&#34;并在获取TimeoutException时更新时间戳。
答案 3 :(得分:0)
不,从未正确关闭其套接字的客户端识别非活动客户端的唯一方法是发送ping或其他内容以检查它们是否仍在那里。
我能看到的可能解决方案是