先说好,这里所说的入门篇,并不是教你们如何入门,而是在我研读部分源码后,写了一个基本的demo,实现了客户端和服务端的交互,在放出我写的源码之前,写简单介绍下netty中的一些核心概念和核心类。
NIO模型
NIO是相对于BIO的一个概念,BIO是阻塞IO,不管进行accept、connect、read、write操作都可能导致阻塞。NIO就是大家常说的非阻塞模型,但是我感觉还是采用多路复用的思想来理解更合适。
如果每一个连接都关联一个线程,那么一个线程很容易由于当前连接没有就绪的事件而阻塞。但是呢,如果一个线程能够同时监控多个连接的事件,那么只要有一个连接就绪了,当前线程就能就行相应的事件操作,但是监控的所有连接都没有就绪事件,当前线程也只能空转了。
上面这张图是nettry服务端的最常用的io模型,主线程进行accept操作,当收到新的连接请求时,会将新来的连接交给子线程来进行io操作。
事件驱动模型
这个词还是常听说的,在io中,有几个事件,accept事件、connect事件、read事件、write事件,当这些事件发生时,才会驱动io线程去做事情,不会阻塞于单一的连接上。
下面介绍一些核心类,netty中的核心组件非常多,下面就抽一些常用到的类来简单介绍下,后面就直接放出我的demo了,以后会根据netty中的细节写一些东西。
Channel
Channel是一个接口,这个会映射到Socket上,看到Channel,大家一定要想到传统编程中的Socket。
一些常用的实现类:
- NioSocketChannel
类似于Socket
- NioServerSocketChannel
类似于ServerSocket
- EpollSocketChannel
和NioSocketChannel类似,只是采用了epoll模式的io,具有更高的性能。
- EpollServerSocketChannel
类似于NioServerSocketChannel,只是采用了epoll模式的io,具有更高的性能。
上面也就说了几种常见的,netty包中定义了非常多的Channel的抽象类和实现类。
EventLoopGroup
是一个接口,可以注册Channel,在之后的事件循环中,可以查看哪些Channel上有事件就绪了,对于一个EventLoop通常就是用一个线程处理内部的select操作,当然可以指定多个EventLoop,每一个EventLoop处理一部分Channel集合。
一些常见的实现类:
- NioEventLoopGroup
- NioEventLoop
- EpollEventLoopGroup
- EpollEventLoop
ServerBootStrap
这个类是服务端的启动入口
BootStrap
这个类是客户端的启动入口
ChannelHandler
这个接口的实现是我们最需要关注的,其实我们进行netty开发,最主要的就是写一堆Handler,不管是编码器、解码器都是一个个Handler。
主要的子类包含如下:
- ChannelInboundHandler
- ChannelOutboundHandler
- ChannelInboundHandlerAdapter
- ChannelOutboundHandlerAdapter
- ChannelDuplexHandler
- ChannelInitializer
- SimpleChannelInboundHandler
包含Inbound的类是入栈事件处理的handler,比如读数据、accept、connect。
包含Outbound的类是出栈事件处理的handler,比如写数据。
上面就是netty中一些核心的东西,netty还包含了很多自定义的编码器、解码器、基于不同协议实现的handler等等。
下面就主要上我的demo了。
我这个代码就是在客户端定义一个枚举值,包含人的7大情绪,然后把数据传到服务端,服务端解析出对应的枚举值,根据枚举值的定义反馈给客户端一个心灵鸡汤的描述。
包含如下几个类:
- Client : 客户端的启动入口
- EmotionClientHandler:客户端的handler
- EmotionDecoder:一个解码器
- EmotionEncoder:一个编码器
- EmotionEnums:一个枚举值类,客户端将枚举值类传给服务端
- EmotionServerHandler:服务端的handler
- Invoker:服务端收到客户端的数据后,进行处理的类
- Server:服务端的启动入口
Client
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* @date 2019-12-7 下午 7:43
**/
public class Client {
private String ip;
private int port;
public Client(String ip, int port) {
this.ip = ip;
this.port = port;
}
public static void main(String[] args) {
for(int i = 0; i < 2; i++) {
new Thread(() -> {
try {
new Client("127.0.0.1", 8889).run();
}catch (Exception e) {}
}).start();
}
}
public void run() throws Exception {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup(2);
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new EmotionEncoder());
pipeline.addLast(new EmotionDecoder());
pipeline.addLast(new EmotionClientHandler());
}
})
.option(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.connect(ip, port).sync();
future.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
EmotionClientHandler
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Random;
/**
* @date 2019-12-7 下午 5:04
**/
public class EmotionClientHandler extends SimpleChannelInboundHandler<String> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channel active");
new Thread(() -> {
EmotionEnums[] emotionEnums = EmotionEnums.values();
Random random = new Random();
while (true) {
try {
EmotionEnums emotion = emotionEnums[random.nextInt(emotionEnums.length)];
ctx.writeAndFlush(emotion);
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("来自于心灵鸡汤的反馈信息:" + msg);
}
}
EmotionDecoder
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* @date 2019-12-7 下午 3:31
**/
public class EmotionDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if(in.readableBytes() < 6) {
return;
}
in.markReaderIndex();
Short high = in.getUnsignedByte(0);
Short low = in.getUnsignedByte(1);
if(high.byteValue() != (byte)0xBA || low.byteValue() != (byte)0xBE) {
throw new RuntimeException("数据格式不合法");
}
in.skipBytes(2);
int msgType = in.readByte();
int remainLen = in.readInt();
if(remainLen > in.readableBytes()) {
in.resetReaderIndex();
return;
}
if(msgType == 1) {
int type = in.readInt();
byte[] mBytes = new byte[in.readInt()];
in.readBytes(mBytes);
String msg = new String(mBytes);
EmotionEnums emotionEnums = EmotionEnums.get(type, msg);
if (emotionEnums != null) {
out.add(emotionEnums);
}
} else if(msgType == 2) {
byte[] mBytes = new byte[remainLen];
in.readBytes(mBytes);
out.add(new String(mBytes));
} else {
throw new RuntimeException("数据格式不对");
}
}
}
EmotionEncoder
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @date 2019-12-7 下午 3:32
*
* magic: 0xBABE
**/
public class EmotionEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
if(msg instanceof EmotionEnums) {
EmotionEnums baseMsg = (EmotionEnums)msg;
int type = baseMsg.getType();
String m = baseMsg.getMsg();
// 先写魔数0xBABE
out.writeByte((byte)0xBA);
out.writeByte((byte)0xBE);
out.writeByte(1);
out.writeInt(0);
// 写枚举类型值
out.writeInt(type);
byte[] mByte = m.getBytes("utf-8");
// 写描述值的长度
out.writeInt(mByte.length);
// 写描述值的字节数组
out.writeBytes(mByte);
out.setInt(3, 4 + mByte.length);
} else if(msg instanceof String) {
String baseMsg = (String)msg;
out.writeBytes(new byte[] {(byte)0xBA, (byte)0xBE});
out.writeByte(2);
byte[] mBytes = baseMsg.getBytes("utf-8");
out.writeInt(mBytes.length);
out.writeBytes(mBytes);
} else {
System.out.println("无效的数据类型");
}
}
}
EmotionEnums
import java.util.HashMap;
import java.util.Map;
/**
* @date 2019-12-7 下午 3:33
* 人的七大情绪的枚举
**/
public enum EmotionEnums {
HAPPY(1, "喜"),
ANGRY(2, "怒"),
WORRIED(3, "忧"),
THOUGHTFUL(4, "思"),
SAD(5, "悲"),
FEARFUL(6, "恐"),
FRIGHTENED(7, "惊");
private EmotionEnums(int type, String msg) {
this.type = type;
this.msg = msg;
}
private static final Map<String, EmotionEnums> map = new HashMap<>();
static {
for(EmotionEnums e : EmotionEnums.values()) {
map.put(e.type + "_" + e.msg, e);
}
}
private int type;
private String msg;
public int getType() {
return type;
}
public String getMsg() {
return msg;
}
public static EmotionEnums get(int type, String msg) {
return map.get(type + "_" + msg);
}
}
EmotionServerHandler
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @date 2019-12-7 下午 4:53
**/
public class EmotionServerHandler extends SimpleChannelInboundHandler<EmotionEnums> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, EmotionEnums msg) throws Exception {
String response = Invoker.invoke(msg);
ctx.writeAndFlush(response);
}
}
Invoker
/**
* @date 2019-12-7 下午 4:55
**/
public class Invoker {
public static String invoke(EmotionEnums emotionEnums) {
if(emotionEnums == EmotionEnums.HAPPY) {
return "人生苦短,请快乐的生活吧,没有什么坎是过不去的!";
} else if (emotionEnums == EmotionEnums.ANGRY) {
return "不要生气,会长皱纹的";
} else if(emotionEnums == EmotionEnums.FEARFUL) {
return "请克服内心的恐惧,无惧未来";
} else if(emotionEnums == EmotionEnums.FRIGHTENED) {
return "对不起呀,吓到你了";
} else if(emotionEnums == EmotionEnums.SAD) {
return "男儿有泪不轻弹,只是未到伤心处";
} else if(emotionEnums == EmotionEnums.THOUGHTFUL) {
return "多思考,才能更快的成长";
} else if(emotionEnums == EmotionEnums.WORRIED) {
return "没什么好担心的,船到桥头自然直";
}
return "";
}
}
Server
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* @date 2019-12-7 下午 5:20
**/
public class Server {
private int port;
public Server(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
new Server(8889).run();
}
public void run() throws Exception {
ServerBootstrap bootstrap = new ServerBootstrap();
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup(8);
try {
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new EmotionEncoder());
pipeline.addLast(new EmotionDecoder());
pipeline.addLast(new EmotionServerHandler());
}
})
.option(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
返回结果截图如下:
简单的netty代码还是很好实现的,如果真的想用netty实现比较有用的功能,获取可以参考dubbo、jsf、各种基于netty实现的rpc框架。