克服服务器程序中的阻塞写入

时间:2013-01-27 06:33:48

标签: java io java-server

我为冗长的文字墙提前道歉。

我正在写一个游戏服务器。基本上有线程接受连接,每个播放器都有单独的线程,以及处理所有更新的线程。每个单独的玩家线程的唯一责任是读取和写入实际客户端的数据包。请记住,所有的I / O都是使用好的方法完成的。 java.io包。

无论如何,当我写完一些内容后,我决定要进行某种压力测试。所以我发了一个bot flooder程序给我的一个朋友。该程序迅速将假玩家连接到服务器。我认为,如果连接不是本地连接,那么它将更加真实,而不是在本地运行。我从测试中发现的是,玩家更新周期时间非常长,除了连接大约200名玩家之外,周期时间会急剧增加(我们从15毫秒到有时超过9000) )。起初,我认为它与接受者线程有某种联系,因为我注意到,当周期减慢时,我正在接收"一个玩家已经连接..."消息不太频繁。但是,在我决定测量更新周期的各个步骤所花费的时间之后,我发现这不是原因。瓶颈在于玩家更新。在我滚动到播放器更新方法的最底层后,我立即发现了原因。

问题是每次播放器更新结束时,都会发送更新数据包。数据包的大小,有时是几千字节,一旦构造就会通过套接字的输出流发送。正如你们许多人所知,写入数据包是一种阻塞操作。阻塞是导致周期时间看似随机的峰值的原因。一旦有几百个玩家连接,服务器将尝试更新所有这些玩家。但有时候,其中一个连接会有点慢。因此,服务器将继续处理玩家,直到它试图处理通过慢速套接字连接的播放器。一旦构造了更新数据包,它就会尝试发送它。但是,由于插槽速度慢,发送块会持续很长时间。当发送操作被阻止时,其他所有玩家都没有被更新。因此,周期有时需要很长时间。

现在,我对如何处理这个问题感到困惑。理想的情况是让服务器构造数据包并将数据包交给单个播放器线程并让它发送(并快速阻塞它自己的小线程)。当构造更新数据包时,服务器线程将存储更新数据包,并可能引发一些信号表明数据包已构造的标志。这听起来不错,但问题是很多时候,单个玩家线程陷入了自己的阻塞操作。播放器线程正在阻塞,因为它正在等待接收数据包。所以更新数据包一旦完成被读取阻塞就会被写入,但我担心更新数据包会在实际构建之后发送,这会让玩家感觉落后。

所以这是我向你们提出建议的时候。你会如何解决这个问题?有人建议我使用NIO,但这需要我重写大部分代码,所以我宁愿用尽我的可能性,因为我采取了不同的路线。

1 个答案:

答案 0 :(得分:1)

正如@Drakosha在评论中所说,你可以使用一个有多个线程服务的消息队列。

也许是这样的:

import java.util.concurrent.LinkedBlockingDeque;
import static java.lang.System.*;
class Message{
    String message;
    public Message(String message) {this.message=message;}
    public String toString(){return message;}
}
public class Test {
static LinkedBlockingDeque<Message> outgoing=new LinkedBlockingDeque<>();
    public static void main(String[] args) {
        for(int i=0;i<10;i++) new Player(outgoing,i).start();
        for(int i=0;i<3;i++)  new Sender(outgoing,i).start();
    }
}
class Player extends Thread{
    private LinkedBlockingDeque<Message> outgoing;
    private int id;
    public Player(LinkedBlockingDeque<Message> outgoing,int id){this.outgoing=outgoing; this.id=id;}
    public void run(){
        for(int i=0;i<10;i++){
            try {
                outgoing.putLast(new Message(String.format("Player %d's message",id)));
                Thread.sleep(20);
            } catch (InterruptedException e) {e.printStackTrace();}
        }
    }
}
class Sender extends Thread{
    private LinkedBlockingDeque<Message> outgoing;
    private int id;
    public Sender(LinkedBlockingDeque<Message> outgoing,int id){this.outgoing=outgoing; this.id=id;}
    public void run(){
        while(true){
            Message m = null;
            try {
                m = outgoing.takeFirst();
                //send message
                if(Math.random()>0.95){
                    out.printf("Sender %d is hanging! %n",id);
                    Thread.sleep(1000);//Slow socket is blocking!
                }
            } catch (InterruptedException e) {e.printStackTrace();}
            out.printf("Sender %d sent message \"%s\" %n",id,m);
        }
    }
}