WebSocket接口初体验

简介: 这两天在调试一个WebSocket的接口,折腾了一天的时间终于弄好了。现在对WebSocket的相关知识点做一个记录。主要从如下几个方面进行介绍。

前言

这两天在调试一个WebSocket的接口,折腾了一天的时间终于弄好了。现在对WebSocket的相关知识点做一个记录。主要从如下几个方面进行介绍。

WebSocket的概念

HTTP请求是基于请求响应的模式,永远是客户端请求服务器端,是单向的请求。如果服务器端有连续的状态变化,客户端就需要通过轮询的方式去获知。也就是每隔一段时间,就发出一个询问,了解服务器有没有新消息,轮询的效率比较低,非常浪费资源。

WebSocket的最大特点就是服务器可以主动向客户端推送消息,客户端也可以主动向服务器端发送消息,是真正的双向平等通信,也就是全双工通信。

WebSocket的特点

1.建立在TCP协议之上,服务器的实现比较容易。

2.与HTTP协议有着良好的兼容性,默认端口号也是80和443,并且握手阶段采用的是HTTP协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器。

3.数据格式比较轻量,性能开销小,通信高效。

4.没有同源限制,原生支持跨域,客户端可以与任意服务器通信。

5.协议标识符是ws(如果加密,则为wss),服务器网址就是URL。

WebSocket的工作过程

建立一个WebSocket连接,客户端浏览器首先要向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加头信息。

客户端请求

GET / HTTP/1.1
Request URL: ws://localhost:8080/webSocketServer/websocket/testname
Connection: Upgrade
Host: localhost:8080
Origin: http://localhost:63342
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: oxTUjq93ipRDk4gXWhi+mg==
Sec-WebSocket-Version: 13
Upgrade: websocket

服务器响应

Connection: upgrade
Date: Sat, 03 Oct 2020 03:26:03 GMT
Sec-WebSocket-Accept: Jjxh2cOWicbJdcdZ3rhcAeNdoHQ=
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15
Upgrade: websocket

Connection 必须设置Upgrade,表示客户端希望连接升级。

Upgrade字段必须设置WebSocket,表示希望升级到Websocket协议。

Sec-WebSocket-Key 是随机的字符串,服务器会用这些数据来构造出一个SHA-1的信息摘要,把"

Sec-WebScoket-Key"加上一个特殊字符串"oxTUjq93ipRDk4gXWhi+mg=="然后计算 SHA-1 摘要,之后进行 BASE-64编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端。如此操作,可以尽量避免普通 HTTP 请求被误认为Websocket 协议。

服务器解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的WebSocket连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接,http和WebSocket的连接生命周期如下:

ba745db9b334504a8a052020ab25d978_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70#pic_center.png

在SpringBoot中整合WebSocket

服务端

引入依赖

首先我们需要引入WebSocket的starter模块,这个模块的作用开启WebSocket的模块功能,如下所示:

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

配置基础类

依赖引入之后,我们需要配置ServerEndpointExporter的Bean,这个Bean作用是我们可以通过@ServerEndpoint注解声明WebSocket。

