先說好,這裏所說的入門篇,並不是教你們如何入門,而是在我研讀部分源碼後,寫了一個基本的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框架。