前言(可跳过)
我觉的学习一个新技术,最开始的开始不是看什么概念,而是直接撸示例代码。在看概念性的内容和实践中找到一种平衡,不要一直连续看概念性的内容,也不要只是埋在代码里。
1、自己debug看看,把代码运行的流程仔细的看看,每一行,每一个陌生的类型和方法签名。
如果示例代码给的好,让你可以运行,然后debug起来,就已经入门了。
2、接着就是看一些其他的特性,对关键的特性或者你感兴趣的特性的原理进行学习,基本上就是高级使用了。当然这一部分根据你的基础可能会时间很长,可能不长。
因为高级特性往往涉及到底层的复杂实现,或者操作系统内核调用的实现(这个一般知道具体哪里使用什么系统调用,这个系统调用是做什么的即可)。高级特性还会涉及到一些设计模式(设计原则和各种设计模式)、数据结构和算法的使用(数组、队列、链表、树等等)、基于硬件API的优化(字宽对齐)。
说了这么多,就是想说这篇文章会直接上代码:),源码
TimeServer.java
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;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
public class TimeServer {
public void bind(int port) throws Exception{
//配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
//SocketChannel启动器
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new ChildChannelHandler());
//绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
//等待服务端监听端口关闭
f.channel().closeFuture().sync();
}finally {
//优雅推出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if(args != null && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
//do nothing
}
}
new TimeServer().bind(port);
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//新增两个解码器
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new TimeServerHandler());
}
}
}
使用Netty开发服务端的步骤大体如上面的代码,我一开始看了不适应,一脸懵,但是熟悉就好,不要纠结于代码形式:
1、配置两个线程组bossGroup和workerGroup,为什么这样写,这个是由Netty选用的线程模型决定的,可以看Netty线程模型了解更多。
2、接下来的大部分工作是在try方法体内进行的,首先new一个ServerBootstrap启动器对象
3、将两个线程组配置到启动器中
4、将NioServerSocketChannel配置到启动器中,Netty是对JavaNIO的封装,服务端通信使用NioServerSocketChannel,NioServerSocketChannel是基于NIO selector的实现,包装了NIO中的ServerSocketChannel。
5、设置backlog为1024(backlog概念待查,目前不影响进度)
6、配置绑定IO事件的处理器,一般用来写业务逻辑。
7、使用同步方式绑定到一个端口,返回一个ChannelFuture
8、通过ChannelFuture同步的关闭channel通信
9、最后finally中关闭两个线程组
ChildChannelHandler这里作为TimeServer.java的内部类
ChildChannelHandler内部,在channel的pipeline上加了两个解码处理器,后续会讲到其作用,还有一个自定义处理器TimeServerHandler。
何时使用内部类?当A类对B类依赖很高,且B类不会被其他类依赖,就可以将B类当做A的内部类,这体现了封装的思想,尽可能的降低类的可见性。
如果要问,为什么不直接把B类的处理逻辑直接写在A类的某个单独抽出来的方法中?
这又是一种封装的思路了,如果A类中的某些方法做的事情违反了单一职责原则,就值得思考下是不是可以将该方法写成一个类,当然并不总是必须的,不必为了抽象而抽象。
TimeServerHandler.java
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
private int counter;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String body = (String) msg;
System.out.println("The time server receive order : " + body
+ " ; the counter is : " + ++counter);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(
System.currentTimeMillis()).toString() : "BAD ORDER";
currentTime = currentTime + System.getProperty("line.separator");
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.writeAndFlush(resp);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
TimeServerHandler继承自ChannelInboundHandlerAdapter
重写了两个方法channelRead和exceptionCaught,方法的意思就是字面意思。
channelRead内部:
1、首先将客户端传过来的msg转成字符串
2、输出带有客户端传的信息和count信息的字符串到控制台
3、如果客户端传过来的信息和QUERY TIME ORDER相等(不区分大小写),就返回当前事件,否则返回BAD ORDER
4、将currentTime的二进制数据构造成一个大端对齐的ByteBuf
5、将缓冲区的数据从channel的一端发送到另一端。
TimeClient.java
import io.netty.bootstrap.Bootstrap;
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.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
public class TimeClient {
public void connect(int port,String host) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new TimeClientHandler());
}
});
//发起异步连接操作
ChannelFuture f = b.connect(host,port).sync();
//等待客户端链路关闭
f.channel().closeFuture().sync();
}finally {
//优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
int port = 8080;
if(args != null && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
}
}
new TimeClient().connect(port,"localhost");
}
}
1、创建一个EvenLoop线程组
2、创建一个Bootstrap启动实例
3、在Bootstrap实例上配置线程组、channel、option和handler,和服务端的使用很类似
handler中配置了两个Netty提供的解码器和一个自定义解码器
4、使用Bootstrap实例同步连接IP和端口
5、等待客户端链路关闭
6、finlly中退出线程组
TimeClientHandler.java
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.logging.Logger;
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());
private int counter;
private byte[] req;
public TimeClientHandler(){
req = ("QUERY TIME ORDER" + System.getProperty("line.separator"))
.getBytes();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf message = null;
for (int i = 0; i < 100; i++) {
message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String) msg;
System.out.println("Now is : " + body + " ; the counter is : "
+ ++counter);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 释放资源
logger.warning("Unexpected exception from downstream : "
+ cause.getMessage());
ctx.close();
}
}
TimeClientHandler也是继承自ChannelInboundHandlerAdapter
重写了三个方法:
channelActive中向channel另一端发送100条信息
channelRead负责读取channel另一端发过来的数据
exceptionCaught负责处理异常