三分钟带你实现一个微信聊天室,快和心中想念的人聊个天websocket高级版|Java 开发实战

1,869 阅读5分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

本文正在参加「Java主题月 - Java 开发实战」,详情查看 活动链接

[TOC]

上一章节我们说了websocket的优缺点,我们通过websocket和http的对比分析,总结出用websocket的场景。今天小编带大家通过一个案例使用下升级版的websocket。

sockjs介绍

SockJs算是一个浏览器库,它提供了一个跨浏览器的api , 他在浏览器和服务端建立了一个低延迟、全双工、跨域通信通道。

产生的原因

向ie这些浏览器可能缺少对websocket的支持,我们上一章节也是在谷歌浏览器下开发完成的。这里对ie这些浏览器没有做测试,但是一些低版本的浏览器的确是不支持的websocket的。 sockJs对浏览器兼容性很大。在原声的websocket基础上进行了优化。sockjs在不支持websocket的浏览器上会采用轮询的方式实现双向通信。

环境搭建

springboot整合sockjs

springboot 对sockjs支持性很良好。只需要在原有的websocket配置上添加已sockjs方式发布就可以了。和之前websocket章节一样我们需要添加对sockjs的配置。只需要withSockJS就可以开启sockjs的支持。另外我们还可以添加拦截器进行对访问的鉴权。然后我们可以将通道发布在指定路径上


@Configuration
//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer   {


    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {//注册STOMP协议的节点(endpoint),并映射指定的url
        //注册一个STOMP的endpoint,并指定使用SockJS协议
        registry.addEndpoint("/endpointAric")
                .setAllowedOrigins("*")
                .addInterceptors(createSessionInterceptor())
                .withSockJS();

    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代理(Message Broker)
        //广播式应配置一个/topic消息代理
        registry.enableSimpleBroker("/topic", "/queue");
        //registry.setApplicationDestinationPrefixes("/app");
        //点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
        registry.setUserDestinationPrefix("/user/");

    }

    /**
     * 配置客户端入站通道拦截器
     */
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.setInterceptors(createUserInterceptor());

    }


    @Bean
    public HandshakeInterceptor createSessionInterceptor(){
        return new SessionAuthHandshakeInterceptor();
    }
    /*将客户端渠道拦截器加入spring ioc容器*/
    @Bean
    public UserInterceptor createUserInterceptor() {
        return new UserInterceptor();
    }


    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration.setMessageSizeLimit(500 * 1024 * 1024);
        registration.setSendBufferSizeLimit(1024 * 1024 * 1024);
        registration.setSendTimeLimit(200000);
    }
}

registry.enableSimpleBroker("/topic", "/queue");设置的topic,queue就是客户端和服务端的通信通道。

使用场景

本节内容我们将通过聊天室体验下websocket的使用。

聊天室开发

上面我们已经配置了服务端的环境,这样我们就可以开发通道内的通信内容。聊天室使用websocket进行通信解决了我们通过ajax调用的实时性。保证了消息的准确性。无延后性。

sockJs的好处就是被springboot封装一层之后!我们开发websocket通道就像我们开发接口一样方便。我们只需要通过messageMapping 注解就可以完成我们数据的接收。剩下的客户端的工作还是之前的一套工作逻辑只需要响应的使用sockjs就可以进行通信了。

点对点通信


/*点对点通信*/
@MessageMapping(value = "/sendToUser")
public void templateTest1(@Payload  String message, @Header("userId") String userId,
                          @Headers Map<String, Object> headers) {
    int i = 1;
    for (SimpUser user : userRegistry.getUsers()) {
        System.out.println("用户" + i++ + "---" + user);
    }
    CustomUser userInfo = (CustomUser) ((Map)(headers.get("simpSessionAttributes"))).get(CoreConstants.USERINFO);
    String fromUserId = String.valueOf(userInfo.getUserId());
    //发送消息给指定用户
    messagingTemplate.convertAndSendToUser(userId, "/queue/message",new AricResponse(fromUserId,userId,message));
    if (!fromUserId.equals(userId)) {
        //给自己发送一条,消息同步
        messagingTemplate.convertAndSendToUser(fromUserId, "/queue/message",new AricResponse(fromUserId,userId,message));
    }
    //消息新增
    messageService.insertBackMessage(fromUserId,userId,message);
}

