為什么Netty受歡迎?
如第一部分所述,netty是一款收到大公司青睞的框架,在我看來,netty能夠受到青睞的原因有三:
- 并發高
- 傳輸快
- 封裝好
Netty為什么并發高
Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)開發的網絡通信框架,對比于BIO(Blocking I/O,阻塞IO),他的并發性能得到了很大提高,兩張圖讓你了解BIO和NIO的區別:
image.png
image.png
這兩圖可以看出,NIO的單線程能處理連接的數量比BIO要高出很多,而為什么單線程能處理更多的連接呢?原因就是圖二中出現的
Selector
。當一個連接建立之后,他有兩個步驟要做,第一步是接收完客戶端發過來的全部數據,第二步是服務端處理完請求業務之后返回response給客戶端。NIO和BIO的區別主要是在第一步。
在BIO中,等待客戶端發數據這個過程是阻塞的,這樣就造成了一個線程只能處理一個請求的情況,而機器能支持的最大線程數是有限的,這就是為什么BIO不能支持高并發的原因。
而NIO中,當一個Socket建立好之后,Thread并不會阻塞去接受這個Socket,而是將這個請求交給Selector,Selector會不斷的去遍歷所有的Socket,一旦有一個Socket建立完成,他會通知Thread,然后Thread處理完數據再返回給客戶端——這個過程是阻塞的,這樣就能讓一個Thread處理更多的請求了。
下面兩張圖是基于BIO的處理流程和netty的處理流程,輔助你理解兩種方式的差別:
image.png
image.png
Netty為什么傳輸快
Netty的傳輸快其實也是依賴了NIO的一個特性——零拷貝。我們知道,Java的內存有堆內存、棧內存和字符串常量池等等,其中堆內存是占用內存空間最大的一塊,也是Java對象存放的地方,一般我們的數據如果需要從IO讀取到堆內存,中間需要經過Socket緩沖區,也就是說一個數據會被拷貝兩次才能到達他的的終點,如果數據量大,就會造成不必要的資源浪費。
Netty針對這種情況,使用了NIO中的另一大特性——零拷貝,當他需要接收數據的時候,他會在堆內存之外開辟一塊內存,數據就直接從IO讀到了那塊內存中去,在netty里面通過ByteBuf可以直接對這些數據進行直接操作,從而加快了傳輸速度。
為什么說Netty封裝好?
要說Netty為什么封裝好,這種用文字是說不清的,直接上代碼:
- 阻塞I/O
public class PlainOioServer {
public void serve(int port) throws IOException {
final ServerSocket socket = new ServerSocket(port); //1
try {
for (;;) {
final Socket clientSocket = socket.accept(); //2
System.out.println("Accepted connection from " + clientSocket);
new Thread(new Runnable() { //3
@Override
public void run() {
OutputStream out;
try {
out = clientSocket.getOutputStream();
out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8"))); //4
out.flush();
clientSocket.close(); //5
} catch (IOException e) {
e.printStackTrace();
try {
clientSocket.close();
} catch (IOException ex) {
// ignore on close
}
}
}
}).start(); //6
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 非阻塞IO
public class PlainNioServer {
public void serve(int port) throws IOException {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
ss.bind(address); //1
Selector selector = Selector.open(); //2
serverChannel.register(selector, SelectionKey.OP_ACCEPT); //3
final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
for (;;) {
try {
selector.select(); //4
} catch (IOException ex) {
ex.printStackTrace();
// handle exception
break;
}
Set<SelectionKey> readyKeys = selector.selectedKeys(); //5
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
if (key.isAcceptable()) { //6
ServerSocketChannel server =
(ServerSocketChannel)key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_WRITE |
SelectionKey.OP_READ, msg.duplicate()); //7
System.out.println(
"Accepted connection from " + client);
}
if (key.isWritable()) { //8
SocketChannel client =
(SocketChannel)key.channel();
ByteBuffer buffer =
(ByteBuffer)key.attachment();
while (buffer.hasRemaining()) {
if (client.write(buffer) == 0) { //9
break;
}
}
client.close(); //10
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
// 在關閉時忽略
}
}
}
}
}
}
- Netty
public class NettyOioServer {
public void server(int port) throws Exception {
final ByteBuf buf = Unpooled.unreleasableBuffer(
Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8")));
EventLoopGroup group = new OioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); //1
b.group(group) //2
.channel(OioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {//3
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { //4
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);//5
}
});
}
});
ChannelFuture f = b.bind().sync(); //6
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync(); //7
}
}
}
相比之下,netty的代碼量少了太多,而且netty還提供自定義協議。
名詞解釋
image.png
- Channel,表示一個連接,可以理解為每一個請求,就是一個Channel。
- ChannelHandler,核心處理業務就在這里,用于處理業務請求。
- ChannelHandlerContext,用于傳輸業務數據。
- ChannelPipeline,用于保存處理過程需要用到的ChannelHandler和ChannelHandlerContext。
- ByteBuf是一個存儲字節的容器,最大特點就是使用方便,它既有自己的讀索引和寫索引,方便你對整段字節緩存進行讀寫,也支持get/set,方便你對其中每一個字節進行讀寫,他的數據結構如下圖所示:
image.png
以上摘抄http://www.lxweimin.com/p/b9f3f6a16911
理解了netty的理論知識,下面就是我們實戰的時候了。
服務端的搭建
NettyServer
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 NettyServer {
private static final int port = 6789; //設置服務端端口
private static EventLoopGroup group = new NioEventLoopGroup(); // 通過nio方式來接收連接和處理連接
private static ServerBootstrap b = new ServerBootstrap();
/**
* Netty創建全部都是實現自AbstractBootstrap。
* 客戶端的是Bootstrap,服務端的則是 ServerBootstrap。
**/
public static void main(String[] args) throws InterruptedException {
try {
b.group(group);
b.channel(NioServerSocketChannel.class);
b.childHandler(new NettyServerFilter()); //設置過濾器
// 服務器綁定端口監聽
ChannelFuture f = b.bind(port).sync();
System.out.println("服務端啟動成功...");
// 監聽服務器關閉監聽
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully(); ////關閉EventLoopGroup,釋放掉所有資源包括創建的線程
}
}
}
new ChannelInitializer()
public class NettyServerFilter extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
// 以("\n")為結尾分割的 解碼器
ph.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
// 解碼和編碼,應和客戶端一致
ph.addLast("decoder", new StringDecoder());
ph.addLast("encoder", new StringEncoder());
ph.addLast("handler", new NettyServerHandler());// 服務端業務邏輯
}
}
NettyServerHandler
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {
/*
* 收到消息時,返回信息
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg)
throws Exception {
// 收到消息直接打印輸出
System.out.println("服務端接受的消息 : " + msg);
if("quit".equals(msg)){//服務端斷開的條件
ctx.close();
}
Date date=new Date();
// 返回客戶端消息
ctx.writeAndFlush(date+"\n");
}
/*
* 建立連接時,返回消息
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("連接的客戶端地址:" + ctx.channel().remoteAddress());
ctx.writeAndFlush("客戶端"+ InetAddress.getLocalHost().getHostName() + "成功與服務端建立連接! \n");
super.channelActive(ctx);
}
}
客戶端搭建
NettyClient
public class NettyClient {
public static String host = "127.0.0.1"; //ip地址
public static int port = 6789; //端口
/// 通過nio方式來接收連接和處理連接
private static EventLoopGroup group = new NioEventLoopGroup();
private static Bootstrap b = new Bootstrap();
private static Channel ch;
/**
* Netty創建全部都是實現自AbstractBootstrap。
* 客戶端的是Bootstrap,服務端的則是 ServerBootstrap。
**/
public static void main(String[] args) throws InterruptedException, IOException {
System.out.println("客戶端成功啟動...");
b.group(group);
b.channel(NioSocketChannel.class);
b.handler(new NettyClientFilter());
// 連接服務端
ch = b.connect(host, port).sync().channel();
star();
}
public static void star() throws IOException{
String str="Hello Netty";
ch.writeAndFlush(str+ "\r\n");
System.out.println("客戶端發送數據:"+str);
}
}
new ChannelInitializer()
public class NettyClientFilter extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
/*
* 解碼和編碼,應和服務端一致
* */
ph.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
ph.addLast("decoder", new StringDecoder());
ph.addLast("encoder", new StringEncoder());
ph.addLast("handler", new NettyClientHandler()); //客戶端的邏輯
}
}
NettyClientHandler
public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("客戶端接受的消息: " + msg);
}
//
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("正在連接... ");
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("連接關閉! ");
super.channelInactive(ctx);
}
}
最終效果
服務端
image.png
客戶端
image.png
項目地址https://github.com/DespairYoke/netty/tree/master/netty-hello