Nginx 配置WSS 解析与实战

Nginx 配置WSS 解析与实战

1. 几个关键概念

1.1 WebSocket

WebSocket 协议是 html5 的一种通信协议,该协议兼容我们常用的浏览器。例如:Chrome、Firefox、IE 等。它可以使客户端和服务端双向数据传输更加简单快捷,并且在TCP连接进行一次握手后,就可以持久性连接,同时允许服务端对客户端推送数据。外加传统模式的协议一般HTTP轻轻可能会包含较长的头部,但真正有效的可能只有小部分,从而占用了很多资源和宽带。因此WebSocket协议不仅可以实时通讯,支持扩展;也可以压缩节省服务资源和宽带。 关于WebSocket的深入理解,可以看这篇文章:Html5 WebSocket

WS 协议 和 WSS 协议两个均是 WebSocket 协议的 SCHEM,两者一个是非安全的,一个是安全的。也是统一的资源标志符。就好比 HTTP协议 和 HTTPS协议的差别。非安全的没有证书,安全的需要 SSL 证书。其中 WSS 表示在 TLS 之上的 WebSocket。

WS 一般默认是 80 端口,而 WSS 默认是 443 端口,大多数网站用的就是 80 和 443 端口。

image
image

1.2 SSL

SSL(Secure Socket Layer,安全套接层) 简单来说是一种加密技术, 通过它, 我们可以在通信的双方上建立一个安全的通信链路, 因此数据交互的双方可以安全地通信, 而不需要担心数据被窃取. 关于 SSL 的深入知识, 可以看这篇文章: SSL/TLS协议运行机制的概述

1.3 WSS

WSS 是 Web Socket Secure 的简称, 它是 WebSocket 的加密版本. 我们知道 WebSocket 中的数据是不加密的, 但是不加密的数据很容易被别有用心的人窃取, 因此为了保护数据安全, 人们将 WebSocket 与 SSL 结合, 实现了安全的 WebSocket 通信, 即 WebSocket Secure。所以说 WSS 是使用 SSL 进行加密了的 WebSocket 通信技术。

1.3 HTTPS

其实 HTTPS 和 WSS 类似, HTTP 之于 HTTPS 就像 WebSocket 之于 WebSocket Secure。
HTTP 协议本身也是明文传输, 因此为了数据的安全性, 人们利用 SSL 作为加密通道, 在 SSL 之上传递 HTTP 数据, 因此 SSL 加密通道上运行的 HTTP 协议就被称为 HTTPS 了。

1.4 总结

SSL 是基础, 在 SSL 上运行 WebSocket 协议就是 WSS; 在 SSL 上运行 HTTP 协议就是 HTTPS。

2. SSL单向/双向认证

2.1 单向认证 SSL 协议的具体过程

  1. 客户端的浏览器向服务器传送客户端 SSL 协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息。
  2. 服务器向客户端传送 SSL 协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。
  3. 客户利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的 CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过, 通讯将断开;如果合法性验证通过,将继续进行第四步。
  4. 用户端随机产生一个用于后面通讯的“对称密码”,然后用服务器的公钥(服务器的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的“预主密码”传给服务器。
  5. 如果服务器要求客户的身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器。
  6. 如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:客户的证书使用日期是否有效,为客户提供证书的 CA 是否可靠,发行CA 的公钥能否正确解开客户证书的发行 CA 的数字签名,检查客户的证书是否在证书废止列表(CRL)中。检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的“预主密码 ”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。
  7. 服务器和客户端用相同的主密码即“通话密码”,一个对称密钥用于 SSL 协议的安全数据通讯的加解密通讯。同时在 SSL 通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。
  8. 客户端向服务器端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知服务器客户端的握手过程结束。
  9. 服务器向客户端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。
  10. SSL 的握手部分结束,SSL 安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。

2.2 双向认证 SSL 协议的具体过程

  1. 浏览器发送一个连接请求给安全服务器。
  2. 服务器将自己的证书,以及同证书相关的信息发送给客户浏览器。
  3. 客户浏览器检查服务器送过来的证书是否是由自己信赖的 CA 中心所签发的。如果是,就继续执行协议;如果不是,客户浏览器就给客户一个警告消息:警告客户这个证书不是可以信赖的,询问客户是否需要继续。
  4. 接着客户浏览器比较证书里的消息,例如域名和公钥,与服务器刚刚发送的相关消息是否一致,如果是一致的,客户浏览器认可这个服务器的合法身份。
  5. 服务器要求客户发送客户自己的证书。收到后,服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务器获得用户的公钥。
  6. 客户浏览器告诉服务器自己所能够支持的通讯对称密码方案。
  7. 服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知浏览器。
  8. 浏览器针对这个密码方案,选择一个通话密钥,接着用服务器的公钥加过密后发送给服务器。
  9. 服务器接收到浏览器送过来的消息,用自己的私钥解密,获得通话密钥。
  10. 服务器、浏览器接下来的通讯都是用对称密码方案,对称密钥是加过密的。

上面所述的是双向认证 SSL 协议的具体通讯过程,这种情况要求服务器和用户双方都有证书。
单向认证 SSL 协议不需要客户拥有 CA 证书,具体的过程相对于上面的步骤,只需将服务器端验证客户证书的过程去掉,以及在协商对称密码方案,对称通话密钥时,服务器发送给客户的是没有加过密的 (这并不影响 SSL 过程的安全性)密码方案。这样,双方具体的通讯内容,就是加过密的数据,如果有第三方攻击,获得的只是加密的数据,第三方要获得有用的信息,就需要对加密 的数据进行解密,这时候的安全就依赖于密码方案的安全。而幸运的是,目前所用的密码方案,只要通讯密钥长度足够的长,就足够的安全。这也是我们强调要求使 用 128 位加密通讯的原因。

3. 证书格式说明

在使用openssl自己生成证书的时候,会发现网上很多例子生成的证书格式都不同,同一篇文章里也会有很多种格式.
所以就需要了解下 不同格式有什么区别和联系

参考博客http://blog.csdn.net/justinjing0612/article/details/7770301

参考博客http://www.cnblogs.com/lzjsky/archive/2010/11/14/1877143.html

  • der,cer文件一般是二进制格式的,只放证书,不含私钥
  • crt文件可能是二进制的,也可能是文本格式的,应该以文本格式居多,功能同der/cer
  • pem文件一般是文本格式的,可以放证书或者私钥,或者两者都有
  • pem如果只含私钥的话,一般用.key扩展名,而且可以有密码保护
  • pfx,p12文件是二进制格式,同时含私钥和证书,通常有保护密码

4. 使用openssl生成证书

因为我的系统是Mac,这里记录的是Mac申请自签名的SSL证书,其他系统也类似,这里不再赘述。

  1. 桌面创建"SSL"文件夹,用来存放申请证书过程中的所有文件。
  1. 打开"终端"并进入到"桌面->SSL"。
  1. 管理员权限(命令:sudo su)。

这个命令超级重要!重要!重要!没有在管理员权限下执行下面的操作,即使你可以完成前面几步,也会在最后一步卡住报错!

sudo su
  1. 创建rootCA.key(命令:openssl genrsa -des3 -out rootCA.key 2048)。

创建时需要输入一个密码,这个密码要记住,下一步有用。

openssl genrsa -des3 -out rootCA.key 2048
image
image
  1. 使用rootCA.key创建rootCA.pem(命令:openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.pem)

创建时需要输入rootCA.key的密码。
其他信息可以随意填写。

openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.pem
image
image
  1. 双击rootCA.pem证书。在钥匙串中双击,并修改为"始终信任"该证书
image
image
  1. 创建v3.ext文件(命令:touch v3.ext)
touch v3.ext

8、编辑v3.ext文件(命令:sudo vim v3.ext)

sudo vim v3.ext

将下列文字复制粘贴进去
注意最后一行,(DNS.1 = xxxxxxx)
这一行可以输入域名或IP。

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName=@alt_names
[alt_names]
DNS.1 = 192.168.0.3
image
image
  1. 创建server.csr和server.key(命令:openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key)

这里要注意倒数第二项,必须填自己的IP地址或域名,如果IP为动态获取的,建议先改成固定IP。

openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key
image
image
  1. 创建server.crt(命令:openssl x509 -req -in server.csr -CA [rootCA.pem路径] -CAkey [rootCA.key路径] -CAcreateserial -out server.crt -days 500 -sha256 -extfile v3.ext)

去掉中括号,把对应文件路径写进去。
如果你报了“Error opening CA Certificate”这个错误,请先检查路径是否正确,如果正确请检查是否执行了第三步,我就卡在这卡了一晚上~
如果一切正常会让你输入密码,输入完成后就会生成server.crt!!

openssl x509 -req -in server.csr -CA [rootCA.pem路径] -CAkey [rootCA.key路径] -CAcreateserial -out server.crt -days 500 -sha256 -extfile v3.ext
image
image
  1. 最后查看SSL文件夹
image
image

5. Nginx配置 WSS

修改 nginx.conf配置文件

map $http_upgrade $connection_upgrade {  
    default upgrade;  
    '' close;  
}  
upstream websocket {  
    server 192.168.0.3:8888;  
}  
server {  
    listen 8888;  
    server_name test.com;
    ssl on;
    ssl_certificate /usr/local/etc/nginx/ZC_chain.crt;
    ssl_certificate_key /usr/local/etc/nginx/ZC_key.key;   
    #ssl_client_certificate /usr/local/etc/nginx/ZC_chain.crt;
    ssl_session_timeout 20m;
    ssl_verify_client off;
    location / {  
        proxy_pass http://websocket;  
        proxy_http_version 1.1;  
        proxy_set_header Upgrade $http_upgrade;  
        proxy_set_header Connection "Upgrade";  
    }  
}

128.190.0.3:8888是真正的服务端地址,nginx所在域名是test.com,代理的端口号是8888,所以前端访问的时候这样配置:

WEBSOCKET_URL: 'wss://test.com:8888',

检查nginx.conf正确性:

nginx -t

重新加载配置文件:

nginx -s reload

6. WebSocketServer(基于SpringBoot)

基于SpringBoot整合WebSocket实现前后端互推消息,该代码可以用于ws协议以及wss协议进行测试。

  1. 首先创建一个springboot项目,网上教程很多,很简单,最终的目录结构如下:
image
image
  1. 项目的pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>webSocket</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>webSocket</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  1. application.properties中配置端口号。
server.port=8888
  1. 配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    /**
     * ServerEndpointExporter 作用
     *
     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
  1. 核心类
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@ServerEndpoint("/webSocket/{sid}")
@Component
public class WebSocketServer {
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static AtomicInteger onlineNum = new AtomicInteger();

    //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
    private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();

    //发送消息
    public void sendMessage(Session session, String message) throws IOException {
        if(session != null){
            synchronized (session) {
//                System.out.println("发送数据:" + message);
                session.getBasicRemote().sendText(message);
            }
        }
    }
    //给指定用户发送信息
    public void sendInfo(String userName, String message){
        Session session = sessionPools.get(userName);
        try {
            sendMessage(session, message);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //建立连接成功调用
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "sid") String userName){
        sessionPools.put(userName, session);
        addOnlineCount();
        System.out.println(userName + "加入webSocket!当前人数为" + onlineNum);
        try {
            sendMessage(session, "欢迎" + userName + "加入连接!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //关闭连接时调用
    @OnClose
    public void onClose(@PathParam(value = "sid") String userName){
        sessionPools.remove(userName);
        subOnlineCount();
        System.out.println(userName + "断开webSocket连接!当前人数为" + onlineNum);
    }

    //收到客户端信息
    @OnMessage
    public void onMessage(String message) throws IOException{
        message = "客户端:" + message + ",已收到";
        System.out.println(message);
        for (Session session: sessionPools.values()) {
            try {
                sendMessage(session, message);
            } catch(Exception e){
                e.printStackTrace();
                continue;
            }
        }
    }

    //错误时调用
    @OnError
    public void onError(Session session, Throwable throwable){
        System.out.println("发生错误");
        throwable.printStackTrace();
    }

    public static void addOnlineCount(){
        onlineNum.incrementAndGet();
    }

    public static void subOnlineCount() {
        onlineNum.decrementAndGet();
    }

}
  1. 在Controller中跳转页面
package com.example.controller;

import com.example.service.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;


@Controller
public class SocketController {

    @Autowired
    private WebSocketServer webSocketServer;

    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    @GetMapping("/webSocket")
    public ModelAndView socket() {
        ModelAndView mav = new ModelAndView("/webSocket");
        //mav.addObject("userId", userId);
        return mav;
    }


}
  1. 前端代码在webSocket.html中:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket</title>

</head>
<body>
<h3>hello socket</h3>
<p>【userId】:<div><input id="userId" name="userId" type="text" value="10"></div>
<p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="20"></div>
<p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="hello websocket"></div>
<p>操作:<div><a onclick="openSocket()">开启socket</a></div>
<p>【操作】:<div><a onclick="sendMessage()">发送消息</a></div>
</body>
<script>
    

    var socket;
    function openSocket() {
        if(typeof(WebSocket) == "undefined") {
            console.log("您的浏览器不支持WebSocket");
        }else{
            console.log("您的浏览器支持WebSocket");
            //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
            var userId = document.getElementById('userId').value;
            var socketUrl="wss://192.168.0.3:8888/webSocket/"+userId;
            console.log(socketUrl);
            if(socket!=null){
                socket.close();
                socket=null;
            }
            socket = new WebSocket(socketUrl);
            //打开事件
            socket.onopen = function() {
                console.log("websocket已打开");
                //socket.send("这是来自客户端的消息" + location.href + new Date());
            };
            //获得消息事件
            socket.onmessage = function(msg) {
                var serverMsg = "收到服务端信息:" + msg.data;
                console.log(serverMsg);
                //发现消息进入    开始处理前端触发逻辑
            };
            //关闭事件
            socket.onclose = function() {
                console.log("websocket已关闭");
            };
            //发生了错误事件
            socket.onerror = function() {
                console.log("websocket发生了错误");
            }
        }
    }
    function sendMessage() {
        if(typeof(WebSocket) == "undefined") {
            console.log("您的浏览器不支持WebSocket");
        }else {
            // console.log("您的浏览器支持WebSocket");
            var toUserId = document.getElementById('toUserId').value;
            var contentText = document.getElementById('contentText').value;
            var msg = '{"toUserId":"'+toUserId+'","contentText":"'+contentText+'"}';
            console.log(msg);
            socket.send(msg);
        }
    }

    </script>
</html>

7. WSS + WebSocketClient

  1. maven依赖

备注:maven仓库官方(https://mvnrepository.com/

<!--        WebSocket start-->
        <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.3.0</version>
        </dependency>
        <!--        WebSocket end-->

这个ws客户端对wss支持不好,源码中的wss client 还要使用证书。可以修改为不验证证书,具体看工具类代码。

  1. 工具类
package com.example.wss;
import java.net.URI;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.java_websocket.client.DefaultSSLWebSocketClientFactory;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
/**
 * <p>
 * WSS工具类
 * </p>
 *
 * @author: hejianhui
 * @create: 2020-08-23 00:27
 * @see MyWssUtil
 * @since JDK1.8
 */
abstract class MyWssUtil extends WebSocketClient {

    public MyWssUtil(URI serverURI) {
        super(serverURI);
        if (serverURI.toString().contains("wss://"))
            trustAllHosts(this);
    }

    public MyWssUtil(URI serverURI, Draft draft) {
        super(serverURI, draft);
        if (serverURI.toString().contains("wss://"))
            trustAllHosts(this);
    }

    public MyWssUtil(URI serverURI, Draft draft, Map<String, String> headers, int connecttimeout) {
        super(serverURI, draft, headers, connecttimeout);
        if (serverURI.toString().contains("wss://"))
            trustAllHosts(this);
    }

    final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };


    static void trustAllHosts(MyWssUtil appClient) {
        System.out.println("start...");
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[]{};
            }

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // TODO Auto-generated method stub

            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // TODO Auto-generated method stub

            }
        }};

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            appClient.setWebSocketFactory(new DefaultSSLWebSocketClientFactory(sc));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  1. 使用工具进行wss协议的接口请求
package com.example.wss;

