Vert.x复健,写个WebSocket聊天室
前端和具体的聊天室内状态流转/Session维护逻辑就不贴了,感觉意义不大,主要放上来的还是Vert.x相关操作。
使用的是协议升级 (HTTP –> WebSocket)的方式,之所以用这种方式,是为了在代码类获取到一些前端传来的信息,一般是当前用户的标识符,当然由于我这就是个Demo,并没有用户系统,所以其实就是前端随机出来的UUID,用户名也是程序内存里自增出来的,看一乐就行。
环境
Vert.x:4.5.10
JDK:17
Maven
引入以下两个依赖:
<dependency> <groupId>io.vertx</groupId> <artifactId>vertx-core</artifactId> <version>${vertx.version}</version> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web</artifactId> <version>${vertx.version}</version> </dependency>
然后是Vert.x打包成 fat jar (可通过 java -jar 执行的jar)的maven配置
需要注意的是,我这里并没有使用官方推荐的Verticle方式部署,而是用的main方法部署。
<build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.6.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <minimizeJar>false</minimizeJar> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <!-- <Main-Class>io.vertx.core.Launcher</Main-Class>--> <!-- <Main-Verticle>com.skypyb.VertxMain</Main-Verticle>--> <Main-Class>com.skypyb.VertxMain</Main-Class> </manifestEntries> </transformer> </transformers> <artifactSet /> <outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar</outputFile> </configuration> </execution> </executions> </plugin> </plugins> </build>
实现代码
入口:
public class VertxMain { public static void main(String[] args) { Vertx vertx = Vertx.vertx(); HttpServer httpServer = vertx.createHttpServer(); Router router = Router.router(vertx); router.route("/chatroom").handler(new ChatRoomWebsocketHandler()); router.route("/*").handler(StaticHandler.create()); //代理Vue路由 router.errorHandler(404, ctx -> { if (ctx.request().method() == HttpMethod.GET) { ctx.reroute("/index.html"); } else { ctx.json(new JsonObject().put("code", 404).put("msg", "找不到")); } }); httpServer.requestHandler(router).listen(8888); } }
这里解释一下,errorHandler 用来代理Vue打包出来的静态文件,只要是Java程序处理不了的,就默认委派给Vue处理。
单纯是我自个写的时候为了测试jar打包,所以直接在Vue打包完后,把他丢进了 Vert.x 程序里,一般不会这样整,常见的前后端基本都是分开端口部署的嘛。
ws处理程序:
public class ChatRoomWebsocketHandler implements Handler<RoutingContext> { private final AtomicInteger userNum = new AtomicInteger(0); private final Map<String, JsonObject> userJsonMapping = new HashMap<>(); private final JsonObject DEFAULT_USER = new JsonObject().put("userId", "NAN").put("userName", "备用"); private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd: HH:mm:ss"); @Override public void handle(RoutingContext context) { HttpServerRequest request = context.request(); // 获取每一个链接的ID String id = request.getParam("user-id"); String format = LocalDateTime.now().format(dateTimeFormatter); System.out.println(format + " " + request.uri() + " websocket链接, user-id: " + id); //协议升级 Future<ServerWebSocket> future = request.toWebSocket(); future.onFailure(event -> System.out.println(event.getMessage())); future.onSuccess(webSocket -> { System.out.println(format + " websocket success, user-id: " + id); webSocket.writePing(Buffer.buffer("PING")); //进入聊天室 ChatRoom chatRoom = ChatRoomManager.getInstance().enterChatRoom(id, webSocket); //构造一个Json对象给当前用户, 给他个名字 JsonObject object = new JsonObject(); object.put("userId", id).put(format + " userName", "用户" + userNum.incrementAndGet()); userJsonMapping.put(id, object); // WebSocket 消息 webSocket.frameHandler(handler -> { System.out.println("--消息--, id:" + id); if (handler.isClose()) return; String textData = handler.textData(); System.out.println(String.format("%s -> %s", id, textData)); //构造消息 JsonObject currentUser = userJsonMapping.getOrDefault(id, DEFAULT_USER); JsonObject message = new JsonObject(); message.put("sender", currentUser.getString("userId")) .put("userName", currentUser.getString("userName")) .put("message", textData); //给当前聊天室的每一个WebSocket连接发送消息 chatRoom.fanout(message.encode()); }); // 客户端WebSocket 关闭时,退出聊天室 webSocket.closeHandler(handler -> { System.out.println(format + " --客户端关闭--, id:" + id); ChatRoomManager.getInstance().quitChatRoom(id, webSocket); userJsonMapping.remove(id); }); }); } }
ChatRoomManager 相关是用来做具体的链接维护、逻辑实现。
反正就是里边会维护 ServerWebSocket
这个对象,然后调用他的方法比如 ServerWebSocket#writeTextMessage
之类的,和 Vert.x 没有太大关系,单一职责嘛,就不多说了。