代码基础参考链接,十分感谢。
将服务器作为转接的中间站,用集合存储链接的socket、记录和在线成员。
任意关闭一个客户端或关闭服务器时会抛出 java.net.SocketException: Connection reset 的异常,原因是:一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常。因此此处的解决方法为在ServerThread中buf读取数据时加个try-catch块,有异常后进行相应的处理。
Server.java 服务器
package ChatRoomDemo; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class Server { public static List<Socket> list = new ArrayList<>(); // 客户端连接 public static List<String> record = new ArrayList<>(); // 聊天记录 public static List<String> online_member = new ArrayList<>(); // 在线成员 private static ServerSocket server; public static void main(String[] args) { try { server = new ServerSocket(4233); System.out.println("Chatroom is opening!"); while(true) { Socket socket = server.accept(); list.add(socket); new Thread(new ServerThread(socket)).start(); } } catch (IOException e) { e.printStackTrace(); } } }ServerThread.java 服务器线程
package ChatRoomDemo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class ServerThread implements Runnable { private final Socket socket; public ServerThread(Socket socket) { this.socket = socket; } @Override public void run() { try { BufferedReader buf = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter pw = new PrintWriter(socket.getOutputStream()); // 向客户端提示输入昵称 pw.println("Please enter your nickname:"); pw.flush(); // 读取客户端发送的昵称,并在服务器提示上线 String nickname = buf.readLine(); System.out.println(nickname + " is online"); // 给每个在线的客户端发送该客户端上线记录 for(Socket r : Server.list) { if(!r.equals(this.socket)) { pw = new PrintWriter(r.getOutputStream()); pw.println(nickname + " is online"); pw.flush(); } else { pw = new PrintWriter(r.getOutputStream()); pw.println("Welcome " + nickname); pw.flush(); } } // 在该客户端显示其他已上线的成员,并将自己添加进去 for(String s : Server.online_member) { pw.println(s + " is online"); pw.flush(); } Server.online_member.add(nickname); // 在该客户端显示聊天记录 pw = new PrintWriter(socket.getOutputStream()); for(String s : Server.record) { pw.println(s); pw.flush(); } // 自己聊天的部分 while(true) { String str; try { // 读取客户端发送的聊天信息,并记录 str = buf.readLine(); Server.record.add(nickname + ":" + str); // 若正确读取聊天信息,给所有在线成员刷新该信息 for(Socket r : Server.list) { pw = new PrintWriter(r.getOutputStream()); pw.println(nickname + ":" + str); pw.flush(); } } catch (Exception e) { // 客户端关闭后 System.out.println(nickname + " is offline"); Server.list.remove(socket); Server.online_member.remove(nickname); // 通知其他客户端该成员已下线 for(Socket r : Server.list) { pw = new PrintWriter(r.getOutputStream()); pw.println(nickname + " is offline"); pw.flush(); } break; } } } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }Link.java 客户端连接并运行
package ChatRoomDemo; import java.io.IOException; import java.net.Socket; public class Link { public static void linkstart() { try { Socket socket = new Socket("localhost", 4233); System.out.println("Connect successfully!"); new Thread(new ClientThread1(socket)).start(); // 将信息发送给服务器的线程 new Thread(new ClientThread2(socket)).start(); // 从服务器读取信息的线程 } catch (IOException e) { // 若服务器未开启 System.out.println("Server is closed, please try again later"); } } }ClientThread1.java 客户端发送消息进程
package ChatRoomDemo; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; /* Send message to server */ public class ClientThread1 implements Runnable{ private Socket socket; public ClientThread1(Socket socket) { this.socket = socket; } @Override public void run() { try { BufferedReader buf = new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw = new PrintWriter(socket.getOutputStream()); // 输入昵称,并发送给服务器 String nickname = buf.readLine(); pw.println(nickname); pw.flush(); // 发送聊天信息 while(true) { if(socket.isClosed()) break; String str = buf.readLine(); String date = new SimpleDateFormat("HH:mm:ss").format(new Date()); // 时间 pw.println(str + " " + date); pw.flush(); } buf.close(); pw.close(); } catch (Exception e) { e.printStackTrace(); } } }ClientThread2.java 客户端接收消息进程
package ChatRoomDemo; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.Socket; /* Get message from server */ public class ClientThread2 implements Runnable{ private Socket socket; private BufferedReader buf = null; public ClientThread2(Socket socket) { this.socket = socket; } @Override public void run() { try { buf = new BufferedReader(new InputStreamReader(socket.getInputStream())); while(true) { try { String str = buf.readLine(); if(str!=null) System.out.println(str); } catch (Exception e) { // 服务器关闭 System.out.println("Server is closed, please try to restart"); break; } } buf.close(); } catch (Exception e) { e.printStackTrace(); } } }Client1.java 同所有Client
package ChatRoomDemo; public class Client1 { public static void main(String[] args) { Link.linkstart(); } }
小注意点:不要在一个文件中看到socket用完就close,我就因为在ClientThread中直接finally close导致debug了一下午(ry