联网游戏服务器中ClientHandler的PrintWriter抛出NullPointerException排查
解决ClientHandler中PrintWriter空指针异常问题
问题本质
你遇到的NullPointerException核心原因是线程执行的异步性:
在Server的run方法中,创建ClientHandler并启动线程后,立刻调用handlers.get(0).out.println(),但此时ClientHandler的run方法可能还未执行到out = new PrintWriter(...)初始化代码,out变量仍为null,因此触发空指针异常。此外,ClientHandler内部嵌套额外线程处理输入属于冗余设计,会增加线程管理复杂度。
解决方案
1. 提前初始化IO流(最直接修复)
将PrintWriter和BufferedReader的初始化逻辑移到ClientHandler的构造方法中,确保创建实例时IO流已完成初始化,Server后续调用out时不会出现null。
2. 移除冗余嵌套线程
ClientHandler本身已是Runnable,直接在其run方法中处理客户端输入即可,无需额外启动新线程。
3. 修复其他潜在问题
- 登录逻辑:原代码遍历handler判断用户名重复时,新用户的
name尚未设置,会触发空指针,改为检查players列表; - switch分支:
STARTcase缺少break,会继续执行default分支的广播逻辑; - 线程安全:改用
CopyOnWriteArrayList存储handlers,避免多线程操作ArrayList的并发问题。
修改后的代码示例
ClientHandler代码
import java.io.*; import java.net.Socket; import java.util.ArrayList; public class ClientHandler implements Runnable{ private Socket socket; private ArrayList<ClientHandler> handlers; private Server server; public PrintWriter out; private BufferedReader in; private String name; // 构造方法中初始化IO流,确保实例创建后即可使用 public ClientHandler(Socket socket, ArrayList<ClientHandler> handlers, Server server){ this.socket = socket; this.handlers = handlers; this.server = server; try { out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); } catch (IOException e) { throw new RuntimeException(e); } } public void broadcastMessage(String msg){ System.out.println("Broadcasting"); for (ClientHandler s : this.handlers){ s.out.println("Player: " + msg); } } public static String removePrefix(String s, String prefix) { if (s != null && s.startsWith(prefix)) { return s.split(prefix, 2)[1]; } return s; } public String getName(){ return name; } @Override public void run() { // 直接在当前线程处理输入,移除冗余嵌套线程 try { String msg; while((msg = in.readLine()) != null){ switch (msg.split(" ")[0]){ case "LOGIN": name = removePrefix(msg,"LOGIN "); server.login(name); break; case "HELLO": server.hello(); break; case "PLACE": server.place(removePrefix(msg,"PLACE ")); break; case "QUIT": // todo 实现退出逻辑 break; case "STOP": // todo 实现停止逻辑 break; case "START": server.setStart(); break; // 补充break,避免执行default分支 default: broadcastMessage(msg); break; } } } catch (IOException e) { throw new RuntimeException(e); } finally { // 关闭资源并从列表移除当前handler try { in.close(); out.close(); socket.close(); handlers.remove(this); } catch (IOException e) { e.printStackTrace(); } } } }
Server代码(关键修改部分)
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.concurrent.CopyOnWriteArrayList; public class Server implements Runnable{ // 使用线程安全的CopyOnWriteArrayList存储客户端handler private ArrayList<ClientHandler> handlers = new CopyOnWriteArrayList<>(); private ArrayList<Player> players = new ArrayList<>(); private Game game; private boolean start; // ... 其他原有方法不变 ... public void login(String name){ // 修改登录逻辑:检查players列表中的用户名是否重复 boolean isTaken = false; for (Player p : players){ if (p.getName().equals(name)){ isTaken = true; break; } } if (isTaken){ alreadyTaken(name); } else { // 找到当前客户端对应的handler并添加玩家 for(ClientHandler handler : handlers){ if(handler.getName().equals(name)){ players.add(new HumanPlayer(name, handler)); break; } } } } @Override public void run() { ServerSocket serverSocket; try { serverSocket = new ServerSocket(1800); } catch (IOException e) { throw new RuntimeException(e); } System.out.println("----Server----"); while (!serverSocket.isClosed()) { try { Socket socket = serverSocket.accept(); ClientHandler handler = new ClientHandler(socket,handlers,this); handlers.add(handler); Thread h = new Thread(handler); h.start(); System.out.println("A new client has connected"); // 此时out已完成初始化,不会抛出空指针 handlers.get(0).out.println("START? "+ handlers.size()); if (start){ System.out.println("start request works"); } } catch (IOException e) { throw new RuntimeException(e); } } } }
内容的提问来源于stack exchange,提问作者Atrocitas
相关产品推荐
相关产品推荐

