netty整合websockte并将ws升级wss

这篇具有很好参考价值的文章主要介绍了netty整合websockte并将ws升级wss。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、netty整合websockte

1.1、配置 NettyServer

创建NettyServer:定义两个EventLoopGroup,bossGroup辅助客户端的tcp连接请求,workGroup负责与客户端之间的读写操作。
注意:需要开启一个新的线程来执行netty server, 要不然会阻塞主线程,到时候就无法调用项目的其他controller接口了。

@Component
public class NettyServer {

    @Autowired
    private MyWebSocketHandler webSocketHandler;

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

    public int port = 6655;

    public void start() throws Exception{
        // 主线程组,用于接受客户端的连接,但是不做任何处理,跟老板一样
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 从线程组,主线程组会把任务丢给它,让从线程组去做相应的处理
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {

            ServerBootstrap bootstrap = new ServerBootstrap();
            //boss 等待连接的 队列长度
            bootstrap.option(ChannelOption.SO_BACKLOG,1024);
            // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
            bootstrap.group(workGroup,bossGroup);//绑定线程池
            //指定使用的channel 设置NIO类型的channel
            bootstrap.channel(NioServerSocketChannel.class);
            //绑定监听端口
            bootstrap.localAddress(port);
            // 连接到达时会创建一个通道
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
	                log.info("收到新连接");
	                // webSocket协议本身是基于http协议的,所以这边也要使用http编解码器
	                ch.pipeline().addLast(new HttpServerCodec());
	                //以块的方式来写处理器
	                ch.pipeline().addLast(new ChunkedWriteHandler());
	                //http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合
	                ch.pipeline().addLast(new HttpObjectAggregator(8192));
	                // 自定义的handler,处理业务逻辑
	                ch.pipeline().addLast(webSocketHandler);
	                // 1、对应webSocket,它的数据是以帧(frame)的形式传递
	                // 2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri
	                // 3、核心功能是将http协议升级为ws协议,保持长连接/
	                ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws",null,true,65536*10));
                }
            });
            // 配置完成,绑定server,通过调用sync同步方法阻塞直到绑定成功
            ChannelFuture cf = bootstrap.bind().sync(); // 服务器异步创建绑定
            log.info("启动监听:{}",cf.channel().localAddress());
            // 关闭服务器通道
            cf.channel().closeFuture().sync();
        } finally {//释放资源
            if(bossGroup != null){
                bossGroup.shutdownGracefully().sync();
            }
            if(workGroup != null){
                workGroup.shutdownGracefully().sync();
            }
        }
    }
}

1.2、创建通道连接池

@Component
public class MyChannelHandlerPool {

    public MyChannelHandlerPool(){}

    /**
     * 管理全局的 channel
     * GlobalEventExecutor.INSTANCE  全局事件监听器
     * 一旦将channel 加入 ChannelGroup 就不要用手动去管理channel的连接失效后移除操作,他会自己移除
     */
    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /**
     * 存放用户与Chanel的对应信息,用于给指定用户发送消息
     * 是一个支持高并发更新与查询的哈希表
     */
    private static ConcurrentHashMap<String, Channel> userChannelMap = new ConcurrentHashMap<>();

    /**
     * 存放用户群组对应的用户
     */
    private static ConcurrentHashMap<String, List<String>> chatGroup = new ConcurrentHashMap<>();

    /**
     * 获取用户 channel map
     * @return
     */
    public static ConcurrentHashMap<String, Channel> getUserChannelMap(){
        return userChannelMap;
    }

    /**
     * 获取用户 channel map
     * @return
     */
    public static ConcurrentHashMap<String, List<String>> getChatGroupMap(){
        return chatGroup;
    }
}

1.3、业务逻辑和通道管理

