如何在具有I/O多路复用的Java服务器中异步处理请求?
假设我正在编写一个Java服务器,它通过TCP/IP与客户机通信 服务器使用如何在具有I/O多路复用的Java服务器中异步处理请求?,java,multithreading,sockets,Java,Multithreading,Sockets,假设我正在编写一个Java服务器,它通过TCP/IP与客户机通信 服务器使用I/O多路复用。有一个线程T0,它等待选择器并处理客户端连接。例如,如果连接已准备好读取,则T0将从该连接读取数据 假设服务器已读取传入的请求,现在已准备好处理该请求。由于处理需要时间,请求在另一个线程T1中处理,T0返回等待选择器 假设T1已完成处理并创建响应。现在T0应该开始向客户端连接写入响应。所以我的问题是:T1如何将响应发送到T0 我建议使用只在ServerSocket.accept()上阻塞的服务器线程,一旦
I/O多路复用
。有一个线程T0
,它等待选择器
并处理客户端连接。例如,如果连接已准备好读取,则T0
将从该连接读取数据
假设服务器已读取传入的请求,现在已准备好处理该请求。由于处理需要时间,请求在另一个线程T1
中处理,T0
返回等待选择器
假设
T1
已完成处理并创建响应。现在T0
应该开始向客户端连接写入响应。所以我的问题是:T1
如何将响应发送到T0
我建议使用只在ServerSocket.accept()
上阻塞的服务器线程,一旦它接受了连接,就将其提交给ExecutorService
。虽然从理论上讲,您可以有任意数量的线程,但我不会这样做,因为这会使您的应用程序容易受到DoS攻击。相反,限制线程池的最大大小,并在服务器负载超过上限时使其正常降级
在ExecutorService
的文档中,实际上有很多关于如何执行此操作的说明
更新:我可能误解了你的问题。正如我现在所理解的,您知道上面建议的解决方案,但希望有目的地使用
这将有助于了解您的服务器提供的服务类型以及可能的限制因素(CPU、磁盘I/O、网络等)
您可以为每个传入连接分配一个唯一的请求ID,并将处理程序对象插入该ID下的映射中。然后,如果连接就绪,网络线程将选择相应的处理程序,并要求它接受一定量的输入/产生一定量的输出。当然,这是否适用于您的情况将取决于您的服务器提供的服务。相同的线程T1应该读取、处理并将结果返回给客户端 下面是一个关于如何使用JavaNIOAPI而不将线程数量与客户端数量联系起来的概要
**//Thread T0** //wait for selection keys
...
Iterator it = selector.selectedKeys().iterator( );
while (it.hasNext( )) {
SelectionKey key = (SelectionKey) it.next();
// Is a new connection coming in?
if (key.isAcceptable( )) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept()
// Set the new channel nonblocking
channel.configureBlocking (false);
// Register it with the selector
channel.register (selector, SelectionKey.OP_READ);
}
// Is there data to read on this channel?
if (key.isReadable( )) {
processRequest (key);
}
it.remove( );
}
...
ExecutorService service = Executors.newFixedThreadPool(50);
...
void processRequest(final SelectionKey key) {
**//Thread T1-T50** //deal with request
executorService.submit(new Runnable() {
SocketChannel channel = (SocketChannel) key.channel();
//read data from channel, process it and write back to the channel.
});
)
}
与客户端的所有通信将通过套接字和第二个线程T1进行。T0将仅用于等待新连接。因此,每个客户端连接都有一个线程。您能为1000个客户端提供服务吗?是的,每个客户端连接至少一个线程。关于你的第二个问题,我不做这种类型的编程,所以我不能回答关于1000个客户机的这个特定问题,但我想是这样的,尽管我也认为无论使用何种编程语言,计算机硬件和软件资源都必须受到限制。当然,如果套接字关闭,您会让任何线程(自然)结束。好的。因此,您可能无法为每个连接使用一个(本机)线程来服务1000个客户端。但是,您可以使用I/O多路复用来实现这一点。这就是为什么I/O多路复用是有用的。我将不得不放弃这一点,因为我对多路复用的概念还不是一个完全的新手。我将观看并希望从这个问题线索中学习。谢谢。不幸的是,我认为这个解决方案是次优的。问题是,每个客户端连接有一个线程,然后无法为1000个客户端提供服务器,例如,I/O多路复用(在选择器循环中的线程上)可以提供更多服务。如果您愿意,可以创建一个包含1000个工作线程的线程池。我的意思是,1000个线程对于单个框来说太多了。无论如何,与紧选择器循环中的一个线程相比,每个客户端连接使用一个线程是没有效率的。我同意同步多路复用可能会更有效率。在C中,这将是一条直截了当的道路。另一方面,我认为你不会杀死任何拥有1000个线程的现代机器。(更有可能是1000个套接字…
ExcutorService
s对我来说在Java中感觉更自然。@Michael同样,1000个客户端可以有1个线程池。线程池的大小不是由客户机的数量决定的,事实上,如果客户机有特别苛刻的客户机,那么您可以拥有比客户机更多的线程。谢谢。这与前面建议和讨论的“每个连接的线程”解决方案相同。T1-T50线程仅在客户端发送任何内容时工作。他们不忙着等待套接字上的某些内容。@Michael此解决方案不在每个连接上使用线程,因为执行器的线程可以(并且将)被所有活动连接重用。始终会有x个线程,与当前活动的y连接无关。特别是,x可以是1(Executors.newSingleThreadExecutor
),您正好拥有您在问题中描述的T0
和T1
。@dcernahoschi谢谢。明白了:)假设一个慢客户端每分钟发送一个字节的10K消息。您是否建议为每个要读取的字节提交一个新任务。看起来开销太大了。@afsantos谢谢你的更正。我会考虑的。