@MessageMapping(value = "sendToUser") 该注解实现了接受客户端的请求。对应客户端可以通过 stompClient.send("/sendToUser", {'userId': userId},content);进行发送至服务端,userId是客户端用户发送给指定用户的id, content是发送的内容 服务端通过@Payload和@Header注解接受前端传送的信息。上述代码中获取发送者用户id的地方为什么那么写呢。下面我们看看客户端连接的方式


// 建立连接对象(还未发起连接)
    socket = new SockJS(host+"/endpointAric");

    // 获取 STOMP 子协议的客户端对象
    stompClient = Stomp.over(socket);

    // 向服务器发起websocket连接并发送CONNECT帧
    stompClient.connect(
        {
            userId: currentUser.userId // 携带客户端信息
        },
        function connectCallback(frame) {
            // 连接成功时(服务器响应 CONNECTED 帧)的回调方法
            subscribe();
            console.log("连接成功");
        },
        function errorCallBack(error) {
            // 连接失败时(服务器响应 ERROR 帧)的回调方法
            console.log("连接失败"+error);
            if (errorTimes < 10) {
                errorTimes++;
                setTimeout("connect();",8000);
            }
        }
    );

在连接的时候客户端会将当前用户的id传递进来,这里也解释了为什么服务端那样获取用户信息。然后就通过/queue/message发送给指定的用户。因为我们在配置websocket的时候指定了 registry.setUserDestinationPrefix("/user/");,所以服务端发送给/queue/message了,客户端订阅的时候需要加上user 即 /user/queue/message


function subscribe() {
    stompClient.subscribe('/user/queue/message', function (response) {
        var returnData = JSON.parse(response.body);
        if (returnData.fromUserId == returnData.toUserId) {
            //自己发送的消息需要自己渲染到聊天框中
            setMessageInnerHTML(currentUser.userId, returnData, 0);
        } else if (returnData.fromUserId == currentUser.userId) {
            //自己发送信息给别,自己收到的信息
            setMessageInnerHTML(returnData.toUserId, returnData, 1);
        } else {
            //别人发送的信息
            setMessageInnerHTML(returnData.fromUserId, returnData, 0);
        }
    });
}

群聊


/**
 * 多房间聊天室
 * @param chatRoomId
 * @param message
 * @return
 */
@MessageMapping("/welcome/{chatRoomId}") //当浏览器向服务端发送请求时,通过@MessageMapping映射/welcome这个地址,类似于@ResponseMapping
@SendTo("/topic/getResponse/{chatRoomId}")//当服务器有消息时,会对订阅了@SendTo中的路径的浏览器发送消息
public AricResponse say(@Header("userId") String userId,@DestinationVariable("chatRoomId") String chatRoomId, AricMessage message) {

    try {
        //睡眠1秒
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return new AricResponse(userId,null,"welcome," + message.getName()+chatRoomId + "!");
}

群聊相对与单聊简单很多,我们发送信息的时候只需要发送到房间通道内就行。而在次群聊内的人只需要订阅房号的信息就行了。这里代码不在赘述。有疑问的小伙伴可以通过下方加入战队找到我。

效果

私聊界面

总结

sockjs在websocket基础上进行各个浏览器的兼容,让我们的开发变得友好起来。 如果你使用Java做服务端,同时又恰好使用Spring Framework作为框架,那么推荐使用SockJS,因为Spring Framework本身就是SockJS推荐的Java Server实现,同时也提供了Java 的client实现。

如果你使用Node.js做服务端,那么毫无疑问你该选择Socket.IO,它本省就是从Node.js开始的,当然服务端也提供了engine.io-server-java实现。甚至你可以使用