对于冗长的文字墙,我提前道歉。
我正在写一个游戏服务器。基本上有线程来接受连接,每个玩家都有单独的线程,以及处理所有更新的线程。每个单独的播放器线程的唯一职责是向实际客户端读取和写入数据包。请记住,所有 I/O 都是使用 good ol'java.io
包完成的。
无论如何,当我写完一些内容时,我决定我要进行某种压力测试。所以我向我的一个朋友发送了一个 bot flooder 程序。该程序将假玩家快速连接到服务器。与其在本地运行它,我认为如果连接不是本地的,它会更加真实。我从测试中发现,玩家更新周期时间非常好,除了一旦连接了大约 200 名玩家,周期时间就会开始急剧增加(我们所说的从 15 毫秒到有时超过 9000毫秒))。起初,我认为它与接受者线程有某种关联,因为我注意到当周期变慢时,我收到“玩家已从...连接”消息的频率较低。但是,在我决定测量更新周期的各个步骤所花费的时间后,我发现这不是原因。瓶颈在于播放器更新。在我滚动到播放器更新方法的最底部后,我立即意识到了原因。
问题是在每次播放器更新结束时,都会发送更新数据包。大的,有时是数 KB 大小的数据包在构建后通过套接字的输出流发送。正如你们许多人所知,写入数据包是一个阻塞操作。而这种阻塞是导致周期时间看似随机峰值的原因。一旦有几百个玩家连接,服务器就会尝试更新所有这些玩家。但有时,其中一个连接会有点慢。因此,服务器将继续处理玩家,直到它尝试处理通过慢速套接字连接的玩家。一旦它构造了更新包,它就会尝试发送它。但是,由于套接字速度较慢,发送会阻塞很长时间。当发送操作被阻塞时,其他玩家都没有更新。因此,周期有时会花费极长的时间。
现在,我有点纠结于如何解决这个问题。理想的情况是让服务器构造数据包并将数据包交给单个玩家线程并让它发送它(并愉快地阻塞在它自己的小线程中)。当更新数据包将被构建时,服务器线程将存储更新数据包,并且可能会引发一些标志,表明数据包已构建。这听起来不错,但问题是很多时候,单个播放器线程都陷入了自己的阻塞操作中。播放器线程正在阻塞,因为它正在等待接收数据包。所以更新包一旦被读阻塞完就会被写入,但我担心更新包会在它实际构建后发送,这会让播放器看起来很滞后。
所以这里是我向你们提出建议的时候。你会如何处理这个问题?有人推荐我使用 NIO,但这需要我重写大部分代码,所以我宁愿用尽我的可能性,因为我采取了不同的路线。