Netty的学习与使用

内容纲要

方便阅读
在这里插入图片描述

学习说明

前置知识:NIO

由NIO的客户端主动释放导致异常开启

从上一篇NIO的学习,了解到通过Selector及Reactor模型,可以解决一般阻塞式IO模型的线程空等导致资源浪费的问题;但与此同时,也无法避免有NIO客户端主动断开连接,导致服务器端爆出异常,这些情况,都没有在NIO中处理得很好;所以让我们先来了解一下这个异常,并更好的进入Netty的学习

异常发生的截图
在这里插入图片描述

服务器端代码(原因在read方法内)

package chat.test1;

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

/**
 * @author bbyh
 * @date 2022/11/11 0011 12:31
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 50;

    public static void main(String[] args) throws Exception {
        try (ServerSocketChannel ssc = ServerSocketChannel.open();
             Selector selector = Selector.open()) {
            ssc.bind(new InetSocketAddress(PORT));
            ssc.configureBlocking(false);
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("服务器在 " + PORT + " 开始监听\n");

            while (true) {
                int count = selector.select();
                System.out.println("监听到 " + count + " 个事件");

                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();

                    if (key.isAcceptable()) {
                        SocketChannel sc = ssc.accept();
                        System.out.println("服务器接收到连接: " + sc.getRemoteAddress() + "\n");
                        sc.configureBlocking(false);
                        sc.register(selector, SelectionKey.OP_READ);
                    } else if (key.isReadable()) {
                        SocketChannel sc = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
                        int read = sc.read(buffer);
                        System.out.println("收到来自客户端的数据 " + sc.getRemoteAddress() + " :" + new String(buffer.array(), 0, read) + "\n");
                        sc.write(ByteBuffer.wrap("服务器端已收到,并向客户端问好".getBytes()));
                    }

                    iterator.remove();
                }
            }
        }
    }
}

客户端代码

package chat.test1;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

import static chat.test.reactor.Reactor.*;

/**
 * @author bbyh
 * @date 2022/11/11 0011 12:31
 * @description
 */
public class Client {
    public static void main(String[] args) throws Exception {
        try (SocketChannel sc = SocketChannel.open(new InetSocketAddress(IP, PORT))) {
            System.out.println("客户端连接上 " + sc.getRemoteAddress());

            Scanner scanner = new Scanner(System.in);
            String next;
            while (true) {
                next = scanner.nextLine();
                if ("".equals(next.replaceAll(" ", ""))) {
                    continue;
                }
                sc.write(ByteBuffer.wrap(next.getBytes()));

                ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
                int read = sc.read(buffer);
                System.out.println("客户端接收消息: " + new String(buffer.array(), 0, read));
            }
        }
    }
}

TCP协议的粘包拆包问题

简单来说,粘包拆包问题就是两个数据包没用按我们所想的依次每一个挨个到达,而是可能合成了一个大包,或者两个包中间有交集(其中一个数据包的一部分分到了另一个数据包上),图示如下:
在这里插入图片描述

这种粘包拆包问题的解决方式分为三种:
1、定长数据包,如果数据不够则以空(字节0000,或’\0’,即空内容)填充
2、采用固定的分隔符,例如 ‘/r/n’
3、分为消息头和消息体;其中头部包含消息体的长度内容

Netty的介绍

Netty解决了上述两个问题,客户端直接退出导致服务端的异常、粘包拆包问题

Netty是对Java.nio包的再次封装,所以需要先学习一下nio的相关技术

导一下依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.85.Final</version>
</dependency>

ByteBuf

Netty并没有使用ByteBuffer,而是改为了ByteBuf,写完之后无需 flip,且具有更快的响应速度,并且可以自动扩容

简单使用

简单使用,通过 Unpooled.buffer(20); 进行创建,可以写入、读取,还可以进行读指针归0 discardReadBytes,读写指针都归0 clear()

package netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.util.Arrays;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static void main(String[] args) {
        ByteBuf buf = Unpooled.buffer(20);
        System.out.println("初始状态: " + Arrays.toString(buf.array()));

        buf.writeInt(33333333);
        System.out.println("写入Int状态: " + Arrays.toString(buf.array()));

        System.out.println(buf.readByte());
        System.out.println(buf.readByte());
        System.out.println("读取Byte状态: " + Arrays.toString(buf.array()));

        buf.discardReadBytes();
        System.out.println("丢弃之后: " + Arrays.toString(buf.array()));

        buf.clear();
        System.out.println("清空之后: " + Arrays.toString(buf.array()));

        System.out.println("------------------------------------");

        buf.writeInt(33333333);
        System.out.println("写入Int状态: " + Arrays.toString(buf.array()));

        System.out.println(buf.readByte());
        System.out.println(buf.readByte());
        System.out.println("读取Byte状态: " + Arrays.toString(buf.array()));

        buf.discardReadBytes();
        System.out.println("丢弃之后: " + Arrays.toString(buf.array()));

        buf.clear();
        System.out.println("清空之后: " + Arrays.toString(buf.array()));
    }
}

也可以通过 Unpooled.wrappedBuffer(“123456789”.getBytes()); 进行创建,不过这个默认是只读的,需要 clear 一下,才能写入

package netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.util.Arrays;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static void main(String[] args) {
        ByteBuf buf = Unpooled.wrappedBuffer("123456789".getBytes());
        System.out.println(Arrays.toString(buf.array()));

        System.out.println(buf.readInt());

        buf.clear();
        buf.writeInt(10);
        System.out.println(buf.readInt());

        System.out.println(Arrays.toString(buf.array()));
    }
}

动态扩容

当没有设定最大容量是,就可以动态扩容
扩容的规则是,第一次扩容为64字节,后面按每次扩容为原来的两倍即128、256字节

package netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.util.Arrays;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static void main(String[] args) {
        ByteBuf buf = Unpooled.buffer(10);
        ByteBuf buf = Unpooled.buffer(10, 20);
        ByteBuf buf = Unpooled.buffer(10, 100);
        System.out.println(buf.capacity());

        buf.writeInt(10);
        System.out.println(buf.capacity());

        buf.writeInt(20);
        System.out.println(buf.capacity());

        buf.writeInt(30);
        System.out.println(buf.capacity());

        System.out.println(Arrays.toString(buf.array()));
    }
}

缓冲区的模式

堆缓冲区模式、直接缓冲区、复合缓冲区
前两种模式在NIO中讲过,这里与NIO是一样的,复合缓冲区可以帮助我们更好地对两个缓冲区进行操作,而不用牺牲更大的内存

复合缓冲区的简单使用

package netty;

import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;

import java.util.Arrays;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static void main(String[] args) {
        CompositeByteBuf buffer = Unpooled.compositeBuffer();

        buffer.addComponent(Unpooled.wrappedBuffer("12345".getBytes()));
        buffer.addComponent(Unpooled.wrappedBuffer("67890".getBytes()));

        for (int i = 0; i < buffer.capacity(); i++) {
            System.out.print((char) buffer.getByte(i) + " ");
        }
    }
}

池化缓冲区和非池化缓冲区的区别

简单来说就是池化缓冲区可以采用缓冲池进行复用,减少内存申请

package netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static void main(String[] args) {
        PooledByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
        ByteBuf buffer1 = allocator.directBuffer(10);
        buffer1.writeInt(10);
        System.out.println(buffer1.readInt());
        buffer1.release();

        ByteBuf buffer2 = allocator.directBuffer(10);
        System.out.println(buffer1 == buffer2);

        buffer1.writeInt(10);
        buffer1.writeInt(20);

        System.out.println(buffer1.readInt());
        System.out.println(buffer2.readInt());
    }
}

零拷贝

简单介绍:是一种IO复用技术,可以直接将文件与网络接口联系起来,而不需要通过中间的用户空间进行转发,即不需要文件内容将内核空间复制到用户空间

一般的文件数据流向示意图如下:
在这里插入图片描述

上述流程需要将数据进行多次拷贝移动,比较费时间;这里就可以通过三种方案减少拷贝次数

1、虚拟内存(通过用户空间缓存与页缓存共用一个物理地址,减少一次拷贝)
在这里插入图片描述

2、内存映射(将用户空间缓存映射到页缓存中,直接操作的就是页缓存了,可以减少一次拷贝)
在这里插入图片描述

sendfile方式(Linux中,可以告诉操作系统要把哪个文件发送到Socket,可以一步到位,但是似乎就没办法对文件进行修改了,这里也减少了一次拷贝)
在这里插入图片描述

Netty工作模型

Netty正是以主从Reactor模式,达到高效,大致的工作流程示意图如下:
在这里插入图片描述

简单的Netty实例