import org.java_websocket.WebSocket;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 进行wss协议的接口请求测试
 * </p>
 *
 * @author: hejianhui
 * @create: 2020-08-23 00:30
 * @see WssTest
 * @since JDK1.8
 */
public class WssTest {
    public static List<String> result = new ArrayList<>();

    public static String initmsg = "{'lat':'118.817891','lng':'31.931724','speed':'0','distance':'0'}";

    public static void main(String[] args) throws URISyntaxException {
        Map<String, String> headers = new HashMap<>();
        // 根据服务端具体进行配置
        // headers.put("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits");
        // headers.put("Sec-WebSocket-Key", "");
        // headers.put("Sec-WebSocket-Protocol", "x-access-token, ");
        // headers.put("Sec-WebSocket-Version", "13");
        // 以下请求头可以不用传
        // headers.put("Connection", "Upgrade");
        // headers.put("Upgrade", "websocket");
        // headers.put("Accept-Encoding", "gzip, deflate, br");
        // headers.put("Accept-Language", "zh-CN,zh;q=0.9");
        // headers.put("Cache-Control", "no-cache");
        // headers.put("Host", "test.com");
        // headers.put("Origin", "https://test.com");
        // headers.put("Pragma", "no-cache");
        // headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36");

        Draft draft = new Draft_17();
        draft.setParseMode(WebSocket.Role.CLIENT);
        new MyWssUtil(new URI("wss://****************填写自己的url******************")
                , draft
                , headers
                , 10
        ) {

            @Override
            public void onClose(int arg0, String arg1, boolean arg2) {
                System.out.println(String.format("onClose:【%s】【%s】【%s】", arg0, arg1, arg2));
            }

            @Override
            public void onError(Exception arg0) {
                System.out.println(String.format("onError:%s", arg0));

            }

            @Override
            public void onMessage(String arg0) {
                if (!arg0.equals("pong")) {
                    result.add(arg0);
                    System.out.println(String.format("onMessage:%s", arg0));
                }
                this.send(arg0);
            }

            @Override
            public void onOpen(ServerHandshake arg0) {
                System.out.println(String.format("onOpen:%s", arg0));
                this.send(initmsg);
            }
        }.connect();
    }
}

完整代码地址:https://github.com/org-hejianhui/websocket

部分图片来源于网络,版权归原作者,侵删。

以上谢谢大家,求赞求赞求赞!

💗 大佬们随手关注下我的wx公众号【一角钱小助手】和 掘金专栏【一角钱】 更多干货等你来~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350