LỜI CAM ĐOANĐược sự cho phép của thầy để thực hiện đề tài “Chương trình chat giữa client vàserver theo cầu nối TCP, chế độ đồng thời” cho đồ án môn “Lập trình mạng máy tính”.Chúng em xin
PHÂN TÍCH CODE
Tổng quan hệ thống
3.1.1 Kiến trúc tổng thể Ứng dụng chat được xây dựng dựa trên mô hình Client-Server và giao thức
TCP/IP qua Socket, giúp kết nối máy khách (Client) với máy chủ (Server) trong cùng một mạng hoặc qua internet.
Mô hình Client-Server cho phép nhiều client kết nối và giao tiếp thông qua một server trung tâm Khi một client gửi tin nhắn, tin nhắn đó sẽ được chuyển đến server, và từ server, nó sẽ được phân phối lại cho các client khác.
Giao thức TCP/IP: Đảm bảo dữ liệu được truyền tải toàn vẹn giữa client và server.
Multi-threading: Cho phép server có khả năng xử lý nhiều client cùng lúc thông quaExecutorService, đảm bảo server luôn phản hồi nhanh chóng cho các client.
Java Swing: Được sử dụng để xây dựng giao diện cho client.
Hình 3.1 Sơ đồ kiến trúc hệ thống
ChatClientGUI: Chứa giao diện và logic xử lý của client.
ChatServerAdvanced: Quản lý các kết nối và phân phối tin nhắn giữa các client.
ClientHandler: Một lớp con của ChatServerAdvanced, xử lý từng client riêng lẻ
Hình 3.2 Sơ đồ tuần tự gửi tin nhắn
Phân tích chi tiết
Lắng nghe các kết nối từ client.
Quản lý danh sách các client hiện đang kết nối.
Phân phối tin nhắn từ một client đến các client khác. a) Khởi tạo Server:
ServerSocket serverSocket = new ServerSocket(PORT);while (true) {
ClientHandler clientHandler = new ClientHandler(clientSocket); pool.execute(clientHandler);
ServerSocket serverSocket = new ServerSocket(PORT);: Tạo một server socket lắng nghe trên cổng xác định, thường là 1234.
clientSocket = serverSocket.accept();: Chờ đợi kết nối từ client và chấp nhận kết nối mới.
pool.execute(clientHandler);: Tạo một luồng mới từ pool cho mỗi client và chạy phương thức xử lý của ClientHandler. b) Quản lý kết nối của các client
The server maintains a list of all clients using two sets: a private static final set of ClientHandler objects, referred to as clientHandlers, and a private static final set of usernames, known as userNames Both sets are created using ConcurrentHashMap.newKeySet() to ensure thread safety and efficient access.
clientHandlers: Lưu trữ các đối tượng ClientHandler để quản lý kết nối của mỗi client.
User names are essential for each client to prevent name duplication and effectively manage the list of online users The method for broadcasting messages is implemented in the public void broadcastMessage(String message) method, which iterates through all ClientHandler instances and sends the specified message to each client.
Phương thức broadcastMessage phân phối tin nhắn tới tất cả các client đang kết nối Mỗi client nhận tin nhắn thông qua phương thức sendMessage của ClientHandler.
3.2.2 Phía Client a) Khởi tạo giao diện người dùng
Client sử dụng các thành phần giao diện chính: private JFrame frame;private JTextArea messageArea;private JTextField inputField; private JTextArea userListArea;private JButton sendButton;private JButton exitButton;
JFrame (frame): Cửa sổ chính của ứng dụng.
JTextArea (messageArea): Khu vực hiển thị các tin nhắn từ tất cả người dùng.
JTextField (inputField): Nơi người dùng nhập tin nhắn.
JTextArea (userListArea): Khu vực hiển thị danh sách người dùng trực tuyến.
JButton (sendButton, exitButton): Nút gửi và nút thoát. b) Kết nối tới Server
A client connects to a server by creating a socket and then establishes input/output streams to send and receive messages This is done using the following code: `socket = new Socket(serverAddress, PORT); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream()));`.
new Socket(serverAddress, PORT): Kết nối đến địa chỉ IP của server qua cổng 1234.
out và in: Các luồng nhập/xuất để gửi và nhận tin nhắn. c) Đăng nhập
Client yêu cầu người dùng nhập tên và gửi đến server: userName = JOptionPane.showInputDialog(frame, "Nhập tên của bạn:"); out.println(userName);
JOptionPane: Hiển thị hộp thoại cho người dùng nhập tên.
out.println(userName): Gửi tên đến server, nơi sẽ kiểm tra tên trùng lặp và phản hồi nếu hợp lệ. d) Gửi/nhận tin nhắn
Client gửi tin nhắn nhập từ inputField và nhận tin nhắn từ server qua in:
// Gửi tin nhắn out.println(inputField.getText());
// Nhận tin nhắnwhile ((message = in.readLine()) != null) { messageArea.append(message + "\n");
Gửi: out.println() gửi dữ liệu từ inputField đến server.
Nhận: messageArea.append() thêm tin nhắn nhận được vào khu vực hiển thị.
Luồng hoạt động
ServerSocket serverSocket = new ServerSocket(PORT); while (true) {
ClientHandler clientHandler = new ClientHandler(clientSocket); pool.execute(clientHandler);
Server lắng nghe kết nối từ các client, tạo một ClientHandler cho mỗi kết nối mới và chạy trên một luồng trong pool.
3.3.2 Kết nối client socket = new Socket(serverAddress, PORT); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); Client kết nối đến server qua địa chỉ IP và cổng (ở đây là localhost và cổng 1234), sau đó thiết lập luồng để gửi (out) và nhận (in) dữ liệu.
3.3.3 Xử lý đăng nhập userName = JOptionPane.showInputDialog(frame, "Nhập tên của bạn:"); out.println(userName);
Yêu cầu người dùng nhập tên Nếu tên hợp lệ và không bị trùng, client sẽ gửi tên này đến server và đăng nhập thành công.
// Gửi tin nhắn out.println(inputField.getText());
// Nhận tin nhắn while ((message = in.readLine()) != null) { messageArea.append(message + "n");
Gửi: Client gửi tin nhắn đến server qua out.
Nhận: Client lắng nghe tin nhắn từ server và cập nhật vào messageArea.
Các tính năng chính
3.4.1 Quản lý người dùng Ứng dụng thực hiện các chức năng quản lý người dùng bao gồm kiểm tra tên trùng lặp, cập nhật danh sách người dùng trực tuyến, và thông báo cho các người dùng khác khi có người tham gia hoặc rời khỏi hệ thống chat. a) Kiểm tra trùng tên người dùng
Trong ChatServerAdvanced, khi client gửi tên đăng nhập, server sẽ xác minh tính duy nhất của tên này Nếu tên đã tồn tại, client sẽ nhận được thông báo yêu cầu nhập lại tên khác.
Ví dụ mã: if (userNames.contains(userName)) { out.println("Tên này đã được sử dụng Vui lòng chọn tên khác.");
} else { userNames.add(userName); out.println("Chào mừng bạn " + userName + "!");
userNames.contains(userName): Kiểm tra xem tên đã tồn tại trong danh sách người dùng trực tuyến chưa.
out.println( ): Gửi phản hồi đến client về trạng thái đăng nhập. b) Cập nhật danh sách người dùng
Danh sách người dùng sẽ được cập nhật tự động khi có người mới tham gia hoặc rời khỏi Server sẽ phát danh sách này đến tất cả client thông qua phương thức broadcastMessage.
Ví dụ mã: public void broadcastUserList() {
StringBuilder userList = new StringBuilder("Người dùng trực tuyến: "); for (String user : userNames) { userList.append(user).append(", ");
} c) Thông báo tham gia/rời khỏi
Mỗi khi có người tham gia hoặc rời khỏi, server sẽ gửi tin nhắn thông báo đến tất cả client để cập nhật tình trạng người dùng.
Ví dụ mã: public void notifyUserJoined(String userName) { broadcastMessage("Người dùng " + userName + " đã tham gia chat.");
} public void notifyUserLeft(String userName) { broadcastMessage("Người dùng " + userName + " đã rời khỏi chat.");
3.4.2 Xử lý tin nhắn Ứng dụng có thể gửi tin nhắn cho tất cả người dùng (broadcast) hoặc gửi tin nhắn riêng lẻ Các tin nhắn có thể được định dạng để phân biệt tin nhắn hệ thống và tin nhắn từ người dùng. a) Gửi tin nhắn riêng lẻ
Client gửi tin nhắn nhập từ giao diện người dùng và server sẽ xử lý tin nhắn bằng cách gửi đến tất cả client khác.
Ví dụ mã: public void sendMessageToAll(String message) { for (ClientHandler client : clientHandlers) { client.sendMessage(message);
BroadcastMessage giúp server gửi tin nhắn đến toàn bộ client đang kết nối Mỗi client sẽ hiển thị tin nhắn đó trong khu vực messageArea.
Ví dụ mã: public void broadcastMessage(String message) { for (ClientHandler client : clientHandlers) { client.sendMessage(message);
Giao diện của client hiển thị danh sách tin nhắn và người dùng trực tuyến, đồng thời cho phép người dùng nhập và gửi tin nhắn một cách dễ dàng, tất cả được xây dựng bằng Java Swing.
Danh sách tin nhắn: Khu vực messageArea hiển thị tin nhắn mới nhất từ server.
Danh sách người dùng trực tuyến: Khu vực userListArea sẽ tự động cập nhật mỗi khi có người dùng tham gia hoặc rời khỏi.
Ô nhập tin nhắn: inputField cho phép người dùng nhập tin nhắn và sendButton để gửi tin nhắn.
3.4.4 Xử lý lỗi và ngoại lệ Ứng dụng xử lý các tình huống lỗi như mất kết nối, lỗi đăng nhập hoặc thoát không an toàn Điều này giúp đảm bảo hệ thống ổn định ngay cả khi có sự cố. a) Mất kết nối
Nếu client mất kết nối, server sẽ xóa client đó khỏi danh sách và thông báo đến các client khác.
Ví dụ mã: public void removeClient(ClientHandler client) { clientHandlers.remove(client); broadcastMessage(client.getUserName() + " đã mất kết nối.");
} b) Đóng kết nối an toàn
Khi client rời khỏi, server sẽ đóng socket và cập nhật danh sách người dùng.
Ví dụ mã: public void safeDisconnect() { try { socket.close(); userNames.remove(userName); clientHandlers.remove(this); broadcastMessage(userName + " đã thoát.");
Các công nghệ sử dụng
Socket: Dùng cho client để kết nối đến server và truyền dữ liệu.
ServerSocket: Tạo ra trên server để chờ đợi và chấp nhận kết nối từ các client.
ServerSocket serverSocket = new ServerSocket(1234);Socket clientSocket serverSocket.accept();
ServerSocket lắng nghe các yêu cầu kết nối từ client, còn Socket được client sử dụng để tạo kết nối đến server.
Giao tiếp giữa client và server thực hiện qua các luồng dữ liệu (InputStream vàOutputStream), giúp truyền tải tin nhắn và dữ liệu khác.
3.5.2 Multithreading Để server có thể xử lý nhiều client đồng thời, ứng dụng sử dụng đa luồng (multithreading) vớiExecutorService.
ExecutorService pool = Executors.newFixedThreadPool(10);ClientHandler clientHandler = new ClientHandler(clientSocket); pool.execute(clientHandler);
ExecutorService là một bộ quản lý luồng giúp server xử lý nhiều client cùng lúc, tăng tốc độ và hiệu suất xử lý.
Mỗi ClientHandler được chạy trong một luồng riêng, do đó nếu một client gặp lỗi, không ảnh hưởng đến các client khác.
3.5.3 GUI (Giao diện người dùng)
Java Swing là công cụ để phát triển giao diện người dùng cho ứng dụng client, bao gồm các thành phần như JFrame, JTextArea, JTextField và JButton, giúp hiển thị tin nhắn, danh sách người dùng và cho phép nhập tin nhắn.
Ví dụ mã: frame = new JFrame("Chat Room"); messageArea = new JTextArea(16, 50); inputField = new JTextField(50); sendButton = new JButton("Send");
JFrametạo cửa sổ chính của ứng dụng.
JTextAreavàJTextFieldcho phép hiển thị và nhập tin nhắn.
JButtondùng cho các thao tác gửi tin nhắn và thoát.
3.5.4 Collection Framework Ứng dụng sử dụng ConcurrentHashMap để lưu trữ danh sách client và quản lý tên người dùng, giúp tăng tính ổn định trong môi trường đa luồng.
Ví dụ mã: private static final Set clientHandlers ConcurrentHashMap.newKeySet();private static final Set userNames ConcurrentHashMap.newKeySet();
ConcurrentHashMap cung cấp các phương thức truy xuất an toàn cho nhiều luồng, giúp quản lý dữ liệu hiệu quả và an toàn khi có nhiều client kết nối và xử lý đồng thời.
3.5.5 Error Handling Ứng dụng có xử lý các lỗi phổ biến như lỗi kết nối, mất kết nối, và lỗi đăng nhập. Việc này đảm bảo ứng dụng hoạt động ổn định và dễ bảo trì.
Ví dụ mã: try { socket.close();
Các lỗi kết nối hoặc vào ra thường được bao quanh bởi try-catch, giúp phát hiện và xử lý lỗi mà không gây gián đoạn ứng dụng.
KẾT QUẢ
Client
Hình 4.1 Tạo tên tài khoản
- Thực hiện chức năng đăng ký tên tài khoản:
+Nếu 2 Client đăng nhập cùng tên thì sẽ không chat được.
+ Nếu đăng nhập bằng tên đã tồn tại, vui lòng chọn tên khác chưa được tạo trước đó.
Có thể tái sử dụng: Bạn có thể triển khai procedure được lưu trữ b
- Thực hiện chức năng đăng nhập:
+ Đăng nhập vào bằng các tên khác nhau thành công.
4.1.3 Chức năng giữa các Client
Hình 4.3 Chức năng chat giữa các Client
+ Các Client đăng nhập thành công và có thể bắt đầu chat qua lại với nhau.
Server
Chương trình máy chủ chat này cho phép nhiều người dùng kết nối và giao tiếp qua việc gửi và nhận tin nhắn trong một phòng chat chung Các chức năng chính của đoạn code bao gồm việc quản lý kết nối và truyền tải tin nhắn giữa các client.
Server lắng nghe các kết nối từ client qua cổng 1234 (ServerSocket serverSocket = new ServerSocket(1234);).
Khi một khách hàng mới kết nối, hệ thống sẽ tạo ra một ClientHandler mới để xử lý kết nối đó Chương trình sử dụng ExecutorService nhằm quản lý hiệu quả các luồng xử lý của từng khách hàng, cho phép tối đa 10 luồng hoạt động đồng thời.
Hình 4.4 Server kết nối nhiều client
4.2.2 Đăng nhập và kiểm tra tên người dùng
Khi client kết nối thành công, server yêu cầu người dùng nhập tên.
Nếu tên người dùng đã tồn tại, server yêu cầu nhập tên khác cho đến khi tìm được tên không trùng lặp.
Tên người dùng của tất cả các client đang trực tuyến được lưu trong tập hợp userNames sử dụng cấu trúc dữ liệu ConcurrentHashMap.
Hình 4.5 Lỗi khi nhập trùng tên người dùng
4.2.3 Gửi tin nhắn tới tất cả các client
Sau khi đăng nhập thành công, người dùng có thể gửi tin nhắn.
Máy chủ sử dụng phương thức sendToAllClients(String message) để gửi tin nhắn đến tất cả các client đang kết nối Mỗi client sẽ nhận được tin nhắn với định dạng {Tên người dùng}: {Nội dung tin nhắn}.
Hình 4.6 Server gửi tin nhắn tới các Client
4.2.4 Cập nhật danh sách người dùng trực tuyến
Khi một người dùng tham gia hoặc rời khỏi, server sẽ thông báo cho tất cả các client về sự kiện này và cập nhật danh sách người dùng trực tuyến.
Danh sách người dùng trực tuyến được hiển thị dưới dạng chuỗi "Người dùng trực tuyến: , , ", giúp các client dễ dàng nhận biết ai đang hoạt động trực tuyến.
Hình 4.7 Số người dùng trực tuyến
4.2.5 Xử lý việc client rời khỏi
Khi một client ngắt kết nối hoặc gặp lỗi, server sẽ xóa người dùng khỏi danh sách userNames và thông báo cho các client khác rằng người dùng đó đã rời khỏi.
Hình 4.8 Thông báo sao khi người dùng rời đi
Tóm tắt chức năng Đa người dùng: Hỗ trợ nhiều client cùng lúc và xử lý song song với ExecutorService.
Quản lý và kiểm tra tên người dùng: Đảm bảo rằng mỗi người dùng có một tên duy nhất.
Truyền tin nhắn: Tất cả các tin nhắn đều được truyền cho mọi người dùng trong phòng chat.
Cập nhật danh sách người dùng: Server quản lý và thông báo danh sách người dùng trực tuyến.
Lớp server này đóng vai trò quan trọng trong ứng dụng chat đa người dùng, quản lý kết nối và trao đổi thông tin giữa các client, đồng thời theo dõi và cập nhật danh sách người dùng trực tuyến.
NHẬN XÉT VÀ KẾT LUẬN
Kết quả đạt được
5.1.1 Giao tiếp hai chiều giữa Client và Server
Client và Server có thể gửi và nhận thông điệp đồng thời qua kết nối TCP, tạo ra một cuộc trò chuyện hai chiều.
TCP đảm bảo các gói tin được gửi đến đích theo đúng thứ tự và không bị mất.
5.1.2 Chế độ đồng thời (Concurrent Mode)
Nhiều client có thể kết nối đồng thời với một server.
Máy chủ có khả năng xử lý nhiều yêu cầu đồng thời từ các client thông qua việc sử dụng luồng hoặc tiến trình Mỗi kết nối client được quản lý bởi một luồng hoặc tiến trình riêng biệt, không làm ảnh hưởng đến các kết nối khác Điều này nâng cao hiệu suất và khả năng mở rộng của hệ thống, cho phép hỗ trợ nhiều người dùng cùng lúc.
5.1.3 Kết nối ổn định và đáng tin cậy
TCP đảm bảo một kết nối ổn định, ngăn chặn mất mát và trùng lặp dữ liệu Các gói dữ liệu bị lỗi sẽ được phát hiện và gửi lại, giúp duy trì cuộc trò chuyện giữa client và server một cách liền mạch và không bị gián đoạn.
5.1.4 Khả năng quản lý phiên kết nối
Server có khả năng quản lý nhiều phiên kết nối với các client khác nhau, theo dõi trạng thái của từng phiên một cách độc lập.
Việc đóng và mở các kết nối TCP có thể được thực hiện một cách có kiểm soát khi người dùng ngắt kết nối.
Chương trình chat có thể được sử dụng cho nhiều mục đích khác nhau, bao gồm hỗ trợ khách hàng trực tuyến, trò chuyện nhóm, và các ứng dụng cần giao tiếp thời gian thực giữa nhiều người dùng.
Hạn chế
5.2.1 Sử dụng tài nguyên hệ thống cao
Khi nhiều client kết nối, server cần tạo một thread hoặc process riêng cho mỗi kết nối, dẫn đến việc tăng mức sử dụng tài nguyên hệ thống như bộ nhớ và CPU.
Quản lý không hiệu quả có thể dẫn đến tình trạng quá tải, làm chậm hệ thống hoặc thậm chí gây sập server khi có quá nhiều kết nối.
5.2.2 Quản lý đồng thời phức tạp
Việc xử lý các kết nối đồng thời đòi hỏi phải sử dụng cơ chế đồng bộ để ngăn chặn xung đột tài nguyên giữa các luồng hoặc tiến trình Nếu không thực hiện cẩn thận, có thể dẫn đến các vấn đề nghiêm trọng như deadlock, race condition hoặc starvation.
Việc phát hiện và khắc phục lỗi này không phải lúc nào cũng đơn giản.
5.2.3 Khả năng mở rộng bị giới hạn
Mặc dù chế độ đồng thời cho phép nhiều client kết nối cùng lúc, nhưng khi số lượng client vượt quá giới hạn (hàng nghìn hoặc nhiều hơn), việc tạo ra một thread hoặc process cho mỗi kết nối sẽ trở nên không khả thi do hạn chế về tài nguyên hệ thống.
Khi số lượng kết nối vượt quá khả năng xử lý, server có thể bị quá tải và không thể đáp ứng kịp thời.
5.2.4 Độ trễ (Latency) trong việc tạo và hủy thread/process
Việc tạo và hủy thread hoặc process có thể làm tăng độ trễ trong xử lý yêu cầu của client, đặc biệt khi có nhiều kết nối Điều này ảnh hưởng tiêu cực đến tốc độ phản hồi và hiệu suất tổng thể của hệ thống.
5.2.5 Không tối ưu cho các ứng dụng lớn với số lượng người dùng rất lớn Đối với các ứng dụng chat có hàng ngàn người dùng hoạt động cùng lúc (như các ứng dụng chat phổ biến hoặc mạng xã hội), việc sử dụng TCP với cách tiếp cận mỗi kết nối một thread/process không còn tối ưu.
Các giải pháp như I/O không chặn, lập trình bất đồng bộ, hoặc sử dụng giao thức UDP có thể là lựa chọn tối ưu hơn trong tình huống này.
5.2.6 Khả năng lỗi hoặc ngắt kết nối không mong muốn
Khi một kết nối TCP bị gián đoạn giữa client và server do sự cố mạng, điều này có thể dẫn đến việc mất phiên làm việc và yêu cầu thiết lập lại kết nối, gây bất tiện cho người dùng.
TCP yêu cầu một quy trình phức tạp để đóng và mở kết nối, điều này có thể gây ra sự chậm trễ cho hệ thống khi có nhiều yêu cầu mới cần thiết lập kết nối.
5.2.7 Không phù hợp cho các ứng dụng yêu cầu thời gian thực rất nghiêm ngặt
Mặc dù TCP cung cấp độ tin cậy cao, nhưng không phải là lựa chọn tốt nhất cho các ứng dụng cần độ trễ thấp hoặc yêu cầu thời gian thực nghiêm ngặt, chẳng hạn như game trực tuyến và ứng dụng phát video trực tiếp.
Hướng phát triển
+ Tối ưu hóa tài nguyên hệ thống
+ Chuyển sang mô hình xử lý sự kiện (Event-driven Model)
+ Sử dụng WebSocket thay thế TCP
+ Tối ưu hóa khả năng mở rộng (Scalability)
+ Cải thiện quản lý kết nối và phiên làm việc
+ Sử dụng giao thức tiên tiến hơn
+ Tích hợp mã hóa và bảo mật nâng cao
+ Sử dụng công nghệ điện toán đám mây (Cloud Computing)
+ Tích hợp các tính năng mở rộng
+ Sử dụng Microservices Architecture (Kiến trúc vi dịch vụ)