Netty的使用相较于NIO更集中化,写一些简单的配置即可,简洁方便

服务器端

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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.NioServerSocketChannel;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 50;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                ctx.writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,并向客户端问好".getBytes()));
                            }
                        });
                    }
                });
        bootstrap.bind(PORT);
    }
}

客户端(与一般的NIO客户端一样)

package netty;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

import static netty.Server.*;

/**
 * @author bbyh
 * @date 2022/11/11 0011 12:31
 * @description
 */
public class Client {
    public static void main(String[] args) throws Exception {
        try (SocketChannel sc = SocketChannel.open(new InetSocketAddress(IP, PORT))) {
            System.out.println("客户端连接上 " + sc.getRemoteAddress());

            Scanner scanner = new Scanner(System.in);
            String next;
            while (true) {
                next = scanner.nextLine();
                if ("".equals(next.replaceAll(" ", ""))) {
                    continue;
                }
                sc.write(ByteBuffer.wrap(next.getBytes()));

                ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
                int read = sc.read(buffer);
                System.out.println("客户端接收消息: " + new String(buffer.array(), 0, read));
            }
        }
    }
}

Channel通道

Channel和NIO里面类似,但是功能更强大

测试Handler的生命周期流程

自己定义的一个Handler
其中 exceptionCaught 可以统一处理异常

package netty;

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

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/12 0012 12:19
 * @description
 */
public class TestHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) {
        System.out.println("channelRegistered");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) {
        System.out.println("channelUnregistered");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("channelActive");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        System.out.println("channelInactive");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
        ByteBuf back = ctx.alloc().buffer();
        back.writeCharSequence("服务器端已收到,并向客户端问好", StandardCharsets.UTF_8);
        ctx.writeAndFlush(back);
        System.out.println("channelRead");
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        System.out.println("channelReadComplete");
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
        System.out.println("userEventTriggered");
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) {
        System.out.println("channelWriteAbilityChanged");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        System.out.println("exceptionCaught" + cause);
    }
}

服务器端则采用自己定义的Handler作为处理器即可

package netty;

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

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 50;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast(new TestHandler());
                    }
                });
        bootstrap.bind(PORT);
    }
}

测试流程结果的注册流程为:
先注册、然后激活可用、然后读取、读取完毕,关闭时候则再次读取、然后不可用,然后取消注册

服务器在 9000 开启监听
channelRegistered
channelActive
nioEventLoopGroup-3-1 收到客户端消息: 12
channelRead
channelReadComplete
nioEventLoopGroup-3-1 收到客户端消息: 34
channelRead
channelReadComplete
channelReadComplete
exceptionCaughtjava.io.IOException: 远程主机强迫关闭了一个现有的连接。
channelInactive
channelUnregistered

Handler的Pipeline(流水线般的处理)

添加两个Handler即可实现流水线处理,这里演示入站操作;按顺序依次执行,每次由两个Handler捕获,但是只会有一个返回给客户端

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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.NioServerSocketChannel;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 50;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        ByteBuf buf = (ByteBuf) msg;
                                        System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                        ctx.writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器1向客户端问好".getBytes()));
                                        ctx.fireChannelRead(msg);
                                        throw new RuntimeException("我在这里添加异常");
                                    }
                                })
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        ByteBuf buf = (ByteBuf) msg;
                                        System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                        ctx.writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器2向客户端问好".getBytes()));
                                    }

                                    @Override
                                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                                        System.out.println("我是异常处理: " + cause);
                                    }
                                });
                    }
                });
        bootstrap.bind(PORT);
    }
}

对于出站处理器而言,则需要写在前面,或者采用 ctx.channel().writeAndFlush 进行写回,不然就会导致 出站处理器无法被执行到

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 50;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new ChannelOutboundHandlerAdapter() {
                                    @Override
                                    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
                                        System.out.println(msg);
                                        ctx.writeAndFlush(Unpooled.wrappedBuffer(msg.toString().getBytes()));
                                    }
                                })
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        ByteBuf buf = (ByteBuf) msg;
                                        System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                        ctx.fireChannelRead(msg);
                                    }
                                })
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        ByteBuf buf = (ByteBuf) msg;
                                        System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                        ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器2向客户端问好".getBytes()));
//                                        ctx.writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器2向客户端问好".getBytes()));
                                    }
                                });
                    }
                });
        bootstrap.bind(PORT);
    }
}

关于出站处理器的反着执行的测试(实验结果可以发现确实是先执行了出站处理器2,再执行了站处理器1)

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 50;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new ChannelOutboundHandlerAdapter() {
                                    @Override
                                    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
                                        System.out.println(msg + "出站处理器1");
                                        ctx.writeAndFlush(Unpooled.wrappedBuffer((msg.toString() + "出站处理器1").getBytes()));
                                    }
                                })
                                .addLast(new ChannelOutboundHandlerAdapter() {
                                    @Override
                                    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
                                        System.out.println(msg + "出站处理器2");
                                        ctx.writeAndFlush(Unpooled.wrappedBuffer((msg.toString() + "出站处理器2").getBytes()));
                                    }
                                })
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        ByteBuf buf = (ByteBuf) msg;
                                        System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                        ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器向客户端问好".getBytes()));
                                    }
                                });
                    }
                });
        bootstrap.bind(PORT);
    }
}

Eventloop和任务调度

简单说明:Eventloop很像之前的Selector,负责处理监听READ线程
但是也是有不同的,这里的Eventloop负责Handler,Handler可以进行读写,与前面的Reactor的Handler专门处理读线程就有区别了

这里测试Eventloop的读写监听;当采用 EventLoopGroup workGroup = new NioEventLoopGroup(1); 限制线程数时,则会出现监听来不及的情况

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 50;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup(1);

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器向客户端问好".getBytes()));
                                Thread.sleep(10000);
                            }
                        });
                    }
                });
        bootstrap.bind(PORT);
    }
}

通过创建新线程解决耗时任务

此时可以采用创建新线程进行请求处理
新建一个 EventLoopGroup handlerGroup = new DefaultEventLoopGroup(); 来处理写回事件(即一些耗时任务)

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 50;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup(1);
        EventLoopGroup handlerGroup = new DefaultEventLoopGroup();

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                handlerGroup.execute(() -> {
                                    ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器向客户端问好".getBytes()));
                                    try {
                                        Thread.sleep(10000);
                                    } catch (InterruptedException e) {
                                        throw new RuntimeException(e);
                                    }
                                });
                            }
                        });
                    }
                });
        bootstrap.bind(PORT);
    }
}

通过多级处理器解决耗时任务

但毫无疑问,通过多级处理器,这样处理器的个数会增加,且处理器的个数是有限的,所以只能解决部分压力

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;
    public static final int BUFFER_SIZE = 50;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup(1);
        EventLoopGroup handlerGroup = new DefaultEventLoopGroup();

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        ByteBuf buf = (ByteBuf) msg;
                                        System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                        ctx.fireChannelRead(msg);
                                    }
                                })
                                .addLast(handlerGroup, new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                        ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器向客户端问好".getBytes()));
                                        Thread.sleep(10000);
                                    }
                                });
                    }
                });
        bootstrap.bind(PORT);
    }
}

Netty客户端的编写

并不复杂,和服务器端是很类似的

package netty;

import io.netty.bootstrap.Bootstrap;
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.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.nio.charset.StandardCharsets;
import java.util.Scanner;

import static netty.Server.*;

/**
 * @author bbyh
 * @date 2022/11/11 0011 12:31
 * @description
 */
public class Client {
    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();

        bootstrap.group(new NioEventLoopGroup()).channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println("客户端接收消息: " + (buf).toString(StandardCharsets.UTF_8));
                            }
                        });
                    }
                });

        Channel channel = bootstrap.connect(IP, PORT).channel();
        System.out.println("客户端连接上 " + IP + ": " + PORT);
        Scanner scanner = new Scanner(System.in);
        String input;
        while (true) {
            input = scanner.nextLine();
            if ("".equals(input.replaceAll(" ", ""))) {
                continue;
            }
            channel.writeAndFlush(Unpooled.wrappedBuffer(input.getBytes()));
            if ("exit".equals(input)) {
                break;
            }
        }
    }
}

ChannelFuture

异步执行,返回结果

服务器端测试,当需要保证一些步骤在异步回调执行结束后再执行,则可以通过sync,或者通过添加监听器实现,很类似node.js,前端的一些异步回调

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;

    public static void main(String[] args) throws Exception {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println(Thread.currentThread().getName() + " 收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));
                                ChannelFuture channelFuture = ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器向客户端问好".getBytes()));
                                System.out.println("当前写操作是否已完成: " + channelFuture.isDone());
                            }
                        });
                    }
                });
        ChannelFuture future = bootstrap.bind(PORT);
        future.addListener((ChannelFutureListener) channelFuture -> {
            System.out.println("服务器端绑定事件是否操作完成: " + future.isDone());
            System.out.println("服务器端绑定事件结束后要做的事情");
        });

//        future.sync();
//        System.out.println("服务器端绑定事件是否操作完成: " + future.isDone());
//        System.out.println("服务器端绑定事件结束后要做的事情");
    }
}

客户端的异步测试

package netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.nio.charset.StandardCharsets;
import java.util.Scanner;

import static netty.Server.*;

/**
 * @author bbyh
 * @date 2022/11/11 0011 12:31
 * @description
 */
public class Client {
    public static void main(String[] args) throws Exception {
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup loopGroup = new NioEventLoopGroup();

        bootstrap.group(loopGroup).channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println("客户端接收消息: " + (buf).toString(StandardCharsets.UTF_8));
                            }
                        });
                    }
                });

        Channel channel = bootstrap.connect(IP, PORT).channel();
        System.out.println("客户端连接上 " + IP + ": " + PORT);
        Scanner scanner = new Scanner(System.in);
        String input;
        while (true) {
            input = scanner.nextLine();
            if ("exit".equals(input)) {
                ChannelFuture future = channel.close();
                future.sync();
                break;
            }
            if ("".equals(input.replaceAll(" ", ""))) {
                continue;
            }
            channel.writeAndFlush(Unpooled.wrappedBuffer(input.getBytes()));
        }
        loopGroup.shutdownGracefully();
    }
}

Promise的使用

简单示例

package netty;

import io.netty.channel.DefaultEventLoop;
import io.netty.util.concurrent.DefaultPromise;

/**
 * @author bbyh
 * @date 2022/11/12 0012 15:27
 * @description
 */
public class TestPromise {
    public static void main(String[] args) throws Exception {
        DefaultPromise<String> promise = new DefaultPromise<>(new DefaultEventLoop());
        System.out.println(promise.isSuccess());
        promise.setSuccess("OK");
        System.out.println(promise.isSuccess());
        System.out.println(promise.get());
    }
}

一般使用,可以代替ChannelFuture的功能

package netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;

    public static void main(String[] args) throws Exception {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                System.out.println("收到客户端消息: " + buf.toString(StandardCharsets.UTF_8));

                                ChannelPromise promise = ctx.newPromise();
                                System.out.println(promise.isSuccess());
                                ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器向客户端问好".getBytes()), promise);

                                promise.sync();
                                System.out.println(promise.isSuccess());
                            }
                        });
                    }
                });
        bootstrap.bind(PORT);
    }
}

编码器和解码器

编码器

简单添加解码器只需要 addLast(new StringDecoder()) 即可直接将客户端的内容接收为字符串,当然也可以自己编写一个解码器

package netty;

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

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline().addLast(new StringDecoder()).addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                System.out.println("收到客户端消息: " + msg);
                                ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("服务器端已收到,处理器向客户端问好".getBytes()));
                            }
                        });
                    }
                });
        bootstrap.bind(PORT);
    }
}

编写自己的解码器,现在只需要将上面的 addLast(new StringDecoder()) 换成自己的 addLast(new TestDecoder()) 即可,同样达到效果

package netty;

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

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author bbyh
 * @date 2022/11/12 0012 15:43
 * @description
 */
public class TestDecoder extends MessageToMessageDecoder<ByteBuf> {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        System.out.println("自己定义的解码器执行中...");
        String text = byteBuf.toString(StandardCharsets.UTF_8);
        list.add(text);
    }
}

解码器

解码器同样简单,只需要添加一个 addLast(new StringEncoder() 即可;当然同样可以自己写一个编码器(addLast(new StringEncoder() 添加的顺序无关的,可以在前也可以在后,前提是使用 ctx.channel() 来发)

package netty;

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 bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new StringDecoder())
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        System.out.println("收到客户端消息: " + msg);
                                        ctx.channel().writeAndFlush("服务器端已收到,处理器向客户端问好");
                                    }
                                })
                                .addLast(new StringEncoder());
                    }
                });
        bootstrap.bind(PORT);
    }
}

同样客户端也可以改为采用编码器解码器的样子

package netty;

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;

import static netty.Server.*;

