1. Netty写的Echo服务器
- 项目结构与依赖
- 确保已经正确设置了开发环境,包括JDK、IDE和Apache Maven。
- 在Maven项目中,需要添加Netty的依赖,以下是一个简单的POM配置示例:
<dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.90.Final</version> <!-- 请根据实际版本号修改 --> </dependency> </dependencies>
- 服务器代码实现
- 创建EchoServerHandler类
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { // 处理接收到的消息 ByteBuf in = (ByteBuf) msg; System.out.println("Server received: " + in.toString(io.netty.util.CharsetUtil.UTF_8)); // 将接收到的消息写回给客户端 ctx.write(in); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { // 当所有数据都被读取完成后,刷新缓冲区并关闭连接 ctx.writeAndFlush(io.netty.buffer.Unpooled.EMPTY_BUFFER) .addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 处理异常 cause.printStackTrace(); ctx.close(); } }
channelRead
方法:当从客户端接收到消息时,会调用这个方法。它将接收到的消息转换为ByteBuf
对象,并打印出来。然后,将消息写回给客户端。channelReadComplete
方法:当所有数据都被读取完成后,会调用这个方法。它使用writeAndFlush
方法将一个空的ByteBuf
写回给客户端,并添加一个ChannelFutureListener
,当写操作完成后,关闭连接。exceptionCaught
方法:当在处理过程中发生异常时,会调用这个方法。它打印异常的堆栈跟踪信息,并关闭连接。
- 创建EchoServer类
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; public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public static void main(String[] args) throws Exception { if (args.length!= 1) { System.err.println("Usage: EchoServer <port>"); System.exit(1); } int port = Integer.parseInt(args[0]); new EchoServer(port).start(); } public void start() throws Exception { // 创建EventLoopGroup,用于处理网络事件 EventLoopGroup group = new NioEventLoopGroup(); try { // 创建ServerBootstrap,用于引导服务器启动 ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(group) .channel(NioServerSocketChannel.class) .localAddress(new io.netty.socket.InetSocketAddress(port)) .childHandler(new ChannelInitializer<io.netty.channel.socket.nio.SocketChannel>() { @Override protected void initChannel(io.netty.channel.socket.nio.SocketChannel ch) throws Exception { // 将EchoServerHandler添加到ChannelPipeline中 ch.pipeline().addLast(new EchoServerHandler()); } }); // 绑定服务器,并等待绑定完成 ChannelFuture future = bootstrap.bind().sync(); // 等待服务器关闭 future.channel().closeFuture().sync(); } finally { // 关闭EventLoopGroup,释放资源 group.shutdownGracefully().sync(); } } }
- 构造函数:初始化Echo服务器,指定服务器要监听的端口。
main
方法:程序的入口点,解析命令行参数,创建EchoServer实例,并调用start
方法启动服务器。start
方法- 创建EventLoopGroup:使用
NioEventLoopGroup
创建一个事件循环组,用于处理网络事件。 - 创建ServerBootstrap:使用
ServerBootstrap
引导服务器启动,设置事件循环组、Channel类型和本地地址。 - 设置ChildHandler:通过
childHandler
方法设置一个ChannelInitializer
,用于初始化每个新连接的ChannelPipeline。在initChannel
方法中,将EchoServerHandler
添加到ChannelPipeline中,以处理客户端的消息。 - 绑定服务器并等待完成:使用
bind
方法绑定服务器到指定的端口,并使用sync
方法阻塞当前线程,直到绑定完成。 - 等待服务器关闭:通过
future.channel().closeFuture().sync()
方法等待服务器关闭,当服务器的Channel关闭时,这个Future会完成。 - 关闭EventLoopGroup:在服务器关闭后,使用
group.shutdownGracefully().sync()
方法关闭EventLoopGroup,释放资源。
- 创建EventLoopGroup:使用
- 创建EchoServerHandler类
- 运行示例
- 编译和运行代码:可以使用Maven命令
mvn clean package
编译项目,然后使用java -jar target/echo-server-1.0-SNAPSHOT.jar <port>
运行服务器,其中<port>
是服务器要监听的端口号。 - 测试功能:可以使用Netty提供的
Telnet
客户端或者其他网络工具来连接到服务器,并发送一些消息。服务器会接收到这些消息,并将它们回送给客户端。
- 编译和运行代码:可以使用Maven命令
以下是一个使用Telnet
客户端测试Echo服务器的示例:
- 打开终端
- 在命令行中输入
telnet localhost <port>
,其中<port>
是服务器监听的端口号。
- 在命令行中输入
- 发送消息
- 连接成功后,可以在终端中输入一些消息,然后按下回车键。服务器会接收到这些消息,并将它们回送给客户端。
- 关闭连接
- 在终端中输入
quit
命令,然后按下回车键,关闭与服务器的连接。
- 在终端中输入
通过以上步骤,就可以使用Netty实现一个简单的Echo服务器,并进行测试。这个例子展示了Netty的基本使用方法,包括如何创建服务器、处理客户端连接和消息、以及如何关闭服务器。
2. 服务器端组件使用
2.1 EventLoopGroup
- 创建:在
EchoServer
类的start
方法中,使用NioEventLoopGroup
创建一个事件循环组,用于处理网络事件。
EventLoopGroup group = new NioEventLoopGroup();
- 作用:它是Netty的核心组件之一,用于处理网络连接的接受、数据的读取和写入等I/O操作。一个
NioEventLoopGroup
包含一个或多个NioEventLoop
,每个NioEventLoop
都有自己的线程,可以并发地处理多个网络连接。
2.2 ServerBootstrap
- 创建与配置:在
start
方法中创建ServerBootstrap
实例,并进行一系列配置。
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
.channel(io.netty.channel.nio.NioServerSocketChannel.class)
.localAddress(new io.netty.socket.InetSocketAddress(port))
.childHandler(new ChannelInitializer<io.netty.channel.socket.nio.SocketChannel>() {
@Override
protected void initChannel(io.netty.channel.socket.nio.SocketChannel ch) throws Exception {
// 将EchoServerHandler添加到ChannelPipeline中
ch.pipeline().addLast(new EchoServerHandler());
}
});
group
方法:设置用于处理服务器端套接字的EventLoopGroup
。channel
方法:指定服务器使用的Channel
类型,这里使用NioServerSocketChannel
,它是基于NIO的服务器套接字通道实现。localAddress
方法:设置服务器要监听的本地地址和端口。childHandler
方法:设置一个ChannelInitializer
,用于初始化每个新连接的SocketChannel
。在initChannel
方法中,将自定义的EchoServerHandler
添加到ChannelPipeline
中。
2.3 ChannelPipeline与ChannelHandler
- ChannelPipeline:它是一个处理管道,用于组织和处理流经
Channel
的数据。当数据在Channel
中流动时,会依次经过ChannelPipeline
中的各个ChannelHandler
。 - ChannelHandler:在这个例子中,自定义了
EchoServerHandler
类,它继承自ChannelInboundHandlerAdapter
,用于处理客户端连接的入站数据。channelRead
方法:当从客户端接收到消息时,会调用这个方法。它将接收到的消息转换为ByteBuf
对象,并打印出来,然后将消息写回给客户端。
public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf in = (ByteBuf) msg; System.out.println("Server received: " + in.toString(io.netty.util.CharsetUtil.UTF_8)); ctx.write(in); }
channelReadComplete
方法:当所有数据都被读取完成后,会调用这个方法。它使用writeAndFlush
方法将一个空的ByteBuf
写回给客户端,并添加一个ChannelFutureListener
,当写操作完成后,关闭连接。
public void channelReadComplete(ChannelHandlerContext ctx) { ctx.writeAndFlush(io.netty.buffer.Unpooled.EMPTY_BUFFER) .addListener(ChannelFutureListener.CLOSE); }
exceptionCaught
方法:当在处理过程中发生异常时,会调用这个方法。它打印异常的堆栈跟踪信息,并关闭连接。
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); }
3. 启动与关闭服务器
- 启动服务器:在
start
方法中,使用bind
方法绑定服务器到指定的端口,并使用sync
方法阻塞当前线程,直到绑定完成。
ChannelFuture future = bootstrap.bind().sync();
- 等待服务器关闭:通过
future.channel().closeFuture().sync()
方法等待服务器关闭,当服务器的Channel
关闭时,这个Future
会完成。 - 关闭EventLoopGroup:在服务器关闭后,使用
group.shutdownGracefully().sync()
方法关闭EventLoopGroup
,释放资源。