草庐IT

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

coder 2024-03-08 原文

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

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

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

1-该协议(protocol)是基于文本的,而不是基于二进制的。客户端和服务器交换以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逐行读取数据。

在该文档中,我找到了一个实用类Channels,它可以从SocketChannel中创建一个Reader。但是据说,如果Channel处于非阻塞模式,那么生成的Reader将无法工作,这与非阻塞模式对于使用我愿意使用的高性能 channel 选择API是强制性的这一事实相矛盾。因此,我怀疑这不是我想做的正确解决方案。
因此,第一个问题如下:如果我不能使用它,那么如何有效而适本地处理换行,并使用缓冲区和 channel 在nio API中将 native Java字符串从/转换为UTF-8编码的数据?
我必须用手玩get/put还是在包装好的字节数组中玩吗?如何从ByteBuffer转换为以UTF-8编码的字符串?我承认不太了解如何使用charset包中的类以及它如何工作。

2-在异步/非阻塞I/O领域中,对本质上必须依次执行的连续读/写的处理又如何呢?
例如,登录过程通常是基于质询-响应的:服务器发送一个问题(特定的计算),客户端发送响应,然后服务器检查客户端给出的响应。
我认为,答案是肯定不会在整个登录过程中都完成发送给工作线程的单个任务,因为它很长,并且有卡住工作线程太多时间的风险(想象一下,这种情况:10个池线程,则有10位玩家尝试同时连接;与已经在线的玩家相关的任务会延迟到再次准备好一个线程为止)。

3-如果两个不同的线程在同一Channel上同时调用Channel.write(ByteBuffer),会发生什么情况?
客户会收到杂乱无章的电话吗?例如,如果一个线程发送“aaaaa”,而另一个发送“bbbbb”,则客户端可以接收“aaabbbbbaa”,还是我确保所有内容均以一致顺序发送?通话返回后,我是否可以修改使用的缓冲区?
或换种说法,我是否需要额外的同步来避免这种情况?
如果我需要附加同步,如何知道写入完成后何时释放锁定等?
恐怕答案并不像在选择器中注册OP_WRITE那样简单。通过尝试这一操作,我注意到我一直在为所有客户端始终获取写就绪事件,大多数情况下尽早退出Selector.select,因为每个客户端每秒仅发送3或4条消息,而选择时每秒执行数百次循环。因此,从潜在的角度来看,主动等待是非常糟糕的。

4-多个线程可以同时在同一个选择器上调用Selector.select,而没有任何并发​​问题,例如丢失事件,安排两次事件等吗?

5-实际上,尼奥是否和据说的一样好?停留在经典的多线程模型上会很有趣,但是不是每个连接创建一个线程,而是使用更少的线程并在连接上循环以使用InputStream.isAvailable查找数据可用性吗?这个想法愚蠢和/或效率低下吗?

最佳答案

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

示例:(初读)

 USER foo
 PASS

(第二读)
 bar

您将需要存储(请参阅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>();

连接 channel 后,将新状态put编码到 map 中
    clients.put(channel,new State());

或将当前状态存储为SelectionKeythe attached object

然后,在执行每个命令时,更新状态。您可以将其编写为整体方法,也可以做其他更有趣的事情,例如State的多态实现,其中每个状态都知道如何处理某些命令(例如LoginState需要USER和PASS,然后将状态更改为新的AuthorizedState)。

3)我不记得使用NIO时每个 channel 有很多异步编写器,但是文档说它是线程安全的(我不会详细说明,因为我没有证明)。关于OP_WRITE,请注意,当写缓冲区为而不是满时,它将发出信号。换句话说,就像here:OP_WRITE几乎总是准备就绪,即套接字发送缓冲区已满时除外,因此您只会导致Selector.select()方法无意识地旋转。

4)是的。 Selector.select()执行blocking selection operation

5)我认为最困难的部分是从每个客户端线程的体系结构切换到将读写与处理分离的另一种设计。完成此操作后,使用 channel 比使用自己的方式阻止流更容易。

关于java - 从经典的多线程到java.nio异步/非阻塞服务器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15348035/

有关java - 从经典的多线程到java.nio异步/非阻塞服务器的更多相关文章

  1. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  4. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  5. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  6. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  7. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  8. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  9. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

  10. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

随机推荐