百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

springboot2.2.X手册:5分钟用Netty搭建高性能异步WebSocket服务

haoteby 2024-12-27 13:32 16 浏览

溪云阁:专注编程教学,架构,JAVA,Python,微服务,机器学习等领域,欢迎关注,一起学习。

断更快两个月了,6月份工作忙到飞起,7月份家里又有事,已经累到躺下就想睡觉的程度了。

现在我们做WebSocket服务,很多时候都是会整合Netty作为服务器,但是有个问题,就是发现网上的整合起来,比较繁琐,各种配置,各种对应,最关键是千篇一律的网文,看得好辛苦了,今天咱们来介绍一个开源的组件,帮你快速搭建基于Netty的WebSocket服务,让你更加轻松,更加专注于业务开发。

组件介绍

netty-websocket-spring-boot-starter是基于Netty服务器来做的WebSocket服务器,不需要配置Netty服务器信息,只需要配置Webscoket的注解就行,目前用起来还是很方便的。

加载包体

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.boots</groupId>
        <artifactId>boots</artifactId>
        <version>1.1.0.RELEASE</version>
    </parent>
    <groupId>boots.weboscket</groupId>
    <artifactId>boots-weboscket</artifactId>
    <version>2.0.0.RELEASE</version>
    <name>boots-weboscket</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>

        <!-- 公共组件:swagger服务+入参出参+统一异常拦截 -->
        <dependency>
            <groupId>com.boots</groupId>
            <artifactId>module-boots-api</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>


        <!-- netty工具类 -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>

        <!-- netty-websocket整合工具类 -->
        <dependency>
            <groupId>org.yeauty</groupId>
            <artifactId>netty-websocket-spring-boot-starter</artifactId>
            <version>0.9.5</version>
        </dependency>


    </dependencies>
</project>


配置文件

######配置基本信息######
##配置应用名称
spring.application.name: boots-websocket
##配置时间格式,为了避免精度丢失,全部换成字符串
spring.jackson.timeZone: GMT+8
spring.jackson.dateFormat: yyyy-MM-dd HH:mm:ss
spring.jackson.generator.writeNumbersAsStrings: true

单个推送后端代码

/**
 * All rights Reserved, Designed By 林溪
 * Copyright:    Copyright(C) 2016-2020
 * Company       溪云阁 .
 */

package com.boots.websocket.websocket;

import org.yeauty.annotation.OnClose;
import org.yeauty.annotation.OnError;
import org.yeauty.annotation.OnMessage;
import org.yeauty.annotation.OnOpen;
import org.yeauty.annotation.ServerEndpoint;
import org.yeauty.pojo.Session;

import com.module.boots.exception.CommonRuntimeException;

import io.netty.handler.codec.http.HttpHeaders;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * 单个推送服务
 * @author:溪云阁
 * @date:2020年8月4日
 */
@Slf4j
@ServerEndpoint(path = "/SingleSocket", host = "127.0.0.1", port = "8900")
public class SingleSocket {

    /**
     * 新建WebSocket的时候,执行该方法
     * @author 溪云阁
     * @param session
     * @param headers void
     */
    @OnOpen
    @SneakyThrows(CommonRuntimeException.class)
    public void onOpen(Session session, HttpHeaders headers) {
        log.info("WebSocket服务连接成功");
    }

    /**
     * 关闭WebSocket的时候,执行该方法
     * @author 溪云阁
     * @param session void
     */
    @OnClose
    @SneakyThrows(CommonRuntimeException.class)
    public void onClose(Session session) {
        log.info("WebSocket服务关闭成功");
    }

    /**
     * WebSocket发生异常的时候,执行该方法
     * @author 溪云阁
     * @param session
     * @param th void
     */
    @OnError
    public void onError(Session session, Throwable th) {
        log.error("{}", th.fillInStackTrace());
        th.printStackTrace();
    }

    /**
     * WebSocket接收到的消息为字符串的时候,指定该方法
     * @author 溪云阁
     * @param session
     * @param msg void
     */
    @OnMessage
    @SneakyThrows(CommonRuntimeException.class)
    public void OnMessage(Session session, String msg) {
        log.info("接收到的信息:{}", msg);
        session.sendText(msg);
    }

}

单个推送前端页面

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>单个推送</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/layui/css/layui.css" media="all">
</head>
<body>

	<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px; margin-left: 310px; margin-right: 310px">
		<legend>输出内容</legend>
	</fieldset>

	<form class="layui-form" action="" style="margin-left: 200px; margin-right: 310px">
		<div class="layui-form-item layui-form-text">
			<div class="layui-input-block">
				<textarea placeholder="请输入内容" class="layui-textarea" rows="20" id="contentArea"></textarea>
			</div>
		</div>
	</form>

	<form class="layui-form layui-form-pane" style="margin-left: 310px; margin-right: 310px" action="">
		<div class="layui-form-item">
			<label class="layui-form-label">输入</label>
			<div class="layui-input-block">
				<input type="text" name="content" id="content" autocomplete="off" placeholder="请输入内容" class="layui-input">
			</div>
		</div>
	</form>
	<div class="layui-form-item" style="margin-left: 310px; margin-right: 310px">
		<button class="layui-btn" onclick="sendMsg()">发送</button>
	</div>

	<script src="/js/jquery.min.js" ></script>
	<script src="/layui/layui.js" charset="utf-8"></script>
	<script>
		var websocket = new WebSocket("ws://127.0.0.1:8900/SingleSocket");
		//WebSocket打开
		websocket.onopen = function(evt) {
			$('#contentArea').html("WebSocket服务连接成功");
		};

		//WebSocket推送
		websocket.onmessage = function(evt) {
			var val = $('#contentArea').val() + "
";
			$('#contentArea').html(val + evt.data);
		};

		//WebSocket关闭
		websocket.onclose = function(evt) {
			$('#contentArea').html("WebSocket服务关闭成功");
		};

		function sendMsg() {
			var text = $('#content').val();
			websocket.send(text);
		}
	</script>

</body>
</html>

群发推送后端代码

/**
 * All rights Reserved, Designed By 林溪
 * Copyright:    Copyright(C) 2016-2020
 * Company       溪云阁 .
 */

package com.boots.websocket.websocket;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.yeauty.annotation.OnClose;
import org.yeauty.annotation.OnError;
import org.yeauty.annotation.OnMessage;
import org.yeauty.annotation.OnOpen;
import org.yeauty.annotation.ServerEndpoint;
import org.yeauty.pojo.Session;

import com.module.boots.exception.CommonRuntimeException;

import io.netty.handler.codec.http.HttpHeaders;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * 群组推送服务
 * @author:溪云阁
 * @date:2020年8月4日
 */
@Slf4j
@ServerEndpoint(path = "/groupSocket", host = "127.0.0.1", port = "8901")
public class GroupSocket {

    // 定义存放Session的缓存对象
    private Map<String, Session> map = new ConcurrentHashMap<>();

    /**
     * 新建WebSocket的时候,执行该方法
     * @author 溪云阁
     * @param session
     * @param headers void
     */
    @OnOpen
    @SneakyThrows(CommonRuntimeException.class)
    public void onOpen(Session session, HttpHeaders headers) {
        // 把Session放到缓存中,后面群发使用
        map.put(session.id().toString(), session);
        log.info("WebSocket服务连接成功");
    }

    /**
     * 关闭WebSocket的时候,执行该方法
     * @author 溪云阁
     * @param session void
     */
    @OnClose
    @SneakyThrows(CommonRuntimeException.class)
    public void onClose(Session session) {
        // 当关闭的时候,删除缓存中的session
        if (map.containsKey(session.id().toString())) {
            map.remove(session.id().toString());
        }
        log.info("WebSocket服务关闭成功");
    }

    /**
     * WebSocket发生异常的时候,执行该方法
     * @author 溪云阁
     * @param session
     * @param th void
     */
    @OnError
    public void onError(Session session, Throwable th) {
        log.error("{}", th.fillInStackTrace());
        th.printStackTrace();
    }

    /**
     * WebSocket接收到的消息为字符串的时候,指定该方法
     * @author 溪云阁
     * @param session
     * @param msg void
     */
    @OnMessage
    @SneakyThrows(CommonRuntimeException.class)
    public void OnMessage(String msg) {
        map.forEach((key, session) -> {
            log.info("接收到的信息:{}", msg);
            session.sendText(msg);
        });

    }

}



群发推送前端页面

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>群发推送</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/layui/css/layui.css" media="all">
</head>
<body>

	<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px; margin-left: 310px; margin-right: 310px">
		<legend>输出内容</legend>
	</fieldset>

	<form class="layui-form" action="" style="margin-left: 200px; margin-right: 310px">
		<div class="layui-form-item layui-form-text">
			<div class="layui-input-block">
				<textarea placeholder="请输入内容" class="layui-textarea" rows="10" id="contentArea1"></textarea>
			</div>
		</div>
	</form>
	
	<form class="layui-form" action="" style="margin-left: 200px; margin-right: 310px">
		<div class="layui-form-item layui-form-text">
			<div class="layui-input-block">
				<textarea placeholder="请输入内容" class="layui-textarea" rows="10" id="contentArea2"></textarea>
			</div>
		</div>
	</form>

	<form class="layui-form layui-form-pane" style="margin-left: 310px; margin-right: 310px" action="">
		<div class="layui-form-item">
			<label class="layui-form-label">输入</label>
			<div class="layui-input-block">
				<input type="text" name="content" id="content" autocomplete="off" placeholder="请输入内容" class="layui-input">
			</div>
		</div>
	</form>
	<div class="layui-form-item" style="margin-left: 310px; margin-right: 310px">
		<button class="layui-btn" onclick="sendMsg()">发送</button>
	</div>

	<script src="/js/jquery.min.js" ></script>
	<script src="/layui/layui.js" charset="utf-8"></script>
	<script>
		var websocket1 = new WebSocket("ws://127.0.0.1:8901/groupSocket");
		var websocket2 = new WebSocket("ws://127.0.0.1:8901/groupSocket");
		//第一个WebSocket打开
		websocket1.onopen = function(evt) {
			$('#contentArea1').html("第一个WebSocket服务连接成功");
		};

		//第一个WebSocket推送
		websocket1.onmessage = function(evt) {
			var val = $('#contentArea1').val() + "
";
			$('#contentArea1').html(val + evt.data);
		};

		//第一个WebSocket关闭
		websocket1.onclose = function(evt) {
			$('#contentArea1').html("第一个WebSocket服务关闭成功");
		};
		
		//第二个WebSocket打开
		websocket2.onopen = function(evt) {
			$('#contentArea2').html("第二个WebSocket服务连接成功");
		};

		//第二个WebSocket推送
		websocket2.onmessage = function(evt) {
			var val = $('#contentArea2').val() + "
";
			$('#contentArea2').html(val + evt.data);
		};

		//第二个WebSocket关闭
		websocket2.onclose = function(evt) {
			$('#contentArea2').html("第二个WebSocket服务关闭成功");
		};

		function sendMsg() {
			var text = $('#content').val();
			websocket1.send(text);
			websocket2.send(text);
		}
	</script>

</body>
</html>

启动类

package com.boots.websocket;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**
 * 服务启动类
 * @author:溪云阁
 * @date:2020年5月2日
 */
@SpringBootApplication
@ComponentScan(basePackages = { "com.module", "com.boots" })
public class BootsWebSocketApplication {

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

}

单个推送测试


群发推送测试


总结及拓展

相比于需要自己整合Netty的配置,目前用起来还是很方便的,Netty的配置可以在注解类ServerEndpoint看到,里面一进去就会发现还是很清楚的。

目前发现有个不足的,就是超过一定时间不连接的时候,就会自动断开,不过这个可以在前端做个超时设置或者心跳检测,就可以了,问题不大,用起来还是很爽的。



--END--

作者:@溪云阁

原创作品,抄袭必究

如需要源码,转发,关注后私信我

部分图片或代码来源网络,如侵权请联系删除,谢谢!

历史文章

springboot2.2.X手册:抛弃ELK,百亿日志+调用链的Easylog很放心

springboot2.2.X手册:Eureka不更,Consul被禁,启用Nacos

springboot2.2.X手册:构建全局唯一的短链接数据中心

springboot2.2.X手册:放弃fastdfs,整合Minio做文件服务器真香

springboot2.2.X手册:分布式系统下,重复提交的解决方案

springboot2.2.X手册:Easypoi导出excel,最新版的手感香不香?

springboot2.2.X手册:项目从100M瘦身到100K,部署省事多了!

springboot2.2.X手册:redis的7种类型100个方法全解析

springboot2.2.X手册:是时候用Lettuce替换Jedis操作Redis缓存了

springboot2.2.X手册:构建多元化的API接口,我们这样子设计

springboot2.2.X手册:基于Jasypt的JavaConfig方式敏感信息加密

springboot2.2.X手册:整合最新版MybatisPlus 3.3.1版本

springboot2.2.X手册:对象复制哪种最快?7种复制方式性能对比

springboot2.2.X手册:基于OSS解决文件存储(一年9元^^,赚了)

springboot2.2.X手册:36个注解详细解析,一目了然

相关推荐

一日一技:用Python程序将十进制转换为二进制

用Python程序将十进制转换为二进制通过将数字连续除以2并以相反顺序打印其余部分,将十进制数转换为二进制。在下面的程序中,我们将学习使用递归函数将十进制数转换为二进制数,代码如下:...

十进制转化成二进制你会吗?#数学思维

六年级奥赛起跑线:抽屉原理揭秘。同学们好,我是你们的奥耀老师。今天一起来学习奥赛起跑线第三讲二进制计数法。例一:把十进制五十三化成二进制数是多少?首先十进制就是满十进一,二进制就是满二进一。二进制每个...

二进制、十进制、八进制和十六进制,它们之间是如何转换的?

在学习进制时总会遇到多种进制转换的时候,学会它们之间的转换方法也是必须的,这里分享一下几种进制之间转换的方法,也分享两个好用的转换工具,使用它们能够大幅度的提升你的办公和学习效率,感兴趣的小伙伴记得点...

c语言-2进制转10进制_c语言 二进制转十进制

#include<stdio.h>intmain(){charch;inta=0;...

二进制、八进制、十进制和十六进制数制转换

一、数制1、什么是数制数制是计数进位的简称。也就是由低位向高位进位计数的方法。2、常用数制计算机中常用的数制有二进制、八进制、十进制和十六进制。...

二进制、十进制、八进制、十六进制间的相互转换函数

二进制、十进制、八进制、十六进制间的相互转换函数1、输入任意一个十进制的整数,将其分别转换为二进制、八进制、十六进制。2、程序代码如下:#include<iostream>usingna...

二进制、八进制、十进制和十六进制等常用数制及其相互转换

从大学开始系统的接触计算机专业,到现在已经过去十几年了,今天整理一下基础的进制转换,希望给还在上高中的表妹一个入门的引导,早日熟悉这个行业。一、二进制、八进制、十进制和十六进制是如何定义的?二进制是B...

二进制如何转换成十进制?_二进制如何转换成十进制例子图解

随着社会的发展,电器维修由继电器时代逐渐被PLC,变频器,触摸屏等工控时代所替代,特别是plc编程,其数据逻辑往往涉及到数制二进制,那么二进制到底是什么呢?它和十进制又有什么区别和联系呢?下面和朋友们...

二进制与十进制的相互转换_二进制和十进制之间转换

很多同学在刚开始接触计算机语言的时候,都会了解计算机的世界里面大多都是二进制来表达现实世界的任何事物的。当然现实世界的事务有很多很多,就拿最简单的数字,我们经常看到的数字大多都是十进制的形式,例如:我...

十进制如何转换为二进制,二进制如何转换为十进制

用十进制除以2,除的断的,商用0表示;除不断的,商用1表示余0时结束假如十进制用X表示,用十进制除以2,即x/2除以2后为整数的(除的断的),商用0表示;除以2除不断的,商用1表示除完后的商0或1...

十进制数如何转换为二进制数_十进制数如何转换为二进制数举例说明

我们经常听到十进制数和二进制数,电脑中也经常使用二进制数来进行计算,但是很多人却不清楚十进制数和二进制数是怎样进行转换的,下面就来看看,十进制数转换为二进制数的方法。正整数转二进制...

二进制转化为十进制,你会做吗?一起来试试吧

今天孩子问把二进制表示的110101改写成十进制数怎么做呀?,“二进制”简单来说就是“满二进一”,只用0和1共两个数字表示,同理我们平常接触到的“十进制”是“满十进一”,只用0-9共十个数字表示。如果...

Mac终于能正常打游戏了!苹果正逐渐淘汰Rosetta转译

Mac玩家苦转译久矣!WWDC2025苹果正式宣判Rosetta死刑,原生游戏时代终于杀到。Metal4光追和AI插帧技术直接掀桌,连Steam都连夜扛着ARM架构投诚了。看到《赛博朋克2077》...

怎么把视频的声音提出来转为音频?音频提取,11款工具实测搞定

想把视频里的声音单独保存为音频文件(MP3/AAC/WAV/FLAC)用于配音、播客、听课或二次剪辑?本文挑出10款常用工具,给出实测可复现的操作步骤、优缺点和场景推荐。1)转换猫mp3转换器(操作门...

6个mp4格式转换器测评:转换速度与质量并存!

MP4视频格式具有兼容性强、视频画质高清、文件体积较小、支持多种编码等特点,适用于网络媒体传播。如果大家想要将非MP4格式的视频转换成MP4的视频格式的话,可以使用MP4格式转换器更换格式。本文分别从...