Như vậy là chúng ta đã đi qua phần 2 của loạt bài hướng dẫn Java Socket và chúng ta xây dựng xong tính năng nhắn tin giữa 2 user.
Trong phần này, chúng ta sẽ thêm một tính năng mới là nhắn tin đến tất cả mọi người đang online. Tính năng này sẽ rất hữu ích cho những ai muốn spam nè =)) đùa vậy thôi, tính năng này khá đơn giản để làm hướng dẫn, các bạn có thể phát triển tính năng Chat Group, rồi rời nhóm các kiểu, nhưng cơ bản sẽ dựa trên tính năng và ý tưởng của phần này.
Cấu trúc dự án
Commoms Project
Như thường lệ, chúng ta cần thay thêm CHAT_ALL Action enum để Server và Client có thể hiểu nhau trong trường hợp Client muốn gửi tin nhắn đến tất cả mọi người.
package com.chat.socket.commoms.enums; public enum Action { GET_USERS_ONLINE, SEND_MESSAGE_TO_USER_SPECIFIC, CHAT_ALL, DISCONNECT }
Thêm 1 GroupMessageRequest chứa danh sách các UserID và một tin nhắn kèm theo sẽ được gửi cho tất cả các user này.
package com.chat.socket.commoms.request; import com.chat.socket.commoms.enums.Action; import lombok.Builder; import lombok.Getter; import lombok.NonNull; import java.util.List; @Getter public class GroupMessageRequest extends Request { private String message; private List<String> uids; @Builder public GroupMessageRequest(@NonNull Action action, String message, List<String> uids) { super(action); this.uids = uids; this.message = message; } }
Thật may mắn là chúng ta có thể sử dụng lại MessageResponse ở phần 1 để chuyển tin nhắn đến các User được liệt kê trong GroupMessageRequest.
package com.chat.socket.commoms.response; import com.chat.socket.commoms.enums.StatusCode; import lombok.Builder; import lombok.Getter; @Getter public class MessageResponse extends Response { private String message; private String senderId; @Builder public MessageResponse(String message, String senderId, StatusCode statusCode) { this.message = message; this.senderId = senderId; this.statusCode = statusCode; } }
Server Project
Ở Server, chúng ta sẽ thêm một trường hợp xử lý cho CHAT_ALL Action enum. Trong GroupMessageRequest, lấy danh sách UserID, duyệt và lấy từng ClientHanlder instance của mỗi User tương ứng để response một MesageResponse.
case CHAT_ALL: { GroupMessageRequest groupMessageRequest = (GroupMessageRequest) request; for (String s : groupMessageRequest.getUids()) { ClientHandler clientHandler = clientHandlers.get(s); if (clientHandler != null) { clientHandler.response(MessageResponse.builder() .senderId(this.getUid()) .message(groupMessageRequest.getMessage()) .statusCode(StatusCode.OK) .build()); } } break; }
Mã nguồn đầy đủ của Server
package com.chat.socket.server; import com.chat.socket.commoms.enums.StatusCode; import com.chat.socket.commoms.request.GroupMessageRequest; import com.chat.socket.commoms.request.MessageRequest; import com.chat.socket.commoms.request.Request; import com.chat.socket.commoms.response.MessageResponse; import com.chat.socket.commoms.response.Response; import com.chat.socket.commoms.response.UserOnlineResponse; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.ObjectUtils; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.*; import java.util.stream.Collectors; public class Server { private ServerSocket serverSocket; private Map<String, ClientHandler> clientHandlers; public Server() { this.clientHandlers = new HashMap<>(); } public void start(int port) { System.out.println("Server starting!!!"); try { serverSocket = new ServerSocket(port); System.out.println(serverSocket.getInetAddress().getHostName()); System.out.println(serverSocket.getLocalPort()); while (true) { ClientHandler clientHandler = new ClientHandler(serverSocket.accept()); clientHandler.start(); this.clientHandlers.put(clientHandler.getUid(), clientHandler); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (serverSocket != null) { serverSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } } private List<String> getUserIdOnline() { return this.clientHandlers.values().stream() .map(ClientHandler::getUid) .collect(Collectors.toList()); } @Getter @Setter private class ClientHandler extends Thread { private Socket clientSocket; private ObjectInputStream in; private ObjectOutputStream out; private String uid; public ClientHandler(Socket socket) throws IOException { this.clientSocket = socket; out = new ObjectOutputStream(clientSocket.getOutputStream()); in = new ObjectInputStream(clientSocket.getInputStream()); this.uid = UUID.randomUUID().toString(); } private void response(Response response) throws IOException { this.out.writeObject(response); this.out.flush(); } @Override public void run() { try { while (true) { Object input = in.readObject(); if (ObjectUtils.isNotEmpty(input)) { Request request = (Request) input; switch (request.getAction()) { case GET_USERS_ONLINE: { this.response(UserOnlineResponse.builder() .userIds(getUserIdOnline()) .statusCode(StatusCode.OK) .build()); break; } case SEND_MESSAGE_TO_USER_SPECIFIC: { ClientHandler clientHandler = clientHandlers.get(((MessageRequest) (request)).getUid()); if (clientHandler == null) { this.response(MessageResponse.builder() .statusCode(StatusCode.BAD_REQUEST) .build()); } else { clientHandler.response(MessageResponse.builder() .message(((MessageRequest) (request)).getMessage()) .senderId(this.getUid()) .statusCode(StatusCode.OK) .build()); } break; } case CHAT_ALL: { GroupMessageRequest groupMessageRequest = (GroupMessageRequest) request; for (String s : groupMessageRequest.getUids()) { ClientHandler clientHandler = clientHandlers.get(s); if (clientHandler != null) { clientHandler.response(MessageResponse.builder() .senderId(this.getUid()) .message(groupMessageRequest.getMessage()) .statusCode(StatusCode.OK) .build()); } } break; } case DISCONNECT: { clientHandlers.remove(this.getUid()); break; } default: break; } } } } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (in != null) { in.close(); } if (out != null) { out.close(); } if (clientSocket != null) { clientSocket.close(); } } catch (Exception e) { e.printStackTrace(); } } } } }
Client Project
Ở ứng dụng phía client, chúng ta cũng thêm một Option cho phép User chọn gửi tin nhắn đến tất cả các User đang online.
case "3": { System.out.print("Enter message: "); scanner.nextLine(); String message = scanner.nextLine(); sendRequest(GroupMessageRequest.builder() .action(Action.CHAT_ALL) .message(message) .uids(this.userOnlines) .build()); break; }
Mã nguồn đầy đủ của Client class.
package com.chat.socket.client; import com.chat.socket.commoms.enums.Action; import com.chat.socket.commoms.enums.StatusCode; import com.chat.socket.commoms.request.GroupMessageRequest; import com.chat.socket.commoms.request.InformationRequest; import com.chat.socket.commoms.request.MessageRequest; import com.chat.socket.commoms.request.Request; import com.chat.socket.commoms.response.MessageResponse; import com.chat.socket.commoms.response.UserOnlineResponse; import org.apache.commons.lang3.ObjectUtils; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class Client { private Socket clientSocket; private ObjectInputStream in; private ObjectOutputStream out; private Scanner scanner; List<String> userOnlines; private void sendRequest(Request req) throws IOException { this.out.writeObject(req); this.out.flush(); } private void close() throws IOException { if (this.in != null) { this.in.close(); } if (this.out != null) { this.out.close(); } if (this.clientSocket != null) { this.clientSocket.close(); } } private void getUserOnlines() throws IOException { sendRequest(InformationRequest.builder().action(Action.GET_USERS_ONLINE).build()); } public void startConnection(String ip, int port) { try { clientSocket = new Socket(ip, port); this.out = new ObjectOutputStream(clientSocket.getOutputStream()); this.in = new ObjectInputStream(clientSocket.getInputStream()); scanner = new Scanner(System.in); userOnlines = new ArrayList<>(); new ResponseProcess().start(); getUserOnlines(); while (true) { System.out.println("Chose your options"); System.out.println("1: GET ALL USER ONLINE"); System.out.println("2: SEND MESSAGE"); System.out.println("3: SEND ALL"); System.out.println("-1: ESC"); String ch = scanner.next(); switch (ch) { case "1": { getUserOnlines(); break; } case "2": { System.out.println("User onlines: "); if (this.userOnlines.isEmpty()) { System.out.println("No user online"); break; } for (int i = 0; i < this.userOnlines.size(); i++) { System.out.println("User " + (i + 1) + ": " + this.userOnlines.get(i)); } System.out.println("Please select number from 1 to " + this.userOnlines.size() + " to send the message"); int choise = scanner.nextInt(); System.out.println(); System.out.print("Enter message: "); scanner.nextLine(); String message = scanner.nextLine(); sendRequest(MessageRequest.builder() .action(Action.SEND_MESSAGE_TO_USER_SPECIFIC) .uid(this.userOnlines.get(choise - 1)) .message(message) .build()); break; } case "3": { System.out.print("Enter message: "); scanner.nextLine(); String message = scanner.nextLine(); sendRequest(GroupMessageRequest.builder() .action(Action.CHAT_ALL) .message(message) .uids(this.userOnlines) .build()); break; } case "-1": { sendRequest(InformationRequest.builder().action(Action.DISCONNECT).build()); close(); return; } default: break; } } } catch (IOException e) { e.printStackTrace(); } finally { try { if (in != null) { in.close(); } if (out != null) { out.close(); } if (clientSocket != null) { clientSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } } private class ResponseProcess extends Thread { @Override public void run() { try { while (true) { Object object = in.readObject(); if (ObjectUtils.isEmpty(object)) { continue; } if (object instanceof UserOnlineResponse) { UserOnlineResponse userOnlineResponse = (UserOnlineResponse) object; if (StatusCode.OK.equals(userOnlineResponse.getStatusCode())) { userOnlineResponse.getUserIds().forEach(s -> System.out.println(s)); userOnlines = userOnlineResponse.getUserIds(); } else { System.out.println("Request failed!!!"); } } if (object instanceof MessageResponse) { MessageResponse messageResponse = (MessageResponse) object; System.out.println("You is received: from " + messageResponse.getSenderId()); System.out.println("Message " + messageResponse.getMessage()); } } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } }
Tóm lược
Như vậy chúng ta đã hoàn thành chức năng gửi tin nhắn cho tất cả các User đang online. Đây là một tính năng đơn giản, các tính năng nâng cao hơn như là Chat group thì các bạn có thể thực hiện được dựa trên tính năng này.
Nếu gặp rắc rối trong quá trình triển khai và kiểm thử, các bạn có thể tham khảo source code tại github.