【Netty聊天系统】吃透 SpringBoot + Netty , 还开发不了通讯系统?

整合SpringBoot + Netty 进行一个小白入门的第一个Java项目,可以作为毕设和学习项目的敲门砖。

项目的开头,创建项目以及添加相关的的依赖。接下来就把手交到我手里吧,跟着我一起走进Netty 。

1、创建 Maven 依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.2.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>


<dependencies>
    <!-- Netty依赖 -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.32.Final</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- protubuf -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>3.5.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java-util</artifactId>
        <version>3.5.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.grpc/grpc-all -->
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-all</artifactId>
        <version>1.11.0</version>
    </dependency>
</dependencies>

2、创建Netty server 服务

why:在创建服务端,要明白为什么EventLoopGroup需要两个?

because:因为netty线程模式,可以去网上找找资料,这类的资料百度。

他用到两个线程组worKerGroup、bossGroup

主线程主要用于接收client连接的接收,创建相关资源。

从线程主要用于处理通道的读写、解码、编码。

可以看下ServerBootstrap的group的方法,入参有两个方法,从方法名就可以看出父和子,如果线程数不够,在定义的时候就定好就,线程数不一定要很多,最好不要超过本身CPU线程,可以通过测试找出来,最合适的线程数。

why:Channel通道是拿来干啥?

because:处理相应的逻辑,netty有个流水线,他把所有的事件放到pip中,他就会顺序执行,而Channel通道也有相应的生命周期比如:

	handlerAdded: handler被添加到channel的pipeline
   channelRegistered: channel注册到NioEventLoop
   channelActive: channel准备好了,可以进行
   channelRead: channel有可以读取的数据
   还有一些注销的事件
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);
        if (childGroup == null) {
            throw new NullPointerException("childGroup");
        }
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        }
        this.childGroup = childGroup;
        return this;
    }

nettSerever的实现,配置相应的端口、通道设置、流水线等,进行一个创建


package com.binglian.netty;

import com.binglian.handler.HelloServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;

@Slf4j
@Component
public class NettyServer {

    // 通过nio方式来接收链接何处理链接
    // 这两定义两个线程组
    // bossGroup 是主线程组,只是接收客户的连接
    // worKerGroup是从线程组,用于处理通道的
    private EventLoopGroup bossGroup;
    private EventLoopGroup worKerGroup;


    // 启动类
    private ServerBootstrap bootstrap = new ServerBootstrap();

    public void run() {
        // 链接监听线程组
        bossGroup = new NioEventLoopGroup(1);

        // 传输处理线程组
        worKerGroup = new NioEventLoopGroup();


        try {

            // 1设置reactor 线程
            bootstrap.group(bossGroup, worKerGroup)
                    .channel(NioServerSocketChannel.class) // 2、设置nio类型的channel
                    .localAddress(new InetSocketAddress(8080)) //3、设置监听端口
                    .option(ChannelOption.ALLOCATOR,
                            PooledByteBufAllocator.DEFAULT) // 4、设置通道选项
                    // 5、装配流水线
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        // 有链接到达时会创建一个channel
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new HelloServerHandler());
//
                        }
                    });
            // 6、开始绑定server
            // 通过调用sync同步方法阻塞直到绑定成功

            ChannelFuture channelFuture = bootstrap.bind().sync();
            log.info("服务启动,端口" + channelFuture.channel().localAddress());
            ChannelFuture closeFuture =
                    channelFuture.channel().closeFuture();
            closeFuture.sync();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 8 优雅关闭EventLoopGroup,
            // 释放掉所有资源包括创建的线程
            worKerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();

        }
    }

    public static void main(String[] args) {
        NettyServer nettyServer = new NettyServer();
        nettyServer.run();
    }
}

3、创建Serever Channel事件

他就是读取客户端发送的信息,以及发送信息到客户端,Netty 必须是字节才能接收到信息,这里就把msg转换成了,ByteBuf,进行一个日志输出。

// 传来的消息必须转换成字节缓冲区,才能接收到。netty必须是字节传输
    ByteBuf byteBuf = (ByteBuf) msg;
    log.info("客户端发来的消息:" + byteBuf.toString(CharsetUtil.UTF_8));
    log.info("msg");

package com.binglian.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Component
@ChannelHandler.Sharable
public class HelloServerHandler extends ChannelInboundHandlerAdapter {

    //连接数
    private AtomicInteger receivedConnecitons = new AtomicInteger();




    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        log.info("channelActive");
        ctx.writeAndFlush(Unpooled.copiedBuffer("你好啊,客户端", CharsetUtil.UTF_8));
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        log.info("channelInactive");
    }


    /**
     * 收到消息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        // 传来的消息必须转换成字节缓冲区,才能接收到。netty必须是字节传输
        ByteBuf byteBuf = (ByteBuf) msg;
        log.info("客户端发来的消息:" + byteBuf.toString(CharsetUtil.UTF_8));
        log.info("msg");
    }


}



4、创建Netty client 服务


package com.binglian.client;

import com.binglian.handler.HelloClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Data
@Slf4j
public class NettyClient {

    private String host;

    private int port;



    private Bootstrap bootstrap;
    private EventLoopGroup group;


    public NettyClient() {

        /**
         * 客户端的是Bootstrap,服务端的则是 ServerBootstrap。
         * 都是AbstractBootstrap的子类。
         **/

        /**
         * 通过nio方式来接收连接和处理连接
         */
        group = new NioEventLoopGroup();
    }

    
    /**
     * 连接
     */
    public void doConnect() {

        try {
            bootstrap = new Bootstrap();

            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .remoteAddress("127.0.0.1", 8080)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new HelloClientHandler());
// ch.pipeline().addLast(chatMsgHandler);
// ch.pipeline().addLast(exceptionHandler);
                        }
                    });
            log.info("客户端开始链接");

            // netty都是异步 所以需要addListener
            ChannelFuture future = bootstrap.connect().sync();
            //关闭连接(异步非阻塞)
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            log.info("客户端连接失败!" + e.getMessage());
        }
    }

    public static void main(String[] args) {
        NettyClient nettyClient = new NettyClient();
        nettyClient.doConnect();
    }
}

5、创建Client Channel事件

代码里面很多Channel的事件,可以看下他是怎么样运行的顺序是怎么样,自己动手试一试。重点代码就在这发送。

当 client 准备就绪,就将”你好“转成utf8发送到服务器
ctx.writeAndFlush(Unpooled.copiedBuffer("你好啊!", CharsetUtil.UTF_8));

package com.binglian.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class HelloClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) 
            throws Exception {
        log.info("channelRegistered: channel注册到NioEventLoop");
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) 
            throws Exception {
        log.info("channelUnregistered: channel取消和NioEventLoop的绑定");
        super.channelUnregistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) 
            throws Exception {
        log.info("channelActive: channel准备就绪");
        ctx.writeAndFlush(Unpooled.copiedBuffer("你好啊!", CharsetUtil.UTF_8));
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) 
            throws Exception {
        log.info("channelInactive: channel被关闭");
        super.channelInactive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) 
            throws Exception {
        log.info("channelRead: channel中有可读的数据" );
        // 传来的消息必须转换成字节缓冲区,才能接收到。netty必须是字节传输
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("服务器发来的消息:" + byteBuf.toString(CharsetUtil.UTF_8));

        super.channelRead(ctx, msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) 
            throws Exception {
        log.info("channelReadComplete: channel读数据完成");
        super.channelReadComplete(ctx);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) 
            throws Exception {
        log.info("handlerAdded: handler被添加到channel的pipeline");
        super.handlerAdded(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) 
            throws Exception {
        log.info("handlerRemoved: handler从channel的pipeline中移除");
        super.handlerRemoved(ctx);
    }
}

最后我们运行一下代码,这里没有和Springboot整合,我们就用两个main方法执行一下(main方法就在上面的 server 服务和 client 服务),看能不能接收到信息、发送信息。

server日志:
在这里插入图片描述

可以服务器启动,当client链接的时候,就接受到客户端发送过来的信息,已经一些生命周期。

client日志:
在这里插入图片描述

  我们看到了顺序:handleradded —> channelRegistered —> channelActive —> channelRead —>channelReadComplete

第一步我们就完成了,接下来就是Netty 的登录和session实现,快跟着我一步步实现通讯系统吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值