6、WebSokect

WebSocket

WebSocket是HTML5提供的一种新的网络通信协议。它实现了服务端与客户端的全双工通信,建立在传输层TCP协议之上,即浏览器与服务端需要先建立TCP协议,再发送WebSocket连接建立请求。

为什么需要 WebSocket?

因为 HTTP 协议有一个缺陷:通信只能由客户端发起,HTTP 协议做不到服务器主动向客户端推送信息

这里写图片描述

比如说我们想要获取一个实时的新闻信息,在每次更新新闻信息后,我们都需要刷新页面才能获取到最新的信息,只有再次发起客户端请求,服务器端才会返回结果。但是服务器端不能做到推送消息给客户端,当然我们可以使用轮询,查看服务器有没有新的消息,比如 "聊天室" 这样的,但是轮询效率是非常低的,因此WebSocket就这样产生了。

建立连接的过程

这里写图片描述

客户端发送请求信息,服务端接收到这个请求并返回响应信息。

当连接建立后,客户端发送http请求时,通过Upgrade:webSocket Connection:Upgrade 告知服务器需要建立的是WebSocket连接,并且还会传递WebSocket版本号、协议的字版本号、原始地址、主机地址, WebSocket相互通信的Header很小,大概只有2Bytes

实现

原生实现

<dependency>
    <groupId>org.java-websocket</groupId>
    <artifactId>Java-WebSocket</artifactId>
    <version>1.5.5</version>
</dependency>

服务端

实现WebSocketServer抽象类

public class WsServer extends WebSocketServer {

    public WsServer(int port){
        super(new InetSocketAddress(port));
    }

    public WsServer(InetSocketAddress address){
        super(address);
    }
    // 建立连接
    @Override
    public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
        broadcast(webSocket.getRemoteSocketAddress().getAddress().getHostAddress() + " 连接服务器!");
        // 获取客户端连接的路径
        // 例如客户端使用ws://ip:port/test连接,获取值为/test
        broadcast("连接的路径: " + clientHandshake.getResourceDescriptor());
    }
    // 连接关闭
    @Override
    public void onClose(WebSocket webSocket, int code, String reason, boolean remote) {
        broadcast(webSocket + " 与服务器断开连接!");
        System.out.println(webSocket + " 与服务器断开连接!");

    }
    // 收到消息
    @Override
    public void onMessage(WebSocket webSocket, String message) {
        // 广播消息,所有连接都会收到
        broadcast(message);
        // 只给当前连接发送消息
        // webSocket.send(message);
        System.out.println(webSocket + ": " + message);
    }
    // 连接异常
    @Override
    public void onError(WebSocket webSocket, Exception ex) {
        ex.printStackTrace();
    }
    // 服务启动
    @Override
    public void onStart() {
        System.out.println("WsServer 启动成功!");
        setConnectionLostTimeout(0);
        setConnectionLostTimeout(100);
    }
}

客户端

public class Client {

    public static void main(String[] args) throws Exception{
        // 创建客户端实例
        WebSocketClient webSocketClient = new WebSocketClient(new URI("ws://localhost:8887/"), new Draft_6455()) {
            //连接服务端时触发
            @Override
            public void onOpen(ServerHandshake handshakedata) {
                System.out.println("与服务器连接成功!");
            }
            //收到服务端消息时触发
            @Override
            public void onMessage(String message) {
                System.out.println("收到消息:" + message);
            }
            //和服务端断开连接时触发
            @Override
            public void onClose(int code, String reason, boolean remote) {
                System.out.println("退出连接!");
            }
            //连接异常时触发
            @Override
            public void onError(Exception ex) {
                ex.printStackTrace();
            }
        };
        // 建立连接
        webSocketClient.connect();
        BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            String in = sysin.readLine();
            // 发送消息
            webSocketClient.send(in);
            if (in.equals("exit")) {
                webSocketClient.close();
                break;
            }
        }
    }
}

SpringBoot整合

服务端

1、添加依赖

<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-websocket</artifactId>  
</dependency> 

2、配置类

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        // 设置最大Text大小
        container.setMaxTextMessageBufferSize(512000);
        // 设置最大Binary大小
        container.setMaxBinaryMessageBufferSize(512000);
        // 设置空闲连接超时ms
        container.setMaxSessionIdleTimeout(5000L);
        return container;
    }
}

3、ws服务端逻辑

@Component
@ServerEndpoint("/test/{id}")
public class WsServer {

    private static final Logger log = LoggerFactory.getLogger(WsServer.class);

    private static final ConcurrentHashMap<String,Session> webSockets = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam("id") String id){
        webSockets.put(id,session);
        log.info("新的客户端id : {} ,URI: {} 连接服务器!",id,session.getRequestURI());
    }

    @OnClose
    public void onClose(Session session, @PathParam("id") String id){
        webSockets.remove(id);
        log.info("客户端id : {} 与服务器断开连接!",id);
    }

    @OnMessage
    public void onMessage(Session session, @PathParam("id") String id, String message){
        log.info("收到客户端 id : {} 发送的消息 : {}",id,message);
    }
    @OnError
    public void onError(Session session, @PathParam("id") String id,Throwable error){
        log.info("客户端id : {} ,发生异常 : {}",id,error);
    }

    /**
     * 发送消息给指定客户端
     * @param message
     * @param id
     */
    public void send(String message,String ... id){
        try {
            for (String i : id){
                webSockets.get(i).getBasicRemote().sendText(message);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

4、启动类

@SpringBootApplication
public class Main implements CommandLineRunner {

    @Autowired
    private WsServer wsServer;

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Main.class);
    }

    @Override
    public void run(String... args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.println("输入目标客户端id : ");
            String ids = scanner.next();
            System.out.println("输入要发送的消息 : ");
            String msg = scanner.next();
            wsServer.send(msg,ids.split(","));
        }
    }
}

前端客户端实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket</title>
</head>
<body>
<input id="text" type="text"/>
<button onclick="send()">发送消息</button>
<hr/>
<button onclick="closeWebSocket()">关闭WebSockeet连接</button>
<hr/>
<div id="message"></div>
</body>
</html>
<script type="text/javascript">
    var  webSocket = null;

    //判断当前浏览器是否支持WebSocket
    if ('WebSocket' in window){
        webSocket = new WebSocket('ws://localhost:8090/webSocket');
    } else{
        alert("当前浏览器不支持WebSocket");
    }

    //连接发生错误的回调方法
    webSocket.onerror = function () {
        setMessageInnerHTML("WebSocket连接发生错误!");
    }

    webSocket.onopen = function () {
        setMessageInnerHTML("WebSocket连接成功!")
    }

    webSocket.onmessage = function (event) {
        setMessageInnerHTML(event.data);
    }

    webSocket.onclose = function () {
        setMessageInnerHTML("WebSocket连接关闭");
    }

    window.onbeforeunload = function () {
        closeWebSocket();
    }

    function closeWebSocket() {
        webSocket.close();
    }

    function send() {
        var message = document.getElementById('text').value;
        webSocket.send(message);
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
</script>