Primary Practice h60
在 ChatServer
、ChatClient
中增加适当代码,并增加适当的类,完成一个简单的聊天室
和之前 OOP 的一个实验类似,利用 Socket 实现 client 和 server 通信。不同的是,本实验需要多客户端和一服务器通信。
0x00 ChatServer 类变量一定包括一个 ServerSocket
。
根据要求,需要从文件中读取到所有的用户名和密码,用 HashMap
保存。
多 client,为了给每一个 client 回复,需要保存目前已经连接的 client。
1 2 3 private final ServerSocket server;private final Map<String, String> passwd;public static List<Socket> clients = new ArrayList<>();
构造方法只需要初始化 server 和 passwd:
1 2 3 4 public ChatServer (int port, String passwordFilename) throws IOException { server = new ServerSocket(port); passwd = readLines(passwordFilename); }
经典:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public Map<String, String> readLines (String filename) throws IOException { String line; Reader reader; Map<String, String> result = new HashMap<>(); reader = new FileReader(filename); LineNumberReader lineReader = new LineNumberReader(reader); while (true ) { line = lineReader.readLine(); if (line == null ) { break ; } if (line.trim().length() == 0 || line.startsWith("#" )) { continue ; } String[] lineContent = line.split("\t" ); result.put(lineContent[0 ], lineContent[1 ]); } return result; }
需要开始监听,所以此类一定继承了 Thread
,startListen
只需要调用 run
方法:
1 2 3 public void startListen () { this .start(); }
重写 run
,因为要持续监听,与多个 client 相连,所以需要新建 ServerThread
类,在此类中调用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public void run () { while (true ) { Socket socket = null ; try { socket = server.accept(); } catch (IOException e) { e.printStackTrace(); } clients.add(socket); new ServerThread(socket, passwd).start(); } }
0x01 ServerThread 构造方法:
1 2 3 4 5 6 7 8 private final Socket socket;private final Map<String, String> passwd;public ServerThread (Socket socket, Map<String, String> passwd) { super (); this .socket = socket; this .passwd = passwd; }
重写 run
,从输入流读入,向输出流写出。读入的是每一个客户端说的话,写出需要向所有客户端写出。
这里不知道怎么处理比较合适,可能会有更好的写法,欢迎提 pr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Override public void run () { try { BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream()); String line = in.readLine(); String passwdStr = line; String isLoggedIn = pass(passwdStr); out.write(isLoggedIn + "\r\n" ); out.flush(); if ("0" .equals(isLoggedIn)) { return ; } while (true ) { line = in.readLine(); for (Socket client : ChatServer.clients) { PrintWriter clientOut = new PrintWriter(client.getOutputStream()); clientOut.write(line + "\r\n" ); clientOut.flush(); } if (1 == 0 ) { break ; } } in.close(); out.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } }
判断用户名和口令是否有效,为了方便就直接返回字符串了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private String pass (String passwdStr) { String[] userPasswd = passwdStr.split("\t" ); if (userPasswd.length != 2 ) { return "0" ; } String username = userPasswd[0 ]; String password = userPasswd[1 ]; Set<Map.Entry<String, String>> passwdEntry = passwd.entrySet(); for (Map.Entry<String, String> entry : passwdEntry) { if (username.equals(entry.getKey())) { if (password.equals(entry.getValue())) { return "1" ; } else { return "0" ; } } } return "0" ; }
0x03 ChatClient 每个客户端有是否登录两种状态:
1 2 3 private boolean isLoggedIn;private final PrintWriter out;private final BufferedReader in;
构造方法:
1 2 3 4 5 6 7 public ChatClient (String ip, int port) throws IOException { Socket client = new Socket(ip, port); out = new java.io.PrintWriter(client.getOutputStream()); in = new BufferedReader(new InputStreamReader(client.getInputStream())); }
登录操作,向服务器写入用户名和密码(用 \t
分隔),服务器会判断并写出到这里的输入流。
是不是有更好的写法?欢迎提 pr。
1 2 3 4 5 6 7 8 9 10 public boolean login (String userName,String password) throws IOException { String passwd = userName + "\t" + password; String get; out.write(passwd + "\r\n" ); out.flush(); get = in.readLine(); isLoggedIn = "1" .equals(get); return isLoggedIn; }
登出:
1 2 3 public void logout () { isLoggedIn = false ; }
发言:
1 2 3 4 5 6 7 public void speak (String str) throws IOException { if (!isLoggedIn) { throw new IOException("Haven't logged in!" ); } out.write(str + "\r\n" ); out.flush(); }
读取发言:
这里是否需要创建消息队列?欢迎评论、issue、提 pr 交流。
1 2 3 4 5 6 public String read () throws IOException { if (!isLoggedIn) { return null ; } return in.readLine(); }
有很多地方写的不是很好,但最后也过了,可能需要特定的测试用例?