Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/402.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
从经典的多线程到java.nio异步/非阻塞服务器_Java - Fatal编程技术网

从经典的多线程到java.nio异步/非阻塞服务器

从经典的多线程到java.nio异步/非阻塞服务器,java,Java,我是一个在线游戏的主要开发者。 玩家使用特定的客户端软件,通过TCP/IP(TCP,而不是UDP)连接到游戏服务器 目前,服务器的体系结构是典型的多线程服务器,每个连接一个线程。 但在高峰时段,通常有300或400人连接,服务器变得越来越滞后 我想知道,如果切换到一个java.nio.*异步I/O模型,用几个线程管理多个连接,性能是否会更好。 在web上查找包含此类服务器体系结构基础知识的示例代码非常容易。然而,经过数小时的谷歌搜索,我没有找到一些更高级问题的答案: 1-协议是基于文本的,而不是

我是一个在线游戏的主要开发者。 玩家使用特定的客户端软件,通过TCP/IP(TCP,而不是UDP)连接到游戏服务器

目前,服务器的体系结构是典型的多线程服务器,每个连接一个线程。 但在高峰时段,通常有300或400人连接,服务器变得越来越滞后

我想知道,如果切换到一个java.nio.*异步I/O模型,用几个线程管理多个连接,性能是否会更好。 在web上查找包含此类服务器体系结构基础知识的示例代码非常容易。然而,经过数小时的谷歌搜索,我没有找到一些更高级问题的答案:

1-协议是基于文本的,而不是基于二进制的。客户端和服务器交换UTF-8编码的文本行。一行文本表示一个命令,每行都以\n或\r\n结尾。 对于经典的多线程服务器,我有这样的代码:

public Connection (Socket sock) {
this.in = new BufferedReader( new InputStreamReader( sock.getInputStream(), "UTF-8" ));
this.out = new BufferedWriter( new OutputStreamWriter(sock.getOutputStream(), "UTF-8"));
new Thread(this) .start();
}
然后在运行时,使用readLine逐行读取数据

在文档中,我发现了一个实用类通道,它可以从SocketChannel中创建一个读卡器。但据说,如果通道处于非阻塞模式,生成的读卡器将无法工作,这与非阻塞模式对于使用我愿意使用的高性能通道选择API是强制性的这一事实相矛盾。因此,我怀疑这不是我想做的事情的正确解决方案。 因此,第一个问题是:如果我不能使用它,如何高效、正确地使用缓冲区和通道在NIOAPI中断开行并将本机java字符串从UTF-8编码数据转换为UTF-8编码数据? 我必须手动玩get/put还是在包装的字节数组中?如何从ByteBuffer转换为UTF-8编码的字符串?我承认我不太了解如何在charset包中使用类以及它是如何工作的

2-在异步/非阻塞I/O世界中,如何处理连续读/写,这些读/写本质上是一个接一个地依次执行的? 例如,登录过程通常基于质询响应:服务器发送一个问题(一个特定的计算),客户端发送响应,然后服务器检查客户端给出的响应。 答案是,我认为,肯定不会在整个登录过程中向工作线程发送单个任务,因为它相当长,有可能冻结工作线程太长时间(想象一下这样的场景:10个池线程,10个玩家同时尝试连接;与已经在线的玩家相关的任务被延迟,直到一个线程再次就绪)

3-如果两个不同的线程同时调用同一通道上的Channel.write(ByteBuffer),会发生什么情况? 客户端是否会收到混淆的行?例如,如果一个线程发送“aaaaa”,而另一个线程发送“bbbbb”,客户端是否会收到“aaabbbbaa”,或者我是否确保所有内容都是按组顺序发送的?是否允许我在返回调用后立即修改使用的缓冲区? 或者换个角度问,我是否需要额外的同步来避免这种情况? 如果我需要额外的同步,如何知道写入完成后何时释放锁等? 我担心答案并不像在选择器中注册OP_WRITE那样简单。通过尝试,我注意到我一直都在为所有客户机准备写操作,退出选择器。提前选择通常是免费的,因为每个客户机每秒只有3或4条消息要发送,而选择循环是按hund执行的每秒的次数。所以,潜在的,积极的等待是非常糟糕的

4-多个线程是否可以同时调用同一选择器上的Selector.select,而不会出现任何并发问题,如丢失事件、将其调度两次等

5-事实上,nio是否如人们所说的那样好?保持经典的多线程模型,但不停止为每个连接创建线程,使用更少的线程并在连接上循环,以使用InputStream.isAvailable查找数据可用性,这是一个愚蠢和/或低效的想法吗?

1)是的。我认为您需要编写自己的非阻塞readLine方法。还请注意,当缓冲区中有多行时,或当存在不完整的行时,可能会发出非阻塞读取信号:

示例:(一读)

(二读)

您需要存储(参见2)未使用的数据,直到有足够的信息准备好处理它

 //channel was select for OP_READ
 read data from channel 
 prepend data from previous read
 split complete lines
 save incomplete line
 execute commands
2) 您需要保持每个客户端的状态

    Map<SocketChannel,State> clients = new HashMap<SocketChannel,State>();
或者存储从
选择键开始的当前状态

然后,在执行每个命令时,更新状态。您可以将其作为一个整体方法编写,或者执行一些更奇特的操作,例如
状态
的多态实现,其中每个状态都知道如何处理某些命令(例如
登录状态
期望用户和传递,然后将状态更改为新的
授权状态

3) 我不记得在每个通道上使用了很多异步编写器的NIO,但是文档中说它是线程安全的(我不会详细说明,因为我没有证据证明这一点)。关于OP_WRITE,请注意,当写入缓冲区未满时,它会发出信号。换句话说,如上所述:OP_WRITE几乎总是准备就绪的,即套接字发送缓冲区已满时除外,因此您只需使您的
选择器.select()
方法无意识地旋转即可

4) 对<代码>选择器。选择()

5) 我认为最困难的部分是从每客户机一个线程的架构切换到一种不同的设计,在这种设计中,读写与处理是分离的。一旦你做到了这一点,使用频道要比用你自己的方式使用阻塞流更容易。

 //channel was select for OP_READ
 read data from channel 
 prepend data from previous read
 split complete lines
 save incomplete line
 execute commands
    Map<SocketChannel,State> clients = new HashMap<SocketChannel,State>();
    clients.put(channel,new State());