Netty网络编程-入门篇(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/QQB67G8COM/article/details/90263730

Netty是一个NIO客户端服务器框架,可以快速轻松地开发协议服务器和客户端等网络应用程序。它极大地简化并简化了TCP和UDP套接字服务器开发等网络编程。
目前版本:4.1.36
支持:
最低Oracle JDK 1.7
支持最新的Maven

JDK 9的用法:
Netty可以在模块化的JDK9应用程序中作为自动模块的集合使用。模块名称遵循反向dns样式,并且由于历史原因派生自子项目名称,而不是根包。它们列在下面:

io.netty.all
io.netty.buffer
io.netty.codec
io.netty.codec.dns
io.netty.codec.haproxy
io.netty.codec.http
io.netty.codec.http2
io.netty.codec.memcache
io.netty.codec.mqtt
io.netty.codec.redis
io.netty.codec.smtp
io.netty.codec.socks
io.netty.codec.stomp
io.netty.codec.xml
io.netty.common
io.netty.handler
io.netty.handler.proxy
io.netty.resolver
io.netty.resolver.dns
io.netty.transport
io.netty.transport.epoll (native omitted - reserved keyword in Java)
io.netty.transport.kqueue (native omitted - reserved keyword in Java)
io.netty.transport.unix.common (native omitted - reserved keyword in Java)
io.netty.transport.rxtx
io.netty.transport.sctp
io.netty.transport.udt

Automatic modules 不提供任何声明依赖项的方法,因此需要在module-info文件中单独列出每个使用的模块。
Netty的支持:
在这里插入图片描述

入门例子:

官方作者开了个玩笑:世界上最简单的协议不是“Hello World”,而是DISCARD,它是一种在没有任何响应的情况下丢弃任何接收数据的协议。意思就说服务器接受到Client的消息,但我什么都不做,直接把消息扔掉。
要实现该DISCARD协议,您唯一需要做的就是忽略所有收到的数据。让我们直接从处理程序实现开始,它处理由Netty生成的I / O事件。

1 服务端代码:

1-1 服务端main代码,关键代码为 sc.pipeline().addLast(new DiscardServerHandler());
Handler为DiscardServer服务器的核心代码类,关键代码意思就是,当有一个Client连接进来时,就会为客户端连接分发一条管道,然后调用其中的Handler进行逻辑处理,所以Client中的所有业务逻辑都是通过handler类来进行处理的,handler类在Netty中很重要,无时无刻都需要使用

package netty;

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;

public class DiscardServer {

    private int port;

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

    public void run() throws Exception {
        //NioEventLoopGroup是一个处理I/O多线程事件环
        //bossGroup接受传入连接,workerGroup通称为工人,一旦boss接受连接并将连接注册到worker
        //第一个线程组 是用于接受Client端连接的
        EventLoopGroup bossGroup = new NioEventLoopGroup();  //(1)TCP
        //第二个线程组 是用于实际的业务处理操作的
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); //(2)创建服务器的辅助类,对server进行一些列的配置
            //把两个工作线程组加入进来
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) //(3)接受传入连接->boss
                    .childHandler(new ChannelInitializer<SocketChannel>() { //(4)ChannelInitializer帮助用户(worker)配置新的Channel
                        @Override
                        protected void initChannel(SocketChannel sc) throws Exception {
                            sc.pipeline().addLast(new DiscardServerHandler());
                        }
                    })
                    /**
                     * 服务器端TCP内核模块维护2个队列,我们称之为A和B吧
                     * 客户端向服务器端connect的时候,会发送带有SYN标志的包(第一次握手)
                     * 服务器端收到客户端发来的SYN时,向客户端发送SYN ACK确认(第二次握手)
                     * 此时TCP内核模块吧客户端连接加入到A队列中,然后服务器收到客户端发来的ACK时(第三次握手)
                     * TCP内核模块把客户端连接从A队列移到B队列,连接完成,应用的accept会返回
                     * 也就是说accept从B队列中取出完成三次握手的连接。
                     * A队列和B队列的长度之和时backlog.当A,B队列的长度之和大于backlog时,新的连接将会被TCP内核拒绝。
                     * 所以,如果时backlog过小,可能出现accept速度跟不上,A,B队列满了,导致新的客户端无法连接。
                     * 要注意的时:backlog对于程序支持的连接数并无影响,backlog影响的只是还没有被accept取出的链接。
                     */
                    //BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放
                    // 已完成三次握手的请求队列的最大长度
                    .option(ChannelOption.SO_BACKLOG, 128) //(5)
                    .option(ChannelOption.SO_RCVBUF, 32*1024) //这是接受缓冲大小
                    .childOption(ChannelOption.SO_KEEPALIVE, true); //(6)是否保持心跳活动
            //绑定端口并开始接受传入的连接
            ChannelFuture f = b.bind(port).sync(); //(7)

            //猜测(没看过源码):代码执行到这里的时候会启动一个子线程一直对服务器进行监听,直到服务器套接字关闭,然后子线程通知main线程进行结束,但是本例不会出现这种状况,官方说这样能够优雅的关闭服务器,哈哈。
            f.channel().closeFuture().sync();
            //以下为主动关闭Netty服务器
            //f.channel().close();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0){
            port = Integer.parseInt(args[0]);
        }

        new DiscardServer(port).run();
    }
}

1-2 Handler代码

package netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;

/**
 * @description 处理服务器端通道,该适配器实现类ChannelInboundHandler接口,
 * 该接口提供可以覆盖各种事件处理程序的方法。现在只需要扩展ChannelInboundHandlerAdapter
 * 而不是去实现ChannelInboundHandler接口
 */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {  //(1)

    // 每当从客户端接受到新数据时,都会使用受到的消息调用此方法,ChannelHandlerContext对象提
    // 供各种操作使您可以触发各种I/O的事件和操作。这里通过write()逐字写入接收到的消息,这里的
    // 写入只是写到缓冲区而已,并没有写到客户端,通过flush()将缓冲区的内容冲刷到客户端
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //(2)
        ByteBuf in = (ByteBuf) msg;
        //静静的丢弃接受到的数据,实现discard协议,ByteBuf是引用一个计数对象,测试的可以把in.release()的注释解除,下面的代码全部注释上
        //in.release();
        try {
            
            while (in.isReadable()){    //(3)
                System.out.println((char)in.readByte());
                System.out.flush();
            }
//            System.out.println(in.toString(CharsetUtil.US_ASCII)); //(3)简化版

            //(4)response
//            ctx.write(msg);
//            ctx.flush();
            ctx.writeAndFlush(msg);//(4)
        } finally {
//            ReferenceCountUtil.release(msg);//(5)关闭关联的通道
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //在引发异常时关闭连接
        cause.printStackTrace();
        ctx.close();
    }
}

显示结果
在cmd窗口运行命令:telnet localhost 8080
在窗口下输入一串字符,发现客户端和服务端什么东西都没有输出,那是因为消息被服务端丢弃了,也不做任何响应
PS:window10下telnet默认时关闭的,需要手动开启:控制面板\所有控制面板项\程序和功能
在“启动或关闭window功能”处。

PS:奇怪的是ByteBuf为啥不像NIO那样使用filp()?是因为ByteBuf内部维护了两个指针,一个读指针,一个写指针,读指针值的是起始位,当数据写入的时候,只有写指针在移动,写指针代表的是末位置。

猜你喜欢

转载自blog.csdn.net/QQB67G8COM/article/details/90263730