2021SC@SDUSC
目录
一、Netty核心组件简单介绍
Netty中的三大核心组件包括Channel、EventLoop、ChannelFuture。
1. Channel接口
基础的IO操作,如绑定、连接、读写等都依赖于底层网络传输所提供的原语,在Java的网络编程中,基础核心类是Socket,而Netty的Channel提供了一组API,极大地简化了直接与Socket进行操作的复杂性,并且Channel是很多类的父类,如EmbeddedChannel、LocalServerChannel、NioDatagramChannel、NioSctpChannel、NioSocketChannel等。
2. EventLoop接口
EventLoop定义了处理在连接过程中发生的事件的核心抽象,之后会进一步讨论,其中Channel、EventLoop、Thread和EventLoopGroup之间的关系如下图所示
可以看到:
· 一个EventLoopGroup包含一个或多个EventLoop。
· 一个EventLoop在生命中周期绑定到一个Thread上。
· EventLoop使用其对应的Thread处理IO事件。
· 一个Channel使用EventLoop进行注册。
· 一个EventLoop可被分配至一个或多个Channel。
3. ChannelFuture接口
Netty中的所有IO操作都是异步的,不会立即返回,需要在稍后确定操作结果。因此Netty提供了ChannelFuture,其addListener方法可以注册一个ChannelFutureListener,当操作完成时,不管成功还是失败,均会被通知。ChannelFuture存储了之后执行的操作的结果并且无法预测操作何时被执行,提交至Channel的操作按照被唤醒的顺序被执行。
在后面的博客中,会结合实例逐一分析。
二、netty初使用
使用netty实现简单的客户端和服务器,初步了解netty使用,更好的进行代码分析
服务端主要包含两部分内容,分为引导和实现服务器处理器。引导服务器完成服务端的创建,如指定端口和处理器等,并未涉及到服务端的具体逻辑,具体业务逻辑可以在处理器中完成。客户端部分的逻辑同服务器类似,也包含引导客户端和实现客户端处理器两部分,客户端连接服务端,并且接收服务端的消息,关闭连接等。
1. 引导服务端:
package test.MavenProject;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
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.net.InetSocketAddress;
public class EchoServer {
private int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws Exception {
//创建NioEventLoopGroup实例来处理事件,如接受连接,读写数据等。
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建ServerBootstrap实例。
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() { //设置childHandler来处理每一次连接。
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoServerHandler());
}
});
// 使用ServerBootstrap的bind方法进行绑定并同步直至其完成绑定。
ChannelFuture f = b.bind().sync();
System.out.println("Server start listen at " + port);
// 等待服务器socket关闭
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
// 指定服务端绑定的端口。
port = 8085;
}
new EchoServer(port).start();
}
}
2. 服务端逻辑实现
package test.MavenProject;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf bb = (ByteBuf) msg;
bb.markReaderIndex();
System.out.println("Server received: " + ByteBufUtil
.hexDump(bb.readBytes(bb.readableBytes())));
bb.resetReaderIndex();
ctx.write(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
当服务器接受到消息后,channelRead方法会被调用,具体消息为msg,用户可以对该消息进行处理,本例中首先将接收的消息进行转化后打印,然后将消息写入ctx中,其中值得注意的是需要标记读索引,然后恢复,否则写入的数据为空。channelReadComplete将之前写入客户端的消息刷新,待操作完成后关闭。exceptionCaught方法则会捕捉处理中的异常。
3. 引导客户端
package test.MavenProject;
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 java.net.InetSocketAddress;
public final class EchoClient {
private String host;
private int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
// 启动客户端
ChannelFuture f = b.connect().sync();
// 直到连接关闭
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
String host = "127.0.0.1";
int port = 8085;
if (args.length == 2) {
host = args[0];
port = Integer.parseInt(args[1]);
}
new EchoClient(host, port).start();
}
}
这里给出了服务端的地址和端口号,Bootstrap的connect函数将会根据指定的地址和端口号连接服务器。
4. 实现客户端逻辑
package test.MavenProject;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
/*
EchoClientHandler继承SimpleChannelInboundHandler,需要重写如下三个函数
· channelActive函数,在建立了与服务端的连接后该函数被调用。
· channelRead0函数,当接收到服务端发送来的消息后被调用。
· exceptionCaught函数,当处理发生异常时被调用。
*/
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
System.out.println("Client received: " + ByteBufUtil
.hexDump(in.readBytes(in.readableBytes())));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
当同服务器的连接建立后,客户端会发送消息至服务端,然后当接收到服务端发送来的消息时,打印该消息。
由于本应用依赖的jar文件使用maven构建,其pom.xml文件如下。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>NettyInAction</groupId>
<artifactId>com.hust.grid.leesf</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<version.jackson.core>2.6.3</version.jackson.core>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<optimize>true</optimize>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.32.Final</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${version.jackson.core}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${version.jackson.core}</version>
</dependency>
</dependencies>
</project>
运行:
启动EchoServer,等待客户端连接。
启动EchoClient,连接服务端并发送消息。
结果:
服务端启动:
客户端建立连接后发送消息:
服务端:
参考: