Netty简介及简单客户端/服务端示例代码

什么是Netty?

        Netty是一个NIO客户机-服务器框架,它支持快速而容易地开发网络应用程序,如协议服务器和客户机。它大大简化和简化了网络编程,如TCP和UDP套接字服务器。

        “快速简单”并不意味着生成的应用程序将遭受可维护性或性能问题的困扰。Netty经过了精心的设计,其经验来自于FTP、SMTP、HTTP以及各种基于二进制和文本的遗留协议的实现。因此,Netty成功地找到了一种不妥协地实现易开发性、性能、稳定性和灵活性的方法。

Netty 的应用场景

互联网行业

1)互联网行业: 在分布式系统中, 各个节点之间需要远程服务调用, 高性能的 RPC 框架必不可少, Netty 作为异步高性能的通信框架, 往往作为基础通信组件被这些 RPC 框架使用。

2)典型的应用有: 阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信, Dubbo 协议默认使用 Netty 作为基础通信组件, 用于实现各进程节点之间的内部通信。

游戏行业

1)无论是手游服务端还是大型的网络游戏, Java 语言得到了越来越广泛的应用。

2)Netty 作为高性能的基础通信组件, 提供了 TCP/UDP 和 HTTP 协议栈, 方便定制和开发私有协议栈, 账号登录服务器。

3)地图服务器之间可以方便的通过 Netty 进行高性能的通信。

大数据领域

1) 经典的 Hadoop 高性能通信和序列化组件 Avro 的 RPC 框架, 默认采用 Netty 进行跨界点通信。

2) 它的 Netty Service 基于 Netty 框架二次封装实现。

代码示例

maven依赖

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

服务端

Constant类

public class Constant {
    // 常量,Server需要的一些参数信息,需要根据实际情况进行自定义修改
    static final int PORT = 8888;   // 可以自定义端口号
    static final String CLIENT_PREFIX = "Server received:";   // Server显示接收到client发送的信息的前缀
    static final String SERVER_PREFIX = "Server send:";   // Server将接收到client发送的信息返回给client的前缀
}

NettyServer类

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;

/*
 * Netty的Server
 * 功能:接收client发送的消息并在消息的前缀加上一些内容后返回给客户端
 * */
public class NettyServer {
    private final int port;

    public static void main(String[] args) {
        int port = Constant.PORT;
        new NettyServer(port).start();
    }

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

    public void start() {
        // 创建两个事件循环组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        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 ch) throws Exception {
                            // 给处理器添加对应的ChannelHandler
                            ch.pipeline().addLast(new StringDecoder()); // 添加字符串解码器
                            ch.pipeline().addLast(new StringEncoder()); // 添加字符串编码器
                            ch.pipeline().addLast(new ServerHandler()); // 添加服务处理器
                        }
                    });

            System.out.println("服务端启动,等待客户端连接...");
            // 绑定端口并启动服务
            ChannelFuture future = bootstrap.bind(8888).sync();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭两个事件循环组,释放资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private class ServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            System.out.println(Constant.CLIENT_PREFIX + msg); // 打印收到的客户端信息
            ctx.writeAndFlush(Constant.SERVER_PREFIX + msg); // 将收到的信息加上前缀返回给客户端
        }
    }

}

客户端

Constant类

public class Constant {
    // 常量,Client需要的一些参数信息,需要根据实际情况进行自定义修改
    static final String HOST = "127.0.0.1";   // 要连接的服务器地址
    static final int PORT = 8888;   // 要连接的服务器端口号
    static final String CLIENT_PREFIX = "Client received:";   // Client显示接收到Server发送的信息的前缀
}

NettyClient类

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 io.netty.util.CharsetUtil;

import java.util.Scanner;

/*
 * Netty的Client
 * 功能:向server发送控制台输入的消息,并接收server发回的消息并显示
 * */
public class NettyClient {
    private final String host;
    private final int port;
    private Channel channel;

    public static void main(String[] args) throws Exception {
        String host = Constant.HOST;
        int port = Constant.PORT;

        new NettyClient(host, port).start();
    }

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

    public void start() throws Exception { // 定义一个名为start的方法,这个方法抛出Exception异常
        EventLoopGroup group = new NioEventLoopGroup(); // 创建一个NioEventLoopGroup对象,它负责处理I/O操作的多线程事件循环
        try { // 开始try-catch块,用于捕获可能的异常
            Bootstrap bootstrap = new Bootstrap(); // 创建一个Bootstrap对象,它是Netty应用程序的入口点
            bootstrap.group(group) // 设置EventLoopGroup,用于处理I/O操作
                    .channel(NioSocketChannel.class) // 指定用于通信的Channel类型
                    .handler(new ChannelInitializer<SocketChannel>() { // 添加一个ChannelInitializer,用于初始化新连接的Channel
                        @Override // 覆盖ChannelInitializer中的初始化方法
                        protected void initChannel(SocketChannel ch) throws Exception { // 初始化Channel
                            ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8)); // 添加一个StringDecoder,用于将字节流解码为字符串,使用UTF-8编码
                            ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8)); // 添加一个StringEncoder,用于将字符串编码为字节流,使用UTF-8编码
                            ch.pipeline().addLast(new ClientHandler()); // 添加一个ClientHandler,用于处理业务逻辑
                        }
                    });

            channel = bootstrap.connect(host, port).sync().channel(); // 使用Bootstrap连接服务器,同步连接并获取到Channel

            Scanner scanner = new Scanner(System.in); // 创建一个Scanner对象,用于从控制台接收用户输入
            while (true) { // 无限循环,直到用户输入exit命令
                System.out.print("请输入信息(exit退出):"); // 向控制台输出提示信息
                String message = scanner.nextLine(); // 从控制台读取用户输入的行,并存储在message变量中
                channel.writeAndFlush(message + "\n"); // 将用户输入的信息通过Channel发送到服务器,并在信息末尾添加换行符以保证服务器能正确接收信息
                if (message.equals("exit")) { // 如果用户输入的信息是exit,则退出循环
                    break;
                }
                Thread.sleep(1000); // 等待1秒,等待客户端接收并打印服务器发送的消息
            }
            scanner.close(); // 关闭Scanner对象,释放资源
            channel.close(); // 关闭Channel对象,释放资源
        } finally { // finally块用于无论try块中的代码是否发生异常都会执行的操作
            group.shutdownGracefully(); // 优雅地关闭EventLoopGroup,释放资源
        }
    }

    private class ClientHandler extends SimpleChannelInboundHandler<String> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            System.out.println(Constant.CLIENT_PREFIX + msg);  // 将服务器的响应打印到控制台
        }
    }
}

运行截图:

先启动服务端,再启动客户端,在客户端控制台输入信息后点击回车,可以在服务器端看到服务器接收到客户端发送的信息;可以在客户端看到服务器返回的信息。

おすすめ

転載: blog.csdn.net/xijinno1/article/details/133215599