Netty案例:WebSocket开发网页版聊天室

目录

1、开发流程

2、具体代码实现

2.1 添加依赖 (pom.xml)

2.2 配置文件(application.yml)

2.3 配置类读取设置

2.4 Netty服务器实现

2.5 WebSocket初始化器和处理器

2.6 Spring Boot启动类

2.7 HTML5客户端 (src/main/resources/static/chat.html)

2.8 启动与测试


1、开发流程

  1. 创建Spring Boot项目

  2. 添加Netty依赖

  3. 配置Netty端口和路径

  4. 实现Netty服务器

  5. 编写WebSocket处理器

  6. 创建HTML5客户端

  7. 启动和测试

2、具体代码实现

2.1 添加依赖 (pom.xml)

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Netty -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.68.Final</version>
    </dependency>
    
    <!-- 配置处理器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2.2 配置文件(application.yml)

netty:
  websocket:
    port: 8081  # Netty服务器端口
    path: /chat # WebSocket路径

2.3 配置类读取设置

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "netty.websocket")
public class NettyConfig {
    private int port;
    private String path;

    // Getters and Setters
    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

2.4 Netty服务器实现

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class NettyServer {
    
    @Autowired
    private NettyConfig nettyConfig;
    
    @Autowired
    private WebSocketInitializer webSocketInitializer;
    
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    
    @PostConstruct
    public void start() throws InterruptedException {
        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup();
        
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(webSocketInitializer)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        
        ChannelFuture future = bootstrap.bind(nettyConfig.getPort()).sync();
        System.out.println("Netty WebSocket服务器启动,端口:" + nettyConfig.getPort());
    }
    
    @PreDestroy
    public void shutdown() {
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
        System.out.println("Netty服务器已关闭");
    }
}

2.5 WebSocket初始化器和处理器

import io.netty.channel.ChannelInitializer;
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;

@Component
public class WebSocketInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    private NettyConfig nettyConfig;

    @Autowired
    WebSocketHandler webSocketHandler;
    
    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        
        // HTTP编解码器
        pipeline.addLast(new HttpServerCodec());
        // 支持大数据流
        pipeline.addLast(new ChunkedWriteHandler());
        // HTTP聚合器
        //post请求分三部分. request line / request header / message body
        // HttpObjectAggregator将多个信息转化成单一的request或者response对象
        pipeline.addLast(new HttpObjectAggregator(65536));
        // WebSocket协议处理器,将http协议升级为ws协议
        pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getPath(), null, true));
        // 自定义消息处理器
        pipeline.addLast(webSocketHandler);
    }
}
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

@Component
@ChannelHandler.Sharable //设置通道共享
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    // 管理所有连接的Channel
    private static final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        clients.add(ctx.channel());
        System.out.println("客户端连接: " + ctx.channel().id().asShortText());
    }
    
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        System.out.println("客户端断开: " + ctx.channel().id().asShortText());
    }
    
    //TextWebSocketFrame: websocket数据是帧的形式处理
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
        // 广播收到的消息
        String text = msg.text();
        System.out.println("收到消息: " + text);
        
        for (Channel channel : clients) {
            if (channel != ctx.channel()) {
                channel.writeAndFlush(new TextWebSocketFrame("用户" + ctx.channel().id().asShortText() + ": " + text));
            } else {
                channel.writeAndFlush(new TextWebSocketFrame("我: " + text));
            }
        }
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

2.6 Spring Boot启动类

2.7 HTML5客户端 (src/main/resources/static/chat.html)

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket聊天室</title>
    <style>
        body { font-family: Arial, sans-serif; }
        #chat-box { height: 400px; border: 1px solid #ccc; padding: 10px; overflow-y: scroll; }
        .message { margin: 5px 0; }
        .self { color: blue; text-align: right; }
        .remote { color: green; }
    </style>
</head>
<body>
    <h1>WebSocket聊天室</h1>
    <div id="chat-box"></div>
    <input type="text" id="message-input" placeholder="输入消息...">
    <button onclick="sendMessage()">发送</button>

    <script>
        // 从配置中获取Netty端口和路径
        const nettyPort = 8081; // 实际项目中应从后端获取
        const wsPath = "/chat";
        
        // 创建WebSocket连接
        const socket = new WebSocket(`ws://${window.location.hostname}:${nettyPort}${wsPath}`);
        
        // 连接建立
        socket.onopen = () => {
            addMessage("系统", "连接服务器成功", "system");
        };
        
        // 接收消息
        socket.onmessage = (event) => {
            addMessage("远程", event.data, "remote");
        };
        
        // 错误处理
        socket.onerror = (error) => {
            console.error("WebSocket错误:", error);
            addMessage("系统", "连接发生错误", "system");
        };
        
        // 连接关闭
        socket.onclose = () => {
            addMessage("系统", "连接已关闭", "system");
        };
        
        // 发送消息
        function sendMessage() {
            const input = document.getElementById("message-input");
            const message = input.value.trim();
            
            if (message) {
                socket.send(message);
                addMessage("我", message, "self");
                input.value = "";
            }
        }
        
        // 添加消息到界面
        function addMessage(sender, content, type) {
            const chatBox = document.getElementById("chat-box");
            const msgElement = document.createElement("div");
            msgElement.className = `message ${type}`;
            msgElement.innerHTML = `<strong>${sender}:</strong> ${content}`;
            chatBox.appendChild(msgElement);
            chatBox.scrollTop = chatBox.scrollHeight;
        }
        
        // 回车发送消息
        document.getElementById("message-input").addEventListener("keypress", (e) => {
            if (e.key === "Enter") sendMessage();
        });
    </script>
</body>
</html>

2.8 启动与测试

启动Spring Boot应用:

mvn spring-boot:run

访问客户端页面:http://localhost:8080/chat.html

打开多个浏览器窗口测试聊天功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熙客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值