@Component
public class WebSocketConfig {
    /**
     * ServerEndpointExporter作用
     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

编写websocket的服务端接口

准备工作做好之后,接下来就是编写WebSocket的服务端的接口,在此Controller中,定义了Session类,通过它来给客户端发送消息,通过ConcurrentHashMap来存储当前连接的客户端。

@Component
@ServerEndpoint("/websocket/{name}")
public class WebSocketController {
    /**
     * 与某个客户端的连接对话,需要通过它来给客户端发送消息
     */
    private Session session;
    /**
     * 标识当前连接客户端的用户名
     */
    private String name;
    private static ConcurrentHashMap<String, WebSocketController> websocketSet = new ConcurrentHashMap<>();
    @OnOpen
    public void OnOpen(Session session, @PathParam(value = "name") String name) {
        this.session = session;
        this.name = name;
        //name是用来表示唯一客户端,如果需要指定发送,需要指定发送通过name来区分
        websocketSet.put(name, this);
        log.info("[WebSocket]连接成功,当前连接人数为={}", websocketSet.size());
    }
    @OnClose
    public void OnClose() {
        websocketSet.remove(this.name);
        log.info("[WebSocket]退出成功,当前连接人数为={}", websocketSet.size());
    }
    @OnMessage
    public void OnMessage(String message) {
        log.info("[WebSocket]收到消息={}", message);
        groupSending("客户端的消息我已经收到了");
    }
    public void groupSending(String message) {
        for (String name : websocketSet.keySet()) {
            try {
                websocketSet.get(name).session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

我们通过@ServerEndpoint注解标识这个类是一个WebSocket接口,通过@OnOpen标记的方法进行WebSocket的服务连接,通过@OnMessage标记的方法进行消息的接收,以及响应消息给客户端,其中给客户端发送消息

websocketSet.get(name).session.getBasicRemote().sendText(message); getBasicRemote()的方法表示通过同步的方式发送消息,getAsyncRemote()的方法表示通过异步的方式发送消息。通过@OnClose标记的方法进行服务的关闭。

客户端的调用

客户端的调用有好几种方式,我们可以通过JS的方式,实例化WebSocketClient的方式,运用OkHttpClient的方式,下面就分别对这三种方式的调用做一个介绍。

通过JS的方式调用服务

 

var websocket = null;
        if ('WebSocket' in window) {
            //用于创建WebSocket对象,webSocketTest对应的是java类的注解值。
            websocket = new WebSocket("ws://localhost:8080/webSocketServer/websocket/testname");
        } else {
            alert("当前浏览器不支持");
        }
//        连接发生错误的时候回调方法;
        websocket.onerror = function () {
            alert("连接错误");
        }
//       连接成功时建立回调方法
        websocket.onopen = function () {
            //WebSocket已连接上,使用send()方法发送数据
            alert("连接成功");
        };
//      收到消息的回调方法
        websocket.onmessage = function (msg) {
            setdivInnerHTML(msg.data);
        };
        //连接关闭的回调方法
        websocket.onclose = function () {
            closed();
            alert("关闭成功");
        };
        function closed() {
            websocket.close();
            alert("点击关闭");
        }
        function send() {
            var message=document.getElementById("message").value;   //注意引号内的内容应该是文本框的id而不能是name
            alert(message);
            websocket.send(message); //给后台发送数据
        }

同样的首先是实例化一个WebSocket对象,然后通过onmessage回调方法接受服务端响应的结果,通过send方法给后台发送数据。

通过实例化WebSocketClient的方式

引入依赖

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

同样的我们需要进行客户端的配置,也就是实例化一个WebSocketClient的实例。主要是建立连接,发送消息。

@Bean
    public WebSocketClient webSocketClient() {
        try {
            WebSocketClient webSocketClient = new WebSocketClient(new URI("ws://localhost:8080/webSocketServer/websocket/name1"), new Draft_6455()) {
                @Override
                public void onOpen(ServerHandshake handshakedata) {
                    log.info("[websocket] 连接成功");
                }
                @Override
                public void onMessage(String message) {
                    log.info("[websocket] 收到消息={}", message);
                }
                @Override
                public void onClose(int code, String reason, boolean remote) {
                    log.info("[websocket] 退出连接");
                }
               @Override
                public void onError(Exception ex) {
                    log.info("[websocket] 连接错误={}", ex.getMessage());
                }
            };
            webSocketClient.connect();
            return webSocketClient;
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        return null;
    }

然后在Controller中通过WebSocketClient的send方法给服务器端发送消息,如下所示;

@RestController
@RequestMapping("/websocket")
public class WebSocketClientController {
    @Autowired
    private WebSocketClient webSocketClient;
    @RequestMapping("/index")
    public String sendMessage(String message) {
        webSocketClient.send("测试消息");
        return "消息发送成功";
    }
}

这是一种通过后端接口调用服务的方式,当然我们还有其他的方式,就像下面这种通过OkHttpClient的方式。

通过OkHttpClient的方式

同样的还是先引入依赖。

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
      <version>3.9.1</version>
  </dependency>

然后定义一个类,继承WebSocketListener,用于建立连接,发送消息。

public class OkHttpClientService extends WebSocketListener {
    private String result = null;
    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        super.onOpen(webSocket, response);
        webSocket.send("需要发送的请求数据");
    }
    @Override
    public void onMessage(WebSocket webSocket, String text) {
        super.onMessage(webSocket, text);
        //其中text是接收到的参数
        result = text;
    }
    public static void main(String[] args) {
        OkHttpClient client = new OkHttpClient.Builder()
                //设置超时时间是5秒
                .connectTimeout(5, TimeUnit.MINUTES)
                .build();
        //http的请求对应的就是websocket中的ws;https的请求对应的就是websocket中的wss
        String url = "ws://localhost:8080/webSocketServer/websocket/name2";
        //实例化Request对象
        Request request = new Request.Builder().url(url).build();
        OkHttpClientService okHttpClientService = new OkHttpClientService();
        client.newWebSocket(request, okHttpClientService);
        //轮询获取结果
        while (okHttpClientService.result == null) {
        }
        System.out.println("接口响应的结果=" + okHttpClientService.result);
    }
}

同样的,还是重写了onOpen方法和onMessage方法,onOpen方法用来发送消息,onMessage方法用来接收服务器的请求。如果是异步的消息的话,我们就需要轮询获取结果。

总结

本文简单首先介绍了WebSocket的基本概念和相关特点,WebSocket是一个全双工通信的协议,它支持客户端向服务端发送消息, 也支持服务端向客户端发送消息, 一次握手,可以多次发送消息。接着就是介绍了在SpringBoot中如何整合WebSocket的相关功能。实现了一个服务端和客户端。

源码地址

https://github.com/XWxiaowei/web_socket_demo.git

参考

https://www.jianshu.com/p/9aa969dd1b4d

https://blog.csdn.net/weixin_38111957/article/details/86352677

相关文章
|
测试技术 Python
07 WebSocket接口:如何测试一个完全陌生的协议接口?
07 WebSocket接口:如何测试一个完全陌生的协议接口?
|
前端开发 Java 应用服务中间件
SpringBoot整合Netty搭建高性能Websocket服务器(实现聊天功能)
之前使用Springboot整合了websocket,实现了一个后端向前端推送信息的基本小案例,这篇文章主要是增加了一个新的框架就是Netty,实现一个高性能的websocket服务器,并结合前端代码,实现一个基本的聊天功能。你可以根据自己的业务需求进行更改。 这里假设你已经了解了Netty和websocket的相关知识,仅仅是想通过Springboot来整合他们。根据之前大家的需求,代码已经上传到了github上。在文末给出。 废话不多说,直接看步骤代码。
1579 0
SpringBoot整合Netty搭建高性能Websocket服务器(实现聊天功能)
|
网络协议 前端开发 安全
websocket和http的瓜葛以及websocket协议实现
websocket和http的瓜葛以及websocket协议实现
websocket和http的瓜葛以及websocket协议实现
|
JavaScript
js实现websocket实例
js实现websocket实例
193 0
|
消息中间件 网络协议 前端开发
SpringBoot轻松整合WebSocket,实现Web在线聊天室
前面为大家讲述了 Spring Boot的整合Redis、RabbitMQ、Elasticsearch等各种框架组件;随着移动互联网的发展,服务端消息数据推送已经是一个非常重要、非常普遍的基础功能。今天就和大家聊聊在SpringBoot轻松整合WebSocket,实现Web在线聊天室,希望能对大家有所帮助。
SpringBoot轻松整合WebSocket,实现Web在线聊天室
|
网络协议 Linux 网络安全
php实现websocket实时消息推送
php实现websocket实时消息推送
425 0
php实现websocket实时消息推送
|
JavaScript 前端开发 Python
Python编程:tornado实现WebSocket通讯
以下实例有点像广播通讯,支持多个客户端连接 代码参考别人的,稍微做了一点点修改 服务器端 websocket_demo.py
97 0
Python编程:tornado实现WebSocket通讯
|
消息中间件 NoSQL 前端开发
通过WebSocket实现日志打印功能
通过WebSocket实现日志打印功能
681 0
通过WebSocket实现日志打印功能
|
网络协议 安全 Java
用Netty实现WebSocket网络聊天室
用Netty实现WebSocket网络聊天室
166 0
用Netty实现WebSocket网络聊天室
|
存储 JSON 网络协议
关于easyswoole实现websocket聊天室的步骤解析
关于easyswoole实现websocket聊天室的步骤解析
415 0