Socket.IO có 2 Java implementation chính là Socket.IO Java và Netty-socketio. Netty-socketio thì chỉ implement tới Socket.IO 2.x còn Socket.IO Java thì đã implement tới latest version của Socket.IO. Do đó, nếu muốn implement một Websocket server với Socket.IO sử dụng Java, các bạn hãy dùng phiên bản Socket.IO Java này các bạn nhé! Trong bài viết này, mình sẽ hướng dẫn các bạn làm điều này và cũng hướng dẫn thêm về cách làm thế nào để gửi và nhận message từ Websocket server sử dụng thư viện Socket.IO-client Java!
Websocket Server
Mình sẽ tạo mới một Maven project để xây dựng Websocket server:
Để tương thích với thư viện Socket.IO-client Java, chúng ta sẽ sử dụng Socket.IO Java version 3.x như sau:
1 2 3 4 5 |
<dependency> <groupId>io.socket</groupId> <artifactId>socket.io-server</artifactId> <version>3.0.2</version> </dependency> |
Trong ví dụ này, mình sẽ start một Websocket server sử dụng Socket.IO Java với một embedded Jetty server:
1 2 3 4 5 |
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>9.4.46.v20220331</version> </dependency> |
Khi một HTTP request tới Jetty server này, chúng ta sẽ upgrade HTTP connection lên thành Websocket connection sử dụng Jetty Websocket server:
1 2 3 4 5 |
<dependency> <groupId>org.eclipse.jetty.websocket</groupId> <artifactId>websocket-server</artifactId> <version>9.4.46.v20220331</version> </dependency> |
và sử dụng Socket.IO Java để handle connection này với Jetty WebSocket adapter module:
1 2 3 4 5 |
<dependency> <groupId>io.socket</groupId> <artifactId>engine.io-server-jetty</artifactId> <version>5.0.1</version> </dependency> |
Bây giờ, chúng ta sẽ đi vào implement Websocket server các bạn nhé!
Chúng ta sẽ cần khởi tạo đối tượng SocketIoServer của Socket.IO Java để integrate với embedded Jetty server trước.
Để khởi tạo đối tượng của class SocketIoServer này, chúng ta cần có đối tượng EngineIoServer.
EngineIoServer là một class từ thư viện Engine.IO Java, một thư viện implement kênh giao tiếp 2 chiều giữa client và server cross-browser/cross-device.
Để có đối tượng EngineIoServer thì chúng ta cần đối tượng EngineIoServerOptions. Chúng ta có thể khởi tạo đối tượng EngineIoServerOptions này với cấu hình mặc định sử dụng phương thức static newFromDefault() của class EngineIoServerOptions, như sau:
1 |
EngineIoServerOptions engineIoServerOptions = EngineIoServerOptions.newFromDefault(); |
Một cấu hình mặc định mà các bạn cần lưu ý là cấu hình về CORS (Cross-origin resource sharing). Các bạn có thể tìm hiểu thêm về CORS trong bài viết của mình về Cấu hình Web Origin trong Keycloak. Mặc định thì tất cả các origin được cho phép, các bạn có thể restrict danh sách origin lại bằng cách sử dụng phương thức setAllowedCorsOrigins() của đối tượng EngineIoServerOptions nếu muốn.
Sau khi đã có đối tượng EngineIoServerOptions, chúng ta có thể khởi tạo đối tượng EngineIoServer như sau:
1 |
EngineIoServer engineIoServer = new EngineIoServer(engineIoServerOptions); |
Và đối tượng SocketIoServer từ đối tượng EngineIoServer:
1 |
SocketIoServer socketIoServer = new SocketIoServer(engineIoServer); |
Sau khi đã có đối tượng SocketIoServer, chúng ta sẽ thêm code để start một embedded Jetty server:
1 |
Server server = new Server(new InetSocketAddress("localhost", 8080)); |
Vì khi một Socket.IO client connect tới Socket.IO Websocket server, nó sẽ request tới server, mặc định với context path là “/socket.io” nên chúng ta cần thêm một servlet để handle cho context path này.
1 2 3 4 5 6 7 8 |
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); servletContextHandler.addServlet(new ServletHolder(new HttpServlet() { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException { engineIoServer.handleRequest(request, response); } }), "/socket.io/*"); |
Như các bạn thấy, mình khởi tạo một đối tượng ServletContextHandler và thêm mới một servlet để handle cho tất cả các request bắt đầu với “/socket.io”. Tất cả các request bắt đầu với “/socket.io” này sẽ được upgrade từ HTTP connection lên Websocket connection sử dụng đoạn code sau:
1 2 3 |
WebSocketUpgradeFilter webSocketUpgradeFilter = WebSocketUpgradeFilter.configureContext(servletContextHandler); webSocketUpgradeFilter.addMapping(new ServletPathSpec("/socket.io/*"), (servletUpgradeRequest, servletUpgradeResponse) -> new JettyWebSocketHandler(engineIoServer)); |
Đối tượng EngineIoServer của thư viện Engine.IO Java như mình có nói ở trên, đảm nhận việc communication 2 chiều giữa client và server, do đó nó sẽ handle tất cả request từ client cho context path “/socket.io” này.
Sau khi đã cấu hình xong cho đối tượng ServletContextHandler, chúng ta cần thêm nó vào handler list của embedded Jetty server:
1 |
server.setHandler(servletContextHandler); |
Như vậy thì chúng ta đã hoàn thành việc integrate Socket.IO Java với embedded Jetty server. Các bạn cần start embedded Jetty server sử dụng phương thức start() của đối tượng Server như sau:
1 |
server.start(); |
Giờ thì chúng ta có thể sử dụng đối tượng SocketIoServer để gửi và nhận message với client.
Các bạn có thể tạo mới một Namespace để làm việc. Đoạn code bên dưới sẽ in ra dòng log “Client … has connected.” khi client connect tới namespace “/”:
1 2 3 4 5 6 7 8 |
SocketIoNamespace ns = socketIoServer.namespace("/"); ns.on("connection", new Emitter.Listener() { @Override public void call(Object... args) { SocketIoSocket socket = (SocketIoSocket) args[0]; System.out.println("Client " + socket.getId() + " has connected."); } }); |
Websocket Client
Mình cũng tạo một Maven project để xây dựng một client application connect tới Websocket server mà chúng ta đã chạy ở trên:
với Socket.IO-client Java dependency như sau:
1 2 3 4 5 |
<dependency> <groupId>io.socket</groupId> <artifactId>socket.io-client</artifactId> <version>2.0.1</version> </dependency> |
Sử dụng một trong các phương thức static socket() định nghĩa trong class IO của Socket.IO-client Java, các bạn có thể tạo mới đối tượng Socket client như sau:
1 2 3 4 5 6 7 8 |
URI uri = URI.create("http://localhost:8080"); // @formatter:off IO.Options options = IO.Options.builder() .build(); // @formatter:on Socket socket = IO.socket(uri, options); |
Tham số của phương thức socket() mà mình sử dụng ở trên lần lượt là URI của Websocket server và những option của client khi connect tới Websocket server này.
Chúng ta có thể in ra dòng chữ “Connected to server” khi connect tới Websocket server mà mình đã start ở trên như sau:
1 2 3 4 5 6 7 8 |
socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() { @Override public void call(Object... args) { System.out.println("Connected to server"); } }); socket.connect(); |
Kết quả:
Phía Websocket server, các bạn cũng sẽ thấy dòng chữ sau được in ra như sau:
Gửi message từ client tới Websocket server
Sau khi kết nối với Websocket server, client có thể gửi một message tới server sử dụng phương thức emit(), ví dụ như sau:
1 |
socket.emit("message", "Hello World"); |
Tham số thứ nhất của phương thức emit() là tên event còn tham số thứ hai là data sẽ gửi tới event này. Trong ví dụ này, mình chỉ đơn giản là gửi một message “Hello World”.
Ở phía Websocket server thì các bạn có thể subscribe vào event trên để nhận data. Chúng ta phải sử dụng đối tượng của class SocketIoSocket gắn với mỗi Websocket connection để làm điều này. Ví dụ để nhận message được emit từ client ở trên, các bạn có thể thêm code trong phần event “connection” như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SocketIoNamespace ns = socketIoServer.namespace("/"); ns.on("connection", new Emitter.Listener() { @Override public void call(Object... args) { SocketIoSocket socket = (SocketIoSocket) args[0]; System.out.println("Client " + socket.getId() + " has connected."); socket.on("message", new Emitter.Listener() { @Override public void call(Object... args) { System.out.println("[Client " + socket.getId() + "] " + args[0]); } }); } }); |
Kết quả khi chúng ta chạy lại Websocket server và sau đó là client như sau:
Gửi message từ Websocket server tới client
Để gửi message từ Websocket server tới client chúng ta cũng sử dụng đối tượng của class SocketIoSocket với phương thức send(). Ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
SocketIoNamespace ns = socketIoServer.namespace("/"); ns.on("connection", new Emitter.Listener() { @Override public void call(Object... args) { SocketIoSocket socket = (SocketIoSocket) args[0]; System.out.println("Client " + socket.getId() + " has connected."); socket.on("message", new Emitter.Listener() { @Override public void call(Object... args) { System.out.println("[Client " + socket.getId() + "] " + args[0]); } }); socket.send("hello", "Hello from Websocket server"); } }); |
Ở phía client, chúng ta có thể subscribe vào event này để nhận data từ Websocket server, ví dụ như sau:
1 2 3 4 5 6 |
socket.on("hello", new Emitter.Listener() { @Override public void call(Object... args) { System.out.println(args[0]); } }); |
Kết quả: