公司使用thrift作為RPC框架,其中通信框架使用netty取代thrift自帶的通信,所以看了官網(wǎng)文檔,然后翻譯下以備學(xué)習(xí)。
問題根源
如今,我們使用通用應(yīng)用程序或者類庫來實(shí)現(xiàn)系統(tǒng)之間地互相訪問。舉個(gè)栗子,我們經(jīng)常使用HTTP客戶端庫從Web服務(wù)器檢索信息,或者通過web service進(jìn)行遠(yuǎn)程過程調(diào)用。
??但是,一個(gè)通用的協(xié)議或者它的實(shí)現(xiàn)有時(shí)候并不能很好地進(jìn)行擴(kuò)展,就像我們不用一個(gè)通用的HTTP服務(wù)器去交換大文件、e-mail、財(cái)務(wù)信息和多人游戲數(shù)據(jù)這種接近實(shí)時(shí)傳輸?shù)南?。我們真正需要的其?shí)是一個(gè)為了特定的目的而專門實(shí)現(xiàn)的高度優(yōu)化的協(xié)議。再舉個(gè)栗子,你可能希望實(shí)現(xiàn)一個(gè)針對基于Ajax的聊天應(yīng)用程序、媒體流或大型文件傳輸而進(jìn)行優(yōu)化的HTTP服務(wù)器,你甚至可以針對自己的需求去設(shè)計(jì)和實(shí)現(xiàn)一個(gè)全新的協(xié)議。
??另一個(gè)不可避免的情況是,當(dāng)你不得不處理遺留的專有協(xié)議時(shí),必須確保能夠兼容老系統(tǒng)。在這種情況下,真正重要的如何快速實(shí)現(xiàn)一個(gè)不影響應(yīng)用程序穩(wěn)定性和性能的協(xié)議。
解決方案
Netty致力于提供一個(gè)事件驅(qū)動(dòng)的異步網(wǎng)絡(luò)應(yīng)用框架和工具,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序。換而言之,Netty就是一個(gè)NIO框架,能夠讓用戶簡單快速地開發(fā)一個(gè)網(wǎng)絡(luò)應(yīng)用程序,比如C/S。它大大簡化和流化(streamlines)了TCP和UDP的Socket服務(wù)器的開發(fā)這類的網(wǎng)絡(luò)編程。
??“快速和簡單”并不意味著最終的應(yīng)用程序?qū)⒃馐芸删S護(hù)性或性能問題。Netty是一個(gè)精心設(shè)計(jì)的框架,它從許多協(xié)議的實(shí)現(xiàn)中吸收了很多的經(jīng)驗(yàn)比如FTP、SMTP、HTTP、許多二進(jìn)制和基于文本的傳統(tǒng)協(xié)議,Netty在不降低開發(fā)效率、性能、穩(wěn)定性、靈活性情況下,成功地找到了解決方案。
??有些用戶可能已經(jīng)找到其他的號稱有同樣的優(yōu)勢的網(wǎng)絡(luò)框架,所以你可能想問是什么讓Netty與眾不同。答案是Netty的哲學(xué)設(shè)計(jì)理念。Netty從出生那天起,就致力于為用戶提供API和實(shí)現(xiàn)方面最舒適的體驗(yàn)。這并不是什么有形的東西,但是隨著你閱讀這本指南并開始玩轉(zhuǎn)Netty,你會(huì)意識到,這種哲學(xué)會(huì)讓你的生活更加輕松。
讓我們開始吧
這個(gè)章節(jié)會(huì)介紹Netty核心的結(jié)構(gòu),并通過一些簡單的例子來幫助你快速入門。當(dāng)你讀完本章節(jié)你馬上就可以用Netty寫出一個(gè)客戶端和服務(wù)端。
開始之前
想要運(yùn)行這一章的栗子,你只需要兩個(gè)東西:最新版本的Netty和1.6版本以上的JDK。你可以在這里 獲取最新版本的Netty,至于JDK,開心就好。
??隨著你繼續(xù)閱讀,你可能會(huì)有更多的關(guān)于本章介紹的類的問題,當(dāng)你想要了解更多的時(shí)候,請移步API指南。為了方便起見,文檔中的所有類名都會(huì)鏈接到API指南上。
寫一個(gè)Discard Server
這個(gè)世上最簡單的協(xié)議可能不是“Hello World”而是Discard。這個(gè)協(xié)議只負(fù)責(zé)接受并拋棄所有收到數(shù)據(jù)而不發(fā)送任何的響應(yīng)。
??實(shí)現(xiàn)一個(gè)Discard協(xié)議,你唯一需要做的事就是忽略所有收到的數(shù)據(jù)。讓我們直接從實(shí)現(xiàn)handler開始,handler就是Netty用來處理I/O時(shí)間的。
package io.netty.example.discard;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Handles a server-side channel.
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
// Discard the received data silently.
((ByteBuf) msg).release(); // (3)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
1. DiscardServerHandler繼承ChannelInboundHandlerAdapter,這個(gè)類是ChannelInboundHandler的一個(gè)實(shí)現(xiàn)。ChannelInboundHandler提供了各種你可以重寫的事件處理方法。目前來說,與自己實(shí)現(xiàn)這個(gè)handler接口相比,繼承ChannelInboundHandlerAdapter已經(jīng)足夠了。
??2. 在這里,我們重寫了channelRead()方法,每當(dāng)接收到新的客戶端數(shù)據(jù)時(shí),接受的信息都會(huì)調(diào)用這個(gè)方法。在這個(gè)栗子中,接受的信息類型是ByteBuf。
??3. 為了實(shí)現(xiàn)Discard協(xié)議,這個(gè)handler需要忽略掉接收到的信息。ByteBuf是一種引用計(jì)數(shù)的類型,所以必須通過release()方法被準(zhǔn)確地釋放掉。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
// Do something with msg
} finally {
ReferenceCountUtil.release(msg);
}
}
1. exceptionCaught()事件處理方法是當(dāng)出現(xiàn)Throwable對象才會(huì)被調(diào)用,即當(dāng)Netty由于IO錯(cuò)誤或者處理器在處理事件時(shí)拋出的異常時(shí)。在大部分情況下,捕獲的異常應(yīng)該被記錄下來并且把關(guān)聯(lián)的channel給關(guān)閉掉。然而這個(gè)方法的處理方式會(huì)在遇到不同異常的情況下有不同的實(shí)現(xiàn),比如你可能想在關(guān)閉連接之前發(fā)送一個(gè)錯(cuò)誤碼的響應(yīng)消息。
??到目前為止我們都干得不錯(cuò)。我們已經(jīng)成功地實(shí)現(xiàn)了Discard Server的一半,剩下的一半就是去敲一個(gè)main方法,在main里面使用DiscardServerHandler去啟動(dòng)Server。
package io.netty.example.discard;
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;
/**
* Discards any incoming data.
*/
public class DiscardServer {
private int port;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (7)
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new DiscardServer(port).run();
}
}