@Component
@ChannelHandler.Sharable
public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

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

    /**
     * channel连接状态就绪以后调用
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        Channel channel = ctx.channel();
        MyChannelHandlerPool.channelGroup.add(channel);
        log.info("新设备上线了~");

    }

    /**
     * channel连接状态断开后触发
     * 同时当前channel会自动从ChannelGroup中被移除
     * @param ctx
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        Channel channel = ctx.channel();
        AttributeKey<String> key = AttributeKey.valueOf("userId");
        Attribute<String> userId = channel.attr(key);
        //这里 ChannelGroup 底层封装会遍历给所有的channel发送消息
        //MyChannelHandlerPool.channelGroup.writeAndFlush(new TextWebSocketFrame("【用户"+userId.get()+"】离开聊天!==> "+new Date()));
        MyChannelHandlerPool.getUserChannelMap().remove(userId.get());
        //打印 ChannelGroup中的人数
        log.info("【{}】连接关闭!",userId.get());
        log.info("当前在线数为: {}", MyChannelHandlerPool.channelGroup.size());
    }

    /**
     * 连接发生异常时触发
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("连接发生异常!");
        ctx.close();
        log.error(cause.toString());
    }

    /**
     * 通道数据读取
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //首次连接是FullHttpRequest
        //以http请求形式接入,但是走的是websocket
        if (null != msg && msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest) msg;
            String url = request.uri();
            Map paramMap = getUrlParams(url);
            log.info("接收到的参数是:{}", JSONObject.toJSONString(paramMap));
            // 将用户ID作为自定义属性加入到channel中,方便随时channel中获取用户ID
            AttributeKey<String> key = AttributeKey.valueOf("userId");
            ctx.channel().attr(key).setIfAbsent((String) paramMap.get("id"));
            //用户id与连接channel绑定
            MyChannelHandlerPool.getUserChannelMap().put((String) paramMap.get("id"), ctx.channel());
            //如果url包含参数,需要处理
            if(url.contains("?")){
                String newUri=url.substring(0,url.indexOf("?"));
                request.setUri(newUri);
            }
        }else if(msg instanceof TextWebSocketFrame){
            //正常的TEXT消息类型
            TextWebSocketFrame frame=(TextWebSocketFrame)msg;
            String data = frame.text();
            log.info("客户端收到服务器数据:{}",data);
            JSONObject jsonObject = (JSONObject) JSONObject.parse(data);
            String uid = jsonObject.getString("uid");
            String targetId = jsonObject.getString("targetId");
            jsonObject.remove("targetId");
            jsonObject.put("source",uid);
            jsonObject.remove("uid");
            sendTargetUserMessage(jsonObject,targetId,uid);
        }
        super.channelRead(ctx, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {

    }

    /**
     * 收到信息后,发给指定用户所在的channel
     * @param message
     */
    private void sendTargetUserMessage(JSONObject message, String targetId,String sourceId){
        JSONObject newMsg = new JSONObject();
        Channel sourceChannel = MyChannelHandlerPool.getUserChannelMap().get(sourceId);
        Channel channel2 = MyChannelHandlerPool.getUserChannelMap().get(targetId);
        if (null != channel2) {
            channel2.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(message)));
            if("printTask".equals(message.getString("msgType"))){
                newMsg.put("msgType","havePrinter");
                sourceChannel.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(newMsg)));
            }
        }else {
            newMsg.put("msgType","noPrinter");
            sourceChannel.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(newMsg)));
            log.error("目标{}不存在",targetId);
        }
    }

    /**
     * 获取路径中的参数值
     * @param url
     */
    private static Map getUrlParams(String url){
        Map<String,String> map = new HashMap<>();
        url = url.replace("?",";");
        if (!url.contains(";")){
            return map;
        }
        if (url.split(";").length > 0){
            String[] arr = url.split(";")[1].split("&");
            for (String s : arr){
                String key = s.split("=")[0];
                String value = s.split("=")[1];
                map.put(key,value);
            }
            return  map;

        }else{
            return map;
        }
    }

}

1.4、服务启动

实现CommandLineRunner 接口,在容器启动后执行如下操作。

@Service
public class NettyStart implements CommandLineRunner {
    @Autowired
    private NettyServer nettyServer;