/**
 * @author bbyh
 * @date 2022/11/11 0011 12:31
 * @description
 */
public class Client {
    public static void main(String[] args) throws Exception {
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup loopGroup = new NioEventLoopGroup();

        bootstrap.group(loopGroup).channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new StringDecoder())
                                .addLast(new StringEncoder())
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        System.out.println("客户端接收消息: " + msg);
                                    }
                                });
                    }
                });

        Channel channel = bootstrap.connect(IP, PORT).channel();
        System.out.println("客户端连接上 " + IP + ": " + PORT);
        Scanner scanner = new Scanner(System.in);
        String input;
        while (true) {
            input = scanner.nextLine();
            if ("exit".equals(input)) {
                ChannelFuture future = channel.close();
                future.sync();
                break;
            }
            if ("".equals(input.replaceAll(" ", ""))) {
                continue;
            }
            channel.writeAndFlush(input);
        }
        loopGroup.shutdownGracefully();
    }
}

编解码器

即结合编码器和解码器的两个功能

自己编写一个编解码器,使用和编码器、解码器一样, addLast(new TestCodec()) 即可

package netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author bbyh
 * @date 2022/11/12 0012 16:03
 * @description
 */
public class TestCodec extends MessageToMessageCodec<ByteBuf, String> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, String s, List<Object> list) throws Exception {
        list.add(Unpooled.wrappedBuffer(s.getBytes()));
    }

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        list.add(byteBuf.toString(StandardCharsets.UTF_8));
    }
}

多级编解码转换

附注:可以添加多个编解码器,所以就简单理解为进行多次处理

采用编码器解决粘包拆包问题

粘包拆包问题的三种解决
1、消息定长
2、特殊分隔符
3、分为头部和消息体(头部附带消息体长度)

对应的解决即为添加一些解码器
1、addLast(new FixedLengthFrameDecoder(10)) (指定数据包为10个字节)
2、addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.wrappedBuffer(“!”.getBytes()))) (以指定分隔符 “!” 为间隔,在识别到该分隔符前,且未满1024字节,都为一个数据包)
第三个相对复杂一些

第三种解决方式

发送端需要加上 addLast(new LengthFieldBasedFrameDecoder(1024, 0, 1))
接收方需要加上 addLast(new LengthFieldPrepender(1))

下面是演示示例

发送端(客户端)

package netty;

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.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

import static netty.Server.*;

/**
 * @author bbyh
 * @date 2022/11/11 0011 12:31
 * @description
 */
public class Client {
    public static void main(String[] args) throws Exception {
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup loopGroup = new NioEventLoopGroup();

        bootstrap.group(loopGroup).channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new LengthFieldBasedFrameDecoder(1024, 0, 1))
                                .addLast(new StringDecoder())
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        System.out.println("客户端接收消息: " + msg);
                                    }
                                })
                                .addLast(new StringEncoder());
                    }
                });

        Channel channel = bootstrap.connect(IP, PORT).channel();
        System.out.println("客户端连接上 " + IP + ": " + PORT);
        Scanner scanner = new Scanner(System.in);
        String input;
        while (true) {
            input = scanner.nextLine();
            if ("exit".equals(input)) {
                ChannelFuture future = channel.close();
                future.sync();
                break;
            }
            if ("".equals(input.replaceAll(" ", ""))) {
                continue;
            }
            channel.writeAndFlush(input);
        }
        loopGroup.shutdownGracefully();
    }
}

接收端(服务器端)

package netty;

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.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new StringDecoder())
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        System.out.println("收到客户端消息: " + msg);
                                        ctx.channel().writeAndFlush("服务器端已收到!处理器向客户端问好");
                                    }
                                })
                                .addLast(new LengthFieldPrepender(1))
                                .addLast(new StringEncoder());
                    }
                });
        bootstrap.bind(PORT);
    }
}

Http编码器与解码器

服务器端可以进行请求获取及返回响应

package netty;

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.http.*;

import java.nio.charset.StandardCharsets;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new HttpRequestDecoder())
                                .addLast(new HttpObjectAggregator(Integer.MAX_VALUE))
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        FullHttpRequest request = (FullHttpRequest) msg;
                                        System.out.println("request.uri(): " + request.uri());

                                        System.out.println("收到客户端消息: " + msg);
                                        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
                                        response.content().writeCharSequence("Hello,world", StandardCharsets.UTF_8);
                                        ctx.channel().writeAndFlush(response);
                                        ctx.channel().close();
                                    }
                                })
                                .addLast(new HttpResponseEncoder());
                    }
                });
        bootstrap.bind(PORT);
    }
}
应用实例–采用nettyHttp代理静态页面(搭建静态服务器)

