基于javaNIO的封装框架 Netty简单入门

Netty服务器与客户端实现及核心机制解析
本文通过一个完整的Netty服务器和客户端示例,展示了如何配置和启动Netty服务,包括BossGroup和WorkerGroup的使用,以及如何处理连接和数据传输。同时,代码中使用了LengthFieldBasedFrameDecoder解决粘包半包问题,确保数据正确解码。此外,还涉及了ChannelPipeline和ChannelHandler在处理业务逻辑中的应用。

先提出三个问题:

1.默认情况下,netty服务端启多少个线程?
2. netty是如何解决 jdk空轮询的bug?
3.netty是如何保证异步串行化?
答案
1.
优秀的博文地址1:此博主还有另一篇优秀博文【netty搭建socket客户端,断线重连,发送数据,接收服务端数据,spring注入service】
2.
Netty框架学习之(一):Netty框架简介
3.
Netty学习(五)—IdleStateHandler心跳机制
4.
Netty学习(六)—WebSocket通信
5
netty事件
netty的 bund 和 outbund
6.
channel讲解

在这里插入图片描述

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

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

基于上图,第一个代码演示 Netty

服务端代码

package com.jidongcloud;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

/**
 * ClassName:Server
 * Package:com.jidongcloud
 *
 * @DATE:2020/11/14 0014 21:09
 * @Author:robin
 */
public class Server {
    //IP地址
    private static final String ip = "127.0.0.1";
    //核心线程数 (一般为cpu的2倍)
    private static final int BIZGROUPSIZE = Runtime.getRuntime().availableProcessors() * 2;
    //定义一个线程的大小
    private static final int BIZTHREADSIZE = 100;
    //端口
    private int port;


    public Server(int port) {
        this.port = port;
    }

    public static void main(String[] args) {
        System.out.println("启动Server..........");
        new Server(8379).start();
    }

    /**
     * @return void
     * @Author robin
     * @Description 启动服务的方法:
     * 如何去连接,以及连接好了如何去处理 IO事件
     * @Date 21:35 2020/11/14 0014
     * @Param
     **/
    //定义 boss线程:管理各种事件的一个组
    EventLoopGroup bossGroup = new NioEventLoopGroup(BIZGROUPSIZE); //用于处理服务器端接收客户端连接
    //定义worker线程
    EventLoopGroup workerGroup = new NioEventLoopGroup(BIZTHREADSIZE); //进行网络通信(读写)
    public void start() {

        try {
            //辅助工具类,用于服务器通道的一系列配置
            ServerBootstrap bootstrap = new ServerBootstrap();
            //绑定两个线程组
            bootstrap.group(bossGroup, workerGroup)
                    //NioServerSocketChannel
                    .channel(NioServerSocketChannel.class) //指定NIO的模式
                    // .handler("");处理服务端的逻辑,比如我的channel被注册上了。
                    //传入  ChannelInitializer 类的抽象方法 initChannel  java8新特性
                    .childHandler(new ChannelInitializer<Channel>() {
                        @Override
                        protected void initChannel(Channel ch) throws Exception { //childHandler是对连接 的处理 handler 父子关系
                            //比如设置 TCP的属性、发送缓冲区、接收缓冲区。
                            //channel核心的初始化流程
                            //  ChannelPipeline不是单独存在,
                            //  它肯定会和Channel、ChannelHandler、ChannelHandlerContext关联在一起,
                            //  所以有关概念这里一起讲。
                            ChannelPipeline pipeline = ch.pipeline();//业务逻辑是由ChannelHandler类去做的。 pipeline是管理ChannelHandler的一个容器。
                            //这里加 ChannelHandler
                            //解码器来解决粘包与半包问题
                            // maxFrameLength:单个包最大的长度;这个值根据实际场景而定,我设置的是1024,固然我的心跳包不大,但是其他包可能比较大
                            //lengthFieldOffset:表示数据长度字段开始的偏移量,比如上面的协议,lengthFieldOffset应该取值为5,
                            // 因为数据长度之前有2个字节的包头,1个字节的功能
                            //lengthFieldLength:数据长度字段的所占的字节数,上面的协议中写的是2个字节,所以取值为2
                            //lengthAdjustment:这里取值为10=7(系统时间) + 1(校验码)+ 2 (包尾),如果这个值取值为0,试想一下,
                            // 解码器跟数据长度字段的取值(这里数据长度内容肯定是1),只向后取一个字节,肯定不对。(lengthAdjustment + 数据长度取值 = 数据长度字段之后剩下包的字节数)
                            //initialBytesToStrip:表示从整个包第一个字节开始,向后忽略的字节数,我设置为0,
                            // 本来可以忽略掉包头的两个字节(即设置为2),但是,实际项目中,需要校验包头取值是否为AA55,来判断包的合法性。

                            pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            //字符串类型解码器和编码器
                            pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));

                            //加一个TCP方式的处理类
                            pipeline.addLast(new ServerHandler());

                        }
                    });
            /**
             * 对于ChannelOption.SO_BACKLOG的解释:
             * 服务器端TCP内核维护有两个队列,我们称之为A、B队列。客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),服务器端
             * 接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到
             * 客户端发送的ACK时(第三次握手),TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept
             * 从B队列中取出完成了三次握手的连接。
             * A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。
             * 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的
             * 连接数并无影响,backlog影响的只是还没有被accept取出的连接
             */
//                    .option(ChannelOption.SO_BACKLOG, 128) //设置TCP缓冲区
//                    .option(ChannelOption.SO_SNDBUF, 32 * 1024) //设置发送数据缓冲大小
//                    .option(ChannelOption.SO_RCVBUF, 32 * 1024) //设置接受数据缓冲大小
//                    .childOption(ChannelOption.SO_KEEPALIVE, true); //保持连接
            //绑定端口,因为比较耗时,所以同步阻塞。sync ,如果进程不在,会绑定端口失败。
            //第一步:  监听
            ChannelFuture future = bootstrap.bind(ip, port).sync();
            //ChannelFuture关闭的时候需要提示一些信息,所以要注册一个监听器 ,sync,因为关闭也是一个阻塞事件。
            future.channel().closeFuture().sync();
            System.out.println("Server start: ");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //优雅的关闭线程
            shutDown();
        }
    }
    private void shutDown() {
        //优雅的关闭线程
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
        System.out.println("已完成优雅关闭Server。。。");
    }
}
  
package com.jidongcloud;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * ClassName:ServerHandler
 * Package:com.jidongcloud
 *
 * @DATE:2020/11/14 0014 21:11
 * @Author:robin TODO: ChannelHandlerAdapter 这个类不行
 */
public class ServerHandler  extends ChannelInboundHandlerAdapter {
    //传播事件,或者说是激活的状态
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
//        super.channelActive(ctx);
        System.out.println("channel active ...");
    }
//一个消息可读了,channelRead就可以被回调。
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("server receive message : "+msg);
        //do something msg
//        ByteBuf buf = (ByteBuf)msg;
//        byte[] data = new byte[buf.readableBytes()];
//        buf.readBytes(data);
//        String request = new String(data, "utf-8");
//        System.out.println("Server: " + request);
//        //写给客户端
//        String response = "我是反馈的信息";
        Channel channel = ctx.channel();
//        channel.writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
        channel.writeAndFlush ("accept message"+msg);
        //.addListener(ChannelFutureListener.CLOSE);
        //关闭channel上下文资源
        ctx.close();

    }
//捕捉异常的方法
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        String message = cause.getMessage();
        System.out.println("异常信息----->"+message);
//        ctx.close();
    }

}

测试网络连接状态:
在这里插入图片描述
回车后出现下面信息说明成功启动服务端

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

客户端代码 :

package com.jidongcloud;

import io.netty.bootstrap.Bootstrap;
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.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;


/**
 * ClassName:Client
 * Package:com.jidongcloud
 *
 * @DATE:2020/11/14 0014 21:12
 * @Author:robin TODO:
 */
public class Client  implements  Runnable{
    //定义worker线程,进行网络通讯,比如 读写操作
  public  static   EventLoopGroup workerGroup = new NioEventLoopGroup();
    @Override
    public void run() {
       try {

           //  辅助工具类,用于服务器通道的一系列配置
           Bootstrap bootstrap = new Bootstrap();
           bootstrap.group(workerGroup)
                   .channel(NioSocketChannel.class)
                   .option(ChannelOption.TCP_NODELAY,true)
                   .handler(new ChannelInitializer<SocketChannel>() {
                       @Override
                       protected void initChannel(SocketChannel socketChannel) throws Exception {
                           socketChannel.pipeline()
                         .addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
                         .addLast("frameEncoder",new LengthFieldPrepender(4))
                         .addLast("decoder",new StringDecoder(CharsetUtil.UTF_8))
                         .addLast("encoder",new StringEncoder(CharsetUtil.UTF_8))
                         .addLast("handler",new ClientHandler());
                       }
                   });
           for (int i = 0; i <10 ; i++) {
               ChannelFuture future = bootstrap.connect("127.0.0.1", 8379).sync();
               future.channel().writeAndFlush("hello,Server"+Thread.currentThread().getName()+"------>"+i);
               // 闭的时候需要提示一些信息,所以要注册一个监听器 ,sync,因为关闭也是一个阻塞事件。
               future.channel().closeFuture().sync();
           }

       }catch (Exception e){
           System.out.println("客户端异常------>"+e.getMessage());
       }finally {
           System.out.println("优雅关闭client。。。");
           workerGroup.shutdownGracefully();
       }
    }



    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i <3 ; i++) {
          new Thread(new Client(),">>>>>this thread"+i).start();
        }
    }

}
package com.jidongcloud;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * ClassName:ClientHandler
 * Package:com.jidongcloud
 *
 * @DATE:2020/11/14 0014 21:12
 * @Author:robin TODO:
 */
public class ClientHandler extends ChannelInboundHandlerAdapter {


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            System.out.println("client receive msg: "+msg);
//            ByteBuf buf = (ByteBuf) msg;
//            byte[] data = new byte[buf.readableBytes()];
//            buf.readBytes(data);
//            System.out.println("Client:" + new String(data).trim());
        } finally {
//            ReferenceCountUtil.release(msg);
        }
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        String message = cause.getMessage();
        System.out.println("Client 异常信息----->"+message);
//        ctx.close();
    }

}

测试结果:
在这里插入图片描述

channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread1------>0
server receive message : hello,Server>>>>>this thread0------>0
server receive message : hello,Server>>>>>this thread2------>0
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread1------>1
server receive message : hello,Server>>>>>this thread2------>1
server receive message : hello,Server>>>>>this thread0------>1
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>2
server receive message : hello,Server>>>>>this thread0------>2
server receive message : hello,Server>>>>>this thread1------>2
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>3
server receive message : hello,Server>>>>>this thread1------>3
server receive message : hello,Server>>>>>this thread0------>3
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>4
server receive message : hello,Server>>>>>this thread1------>4
channel active ...
server receive message : hello,Server>>>>>this thread0------>4
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread0------>5
channel active ...
server receive message : hello,Server>>>>>this thread1------>5
server receive message : hello,Server>>>>>this thread2------>5
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread0------>6
server receive message : hello,Server>>>>>this thread2------>6
server receive message : hello,Server>>>>>this thread1------>6
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread1------>7
server receive message : hello,Server>>>>>this thread0------>7
server receive message : hello,Server>>>>>this thread2------>7
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>8
server receive message : hello,Server>>>>>this thread0------>8
server receive message : hello,Server>>>>>this thread1------>8
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>9
server receive message : hello,Server>>>>>this thread1------>9
server receive message : hello,Server>>>>>this thread0------>9

在这里插入图片描述
完整的demo完成。

分析以上代码流程:
在这里插入图片描述
需求:做一个类似饿了么的业务推送平台。
消费者------>商家(饿了么)----->骑手
在这里插入图片描述
1. 整体架构
业务推送最终是把消费者下单信息推送到骑手手机上。

在这里插入图片描述
2.系统架构:
在这里插入图片描述
3.逻辑架构:微观的架构,具体用到组件
在这里插入图片描述
4.模块划分:

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

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

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
八.消息推送流程:
在这里插入图片描述
最后一个是客户端 client
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
项目结构:

在这里插入图片描述
在这里插入图片描述
首先是开发core代码:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
参考 git

Netty完整框架

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值