    @Override
    public void run(String... args) {
        new Thread(()->{
            try {
                nettyServer.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
}

1.5、浏览器页面连接websocket

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Netty-Websocket</title>
</head>
<body>
<form onSubmit="return false;">
    <label>当前用户ID</label><input type="text" id="uid" value="1" readonly="true"/> <br />
    <label>目标用户ID</label><input type="text" id="targetId" placeholder="这里输入目标用户ID"/> 
    <br/>
    <label>发送的消息</label><input type="text" id="message" placeholder="这里输入消息"/> <br />
   
    <input type="button" value="连接webSocket" onClick="connect(this)" />
    <input type="button" value="断开连接" onClick="closeConnect()" />
    <input type="button" value="发送ws消息" onClick="send()" />
    
    <hr color="black" />
    <h3>服务端返回的应答消息</h3>
    <textarea id="responseText" style="width: 1024px;height: 300px;"></textarea>
</form>
</body>
<script type="text/javascript">
    let socket;
    if(!window.WebSocket){
        window.WebSocket = window.MozWebSocket;
    }
    function connect() {
        if(window.WebSocket){
            var uid = document.getElementById('uid').value;
            socket = new WebSocket("ws://127.0.0.1:6655/ws?id="+uid);
            //有消息时触发
            socket.onmessage = function(event){
                var ta = document.getElementById('responseText');
                ta.value += event.data+"\r\n";
            };
            //socket打开时触发
            socket.onopen = function(event){
                var ta = document.getElementById('responseText');
                ta.value = "欢迎您上线! \r\n";
            };
            //socket关闭时触发
            socket.onclose = function(event){
                var ta = document.getElementById('responseText');
                ta.value += "下线成功,欢迎再次上线!\r\n";
            };
        }else{
            alert("您的浏览器不支持WebSocket协议!");
        }
    }
    //发送消息
    function send(message){
        if(!window.WebSocket){return;}
        // WebSocket 是否建立连接
        if(socket.readyState == WebSocket.OPEN){
            var msgData = {
                uid:document.getElementById('uid').value,
                targetId:document.getElementById('targetId').value,
                msg:document.getElementById('message').value,
            };
            socket.send(JSON.stringify(msgData));
            document.getElementsByName('message').value = "";
        }else{
            alert("WebSocket 连接没有建立成功!");
        }
    }
    function closeConnect() {
        socket.close()
    }
</script>
</html>

netty wss,java,websocket,ssl
netty wss,java,websocket,ssl

二、ws升级wss

ws升级wss需要使用ssL证书,ssL证书是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层与应用层之间对网络连接进行加密。

2.1、自签证书

注意:自签的证书有且之能在本机使用,如将A机生成的证书拷贝B机使用也会出现同样的错误

1.1、win+r输入cmd打开命令窗口
1.2、生成自己jks文件,指定文件保存位置

注意:自签的证书有且之能在本机使用,如将A机生成的证书拷贝B机使用也会出现同样的错误

jks文件地址:C:\Users\admin\Desktop\aa\mystore.jks
dns解析的域名:www.wu.com

keytool -genkey -alias server -keyalg RSA -validity 3650 -keystore C:\Users\admin\Desktop\aa\mystore.jks -ext san=dns:www.wu.com -storepass 1234567
# keytool -genkey -alias server -keyalg RSA -validity 3650 -keystore jks文件地址 -ext san=dns:解析域名 -storepass 密码

netty wss,java,websocket,ssl

查看信息jks文件信息

# keytool -list -keystore jks地址 -v 
keytool -list -keystore mystore.jks -v 

1.3、生成cer证书

# keytool -alias server -exportcert -keystore jks文件地址 -file 生成证书的位置 -storepass jks密码
keytool -alias server -exportcert -keystore C:\Users\admin\Desktop\aa\mystore.jks -file C:\Users\admin\Desktop\aa\mystore.cer -storepass 1234567

netty wss,java,websocket,ssl
成功生成的证书:
netty wss,java,websocket,ssl
1.4、NettyServer配置改动
1.4.1、NettyServer配置改动
新建一个ssl工具类

public class SslUtil {
    
    /**
     * @param type 证书类型
     * @param path 证书路径
     * @param password 证书密码
     * @throws Exception
     */
    public static SSLContext  createSSLContext(String type , String path , String password) throws Exception {
        InputStream inputStream = new FileInputStream(path);
        char[] passArray = password.toCharArray();
        SSLContext sslContext = SSLContext.getInstance("SSLv3");
        KeyStore ks = KeyStore.getInstance(type);
        //加载keytool 生成的文件
        ks.load(inputStream, passArray);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, passArray);
        sslContext.init(kmf.getKeyManagers(), null, null);
        inputStream.close();
        return sslContext;
    }
}

在NettyServer中添加如下代码

SSLContext sslContext = SslUtil.createSSLContext("JKS","C:\\Users\\admin\\Desktop\\aa\\mystore.jks","1234567");
//SSLEngine 此类允许使用ssl安全套接层协议进行安全通信
SSLEngine engine = sslContext.createSSLEngine();
engine.setUseClientMode(false); //服务器端模式
engine.setNeedClientAuth(false); //不需要验证客户端
ch.pipeline().addFirst("ssl", new SslHandler(engine));

netty wss,java,websocket,ssl

1.4.2、前端连接改动,将ws改为wss,并将127.0.0.1改成www.wu.com(生成jks文件时指定dns解析为www.wu.com),hosts文件添加域名映射

修改hosts文件
Windows系统中hosts文件路径:C:\Windows\System32\drivers\etc\hosts
在末尾添加域名ip映射,修改hosts后需要刷新DNS缓存使之生效
在cmd命令行中执行命令:ipconfig/flushdns

netty wss,java,websocket,ssl
1.5、服务启动,前端页面访问
后端报错 io.netty.handler.codec.DecoderException: javax.net.ssl.SSLException: Received fatal alert: certificate_unknown,
这是因为jdk认为我们的证书不可信,我们需要将证书变成jdk信任的证书。
netty wss,java,websocket,ssl
1.6、对证书添加信任

  1. 双击cer证书,安装证书
    netty wss,java,websocket,ssl
    netty wss,java,websocket,ssl
    netty wss,java,websocket,ssl
    netty wss,java,websocket,ssl
    netty wss,java,websocket,ssl
    netty wss,java,websocket,ssl
  2. 导入证书到信任库中
    密钥库默认密码:changeit
    keytool -import -alias mycacert -file C:\Users\admin\Desktop\aa\mystore.cer -storepass changeit
    
    netty wss,java,websocket,ssl
    查看密钥库中的证书
    keytool -v -list -keystore .keystore
    
    查看密钥库中指定的证书
    # keytool -list -keystore .keystore -alias 证书别名
    keytool -list -keystore .keystore -alias foo
    
    检查密钥库,当证书成功导入后们就可以重新启动服务,连接websocket
    
    netty wss,java,websocket,ssl
    netty wss,java,websocket,ssl

2.2、权威机构签发的证书

1. 下载证书jks文件,文件密钥
2. 获取网站证书

netty wss,java,websocket,ssl
netty wss,java,websocket,ssl
netty wss,java,websocket,ssl
netty wss,java,websocket,ssl
netty wss,java,websocket,ssl
netty wss,java,websocket,ssl
netty wss,java,websocket,ssl
netty wss,java,websocket,ssl
3. 安装证书、导入证书到信任库中和上述相同,不在赘述。

三、证书失效

当证书过了有效期限,证书则需要更换,在更换新证书前。务必将旧证书卸载并将证书移除密钥库。

3.1、 证书卸载

Win+R ==> certmgr.msc ==> 找到对应的证书,点击上面的删除
netty wss,java,websocket,ssl
netty wss,java,websocket,ssl
netty wss,java,websocket,ssl
netty wss,java,websocket,ssl文章来源地址https://www.toymoban.com/news/detail-733284.html

注意:这里的“颁发给“名称,我们点击要安装的cer时,看到的名称,可能并不是我们的XXX.cer这个文件名

3.2、将证书从密钥库中移除

# keytool -delete -alias 证书别名 -storepass 密钥库密码
keytool -delete -alias mycacert -storepass changeit

3.3、重新获取证书,按上述步骤操作即可

到了这里,关于netty整合websockte并将ws升级wss的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 运维相关(一) - Vue项目配置WebSocket连接{ws、wss 连接方式}

    项目使用的是ruoyi的前后端分离框架 项目需要使用到 websocket , 在本地使用 ws 连接方式是没问题 , 但是服务器上边使用的是nginx + ssl 证书 https域名访问的方式部署的 使用普通的 ws 连接是不可以成功的 需要使用 wss的方式 2.1 前端 vue.config.js 的代码 这里target: 里边指向的都是后

    2024年02月01日
    浏览(53)
  • Nginx配置WebSocket(WS)和WebSocket Secure(WSS)的完整指南

    Nginx是一款广泛使用的高性能Web服务器和反向代理服务器。除了传统的HTTP和HTTPS协议支持外,Nginx还可以配置WebSocket(WS)和WebSocket Secure(WSS),以便实现实时双向通信。本文将提供关于如何在Nginx中配置和使用WebSocket和WebSocket Secure的详细指南。 要开始配置WebSocket和WebSocket

    2024年02月16日
    浏览(50)
  • ubuntu18安装opensips3.4,开启ws/wss/http接口模块

    、如果是centos 7安装则使用yum 命令。 添加库地址 注意系统类型,选择对应的系统类型和版本   curl https://apt.opensips.org/opensips-org.gpg -o /usr/share/keyrings/opensips-org.gpg   echo \\\"deb [signed-by=/usr/share/keyrings/opensips-org.gpg] https://apt.opensips.org bionic 3.4-releases\\\" /etc/apt/sources.list.d/opensips.list  

    2024年04月14日
    浏览(39)
  • MQTTX工具上的 mqtt mqtts ws wss的区别,以及常见问题解答

    ws --  WebSocket 非加密(端口8083) wss -- WebSocket SSL 加密 (端口8084) mqtt -- mqtt 非加密 (端口1883 ) mqtts --mqtt 加密 (端口8884) WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据 连接地址没有指明协议:WebSocket 作为一种通信协议,

    2023年04月08日
    浏览(49)
  • 记录一次nginx+Websocket反向代理时报错504-gateway TimeOut和各种开发遇到的坑(wss链接404、ws链接400 bad Requset等等)

    需要反向代理转发websocket链接。 1、nginx路径未匹配上 2、链接上后,在默认的http链接时长中没有发送心跳包,nginx自动关闭http链接,一般默认为1分钟 3、http链接转发后并没有升级为websockt链接(Bad Request 400错误) 4、websocket长链接1分钟后自动关闭 5、wss链接通过nginx转发时,

    2024年02月15日
    浏览(53)
  • Netty系列(一):Springboot整合Netty,自定义协议实现

    Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。 也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单

    2023年04月25日
    浏览(55)
  • SpringBoot整合Netty

    简介 Netty是一个基于Java的开源网络应用框架,它提供了高性能、异步事件驱动的网络编程能力。Netty旨在帮助开发者构建高性能、高可靠性的网络应用程序。 Netty提供了简洁的API和丰富的功能,可以轻松处理各种网络通信协议,如TCP、UDP、WebSocket等。它的设计理念是基于事件

    2024年02月08日
    浏览(37)
  • idea专业版和idea社区版整合Tomcat,并将war包部署

    开发过程中,由于需要运用云平台,所以从新配置开发环境,其它或多或少有些许问题,但解决起来较为轻松。而对于部署注册中心Eureka时,应该是由于版本过高原因导致无法使用springboot内置的Tomcat,所以选择减低版本,使用外置的Tomcat8来进行部署,由于我在网上没有找到

    2024年02月09日
    浏览(33)
  • springboot整合netty的正确姿势

    近期做一些物联网方面项目,使用到了tcp协议,之前公司做过socket短连接,网上找了一个简单的demo,很早便学习到nio方面知识,学习了《netty从入门到精通》这本书,同时也根据网上视频做了几个demo,但学习不太深入,刚好物联网项目,就直接使用netty,前期直接使用这个框

    2024年02月05日
    浏览(39)
  • Springboot整合Netty,自定义协议实现

    新建springboot项目,并在项目以来中导入netty包,用fastjson包处理jsonStr。 创建netty相关配置信息文件 yml配置文件—— application.yml netty配置实体类—— NettyProperties 与yml配置文件绑定 通过 @ConfigurationProperties(prefix = \\\"netty\\\") 注解读取配置文件中的netty配置,通过反射注入值,需要在

    2024年02月06日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包