基于Netty的WebSocket开发网页版聊天室

WebSocket与HTTP的区别:

HTTP协议是用在应用层的, 基于TCP协议的基础上,是单向的。 HTTP协议也需要建立三次握手才能够才能发送消息, HTTP连接分为短,长连接, 短连接每次请求都需要三次握手才能发送消息。长连接是在一定的期限内保持连接。保持TCP连接不断开。 客服端与服务端进行通信必须由客服端先发起, 客服端想要实时获取服务端信息就得不断发送长连接。

WebSocket实现了多路复用, 他是全双工通信。 服务端和客服端可以同时发送消息, 并且建立连接后服务端可以主动推送消息到客服端。 而且消息可以不用携带head部分信息。与http的长连接通信来说,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。

实现网页版聊天:SpringBoot + netty(HTTP协议升级为WebSocket)

1. NettyServer:

package com.leiyu.groupchat.netty;

import com.leiyu.groupchat.config.NettyConfig;
import com.leiyu.groupchat.handler.WebSocketChannelInitializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;

@Component
public class NettyServer implements Runnable{

    private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    private EventLoopGroup workerGroup = new NioEventLoopGroup();

    @Autowired
    NettyConfig nettyConfig;

    @Autowired
    WebSocketChannelInitializer webSocketChannelInit;

    /*** 资源关闭--在容器销毁是关闭 */
    @PreDestroy
    public void close() {
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }

    @Override
    public void run() {

        try {
            // 创建启动类
            ServerBootstrap boot = new ServerBootstrap();
            boot.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(webSocketChannelInit) ;
            // 启动
            ChannelFuture future = boot.bind(nettyConfig.getPort()).sync();
            System.out.println("--Netty服务端启动成功---");
            future.channel().closeFuture().sync() ;
        } catch (InterruptedException e) {
            e.printStackTrace();
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }


    }
}

2. WebSocketChannelInitializer实现了ChannelInitializer

@Component
public class WebSocketChannelInitializer extends ChannelInitializer {

    @Autowired
    private NettyConfig nettyConfig ;

    @Autowired
    private WebSocketHandler webSocketHandler ;

    @Override
    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();

        //对http协议的支持.
        pipeline.addLast(new HttpServerCodec()) ;
        // 对大数据流的支持
        pipeline.addLast(new ChunkedWriteHandler()) ;
        //post请求分三部分. request line / request header / message body
        //HttpObjectAggregator将多个信息转化成单一的request或者response对象
        pipeline.addLast(new HttpObjectAggregator(8000)) ;
        // 将http协议升级为ws协议. websocket的支持
        pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getPath())) ;
        // 自定义处理handler
        pipeline.addLast(webSocketHandler) ;


    }
}

3. WebSocketHandler实现SimpleChannelInboundHandler<TextWebSocketFrame>

/**
 *  自定义处理类
 *  TextWebSocketFrame: websocket数据是帧的形式处理
 */
@Component
@ChannelHandler.Sharable
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {


    List<Channel> list = new ArrayList<Channel>() ;

    /**
     * 通 道就绪事件
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        list.add(channel) ;
        // 可以直到谁来连接, 通知上线
        System.out.println("上线了...");
    }

    /**
     *  通知下线
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        list.remove(channel) ;
    }

    /**
     *  读就绪事件
     * @param ctx
     * @param o
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame o) throws Exception {
        Channel channel = ctx.channel();
        String msg = o.text();
        System.out.println("msg:" + msg);
        for (Channel ch:list) {
            if(ch != channel) {
                ch.writeAndFlush(new TextWebSocketFrame(msg)) ;
            }
        }
    }

    /**
     *  异常关闭
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        Channel channel = ctx.channel();
        list.remove(channel) ;
    }
}

4. SpringBoot启动类:

@SpringBootApplication
public class GroupChatApplication implements CommandLineRunner {

    @Autowired
    private NettyServer nettyServer ;

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

    @Override
    public void run(String... args) throws Exception {
        new Thread(nettyServer).start();
    }
}

5. 前端重要部分js代码:

 var ws = new WebSocket("ws://localhost:8081/chat") ;
    ws.onopen = function () {
        console.log("连接成功") ;
    }

    ws.onmessage = function (evt) {
        showMessage(evt.data) ;
    }

    ws.onclose = function () {
        console.log("关闭") ;
    }

    ws.onerror = function () {
        console.log("连接异常") ;
    }

这样基于SpringBoot+Netty+WebSocket网页版群聊就实现了, 想要完整代码的, 评论留言!

分析一下实现群聊的重要逻辑:

1. 在WebSocketHandler里面, 我们的通道就绪事件触发的channelActive()方法可以得到每次客服端连接服务端的channel, 得到每个用户的channel我们需要保存在Map集合中, 当一个channel发出信息时, 服务端只需要遍历集合,排除自己, 发送消息即可。下线以及异常关闭, 都需要移除channel.

2. 除了自定义handler, 我们往pipline中添加, 对HTTP协议支持的HttpServerCodec,对大数据流支持的ChunkedWriteHandler, 把post请求封装成一个resquest、response接收的HttpObjectAggregator, 对HTTP协议升级成webSocket的WebSocketServerProtocolHandler

 3. 在启动内实现CommandLineRunner, 让容器启动后单独开一条线程运行Netty服务器

在这里我想问下各位网友能分析下用户与用户之间的私聊怎么实现?

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值