项目路径图
在这里插入图片描述

解析器

package page;

import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author bbyh
 * @date 2022/11/12 0012 18:10
 * @description
 */
public class PageResolver {
    private static final String PATH_DESC = "/";
    private static final String DIR_PATH = "static/";
    private static final ThreadLocal<PageResolver> INSTANCE = ThreadLocal.withInitial(PageResolver::new);

    private PageResolver() {
    }

    public static PageResolver getInstance() {
        return INSTANCE.get();
    }

    public FullHttpResponse resolveResource(String path) {
        if (path.startsWith(PATH_DESC)) {
            path = path.equals(PATH_DESC) ? "index.html" : path.substring(1);
            try (InputStream stream = this.getClass().getClassLoader().getResourceAsStream(DIR_PATH + path)) {
                if (stream != null) {
                    byte[] bytes = new byte[stream.available()];
                    int read = stream.read(bytes);
                    System.out.println("文件大小: " + read + " 字节");
                    return this.packet(HttpResponseStatus.OK, bytes);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        return this.packet(HttpResponseStatus.NOT_FOUND, "404 Not Found".getBytes());
    }

    private FullHttpResponse packet(HttpResponseStatus status, byte[] data) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
        response.content().writeBytes(data);
        return response;
    }
}

服务器代码

package netty;

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.http.*;
import page.PageResolver;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new HttpRequestDecoder())
                                .addLast(new HttpObjectAggregator(Integer.MAX_VALUE))
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        FullHttpRequest request = (FullHttpRequest) msg;
                                        PageResolver resolver = PageResolver.getInstance();
                                        ctx.channel().writeAndFlush(resolver.resolveResource(request.uri()));
                                        ctx.channel().close();
                                    }
                                })
                                .addLast(new HttpResponseEncoder());
                    }
                });
        bootstrap.bind(PORT);
    }
}

其他内置Handler

日志打印的Handler 添加 addLast(new LoggingHandler(LogLevel.INFO)) 即可

IP筛选的Handler addLast(new RuleBasedIpFilter(new IpFilterRule() 即可,里面默认实现两个方法,可以选择接收或者拒绝的策略

addLast(new RuleBasedIpFilter(new IpFilterRule() {
    @Override
    public boolean matches(InetSocketAddress inetSocketAddress) {
        System.out.println(inetSocketAddress.getHostName());
        return inetSocketAddress.getHostName().equals(IP);
    }

    @Override
    public IpFilterRuleType ruleType() {
        return IpFilterRuleType.REJECT;
    }
}))

侦测长时间不使用的连接,添加 addLast(new IdleStateHandler(10, 10, 0)) 即可
第一个为读操作、第二个为写操作、第三个为读写操作,时间单位为秒,0表示拒绝
当过了这么多时间都没有相应的读写事件时,则会触发事件,在Handler里面进行处理
示例如下:

package netty;

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;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * @author bbyh
 * @date 2022/11/11 0011 20:32
 * @description
 */
public class Server {
    public static final String IP = "127.0.0.1";
    public static final int PORT = 9000;

    public static void main(String[] args) {
        System.out.println("服务器在 " + PORT + " 开启监听");

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

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup).
                channel(NioServerSocketChannel.class).
                childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        channel.pipeline()
                                .addLast(new StringDecoder())
                                .addLast(new IdleStateHandler(10, 10, 0))
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                        System.out.println("服务器收到消息: " + msg);
                                        ctx.channel().writeAndFlush("服务器已收到");
                                    }

                                    @Override
                                    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                                        if (evt instanceof IdleStateEvent) {
                                            IdleStateEvent event = (IdleStateEvent) evt;
                                            if (event.state() == IdleState.WRITER_IDLE) {
                                                System.out.println("已经10秒未向服务器发送数据");
                                            } else if (event.state() == IdleState.READER_IDLE) {
                                                System.out.println("已经10秒未从服务器读取数据");
                                            }
                                        }
                                    }
                                })
                                .addLast(new StringEncoder());
                    }
                });
        bootstrap.bind(PORT);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值