Hướng dẫn về API Java cho WebSocket

1. Khái quát chung

WebSocket cung cấp một giải pháp thay thế cho sự hạn chế của giao tiếp hiệu quả giữa máy chủ và trình duyệt web bằng cách cung cấp giao tiếp máy khách / máy chủ hai chiều, song công, thời gian thực. Máy chủ có thể gửi dữ liệu đến máy khách bất kỳ lúc nào. Bởi vì nó chạy trên TCP, nó cũng cung cấp một giao tiếp mức độ thấp có độ trễ thấp và giảm chi phí của mỗi tin nhắn .

Trong bài viết này, chúng ta sẽ xem xét API Java cho WebSockets bằng cách tạo một ứng dụng giống như trò chuyện.

2. JSR 356

JSR 356 hoặc API Java cho WebSocket, chỉ định một API mà các nhà phát triển Java có thể sử dụng để tích hợp WebSockets trong các ứng dụng của họ - cả ở phía máy chủ cũng như phía máy khách Java.

API Java này cung cấp cả các thành phần phía máy chủ và máy khách:

  • Máy chủ : mọi thứ trong gói javax.websocket.server .
  • Máy khách : nội dung của gói javax.websocket , bao gồm các API phía máy khách và các thư viện chung cho cả máy chủ và máy khách.

3. Xây dựng một cuộc trò chuyện bằng cách sử dụng WebSockets

Chúng tôi sẽ xây dựng một ứng dụng giống như trò chuyện rất đơn giản. Bất kỳ người dùng nào cũng có thể mở cuộc trò chuyện từ bất kỳ trình duyệt nào, nhập tên của họ, đăng nhập vào cuộc trò chuyện và bắt đầu giao tiếp với mọi người được kết nối với cuộc trò chuyện.

Chúng tôi sẽ bắt đầu bằng cách thêm phần phụ thuộc mới nhất vào tệp pom.xml :

 javax.websocket javax.websocket-api 1.1 

Phiên bản mới nhất có thể được tìm thấy ở đây.

Để chuyển đổi các Đối tượng Java thành các biểu diễn JSON của chúng và ngược lại, chúng tôi sẽ sử dụng Gson:

 com.google.code.gson gson 2.8.0 

Phiên bản mới nhất hiện có trong kho Maven Central.

3.1. Cấu hình điểm cuối

Có hai cách cấu hình điểm cuối: dựa trên chú thích và dựa trên phần mở rộng. Bạn có thể mở rộng lớp javax.websocket.Endpoint hoặc sử dụng các chú thích cấp phương pháp chuyên dụng. Vì mô hình chú thích dẫn đến mã rõ ràng hơn so với mô hình lập trình, chú thích đã trở thành lựa chọn mã hóa thông thường. Trong trường hợp này, các sự kiện vòng đời của điểm cuối WebSocket được xử lý bởi các chú thích sau:

  • @ServerEndpoint: Nếu được trang trí bằng @ServerEndpoint, vùng chứa đảm bảo tính khả dụng của lớp dưới dạng máy chủ WebSocket lắng nghe một không gian URI cụ thể
  • @ClientEndpoint : Một lớp được trang trí bằng chú thích này được coi như một ứng dụng khách WebSocket
  • @OnOpen : Phương thức Java với @OnOpen được vùng chứa gọi khi một kết nối WebSocket mới được khởi tạo
  • @OnMessage : Một phương thức Java, được chú thích bằng @OnMessage, nhận thông tin từ vùng chứa WebSocket khi một thông báo được gửi đến điểm cuối
  • @OnError : Một phương thức với @OnError được gọi khi có sự cố với giao tiếp
  • @OnClose : Được sử dụng để trang trí một phương thức Java được vùng chứa gọi khi kết nối WebSocket đóng

3.2. Viết điểm cuối máy chủ

Chúng tôi khai báo một điểm cuối máy chủ WebSocket lớp Java bằng cách chú thích nó bằng @ServerEndpoint . Chúng tôi cũng chỉ định URI nơi điểm cuối được triển khai. URI được xác định tương đối với gốc của vùng chứa máy chủ và phải bắt đầu bằng dấu gạch chéo:

@ServerEndpoint(value = "/chat/{username}") public class ChatEndpoint { @OnOpen public void onOpen(Session session) throws IOException { // Get session and WebSocket connection } @OnMessage public void onMessage(Session session, Message message) throws IOException { // Handle new messages } @OnClose public void onClose(Session session) throws IOException { // WebSocket connection closes } @OnError public void onError(Session session, Throwable throwable) { // Do error handling here } }

Đoạn mã trên là khung điểm cuối máy chủ cho ứng dụng giống như trò chuyện của chúng tôi. Như bạn có thể thấy, chúng tôi có 4 chú thích được ánh xạ tới các phương thức tương ứng của chúng. Dưới đây bạn có thể thấy việc thực hiện các phương pháp như vậy:

@ServerEndpoint(value="/chat/{username}") public class ChatEndpoint { private Session session; private static Set chatEndpoints = new CopyOnWriteArraySet(); private static HashMap users = new HashMap(); @OnOpen public void onOpen( Session session, @PathParam("username") String username) throws IOException { this.session = session; chatEndpoints.add(this); users.put(session.getId(), username); Message message = new Message(); message.setFrom(username); message.setContent("Connected!"); broadcast(message); } @OnMessage public void onMessage(Session session, Message message) throws IOException { message.setFrom(users.get(session.getId())); broadcast(message); } @OnClose public void onClose(Session session) throws IOException { chatEndpoints.remove(this); Message message = new Message(); message.setFrom(users.get(session.getId())); message.setContent("Disconnected!"); broadcast(message); } @OnError public void onError(Session session, Throwable throwable) { // Do error handling here } private static void broadcast(Message message) throws IOException, EncodeException { chatEndpoints.forEach(endpoint -> { synchronized (endpoint) { try { endpoint.session.getBasicRemote(). sendObject(message); } catch (IOException | EncodeException e) { e.printStackTrace(); } } }); } }

Khi người dùng mới đăng nhập ( @OnOpen ) ngay lập tức được ánh xạ tới cấu trúc dữ liệu của người dùng đang hoạt động. Sau đó, một thông báo được tạo và gửi đến tất cả các thiết bị đầu cuối bằng phương pháp quảng bá .

Phương pháp này cũng được sử dụng bất cứ khi nào một tin nhắn mới được gửi ( @OnMessage ) bởi bất kỳ người dùng nào được kết nối - đây là mục đích chính của cuộc trò chuyện.

Nếu tại một số điểm xảy ra lỗi, phương thức với chú thích @OnError sẽ xử lý lỗi đó. Bạn có thể sử dụng phương pháp này để ghi lại thông tin về lỗi và xóa các điểm cuối.

Cuối cùng, khi người dùng không còn kết nối với cuộc trò chuyện, phương thức @OnClose sẽ xóa điểm cuối và truyền phát cho tất cả người dùng rằng người dùng đã bị ngắt kết nối.

4. Loại tin nhắn

Đặc tả WebSocket hỗ trợ hai định dạng dữ liệu trực tuyến - văn bản và nhị phân. API hỗ trợ cả hai định dạng này, thêm khả năng làm việc với các đối tượng Java và thông báo kiểm tra tình trạng (bóng bàn) như được định nghĩa trong đặc tả:

  • Văn bản : Bất kỳ dữ liệu văn bản nào ( java.lang.String , nguyên thủy hoặc các lớp trình bao bọc tương đương của chúng)
  • Binary : Dữ liệu nhị phân (ví dụ: âm thanh, hình ảnh, v.v.) được biểu thị bằng java.nio.ByteBuffer hoặc một byte [] (mảng byte)
  • Đối tượng Java : API giúp bạn có thể làm việc với các biểu diễn gốc (đối tượng Java) trong mã của bạn và sử dụng bộ biến đổi tùy chỉnh (bộ mã hóa / giải mã) để chuyển đổi chúng thành các định dạng trực tuyến tương thích (văn bản, nhị phân) được cho phép bởi giao thức WebSocket
  • Ping-Pong : Một javax.websocket.PongMessage là một thông báo xác nhận được gửi bởi một đồng đẳng WebSocket để phản hồi yêu cầu kiểm tra sức khỏe (ping)

Đối với ứng dụng của chúng tôi, chúng tôi sẽ sử dụng Đối tượng Java. Chúng tôi sẽ tạo các lớp để mã hóa và giải mã tin nhắn.

4.1. Mã hoá

Một bộ mã hóa nhận một đối tượng Java và tạo ra một biểu diễn điển hình phù hợp để truyền dưới dạng một thông điệp như JSON, XML hoặc biểu diễn nhị phân. Mã hóa có thể được sử dụng bằng cách thực hiện các Encoder.Text hoặc Encoder.Binary giao diện.

Trong đoạn mã bên dưới, chúng tôi xác định lớp Message sẽ được mã hóa và trong mã hóa phương thức, chúng tôi sử dụng Gson để mã hóa đối tượng Java thành JSON:

public class Message { private String from; private String to; private String content; //standard constructors, getters, setters }
public class MessageEncoder implements Encoder.Text { private static Gson gson = new Gson(); @Override public String encode(Message message) throws EncodeException { return gson.toJson(message); } @Override public void init(EndpointConfig endpointConfig) { // Custom initialization logic } @Override public void destroy() { // Close resources } }

4.2. Người giải mã

Bộ giải mã đối lập với bộ mã hóa và được sử dụng để chuyển đổi dữ liệu trở lại thành một đối tượng Java. Bộ giải mã có thể được thực hiện bằng cách sử dụng Decoder.Text hoặc Decoder.Binary giao diện.

Như chúng ta đã thấy với bộ mã hóa, phương thức giải mã là nơi chúng ta lấy JSON được truy xuất trong thông báo được gửi đến điểm cuối và sử dụng Gson để chuyển đổi nó thành một lớp Java được gọi là Thông báo:

public class MessageDecoder implements Decoder.Text { private static Gson gson = new Gson(); @Override public Message decode(String s) throws DecodeException { return gson.fromJson(s, Message.class); } @Override public boolean willDecode(String s) { return (s != null); } @Override public void init(EndpointConfig endpointConfig) { // Custom initialization logic } @Override public void destroy() { // Close resources } }

4.3. Đặt Bộ mã hóa và Bộ giải mã trong Điểm cuối của Máy chủ

Hãy ghép mọi thứ lại với nhau bằng cách thêm các lớp được tạo để mã hóa và giải mã dữ liệu ở chú thích cấp lớp @ServerEndpoint :

@ServerEndpoint( value="/chat/{username}", decoders = MessageDecoder.class, encoders = MessageEncoder.class )

Mỗi khi tin nhắn được gửi đến điểm cuối, chúng sẽ tự động được chuyển đổi thành các đối tượng JSON hoặc Java.

5. Kết luận

Trong bài viết này, chúng tôi đã xem xét Java API cho WebSockets là gì và cách nó có thể giúp chúng tôi xây dựng các ứng dụng như trò chuyện thời gian thực này.

Chúng tôi đã thấy hai mô hình lập trình để tạo điểm cuối: chú thích và có lập trình. Chúng tôi đã xác định một điểm cuối bằng cách sử dụng mô hình chú thích cho ứng dụng của mình cùng với các phương pháp vòng đời.

Ngoài ra, để có thể giao tiếp qua lại giữa máy chủ và máy khách, chúng tôi thấy rằng chúng tôi cần bộ mã hóa và bộ giải mã để chuyển đổi các đối tượng Java sang JSON và ngược lại.

API JSR 356 rất đơn giản và mô hình lập trình dựa trên chú thích giúp việc tạo các ứng dụng WebSocket trở nên rất dễ dàng.

Để chạy ứng dụng chúng tôi đã xây dựng trong ví dụ, tất cả những gì chúng tôi cần làm là triển khai tệp war trong máy chủ web và truy cập URL: // localhost: 8080 / java-websocket /. Bạn có thể tìm thấy liên kết đến kho lưu trữ tại đây.