Mục lục
Trong bất kỳ một ngôn ngữ nào, socket là khái niệm đề cập đến các ứng dụng thực thi trên nhiều máy tính, trong đó các máy tính sẽ được kết nối với nhau thông qua mạng.
Chúng ta có 2 giao thức giao tiếp chính trong lập trình socket:
- User Datagram Protocol (UDP)
- Tranfer Control Protocol (TCP)
Điểm khác biệt chính giữa 2 giao thức này là giao thức UDP là connectionless, nghĩa là máy khách và máy chủ sẽ không thiết lập kết nối khi chúng giao tiếp với nhau đồng nghĩa với việc không có sự đảm bảo các gói tin truyền đi và nhận về có bị mất mát dữ liệu hay không. Trong khi đó TCP hoạt động theo hướng connection-oriented, máy khách và máy chủ sẽ thiết lập 1 kết nối khi chúng bắt đầu giao tiếp với nhau, đảm bảo các gói tin truyền đi và nhận về không bị mất mát.
Trong bài viết nàym chúng ta sẽ tìm hiểu về lập trình socket thông qua giao thức TCP/IP, và làm một ứng dụng giao tiếp đơn giản giữ máy khách và máy chủ.
Ứng dụng Chat đơn giản – Java Socket
Trong phần này, chúng ta sẽ chia ứng dụng Chat ra thành 2 phần client và server. Server đóng vai trò là một máy chủ tập trung tiếp nhận các kết nối từ nhiều client khác nhau. Client là các ứng dụng được triển khai ở phía người dùng, cho phép kết nối đến server và sử dụng các dịch vụ mà nó cung cấp.
Trong ví dụ này, server sẽ được xây dựng khi khởi chạy sẽ lắng nghe các kết nối đến từ các client, khi có client kết nối đến kèm theo tin nhắn server sẽ tiếp nhận và gửi trả lại cho client một tin nhắn đơn giản.
Server
Client Handler class
Tại ứng dụng phía máy chủ, trước tiên chúng ta cần xây dựng một class ClientHandler để xử lý riêng cho từng client kết nối đến. Với mỗi kết nói ClientHandler sẽ tạo thread mới để thực thi trên đó nhầm tối ưu hiệu suất cho chương trình.
Note: ClientHandler là 1 inner class của Server class, chúng ta sẽ khởi tạo phía bên dưới
package com.chat.socket.server; import java.io.*; import java.net.Socket; public class Server { //... private class ClientHandler extends Thread { private Socket clientSocket; private BufferedWriter out; private BufferedReader in; public ClientHandler(Socket socket) { this.clientSocket = socket; } @Override public void run() { try { in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); out = new BufferedWriter(new PrintWriter(clientSocket.getOutputStream())); System.out.println("Client connected"); String message = in.readLine(); System.out.println("Message From Client: " + message); out.write("Hello Client"); out.newLine(); out.flush(); } catch (IOException 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(); } } } } }
Trong ClientHandler, mình sử dụng BufferedReader để làm thiết bị đầu vào cho server, nhận tin nhắn từ client gửi đến và BufferedWriter làm thiết bị đầu ra, dùng để gửi tin nhắn đến client.
Đối tượng Socket đại diện cho kết nối giữa client và server, các thao tác nhận và gửi tin đều thông qua đối tượng này. Nó sẽ được tạo ra khi server chấp nhận kết nối của client.
Khi một client kết nối nối đến, ClientHandler sẽ đọc tin nhắn được kèm theo và xuất ra màn hình, sau đó gửi trả cho client tin nhắn “Hello client“.
Note: Chúng ta có thể sử dụng bất kỳ một đối tượng nào thừa kế từ InputStream, và OutputStream để làm thiết bị đầu vào và đầu ra. Tuy nhiên mình sử dụng BufferedReader, và BufferedWriter vì nó có method gửi string khá tiện. Chú ý khi sử dụng các đối tượng này, sau mỗi message chúng ta phải dùng đến newLine() và flush() method để tin nhắn có thể chuyển đi.
Server class
Sau khi tạo xong ClientHandler, kết tiếp chúng ta sẽ tạo một Server class, đây là đối tượng chính trong ứng dụng phía máy chủ, nó sẽ đảm nhiệm việc mở một cổng giao tiếp và lắng nghe các kết nối từ client gửi đến, chấp nhận và khởi tạo một ClientHandler object xử lý cho kết nối vừa được chấp thuận.
package com.chat.socket.server; import java.io.*; import java.net.ServerSocket; public class Server { private ServerSocket serverSocket; public void start(int port) { System.out.println("Server starting!!!"); try { serverSocket = new ServerSocket(port); while (true) { new ClientHandler(serverSocket.accept()).start(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (serverSocket != null) { serverSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } } private class ClientHandler extends Thread {...} }
Server sẽ chạy trên một máy tính và mở một socket trên trên một cổng (port). Đây được xem như một endpoint mà các client muốn kết nối đến phải thông qua địa chỉ của máy tính và port mà nó mở.
while (true) { new ClientHandler(serverSocket.accept()).start(); }
Vòng lặp while(true) để đảm bảo cho server luôn hoạt động, khi gặp serverSocket.accept() method, vòng lặp sẽ rơi vào trạng thái chờ. Cho đến khi có một client kết nối đến serverSocket.accept() sẽ trả về một Socket Object đại diện cho kết nối giữa client và server và giải thoát vòng lặp khỏi trạng thái chờ.
Main class
Main class là nơi khởi chạy ứng dụng máy chủ, nó sẽ tạo ra một Server object và mở kết nối ở cổng bất kỳ. Ở đây, chúng ta sẽ mở port 8080.
public class Main { public static void main(String[] agrs) { new Server().start(8080); } }
Client
Ứng dụng phía người dùng, muốn kết nối đến server cần phải biết chính xác 2 thông tin: địa chỉ IP và port của server để chúng ta có thể khởi tạo một Socket object đại diện cho kết nối giữa client và server.
clientSocket = new Socket(ip, port);
Client class
Trước tiên chúng ta cần tạo Client class đảm nhiệm việc kết nối đến server, và gửi một tin nhắn đơn giản kèm theo sau khi kết nối thành công.
package com.chat.socket.client; import java.io.*; import java.net.Socket; public class Client { private Socket clientSocket; private BufferedReader in; private BufferedWriter out; public void startConnection(String ip, int port) { try { clientSocket = new Socket(ip, port); out = new BufferedWriter(new PrintWriter(clientSocket.getOutputStream())); in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); out.write("Hello server"); out.newLine(); out.flush(); String message = in.readLine(); System.out.println("Message from Server: " + message); out.close(); } 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 void close() throws IOException { if (this.in != null) { this.in.close(); } if (this.out != null) { this.close(); } if (this.clientSocket != null) { this.clientSocket.close(); } } }
Trong này, mình cũng sử dụng BufferedReader để làm thiết bị đầu vào và BufferedWriter làm thiết bị đầu ra cho client, sau khi kết nối đến server thành công, nó sẽ gửi kèm một tin nhắn “Hello server” và lắng nghe tin nhắn từ server gửi về. Chúng ta có một hàm close() để giải phóng vùng nhớ khi muốn ngắt kết nối đến server.
Main class
Đây là nơi gửi chạy ứng dụng client, khởi tạo một Client object và mở kết nối đến server với các thông tin IP, port của server.
package com.chat.socket.client; public class Main { public static void main(String[] agrs) { Client client = new Client(); client.startConnection("127.0.0.1", 8080); } }
Do ứng dụng client và server đều chạy trên cùng môht máy nên sẽ dùng địa chỉ IP local là “127.00.0.1” hoặc “localhost“. Nếu các bạn chạy ở một máy khác, các bạn có thể kiểm tra IP của server bằng cách mở CMD và gõ lệnh ipconfig.
Chat Application Demo
Sau khi triển khai thành công ứng dụng client và server. Chúng ta tiến hành khởi chạy Server trước hết để nó mở kết nối và lắng nghe các kết nối từ client. Phía client chúng ta có thể chạy bao nhiêu ứng dụng tuỳ thích hoạt chạy trên các máy chủ khác khác, điều kiện là phải đúng địa chỉ IP và port của server.
Tóm lược
Bài viết này đã cho chúng ta nhìn thấy cái nhìn tổng qua về lập trình socket trong Java core. Tuy nhiên hiện nay đã có rất nhiều thư viện hỗ trợ lập trình socket mạnh mẽ hơn và đỡ tốn công hơn khi sử dụng.
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.
Nguồn tham khảo