首页 > 解决方案 > Java:使用线程时发生套接字关闭错误

问题描述

我正在构建一个多线程聊天服务器。

多线程服务器(Manager类)可以为许多客户端提供服务。它接收来自客户端的消息,并向所有客户端广播。

客户端(Peer类)有两个线程——SendThread用于向服务器发送消息。ReceiveThread收听服务器广播。

但是,在运行客户端程序时,它会捕获异常并说socket closed.

我的服务器类代码如下:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class Manager {
    public int port;
    public ArrayList<Socket> clients;

    public Manager(int port) throws IOException {
        this.port = port;
        this.clients = new ArrayList<>();

        try (ServerSocket server = new ServerSocket(port)){
            System.out.println("Waiting for client connection-");
            while (true){
                Socket client = server.accept();
                clients.add(client);
                System.out.println("Client applies for connection");

                Thread t = new Thread(new serverClientThread(client));
                t.start();
            }
        }
    }

    public class serverClientThread implements Runnable {
        private Socket client;

        public serverClientThread(Socket client){
            this.client = client;
        }

        @Override
        public void run() {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(this.client.getInputStream()));

                while (true){
                    // read
                    String line = reader.readLine();
                    if (line != null){
                        System.out.println("I received "+line);
                        // write

                        // broadcast
                        broadcast("I received " + line);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }


    // broadcast the message to all clients
    public synchronized void broadcast(String message) throws IOException {
        for (Socket client:this.clients){
            if (client.isClosed()){
                continue;
            }
            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()))){
                writer.write("I received " + message);
                writer.newLine();
                writer.flush();
            }
        }
    }
}

客户端类的代码如下:

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Peer {
    public String hostname;
    public int port;

    public Peer(String hostname, int port){
        this.hostname = hostname;
        this.port = port;


        try (Socket socket = new Socket(hostname, port)){
            // create writer
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            Thread t1 = new Thread(new SendThread(writer));
            Thread t2 = new Thread(new ReceiveThread(reader));

            t1.start();
            t2.start();

        } catch (UnknownHostException e) {
            e.printStackTrace();
            System.exit(-1);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    public class SendThread implements Runnable{
        private BufferedWriter writer;

        public SendThread(BufferedWriter writer){
            this.writer = writer;
        }

        @Override
        public void run() {
            Scanner sc = new Scanner(System.in);
            while (true) {
                System.out.print("Enter a String: ");
                String str = sc.nextLine();
                // send to server
                if (str != null){
                    try {
                        this.writer.write(str);
                        this.writer.newLine();
                        this.writer.flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public class ReceiveThread implements Runnable{
        private BufferedReader reader;

        public ReceiveThread(BufferedReader reader){
            this.reader = reader;
        }

        @Override
        public void run() {
            while (true){
                String res = null;
                try {
                    res = this.reader.readLine();
                    if (res != null){
                        System.out.println("Server response: "+ res);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    System.exit(-1);
                }
            }
        }
    }
}

错误信息是:

java.net.SocketException: Socket closed
        at java.base/java.net.SocketInputStream.socketRead0(Native Method)
        at java.base/java.net.SocketInputStream.socketRead(SocketInputStream.java:115)
        at java.base/java.net.SocketInputStream.read(SocketInputStream.java:168)
        at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
        at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        at java.base/java.io.InputStreamReader.read(InputStreamReader.java:185)
        at java.base/java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.base/java.io.BufferedReader.readLine(BufferedReader.java:326)
        at java.base/java.io.BufferedReader.readLine(BufferedReader.java:392)
        at Peer$ReceiveThread.run(Peer.java:86)
        at java.base/java.lang.Thread.run(Thread.java:834)

它发生在ReceiveThreadPeer 类中。

感谢您提供任何帮助。谢谢!

一哥

标签: javamultithreadingsockets

解决方案


由于您使用的是try-with-resources,因此在您启动 t1 和 t2 后,套接字会立即自动关闭。

你可以想到

try (Socket socket = new Socket(hostname, port)){
  // [...]
  t1.start();
  t2.start();
}
// 

像这样:

Socket socket;
try {
  socket = new Socket(hostname, port)
  // [...]
  t1.start();
  t2.start();
} catch (/* [...] */) {
} finally {
  if (socket != null) {
    socket.close(); // <- here the socket is closed
  }
}

并且由于线程在后台运行,因此t1.start()不会等到线程 1 完成 -> 套接字已关闭。

没有资源尝试:

public class Peer {

  private Socket socket;
  // [...]

  public Peer(String hostname, int port) {
    // [...]
    try {
      this.socket = new Socket(hostname, port);
      // [...]
    } catch (UnknownHostException | IOException ex) {
      ex.printStackTrace();
      System.exit(-1);
    }
  }

  // Call this method when your program exits
  public void close() {
    if (this.socket != null) {
      this.socket.close();
    }
  }
}

推荐阅读