Primary Practice h60
在 ChatServer、ChatClient 中增加适当代码,并增加适当的类,完成一个简单的聊天室
和之前 OOP 的一个实验类似,利用 Socket 实现 client 和 server 通信。不同的是,本实验需要多客户端和一服务器通信。
0x00 ChatServer
类变量一定包括一个 ServerSocket。
根据要求,需要从文件中读取到所有的用户名和密码,用 HashMap 保存。
多 client,为了给每一个 client 回复,需要保存目前已经连接的 client。
| 12
 3
 
 | private final ServerSocket server;private final Map<String, String> passwd;
 public static List<Socket> clients = new ArrayList<>();
 
 | 
构造方法只需要初始化 server 和 passwd:
| 12
 3
 4
 
 | public ChatServer (int port, String passwordFilename) throws IOException {server = new ServerSocket(port);
 passwd = readLines(passwordFilename);
 }
 
 | 
经典:
| 12
 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 方法:
| 12
 3
 
 | public void startListen() {this.start();
 }
 
 | 
重写 run,因为要持续监听,与多个 client 相连,所以需要新建 ServerThread 类,在此类中调用它:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | @Overridepublic 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
构造方法:
| 12
 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。
| 12
 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
 
 |     @Overridepublic 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();
 }
 }
 
 | 
判断用户名和口令是否有效,为了方便就直接返回字符串了:
| 12
 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
每个客户端有是否登录两种状态:
| 12
 3
 
 | private boolean isLoggedIn;private final PrintWriter out;
 private final BufferedReader in;
 
 | 
构造方法:
| 12
 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。
| 12
 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;
 }
 
 | 
登出:
| 12
 3
 
 | public void logout() {isLoggedIn = false;
 }
 
 | 
发言:
| 12
 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 交流。
| 12
 3
 4
 5
 6
 
 | public String read() throws IOException {if (!isLoggedIn) {
 return null;
 }
 return in.readLine();
 }
 
 | 
有很多地方写的不是很好,但最后也过了,可能需要特定的测试用例?