前言

这章节开始讲解关于与前端通信相关知识。实现一个在线聊天室类似的功能或者后端推送消息到前端,在没有WebSocket时,读大学那会儿还有接触过DWR(Direct Web Remoting),也使用过轮询的方式,当Servlet3.0出来后,也有使用其异步连接机制进行前后端通信的。今天我们就来说说WebSocket。它是HTML5开始提供的。

关于WebSocket

WebSocketHTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。

WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

浏览器通过JavaScript向服务器发出建立WebSocket连接的请求,连接建立以后,客户端和服务器端就可以通过TCP连接直接交换数据。

当获取Web Socket连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage事件来接收服务器返回的数据。

对于前端,创建一个WebSocket对象,如下:

var Socket = new WebSocket(url, [protocol] );

说明:第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。

WebSocker属性

以下是WebSocket对象的属性。假定我们使用了以上代码创建了Socket对象:

属性 描述
Socket.readyState 只读属性 readyState 表示连接状态,可以是以下值:
0 – 表示连接尚未建立。
1 – 表示连接已建立,可以进行通信。
2 – 表示连接正在进行关闭。
3 – 表示连接已经关闭或者连接不能打开。
Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

WebSocket事件

以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:

事件 事件处理程序 描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发

WebSocket方法

以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:

方法 描述
Socket.send() 使用连接发送数据
Socket.close() 关闭连接

WebSocket实践

前面介绍了在浏览器端webSocket的相关知识点,现在我们就来搭建一个后台对接应用,以实现一个简单的在线聊天室。

一点知识

后端关于WebSocket的实现是基于JSR356标准的。该标准的出现,统一了
WebSocket的代码写法。只要支持web容器支持JSR356标准,那么实现方式是一致的。而目前实现方式有两种,一种是注解方式,另一种就是继承继承javax.websocket.Endpoint类了。

常用注解说明

  • @WebSocketEndpoint
    注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端。注解的值将被用于监听用户连接的终端访问URL地址。
  • @onOpen
    打开一个新连接,即有新连接时,会调用被此注解的方法。
  • @onClose
    关闭连接时调用。
  • @onMessage
    当服务器接收到客户端发送的消息时所调用的方法。
  • @PathParam
    接收uri参数的,与@PathVariable功能差不多,可通过url获取对应值

搭建一个简易聊天室

0.加入POM依赖。

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-websocket</artifactId>

</dependency>

1.编写控制层,对应WebSocket的各事件。同时抽取了个公用类,进行通用方法调用。

WebSocketController.java

@Controller
@ServerEndpoint("/chat/{usernick}")
public class WebSocketController {

    private static Logger logger = Logger.getLogger(WebSocketController.class);

    @OnOpen
    public void onOpen(@PathParam("usernick") String userNick, Session session) {
        String message = "游客[" + userNick + "]加入聊天室!";
        logger.info(message);
        WebSocketUtil.addSession(userNick,session);
        WebSocketUtil.sendMessageForAll(message);
    }

    @OnClose
    public void onClose(@PathParam("usernick") String userNick, Session session) {
        String message="游客["+userNick+"]退出聊天室!";
        logger.info(message);
        WebSocketUtil.removeSession(userNick,session);
        WebSocketUtil.sendMessageForAll(message);
    }

    @OnMessage
    public void onMessage(@PathParam("usernick") String userNick, String message) {
        String info="游客["+userNick+"]:"+message;
        logger.info(info);
        WebSocketUtil.sendMessageForAll(message);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {

    }
}

WebSocketUtil.java

public class WebSocketUtil {

    public static final Map<String, Session> ONLINE_SESSION=new ConcurrentHashMap<>();

    public static void addSession(String userNick, Session session){
        ONLINE_SESSION.put(userNick,session);
    }

    public static void removeSession(String userNick, Session session){
        ONLINE_SESSION.remove(userNick);
    }

    public static void sendMessage(Session session, String message){
        if(session==null){
            return;
        }
        RemoteEndpoint.Async async=session.getAsyncRemote();
        async.sendText(message);
    }

    public static void sendMessageForAll(String message){
        ONLINE_SESSION.forEach((userNick,session)->sendMessage(session, message));
    }
}

注意点:

  • @ServerEndpoint的value值填写时,开头需要加上/,不然会提示路径无效。
  • 需要加上类型@Component注解,使得能被扫描到。
  • 这里的session等,都在包javax.websocket包下的,注意区分。

2.编写配置类

WebSocketConfig.java

申明一个Websocket endpoint类,

@Configuration
public class WebSocketConfig {
    /**
     * 会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     * 要注意,如果使用独立的servlet容器,
     * 而不是直接使用springboot的内置容器,
     * 就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

3.编写主启动类,主要是加入注解@EnableWebSocket

@ServletComponentScan
@SpringBootApplication
@EnableWebSocket
public class FirstSpringBootApplication {

	public static void main(String[] args) {
		SpringApplication.run(FirstSpringBootApplication.class, args);
	}
}

3.启动应用,利用在线的测试工具进行测试。这里直接使用了http://coolaf.com/tool/chattest进行测试。当然也可以自己写一个html了。

首先,输入我们的服务地址:ws://127.0.0.1:8080/chat/lq,连接后就可以看见服务器返回的消息了。

springboot(13)--websocket

然后可以愉快聊天了,简单的一个聊天室就完成了。

相关文章: