Springboot+Netty+Websocket实现消息推送实例_springboot + netty +websocket 实现推送消息

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

@Autowired
private ServerChannelInitializer serverChannelInitializer;

@Bean
ApplicationRunner nettyRunner() {
    return args -> {
        //new 一个主线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //new 一个工作线程组
        EventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap()
                .group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(serverChannelInitializer)
                //设置队列大小
                .option(ChannelOption.SO_BACKLOG, 1024)
                // 两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        //绑定端口,开始接收进来的连接
        try {
            ChannelFuture future = bootstrap.bind(port).sync();
            log.info("服务器启动开始监听端口: {}", port);
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //关闭主线程组
            bossGroup.shutdownGracefully();
            //关闭工作线程组
            workGroup.shutdownGracefully();
        }
    };
}

}


### 3.netty服务端处理器


 

package com.test.netty;

import com.test.common.util.JsonUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.net.URLDecoder;
import java.util.*;

/**
* @author test
*


* netty服务端处理器
**/
@Slf4j
@Component
@ChannelHandler.Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler {

@Autowired
private ServerChannelCache cache;
private static final String dataKey = "test=";

@Data
public static class ChannelCache {
}


/\*\*

* 客户端连接会触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
log.info(“通道连接已打开,ID->{}…”, channel.id().asLongText());
}

@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
        Channel channel = ctx.channel();
        WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
        String requestUri = handshakeComplete.requestUri();
        requestUri = URLDecoder.decode(requestUri, "UTF-8");
        log.info("HANDSHAKE\_COMPLETE,ID->{},URI->{}", channel.id().asLongText(), requestUri);
        String socketKey = requestUri.substring(requestUri.lastIndexOf(dataKey) + dataKey.length());
        if (socketKey.length() > 0) {
            cache.add(socketKey, channel);
            this.send(channel, Cmd.DOWN_START, null);
        } else {
            channel.disconnect();
            ctx.close();
        }
    }
    super.userEventTriggered(ctx, evt);
}

@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    Channel channel = ctx.channel();
    log.info("通道连接已断开,ID->{},用户ID->{}......", channel.id().asLongText(), cache.getCacheId(channel));
    cache.remove(channel);
}

/\*\*

* 发生异常触发
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
Channel channel = ctx.channel();
log.error(“连接出现异常,ID->{},用户ID->{},异常->{}…”, channel.id().asLongText(), cache.getCacheId(channel), cause.getMessage(), cause);
cache.remove(channel);
ctx.close();
}

/\*\*

* 客户端发消息会触发
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
try {
// log.info(“接收到客户端发送的消息:{}”, msg.text());
ctx.channel().writeAndFlush(new TextWebSocketFrame(JsonUtil.toString(Collections.singletonMap(“cmd”, “100”))));
} catch (Exception e) {
log.error(“消息处理异常:{}”, e.getMessage(), e);
}
}

public void send(Cmd cmd, String id, Object obj) {
    HashMap<String, Channel> channels = cache.get(id);
    if (channels == null) {
        return;
    }
    Map<String, Object> data = new LinkedHashMap<>();
    data.put("cmd", cmd.getCmd());
    data.put("data", obj);
    String msg = JsonUtil.toString(data);
    log.info("服务器下发消息: {}", msg);
    channels.values().forEach(channel -> {
        channel.writeAndFlush(new TextWebSocketFrame(msg));
    });
}

public void send(Channel channel, Cmd cmd, Object obj) {
    Map<String, Object> data = new LinkedHashMap<>();
    data.put("cmd", cmd.getCmd());
    data.put("data", obj);
    String msg = JsonUtil.toString(data);
    log.info("服务器下发消息: {}", msg);
    channel.writeAndFlush(new TextWebSocketFrame(msg));
}

}


### 4.netty服务端缓存类


 

package com.test.netty;

import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class ServerChannelCache {
private static final ConcurrentHashMap<String, HashMap<String, Channel>> CACHE_MAP = new ConcurrentHashMap<>();
private static final AttributeKey CHANNEL_ATTR_KEY = AttributeKey.valueOf(“test”);

public String getCacheId(Channel channel) {
    return channel.attr(CHANNEL_ATTR_KEY).get();
}

public void add(String cacheId, Channel channel) {
    channel.attr(CHANNEL_ATTR_KEY).set(cacheId);
    HashMap<String, Channel> hashMap = CACHE_MAP.get(cacheId);
    if (hashMap == null) {
        hashMap = new HashMap<>();
    }
    hashMap.put(channel.id().asShortText(), channel);
    CACHE_MAP.put(cacheId, hashMap);
}

public HashMap<String, Channel> get(String cacheId) {
    if (cacheId == null) {
        return null;
    }
    return CACHE_MAP.get(cacheId);
}

public void remove(Channel channel) {
    String cacheId = getCacheId(channel);
    if (cacheId == null) {
        return;
    }
    HashMap<String, Channel> hashMap = CACHE_MAP.get(cacheId);
    if (hashMap == null) {
        hashMap = new HashMap<>();
    }
    hashMap.remove(channel.id().asShortText());
    CACHE_MAP.put(cacheId, hashMap);
}

}


### 5.netty服务初始化器


 

package com.test.netty;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* @author test
*


* netty服务初始化器
**/
@Component
public class ServerChannelInitializer extends ChannelInitializer {

@Autowired
private NettyServerHandler nettyServerHandler;

@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
    ChannelPipeline pipeline = socketChannel.pipeline();
    pipeline.addLast(new HttpServerCodec());
    pipeline.addLast(new ChunkedWriteHandler());
    pipeline.addLast(new HttpObjectAggregator(8192));
    pipeline.addLast(new WebSocketServerProtocolHandler("/test.io", true, 5000));
    pipeline.addLast(nettyServerHandler);
}

}


### 6.html测试


 

test
  <script type="text/javascript">
     function WebSocketTest()
     {
        if ("WebSocket" in window)
        {
           alert("您的浏览器支持 WebSocket!");
           
           // 打开一个 web socket
           var ws = new WebSocket("ws://localhost:port/test.io");
            
           ws.onopen = function()
           {
              // Web Socket 已连接上,使用 send() 方法发送数据
              ws.send("发送数据");
              alert("数据发送中...");
           };
            
           ws.onmessage = function (evt) 
           { 
              var received_msg = evt.data;
              alert("数据已接收...");
           };
            
           ws.onclose = function()
           { 
              // 关闭 websocket
              alert("连接已关闭..."); 
           };
        }
        
        else
        {
           // 浏览器不支持 WebSocket
           alert("您的浏览器不支持 WebSocket!");
        }
     }
  </script>
  <div id="sse">
     <a href="javascript:WebSocketTest()">运行 WebSocket</a>
  </div>

### 7.vue测试



![img](https://img-blog.csdnimg.cn/img_convert/bf17d9f0e1c9dc25b0d7a28f2536a230.png)
![img](https://img-blog.csdnimg.cn/img_convert/35b5fb70e807ddc0505fa3cfaee19a5c.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

      
   </body>
</html>

7.vue测试

[外链图片转存中…(img-TMHrjIFk-1715842713578)]
[外链图片转存中…(img-uo7q6Um2-1715842713579)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现局域网音视频通话可以用Spring Boot作为后端框架,Netty作为网络通信框架,WebSocket作为实现双向通信的协议。以下是一个简单的实现过程: 1. 首先需要搭建一个Spring Boot项目,可以使用Spring Initializr来快速生成项目。在pom.xml中添加NettyWebSocket的依赖,例如: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.25.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建一个WebSocket处理器类,用来处理WebSocket的连接、关闭和消息收发等逻辑。例如: ```java @Component @ServerEndpoint("/video-chat") public class VideoChatHandler { private static final Logger LOGGER = LoggerFactory.getLogger(VideoChatHandler.class); @OnOpen public void onOpen(Session session) { LOGGER.info("WebSocket opened: {}", session.getId()); } @OnMessage public void onMessage(String message, Session session) { LOGGER.info("Received message: {}", message); // TODO: 处理收到的消息 } @OnClose public void onClose(Session session) { LOGGER.info("WebSocket closed: {}", session.getId()); } @OnError public void onError(Throwable error) { LOGGER.error("WebSocket error", error); } } ``` 3. 在Spring Boot的配置类中添加WebSocket的配置,例如: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Autowired private VideoChatHandler videoChatHandler; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(videoChatHandler, "/video-chat").setAllowedOrigins("*"); } } ``` 4. 使用Netty实现音视频的传输。可以使用Netty提供的UDP协议来实现多人音视频通话,也可以使用TCP协议来实现点对点的音视频通话。需要根据实际情况选择相应的协议,这里以TCP协议为例: ```java @Component public class VideoChatServer { private static final Logger LOGGER = LoggerFactory.getLogger(VideoChatServer.class); @Value("${server.video-chat.port}") private int port; @PostConstruct public void start() { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // TODO: 添加音视频相关的编解码器和处理器 } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture future = bootstrap.bind(port).sync(); LOGGER.info("Video chat server started on port {}", port); future.channel().closeFuture().sync(); } catch (InterruptedException e) { LOGGER.error("Video chat server interrupted", e); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } } ``` 5. 在WebSocket处理器中实现音视频数据的收发逻辑。当收到音视频数据时,可以将数据转发给所有连接的WebSocket客户端。例如: ```java @Component @ServerEndpoint("/video-chat") public class VideoChatHandler { private static final Logger LOGGER = LoggerFactory.getLogger(VideoChatHandler.class); private List<Session> sessions = new CopyOnWriteArrayList<>(); @OnOpen public void onOpen(Session session) { LOGGER.info("WebSocket opened: {}", session.getId()); sessions.add(session); } @OnMessage public void onMessage(ByteBuffer buffer, Session session) throws IOException { LOGGER.info("Received video data from {}", session.getId()); byte[] data = new byte[buffer.remaining()]; buffer.get(data); for (Session s : sessions) { if (s.isOpen() && !s.getId().equals(session.getId())) { s.getBasicRemote().sendBinary(ByteBuffer.wrap(data)); } } } @OnClose public void onClose(Session session) { LOGGER.info("WebSocket closed: {}", session.getId()); sessions.remove(session); } @OnError public void onError(Throwable error) { LOGGER.error("WebSocket error", error); } } ``` 6. 在前端页面中使用WebSocket实现音视频通话。可以使用WebRTC等技术来实现音视频采集、编解码、传输等功能。这里不再赘述。 以上就是一个简单的局域网音视频通话的实现过程。需要注意的是,音视频通话涉及到的技术较多,需要根据实际情况进行选择和配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值