Java - 多线程服务器仅适用于一个连接

时间:2013-02-23 19:03:28

标签: java multithreading sockets client-server

我正在编写一个多人游戏,我正在使用套接字在Java中编写自己的服务器。

我成功地能够使用Java教程和一些谷歌搜索来启动和运行服务器,但是当我尝试实现多线程服务器来处理多个客户端时,它的工作效果不是很好。

连接的第一个客户端仍然可以正常工作,但连接的任何其他客户端只会坐在那里,在将输入发送到服务器后什么都不做。这是我的代码:

//This class handled Server/client communication for one client.
public class GameRoom implements Runnable {
  public GameRoom(Socket socket) {
    this.socket = socket;
  }

  public void run() {
    logger.info("Game room has been created.");

    while(true) {
      try {
        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        out = new PrintWriter(socket.getOutputStream());
        String clientResponse = in.readLine();
        out.println("You wrote " + clientResponse);
        out.flush();
      } catch (IOException e) {
        logger.severe(e.getMessage());
        throw new RuntimeException();
      }
    }
  }

  //Will eventually change the string to a GSON object and delegate the appropriate actions based on the gson object type.
  private String delegateClientInput(String clientInput) {
    return "I heard you say: " + clientInput + "\n";
  }

  private BufferedReader in;
  private PrintWriter out;
  private Socket socket;
  private static final Logger logger = LogUtil.initializeServerLog(GameRoom.class.toString());
}

/*
 * This class houses the server socket itself.  Handles connecting to multiple clients.
 */
public class ServerClientThreadPool extends Thread {
  public static void main(String[] args) {
    ServerClientThreadPool serverClientThreadPool = new ServerClientThreadPool();
    serverClientThreadPool.startServer();
  }

  ServerClientThreadPool() {
    try {
      serverListener = new ServerSocket(GAME_ROOM_PORT);
    } catch (IOException e) {
      logger.severe(e.getMessage());
      throw new RuntimeException();
    }
  }

  public void startServer() {
    for(int i = 0; i < MAX_CONNECTIONS; i++) {
      try {
        GameRoom gameRoom = new GameRoom(serverListener.accept());
        gameRoom.run();
      } catch (IOException e) {
        logger.severe(e.getMessage());
        throw new RuntimeException();
      }
    }
  }

  private static final int GAME_ROOM_PORT = 18000;
  private ServerSocket serverListener;
  private static final int MAX_CONNECTIONS = 100;
  private static final Logger logger = LogUtil.initializeServerLog(ServerClientThreadPool.class.getName());
}

这是客户端的主要功能,当然是一个单独的程序:

ClientSocketWrapper clientSocketWrapper = ClientSocketWrapper.createSocketWrapperAndConnectTo("localhost", 18000);

/** MAIN ENTRY POINT FOR CLIENT */
while(true) {
  clientSocketWrapper.updateInputOutputStream();
  clientSocketWrapper.writeToOutputStream(executeClientInputProcessing());
  updateGameState(clientSocketWrapper.getServerResponse());
}

我意识到你无法真正看到这些方法中发生了什么,但它基本上只是像Java教程那样实现客户端,并且它按预期工作。如果您认为我需要发布在这里运行的方法,请告诉我,但我认为问题出在服务器端。

奖励问题:我也想知道我是否走在正确的轨道上。游戏相当复杂,但我只是要使用Gson序列化和反序列化对象,并根据来回发送的gson字符串委派相应的操作。谷歌搜索服务器/客户端架构后,似乎我的方法太简单了。我很难找到很好的资源来学习更高级的服务器/客户端架构,所以任何指向优秀书籍或教程的链接都将是一个很大的帮助。

全部谢谢!

1 个答案:

答案 0 :(得分:3)

您不想直接致电gameRoom.run()。这导致它在当前线程中执行,该线程占用了服务器循环,直到run()方法完成。相反,您应该在单独的线程中运行它。从ExecutorService工厂类获得的Executors将是一种处理它的简单方法。例如,在ServerClientThreadPool中创建一个ExecutorService:

public class ServerClientThreadPool {
    private ExecutorService executorService = Executors.newCachedThreadPool();

然后在接受套接字连接后将gameRoom提交给服务:

GameRoom gameRoom = new GameRoom(serverListener.accept());
executorService.submit(gameRoom);

请注意,我没有在上面的ServerClientThreadPool上编写extends Thread。你不是在上面的代码中以任何方式使用它作为一个线程,除非你需要服务器在其他事情发生的同时接受连接,否则它可能是不对的(除了游戏之外)房间,已经在单独的线程中处理)。如果确实需要,那么它也应该是Runnable,而不是Thread,也可以提交给ExecutorService

根据您的代码中有关线程如何工作的明显混淆,我建议您重新访问Java "Concurrency" tutorial,其中包括a section on Executors

奖励:我认为来回传递JSON是一种合理的通信方式。这样做意味着你并不过分关注性能,所以我建议你自己停止处理套接字,而是在服务器端使用嵌入式HTTP服务器,在客户端使用简单的HTTP客户端。这将为您处理所有套接字和线程无意义,让您放心地担心游戏逻辑的细节。

例如,embedding Jetty是一项微不足道的工作,并且有许多功能强大且简洁的HTTP / REST客户端,例如Apache Http ClientJersey REST Client