什么是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); // 将服务器的响应打印到控制台
}
}
}
运行截图:
先启动服务端,再启动客户端,在客户端控制台输入信息后点击回车,可以在服务器端看到服务器接收到客户端发送的信息;可以在客户端看到服务器返回的信息。