【实战】基于Netty实现WebSocket聊天室

本文旨在探讨如何利用Netty实现WebSocket聊天室。首先介绍了Netty与Tomcat的区别,重点在于Netty的高并发、传输速度快以及良好的封装性。接着详细讲述了基于Netty构建WebSocket聊天室的步骤,包括项目创建和代码编写。最后提供了项目的相关资源下载链接。
摘要由CSDN通过智能技术生成

1.目的

  • 学习和了解Netty的应用场景和使用方式

2.Netty是什么

  • Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。

2.1.Netty和Tomcat的区别

  • Netty和Tomcat最大的区别就在于通信协议,Tomcat是基于Http协议的。Netty能通过编程自定义各种协议,因为netty能够通过codec自己来编码/解码字节流,完成类似redis访问的功能。

2.2.Netty为什么流行

2.2.1并发高

  • 对比于BIO(Blocking I/O,阻塞IO),Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架。当一个连接建立之后,NIO有两个步骤要做(接收完客户端发过来的全部数据、处理完请求业务之后返回response给客户端),NIO和BIO的区别主要是在第一步。在BIO中,等待客户端发数据这个过程是阻塞的,一个线程只能处理一个请求而最大线程数是有限的,BIO不能支持高并发。
  • 而NIO中,当一个Socket建立好之后,Thread并不会阻塞去接收这个Socket,而是将这个请求交给Selector,Selector会不断的去遍历所有的Socket,一旦有一个Socket建立完成,他会通知Thread,然后Thread处理完数据再返回给客户端——这个过程是不阻塞的。

2.2.2.传输快

  • 依赖NIO的一个特性——零拷贝。Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是Java对象存放的地方,一般我们的数据如果需要从IO读取到堆内存,中间需要经过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达他的的终点。针对这种情况,当Netty需要接收数据的时候,他会在堆内存之外开辟一块内存,数据就直接从IO读到了那块内存中去,在netty里面通过ByteBuf可以直接对这些数据进行直接操作,从而加快了传输速度。

2.2.3.封装好

3.基于Netty实现WebSocket聊天室

3.1创建simple_webchat项目

在这里插入图片描述
在这里插入图片描述

3.2编写代码

  • pom.xml
<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.23</version>
    </dependency>
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.6.Final</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.1.7</version>
    </dependency>
</dependencies>
  • Server
public interface Server {
   
    void start();
    void shutdown();
}
  • BaseServer
public abstract class BaseServer implements Server{
   
    protected Logger logger = LoggerFactory.getLogger(getClass());
    protected String host = "localhost";
    protected int port = 8099;
    /**
     * 1. NioEventLoopGroup是用来处理I/O操作的多线程事件循环器,
     * 在这个例子中我们实现了一个服务端的应用,因此会有2个 NioEventLoopGroup 会被使用。
     * 第一个经常被叫做‘boss’,用来接收进来的连接。第二个经常被叫做‘worker’,
     * 用来处理已经被接收的连接,一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上
     */
    protected DefaultEventLoopGroup defLoopGroup;
    protected NioEventLoopGroup bossGroup;
    protected NioEventLoopGroup workGroup;
    protected NioServerSocketChannel ssch;
    protected ChannelFuture cf;
    /**
     * 2.ServerBootstrap是一个启动 NIO 服务的辅助启动类。
     * 你可以在这个服务中直接使用 Channel
     */
    protected ServerBootstrap b;
    public void init(){
   
        defLoopGroup = new DefaultEventLoopGroup(8, new ThreadFactory() {
   
            private AtomicInteger index = new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
   
                return new Thread(r, "DEFAULTEVENTLOOPGROUP_" + index.incrementAndGet());
            }
        });
        bossGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
   
            private AtomicInteger index = new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
   
                return new Thread(r, "BOSS_" + index.incrementAndGet());
            }
        });
        workGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 10, new ThreadFactory() {
   
            private AtomicInteger index = new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
   
                return new Thread(r, "WORK_" + index.incrementAndGet());
            }
        });
        b = new ServerBootstrap();
    }
    @Override
    public void shutdown() {
   
        if (defLoopGroup != null) {
   
            defLoopGroup.shutdownGracefully();
        }
        bossGroup.shutdownGracefully();
        workGroup.shutdownGracefully();
    }
}
  • UserInfo
public class UserInfo {
   
    private static AtomicInteger uidGener = new AtomicInteger(1000);
    private boolean isAuth = false; // 是否认证
    private long time = 0;  // 登录时间
    private int userId;     // UID
    private String nick;    // 昵称
    private String addr;    // 地址
    private Channel channel;// 通道
	//其他get和set方法直接生成就行
	public void setUserId() {
   
        this.userId = uidGener.incrementAndGet();
    }
  • MessageHandler
/**
 * 1.SimpleChannelInboundHandler实现了ChannelInboundHandler接口,
 *       提供了许多事件处理的接口方法,然后你可以覆盖这些方法
 */
public class MessageHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
   
    private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class);
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame)
            throws Exception {
   
        UserInfo userInfo = UserInfoManager.getUserInfo(ctx.channel());
        if (userInfo != null && userInfo.isAuth()) {
   
            JSONObject json = JSONObject.parseObject(frame.text());
            // 广播返回用户发送的消息文本
            UserInfoManager.broadcastMess(userInfo.getUserId(), userInfo.getNick(), json.getString("mess"));
        }
    }
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
   
        UserInfoManager.removeChannel(ctx.channel());
        UserInfoManager.broadCastInfo(ChatCode.SYS_USER_COUNT,UserInfoManager.getAuthUserCount());
        super.channelUnregistered(ctx);
    }
}
  • UserAuthHandler
/**
 * 1.SimpleChannelInboundHandler实现了ChannelInboundHandler接口,
 *      提供了许多事件处理的接口方法,然后你可以覆盖这些方法
 */
public class UserAuthHandler extends SimpleChannelInboundHandler<Object> {
   
    private static final Logger logger = LoggerFactory.getLogger(UserAuthHandler.class);
    private WebSocketServerHandshaker handshaker;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
   
        if (msg instanceof FullHttpRequest) {
   
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
   
            handleWebSocket(ctx, (WebSocketFrame) msg);
        }
    }
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
   
        if (evt instanceof IdleStateEvent) {
   
            IdleStateEvent evnet = (IdleStateEvent) evt;
            // 判断Channel是否读空闲, 读空闲时移除Channel
            if (evnet.state().equals(IdleState.READER_IDLE)) {
   
                final String remoteAddress = NettyUtil.parseChannelRemoteAddr(ctx.channel());
                logger.warn("NETTY SERVER PIPELINE: IDLE exception [{}]"
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值