首页 > 经验记录 > Vert.x复健,写个WebSocket聊天室

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 没有太大关系,单一职责嘛,就不多说了。

           


CAPTCHAis initialing...
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00