Netty核心及源码分析

Netty介绍

  1)Netty是由JBOSS提供的一个Java开源框架,现为Github上独立的项目

  2)Netty是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠的网络IO程序

  3)Netty主要针对在TCP协议下,面向Clients端的高并发应用,或者Peer-to-Peer场景下的大量数据持续传输应用

  4)Netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景

  5)要透彻理解Netty,需要先学习NIO,这样我们才能Netty源码

BIO

   I/O模型基本说明

     1) I/O模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能

     2)Java共支持3种网络编程模式:BIO、NIO、AIO

     3)Java BIO:同步并阻塞(传统阻塞型)服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销

     4)Java NIO:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理

     5)Java AIO:异步非阻塞,AIO引入异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序去处理,一般用于连接数较多且连接时间较长的应用

BIO、NIO、AIO适用的场景分析(面试题)

 1)BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前唯一的选择,但程序简单易理解

 2)NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等,编程比较复杂,JDK1.4开始支持

 3)AIO方式使用于连接数目多且链接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持

Java BIO(blocking io)基本介绍

 1)Java BIO 就是传统的java io 编程,其相关的类和接口在java.io

 2) BIO 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户端连接服务器)

 3)BIO方式用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,程序简单易理解

java BIO 应用实例

实例说明

  1)使用BIO模型编写一个服务器端,监听6666端口,当有客户端连接时,就启动一个线程与之通讯

  2)要求使用线程池机制改善,可以连接多个客户端

  3)服务器可以接收客户端发送的数据(telnet方式即可)

  4)代码演示

Java BIO 问题分析

  1)每个请求都需要创建独立的线程,与对应的客户端进行数据Read,业务处理,数据Write

  2)当并发较大时,需要创建大量线程来处理连接,系统资源占用较大

  3)连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在Read操作上,造成线程资源浪费

NIO

Java NIO基本介绍

   1)Java NIO 全称 java non-blocking IO, 是指JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO(即New IO),是同步非阻塞的

   2)NIO相关类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写。

   3)NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)

   4)NIO是面向缓冲区,或者面向块编程的,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就是增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络

NIO和BIO的比较

 1)BIO以流的方式处理数据,而NIO以块的方式处理数据,块I/O的效率比流I/O高很多

 2)BIO是阻塞的,NIO则是非阻塞的

 3)BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入通道中。Selector(选择器)用于监听多个通道,因此使用单个线程就可以监听多个客户端通道

Selector、Channel和Buffer的关系

  1)每个channel都会对应一个Buffer

  2)Selector 对应一个线程,一个线程对应多个channel(连接)

  3)程序切换到哪个channel是由事件决定的,Event就是一个重要的概念

  4)Selector会根据不同的事件,在各个通道上切换

  5)Buffer就是一个内存块,底层是有一个数组

  6)数据的读取写入是通过Buffer, BIO中要么是输入流,或者是输出流,不能双向,但是NIO的Buffer是可以读也可以写,需要flip方法切换

  7)channel是双向的,可以返回底层操作系统的情况,比如Linux,底层的操作系统通道就是双向的。

channel(通道)

  NIO通道类似于流,但是有些区别如下:

  通道可以同时进行读写,而流只能读或者只能写

  通道可以实现异步读写数据

  通道可以从缓冲读数据,也可以写数据到缓冲

 channel基本介绍

  1)BIO中的stream是单向的,例如FileInputStream对象只能进行读取数据的操作,而NIO中的通道(Channel)是双向的,可以读操作,也可以写操作

  2)Channel在NIO中是一个接口

       public interface Channel extends Closeable{}

  3) 常用的Channel类有:FileChannel、DatagramChannel、ServerSocketChannel和SocketChannel

  4)FileChannel用于文件的数据读写,DatagramChannel用于UDP的数据读写,ServerSocketChannel和SocketChannel用于TCP的数据读写

FileChannel类

public int read(ByteBuffer dst)  //从通道读取数据并放到缓冲区

public int write(ByteBuffer src)  //把缓冲区的数据写到通道中

public long transferFrom(ReadableByteChannel src, long position, long count)  //从目标通道复制数据到当前通道

public long transferTo(long position, long count, WritableByteChannel target)  //把数据从当前通道复制给目标通道

应用实例本地文件写数据

  1)使用前面学习后的ByteBuffer(缓冲)和FileChannel(通道),将“hello,尚硅谷”写入到“file01.txt中”

  2)文件不存在就创建

  3)代码

package com.hzz.netty.nio;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Author: curry
 * @Date: Created in 15:38 2023/9/6
 * @Modified By:
 * @description:
 */
public class NIOFileChannel01 {

    public static void main(String[] args) throws Exception {
        String str = "hello,尚硅谷";

        //创建一个输出流->channel
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");

        //通过fileOutputStream 获取对应的 FileChannel
        //这个fileChannel真实类型是 FileChannelImpl
        FileChannel fileChannel = fileOutputStream.getChannel();

        //创建一个缓冲区 ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //将str放入byteBuffer
        byteBuffer.put(str.getBytes());

        //对byteBuffer 进行flip
        byteBuffer.flip();

        //将byteBuffer数据写入到fileChannel
        fileChannel.write(byteBuffer);
        fileOutputStream.close();;

    }

}

  本地文件内容读取的控制台

public class NIOFileChannel02 {

    public static void main(String[] args) throws Exception {

        //创建文件的输入流
        File file = new File("d:\\file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);

        //通过fileInputStream获取对应的FileChannel->实际类型 FileChannelImpl
        FileChannel fileChannel =fileInputStream.getChannel();

        //创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());

        //将通道的数据读入到Buffer
        fileChannel.read(byteBuffer);

        //将byteBuffer 的字节数据转成String
        System.out.println(new String(byteBuffer.array()));


    }

}

应用实例3-使用一个Buffer完成文件读取

  实例要求:

    1)使用FileChannel(通道)和方法 read write  完成文件的拷贝

    2)拷贝一个文本文件1.txt,放在项目下即可

    3)代码

package com.hzz.netty.nio;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Author: curry
 * @Date: Created in 16:34 2023/9/6
 * @Modified By:
 * @description:
 */
public class NIOFileChannel03 {

    public static void main(String[] args) throws Exception {
        File file = new File("d:\\file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        FileChannel fileChannel01 = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel fileChannel02 = fileOutputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(512);


        while(true){

            //这里有一个重要的操作,一定不要忘了

            /*position = 0;
            limit = capacity;
            mark = -1;
            return this;*/

            byteBuffer.clear();

            int read = fileChannel01.read(byteBuffer);
            if (read ==-1){  //标识读完
                break;
            }

            //将buffer中的数据写入到fileChannel02 --- 2.txt
            byteBuffer.flip();
            fileChannel02.write(byteBuffer);
        }
    }

}

Selector(选择器)

  Selector是一个抽象类,常用方法和说明如下

public abstract class Selector implements Clossable{
    
    public static Selector open();  //得到一个选择器对象

    public int select(long timeout);  //监控所有注册的通道,当其中有IO操作可以进行时将对应的的SelectionKey加入到内部集合并返回,参数用来设置超时时间
    
    public Set<SelectionKey> selectKeys();//从内部集合中得到所有的SelectionKey
}

selector.select()  //阻塞

selector.select(1000) //阻塞1000毫秒,在1000毫秒后返回

selector.wakeup()  //唤醒selector

selector.selectorNow()  //不阻塞,立马返回

NIO非阻塞网络相关的(Selector、SelectorKey、ServerScoketChannel和SocketChannel)

  1、当客户端连接时,会通过ServerSocketChannel得到SocketChannel

  2、将socketChannel注册到Selector上,registor(Selector sel, int ops) 一个selector上可以注册多个SocketChannel

  3、注册后返回一个SelectorKey 会和该Selector关联(集合)

  4、Selector进行监听select 方法返回有事件发生的通道的个数

  5、进一步得到各个SelectorKey(有事件发生)

  6、再通过SeletorKey反向获取SocketChannel,方法channel()

  7、可以通过得到channel完成业务处理

ServerSocketChannel

 1)ServerSocketChannel在服务器端监听新的客户端Socket连接

 2)相关方法如下     

  

public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel{
    public static ServerSocketChannel open();  //得到一个ServerSocketChannel通道
    
    public final ServerSocketChannel bind(SocketAddress local);  //设置服务器端端口号
    
    public final SelectableChannel configureBlocking(boolean block);  //设置阻塞或非阻塞模式,取值false表示采用非阻塞模式
    
    public SocketChannel accept();  //接受一个连接,返回代表这个连接的通道对象
    
    public final SelectionKey register(Selector sel, int ops); //注册一个选择器并设置监听事件
}

群聊系统

服务器端代码

package com.hzz.netty.nio.groupchat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * @Author: curry
 * @Date: Created in 14:54 2023/9/27
 * @Modified By:
 * @description:
 */

public class GroupChatServer {


    //定义属性
    private Selector selector;
    private ServerSocketChannel listenChannel;
    private static final int PORT = 6667;

    //构造器
    //构造器初始化工作
    public GroupChatServer() {

        try {
            //得到选择器
            selector=Selector.open();
            //初始化ServerSocketChannel
            listenChannel = ServerSocketChannel.open();
            //绑定端口
            listenChannel.socket().bind(new InetSocketAddress(PORT));
            //设置非阻塞模式
            listenChannel.configureBlocking(false);
            //将该listenChannel注册到selector
            listenChannel.register(selector, SelectionKey.OP_ACCEPT);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //监听
    public void listen(){
        try {

            //循环处理
            while (true){
                int count = selector.select();
                if (count>0){ //有事件处理

                    //遍历得到selectionKey集合
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()){
                        //取出selectorKey
                        SelectionKey key = iterator.next();

                        //监听到accept
                        if (key.isAcceptable()){
                            SocketChannel sc = listenChannel.accept();
                            sc.configureBlocking(false);
                            //将该sc注册到selector
                            sc.register(selector,SelectionKey.OP_READ);
                            //提示
                            System.out.println(sc.getRemoteAddress()+"上线");
                        }
                        if(key.isReadable()){  //通道发送read事件,即通道是可读的状态
                            //处理读(专门写方法...)
                            read(key);
                        }
                        //当前的key删除,防止重复处理
                        iterator.remove();
                    }

                }else {
                    System.out.println("等待....");
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {

        }
    }

    /**
     * 读取客户端消息
     * @param key
     */
    private void read(SelectionKey key){

        //定义一个SocketChannel
        SocketChannel channel = null;

        try {
            //得到channel
            channel =(SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int count = channel.read(buffer);
            //根据count的值做处理
            if (count>0){
                //把缓存去的数据转成字符串
                String msg = new String(buffer.array());
                System.out.println("form 客户端:"+msg);

                //向其他客户端转发消息(去掉自己),专门写一个方法来处理
                sendInfoToOtherClients(msg,channel);

            }
        }catch (IOException e){
            try {
                System.out.println(channel.getRemoteAddress()+"离线了...");
                //取消注册
                key.cancel();
                //关闭通道
                channel.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }


    }

    /**
     * 转发消息给其他客户端(通道)
     * @param msg
     * @param self
     */
    private void sendInfoToOtherClients(String msg,SocketChannel self) throws IOException {
        System.out.println("服务器转发消息中...");
        //遍历 所有注册到selector 上的SocketChannel并排除self
        for (SelectionKey key : selector.keys()) {

            //通过key取出对应的SocketChannel
            Channel targetChannel = key.channel();

            //排除自己
            if(targetChannel instanceof  SocketChannel && targetChannel !=self){

                //转型
                SocketChannel dest = (SocketChannel)targetChannel;
                //将msg存储到buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                //将buffer数据写入通道
                dest.write(buffer);

            }

        }
    }


    public static void main(String[] args) {

        //创建一个服务器对象
        GroupChatServer groupChatServer = new GroupChatServer();
        groupChatServer.listen();

    }

}

客户端代码

package com.hzz.netty.nio.groupchat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;

/**
 * @Author: curry
 * @Date: Created in 15:37 2023/9/27
 * @Modified By:
 * @description:
 */
public class GroupChatClient {

    //定义相关属性
    private final String HOST="127.0.0.1";  //服务器的ip
    private final int PORT = 6667;  //服务器端口
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    //构造器,完成初始化工作
    public GroupChatClient() throws IOException {

        selector = Selector.open();
        //连接服务器
        socketChannel = socketChannel.open(new InetSocketAddress(HOST, PORT));
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //将channel注册到selector
        socketChannel.register(selector, SelectionKey.OP_READ);
        //得到username
        username = socketChannel.getLocalAddress().toString().substring(1);

        System.out.println(username+ " is ok...");
    }

    //向服务器发送消息
    public void sendInfo(String info){
        info = username + "说" +info;

        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 读取服务器端回复的消息
     */
    public void readInfo(){

        try {
            int readChannels = selector.select();
            if (readChannels>0){  //有可以用的通道
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    if (key.isReadable()){
                        //得到相关的通道
                        SocketChannel sc = (SocketChannel) key.channel();
                        //得到一个Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        sc.read(buffer);
                        //把读到的缓冲区的数据转成字符串
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim()+"haha");
                    }
                }
                iterator.remove();  //删除当前的selectionKey,防止重复操作
            }else{
                System.out.println("没有可以用的通道...");
            }
        }catch (Exception e){

        }

    }

    public static void main(String[] args) throws IOException {

        //启动我们的客户端
        GroupChatClient chatClient = new GroupChatClient();

        //启动一个线程,每隔3秒,读取从服务器发送的数据
        new Thread(){
            public void run(){
               while (true){
                   chatClient.readInfo();
                   try{
                       Thread.currentThread().sleep(3000);
                   }catch (InterruptedException e){
                       e.printStackTrace();
                   }
               }
            }
        }.start();

        //发送数据给服务器
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
            String s = scanner.nextLine();
            chatClient.sendInfo(s);
        }

    }


}

NIO与零拷贝

  1)mmap适合小数据量读写,sendFile适合大文件传输

  2)mmap需要4次上下文切换,3次数据拷贝,sendFile需要3次上下文切换,最少2次数据拷贝

  3)sendFile可以利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)

netty底层原理

Reactor模式

  单Reactor单线程

    优点:模型简单,没有多线程,进程通信、竞争的问题,全部都在一个线程中完成

    缺点:性能问题,只有一个线程,无法完全发挥多核CPU的性能。Handler在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈

  单Reactor多线程

  主从Reactor多线程 

netty线程模式

  Netty主要基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor

netty模型

NettyServer

package com.hzz.netty.netty.simple;

import io.netty.bootstrap.ServerBootstrap;
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;

/**
 * @Author: curry
 * @Date: Created in 15:43 2023/10/11
 * @Modified By:
 * @description:
 */
public class NettyServer {

    public static void main(String[] args) throws InterruptedException {

        //创建BossGroup和WorkerGroup

        //1.创建两个线程组bossGroup和workerGroup
        //2.bossGroup只是处理连接请求,真正的和客户端处理业务,会交给workerGroup完成
        //3.两个都是无限循环
        //4.bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数
        //默认实际cpu核数 * 2
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);

        try {


            //创建服务器端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();

            //使用链式编程来进行配置
            bootstrap.group(bossGroup,workerGroup)  //设置两个线程组
                    .channel(NioServerSocketChannel.class)  //使用NioServerSocketChannel作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG,128)  //设置线程池队列得到连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE,true)  //设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {  //创建一个通道初始化对象(匿名对象)
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {

                            socketChannel.pipeline().addLast(new NettyServerHandler());

                        }
                    });  //给我们的workerGroup的EventLoop对应的管道设置处理器

            System.out.println("...服务器 is ready...");

            //绑定一个端口并且同步处理,生成一个ChannelFuture对象
            //启动服务器(并绑定端口)
            ChannelFuture cf = bootstrap.bind(6668).sync();

            //对关闭通道进行监听
            cf.channel().closeFuture().sync();

        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

}

NettyServerHandler

package com.hzz.netty.netty.simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;

import java.nio.charset.Charset;

/**
 * @Author: curry
 * @Date: Created in 16:22 2023/10/11
 * @Modified By:
 * @description: 1. 自定义一个handler需要继承netty规定好的某个HandlerAdapter
 *
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {


    /**
     * 读取数据事件(这里我们可以读取客户端发送的消息)
     * @param ctx  上下文对象,含有管道pipeline, 通道channel, 地址
     * @param msg  就是客户端发送的数据, 默认是Object
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务器读取线程"+Thread.currentThread().getName());
        System.out.println("server ctx =" + ctx);
        System.out.println("看看channel和pipeline的关系");
        Channel channel = ctx.channel();
        ChannelPipeline pipeline = ctx.pipeline();  //本质是一个双向链表,出站入站
        //将msg转成一个ByteBuf
        //ByteBuf 是 Netty 提供的,不是NIO的ByteBuffer
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发送的消息是:"+buf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:"+ctx.channel().remoteAddress());

    }

    /**
     *  数据读取完毕
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //writeAndFlush 是write+flush
        //将数据写入到缓存,并刷新
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端",CharsetUtil.UTF_8));
    }

    /**
     * 处理异常  一般需要关闭通道
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
    }
}

NettyClient

package com.hzz.netty.netty.simple;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * @Author: curry
 * @Date: Created in 16:49 2023/10/11
 * @Modified By:
 * @description:
 */
public class NettyClient {

    public static void main(String[] args) throws InterruptedException {

        //客戶端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();

        try {

            //创建客户端启动对象
            //注意客户端使用的不是ServerBootStrap而是Bootstrap
            Bootstrap bootstrap = new Bootstrap();

            //设置相关参数
            bootstrap.group(group)  //设置线程组
            .channel(NioSocketChannel.class)  //设置客户端通道的实现类(反射)
            .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    socketChannel.pipeline().addLast(new NettyClientHandler());
                }
            });

            System.out.println("客户端 ok...");

            //启动客户端去连接服务器端
            //关于ChannelFuture要分析,涉及到netty的异步模型
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();

            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } finally {

            group.shutdownGracefully();

        }


    }

}

NettyClientHandler

package com.hzz.netty.netty.simple;

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;

/**
 * @Author: curry
 * @Date: Created in 15:15 2023/10/12
 * @Modified By:
 * @description:
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 当通道就绪就会触发该方法
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client"+ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server:喵", CharsetUtil.UTF_8));
    }

    /**
     * 当通道有读取事件就会触发
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:"+byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器地址是:"+ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

Netty群聊

GroupChatServer

package com.hzz.netty.netty.groupchat;


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

/**
 * @Author: curry
 * @Date: Created in 13:09 2023/10/20
 * @Modified By:
 * @description:
 */
public class GroupChatServer {

    private int port;  //监听端口

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

    public void  run() throws InterruptedException {

        //创建两个线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup();   //8个NioEventLoop

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();

            bootstrap.group(bossGroup,workGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,128)
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                            //获取到pipeline
                            ChannelPipeline pipeline = ch.pipeline();
                            //向pipeline中加入解码器
                            pipeline.addLast("decoder",new StringDecoder());
                            //向pipeline中加入编码器
                            pipeline.addLast("encoder",new StringEncoder());

                            //加入自己的业务处理handler
                            pipeline.addLast(new GroupChatServerHandler());

                        }
                    });

            System.out.println("netty 服务器启动");
            ChannelFuture channelFuture = bootstrap.bind(port).sync();

            //监听关闭时间
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }


    public static void main(String[] args) throws InterruptedException {
       new GroupChatServer(7000).run();
    }

}

GroupChantServerHandler

package com.hzz.netty.netty.groupchat;

import com.hzz.signmode.interpreter.VarExpression;
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.util.concurrent.GlobalEventExecutor;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Author: curry
 * @Date: Created in 13:27 2023/10/20
 * @Modified By:
 * @description:
 */
public class GroupChatServerHandler extends SimpleChannelInboundHandler {

    //定义一个channel组,管理所有的channel
    //GlobalEventExecutor.INSTANCE 是全局的事件执行器,是一个单例
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 一旦连接建立,第一个被执行
     * 将当前channel加入到channelGroup
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //将该客户加入聊天的信息推送给在线的客户端

        /**
         * 该方法会将channelGroup中的所有channel遍历,并发送 我们不需要自己遍历
         */
        channelGroup.writeAndFlush("[客户端]"+channel.remoteAddress()+"加入聊天"+sdf.format(new Date())+"\n");

        channelGroup.add(channel);
    }

    /**
     * 断开连接,将xxx客户离开的信息推送给当前在线的客户
     *
     * @param ctx
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush("[客户端]"+channel.remoteAddress()+"离开了\n");
        System.out.println("channelGroup size"+channelGroup.size());
    }

    /**
     * 表示channel处于活动状态,提示xxx上线
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println(channel.remoteAddress()+"上线了~");
    }


    /**
     * 表示channel处于非活动状态,提示xxx下线了
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress()+"离线了~");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {

        //获取到当前的channel
        Channel channel = ctx.channel();
        //这时我们遍历channelGroup,根据不同的情况,回送不同的消息
        channelGroup.forEach(ch->{
            if(!channel.equals(ch)){  //不是当前的channel,转发消息
                ch.writeAndFlush("[客户]"+channel.remoteAddress()+"发送消息"+msg+sdf.format(new java.util.Date())+"\n");
            }else{
                ch.writeAndFlush("[自己发送了消息]"+msg+"\n");
            }
        });
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //关闭通道
        ctx.close();
    }
}

GroupChatClient

package com.hzz.netty.netty.groupchat;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

/**
 * @Author: curry
 * @Date: Created in 14:18 2023/10/20
 * @Modified By:
 * @description:
 */
public class GroupChatClient {

    //属性
    private final String host;

    private final int port;

    public GroupChatClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void run() throws Exception{
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                            //得到pipeline
                            ChannelPipeline pipeline = ch.pipeline();

                            //加入相关的handler
                            pipeline.addLast("decoder",new StringDecoder());
                            pipeline.addLast("encoder",new StringEncoder());
                            //加入自定义的
                            pipeline.addLast(new GroupChatClientHandler());
                        }
                    });

            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            //得到channel
            Channel channel = channelFuture.channel();
            System.out.println("--------"+channel.localAddress()+"--------");
            //客户端需要输入信息,创建一个扫描器
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()){
                String msg = scanner.nextLine();
                channel.writeAndFlush(msg+"\r\n");
            }
        } finally {
            group.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws Exception {
        new GroupChatClient("127.0.0.1",7000).run();
    }

}

GroupChatClientHandler

package com.hzz.netty.netty.groupchat;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @Author: curry
 * @Date: Created in 14:59 2023/10/20
 * @Modified By:
 * @description:
 */
public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg.trim()+"SB");
    }
}

Netty通过WebSocket编程实现服务器和客户端长连接

hello.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    var socket;
    //判断当前浏览器是否支持websocket
    if (window.WebSocket){
        //go on
        socket = new WebSocket("ws://localhost:7000/hello")

        //相当于channelRead0, ev收到服务器端回送的消息
        socket.onmessage = function (ev) {
            var rt = document.getElementById("responseText")
            rt.value = ev.data;
        }

        //相当于连接开启(感知到连接开启)
        socket.onopen = function (ev) {
            var rt = document.getElementById("responseText")
            rt.value="连接开启了..."
        }

        //相当于连接关闭了(感知到连接关闭了)
        socket.onclose = function (ev) {
            var rt = document.getElementById("responseText")
            rt.value=rt.value+"\n"+"连接关闭了..."
        }

    }else{
        alert("当前浏览器不支持websocket")
    }

    function send(message) {
        if (!window.socket){   //先判断socket是否创建好
            return;
        }
        if (socket.readyState = WebSocket.OPEN){
            //通过socket发送消息
            socket.send(message)
        }else{
            alert("连接没有开启")
        }
    }

</script>
    <form onsubmit=" return false">
        <textarea name="message" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="发生消息" onclick="send(this.form.message.value)">
        <textarea id="responseText" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
    </form>
</body>
</html>

MyServer

package com.hzz.netty.netty.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
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.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

/**
 * @Author: curry
 * @Date: Created in 16:06 2023/10/24
 * @Modified By:
 * @description:
 */
public class MyServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup)
            .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            //因为基于http协议,使用http的编码和解码器
                            pipeline.addLast(new HttpServerCodec());
                            //是以块方式处理,添加ChunkedWriteHandler处理器
                            pipeline.addLast(new ChunkedWriteHandler());


                            /**
                             * 说明
                             * 1. http数据在传输过程中是分段的,httpObjectAggregator,就可以将多段聚合
                             * 2. 这就是为什么, 当浏览器发送大量数据时,就会发出多次http请求
                             */
                            pipeline.addLast(new HttpObjectAggregator(8192));

                            /**
                             * 说明
                             * 1. 对于webSocket,它的数据是以帧(frame)形式传递的
                             * 2. 可以看WebSocketFrame下面有六个子类
                             * 3. 浏览器请求时: ws://localhost:7000/xxx  表示请求的uri
                             * 4. WebSocketServerProtocolHandler 核心功能将一个http协议升级为ws协议,保持长连接
                             *
                             */
                            pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));

                            //自定义handler处理业务逻辑
                            pipeline.addLast(new MyTextWebSocketFrameHandler());


                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

MyTextWebSocketFrameHandler

package com.hzz.netty.netty.websocket;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.time.LocalDateTime;

/**
 * @Author: curry
 * @Date: Created in 16:57 2023/10/24
 * @Modified By:
 * @description:
 */
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {



    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

        System.out.println("服务器接收到消息"+msg.text());

        //回复消息
        ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间"+ LocalDateTime.now()+""+msg.text()));


    }

    /**
     * Do nothing by default, sub-classes may override this method.
     * 当web客户端连接后,触发方法
     *
     * @param ctx
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {

        //id表示唯一的一个值 LongText是唯一的, asShortText不是唯一的
        System.out.println("handlerAdded 被调用"+ctx.channel().id().asLongText());
        System.out.println("handlerAdded 被调用"+ctx.channel().id().asShortText());
    }

    /**
     * Do nothing by default, sub-classes may override this method.
     *
     * @param ctx
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved 被调用"+ctx.channel().id().asLongText());
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常发生"+cause.getMessage());
        ctx.close();  //关闭连接
    }
}

netty出站和入站

从socket到channelPipeline叫入站

从channelPipeline到socket叫出站

netty自定义数据包解决粘包,拆包问题

  1、创建myServer服务端,创建bossGroup,workerGroup,创建ServerBootStrap;

        serverBootStrap绑定bossGroup,workerGroup,定义channel为NioServerChannel.class,定义           MyServerInitializer,serverBootStrap绑定端口

package com.hzz.netty.netty.protocoltcp;
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;

/**
 * @Author: curry
 * @Date: Created in 15:06 2023/11/7
 * @Modified By:
 * @description:
 */
public class MyServer {

    public static void main(String[] args) throws InterruptedException {

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


        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class).childHandler(new MyServerInitializer());   //自定义一个初始化类

            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

}

2、创建MyServerInintializer继承ChannelInitializer<SocketChannel> 重写ChannelInitializer的              initChannel方法,在initChannel中添加 解码器handler,自定义MyServerHandler

package com.hzz.netty.netty.protocoltcp;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

/**
 * @Author: curry
 * @Date: Created in 16:16 2023/11/8
 * @Modified By:
 * @description:
 */
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new MyMessageDecoder());
        pipeline.addLast(new MyServerHandler());
    }
}

3、创建解码器handler  MyMessageDecoder 继承ReplayingDecorder<Void>

package com.hzz.netty.netty.protocoltcp;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;

import java.util.List;

/**
 * @Author: curry
 * @Date: Created in 15:38 2023/11/9
 * @Modified By:
 * @description:
 */
public class MyMessageDecoder extends ReplayingDecoder<Void> {


    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("MyMessageDecoder 方法被调用");

        //这里需要将得到的二进制字节码->MessageProtocol 数据包(对象)
        int len = in.readInt();

        byte[] content = new byte[len];

        in.readBytes(content);

        //封装成messageProtocol对象
        MessageProtocol messageProtocol = new MessageProtocol();
        messageProtocol.setLen(len);
        messageProtocol.setContent(content);

        out.add(messageProtocol);

    }
}

4、创建自定义MyServerHandler 继承SimpleChannelInboundHandle<要传输的数据格式>,重写                channeRead0方法

package com.hzz.netty.netty.protocoltcp;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.nio.charset.Charset;
import java.util.UUID;

/**
 * @Author: curry
 * @Date: Created in 16:19 2023/11/8
 * @Modified By:
 * @description:
 */
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {

    private int count;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {

        /*yte[] buffer = new byte[msg.readableBytes()];
        msg.readBytes(buffer);

        //将buffer转成一个字符串
        String message = new String(buffer, Charset.forName("utf-8"));

        System.out.println("服务器端接收到数据"+message);
        System.out.println("服务器端接收到消息量="+(++this.count));


        //服务器回送数据给客户端,回送一个随机id
        ByteBuf responseByteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString(), Charset.forName("utf-8"));
        ctx.writeAndFlush(responseByteBuf);*/

        //接收到数据,并处理
        int len = msg.getLen();
        byte[] content = msg.getContent();
        System.out.println("服务端接收到信息如下");
        System.out.println("长度="+len);
        System.out.println("内容="+new String(content,Charset.forName("utf-8")));

        System.out.println("服务器接收到消息包数量="+(++this.count));

    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

5、创建myClient客户端,创建NioEventLoopGroup,创建Bootstrap,bootstrp绑定                               NioEventLoopGroup,channel为NioSocketChannel.class,绑定MyClientInitializer,bootstrap         绑定主机名和端口

package com.hzz.netty.netty.protocoltcp;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;


/**
 * @Author: curry
 * @Date: Created in 15:27 2023/11/7
 * @Modified By:
 * @description:
 */
public class MyClient {

    public static void main(String[] args) throws InterruptedException {

        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class).
                    handler(new MyClientInitializer());

            ChannelFuture cha = bootstrap.connect("localhost", 7000).sync();
            cha.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }

    }
}

6、创建MyClientInitializer继承Initializer<SocketChannel> 重写initChannel方法,在initChannel方法中加入自定义handler和编码器handler

package com.hzz.netty.netty.protocoltcp;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

/**
 * @Author: curry
 * @Date: Created in 16:08 2023/11/8
 * @Modified By:
 * @description:
 */
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new MyMessageEncoder());

        pipeline.addLast(new MyClientHandler());

    }
}

7、创建编码器handler MyMessageEncoder继承MessageToByteEncoder<要传输的数据格式>

package com.hzz.netty.netty.protocoltcp;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * @Author: curry
 * @Date: Created in 15:34 2023/11/9
 * @Modified By:
 * @description:
 */
public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol> {

    @Override
    protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
        System.out.println("MyMessageEncoder 被调用");
        out.writeInt(msg.getLen());
        out.writeBytes(msg.getContent());
    }
}

8、创建MyClientHandler继承SimpleChannelInboundHandler<要传输的数据格式>

package com.hzz.netty.netty.protocoltcp;

import com.hzz.signmode.interpreter.VarExpression;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.nio.charset.Charset;

/**
 * @Author: curry
 * @Date: Created in 16:11 2023/11/8
 * @Modified By:
 * @description:
 */
public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {

    private int count;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        for (int i=0; i<5;i++){
            String msg ="今天天气冷,吃火锅";
            byte[] content = msg.getBytes(Charset.forName("utf-8"));
            int length = msg.getBytes(Charset.forName("utf-8")).length;

            //创建协议包
            MessageProtocol messageProtocol = new MessageProtocol();
            messageProtocol.setContent(content);
            messageProtocol.setLen(length);

            ctx.writeAndFlush(messageProtocol);

        }

    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {

       /* byte[] bytes = new byte[msg.readableBytes()];
        msg.readBytes(bytes);
        String message = new String(bytes, Charset.forName("utf-8"));

        System.out.println("客户端接收的消息="+message);
        System.out.println("客户端接收消息的数量="+(++this.count));*/
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常消息是"+cause.getMessage());
        ctx.close();
    }
}

9、要传输的数据格式MessageProtocol

package com.hzz.netty.netty.protocoltcp;

/**
 * @Author: curry
 * @Date: Created in 15:20 2023/11/9
 * @Modified By:
 * @description:  协议包
 */
public class MessageProtocol {

    private int len;  //关键
    private byte[] content;


    public int getLen() {
        return len;
    }

    public void setLen(int len) {
        this.len = len;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }
}

ChannelPipline调度handler的源码剖析

  1)Context包装handler,多个Context在pipline中形成了双向链表,入站叫inbound,有head节点开始,出站叫outhead,有tail节点开始

  2)而节点中的传递通过AbstractChannelHandlerContext类内部的fire系列方法,找到当前节点的下一个节点不断的循环传播,是一个过滤器形式完成handler的调度

 3)pipeline首先会调用Context的静态方fire,并传入context

 4)然后静态方法调用Context的invoker方法,而invoker方法内部会调用Context所包含的Handler真正到的方法,调用结束如果还需要继续向后传递,就调用Context的fire方法,往复循环

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值