在這一章節(jié)里,會以一個簡單的例子來展示Netty的核心結(jié)構(gòu),來讓你快速入門。當(dāng)你看完這一章節(jié)之后,你應(yīng)該可以寫一個客戶端和服務(wù)端。
如果你更喜歡自頂向下的學(xué)習(xí)方式,你可能想要從章節(jié)2[結(jié)構(gòu)概覽]開始,然后再回到這里。
Before Getting Started 在開始之前
要運行這個章節(jié)的例子的話,最低的要求是最新版的Netty和jdk1.6或以上。最新版本的Netty的下載地址下載。jdk略。
當(dāng)你在閱讀的時候,你可能會對這一章節(jié)介紹的類有更多的問題。請隨時查詢API文檔。為了你的方便,所有的類名都連接到在線的API文檔。
如果有錯誤的信息,請不要猶豫,聯(lián)系Netty的社區(qū)。
Writing a Discard Server 寫一個拋棄信息的服務(wù)器
最簡單的協(xié)議不是"Hello, World"而是DISCARD。這是一個對所有收到的數(shù)據(jù)不做任何響應(yīng)的協(xié)議。
為了實現(xiàn)DISCARD協(xié)議,你需要做的事情就是忽略所有收到的數(shù)據(jù)。讓我們直接從處理I/O時間的handler的實現(xiàn)開始吧。
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繼承了實現(xiàn)了ChannelInboundHandler
的ChannelInboundHandlerAdapter。ChannelInboundHandler提供了各種你可以重寫的事件處理方法。就現(xiàn)在來說,只需要繼承 ChannelInboundHandlerAdapter就夠了,不需要自己實現(xiàn)handler接口。
2.我們重寫了channelRead()事件處理方法。當(dāng)收到消息的時候這個方法會被調(diào)用。在這個例子中,消息的類型是 ByteBuf。
3.為了實現(xiàn)DISCARD協(xié)議,這個handler必須忽略收到的消息。ByteBuf
是一個通過release()方法釋放的引用計數(shù)的對象。請注意,這個handler方法的責(zé)任就是使任何引用計數(shù)對象通過到達(dá)下一個handler。通常,channelRead()處理方法會像下面這樣實現(xiàn):
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
// Do something with msg
} finally {
ReferenceCountUtil.release(msg);
}
}
4.當(dāng)Netty拋出一個I/O異常或者h(yuǎn)andler實現(xiàn)方法拋出一個異常的時候exceptionCaught()事件處理方法就會被調(diào)用。大多數(shù)情況下,被catch到的異常應(yīng)該被記錄日志,它所關(guān)聯(lián)的channel也應(yīng)該被關(guān)閉,不過這個方法的具體實現(xiàn)可以根據(jù)你的情況而定。例如,你可能會在連接關(guān)閉之前發(fā)送一個包含錯誤碼的響應(yīng)消息。
到目前為止一切順利,我們已經(jīng)實現(xiàn)了一半DISCARD服務(wù)器了。剩下的就是寫main()方法來啟動DiscardServerHandler。
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();
}
}
1.NioEventLoopGroup
是一個處理I/O操作的多線程事件循環(huán)器。Netty提供了多種EventLoopGroup實現(xiàn)用來處理不同的傳輸。在這個例子里,我們實現(xiàn)一個服務(wù)端的應(yīng)用,所以會用到兩個NioEventLoopGroup。第一個被稱為"boss",它接收到來的連接。第二個被稱為"worker",用來處理已經(jīng)被建立的連接,一旦boss接收了連接,他就會把連接注冊到worker上。具體會使用到多少線程,線程怎么映射到已經(jīng)創(chuàng)建了的Channel上的取決于EventLoopGroup
的實現(xiàn),甚至可以用構(gòu)造函數(shù)來配置。
2.ServerBootstrap
是一個用來啟動服務(wù)器的輔助類。你也可以直接用Channel
來啟動服務(wù)器。然而,請注意這是一個繁瑣的過程,在大多數(shù)情況下,你都不需要做。
3.這里,我們指定使用 NioServerSocketChannel來初始化一個新的Channel
來接收到來的連接。
4.被指定的處理器會一直被用來處理新接收的Channel。ChannelInitializer
是一個用來幫助我們配置新Channel
的特別handler。最常見的情況是你想要通過增加譬如DiscardServerHandler的handler實現(xiàn)給新的Channel
配置 ChannelPipeline最終實現(xiàn)你的網(wǎng)絡(luò)應(yīng)用。當(dāng)你的應(yīng)用變得復(fù)雜時,你可能會增加更多的handler到pipeline,然后提取提取一個匿名類到最上層。
5.你也可以給Channel的實現(xiàn)設(shè)置參數(shù),我們正在寫一個TCP/IP的服務(wù)器,所以我們可以設(shè)置socket選項為tcpNpDelay和keepAlive。請聯(lián)系 ChannelOption
和特定的ChannelConfig
實現(xiàn)的API文檔來獲取一個被支持的ChannelOption的概覽。
6.你有沒有注意到option()和childOption()?前者是為了NioServerSocketChannel接收連接,后者是為了被parent ServerChannel
接收的Channel,在這里, ServerChannel
指的是 NioServerSocketChannel。
7.現(xiàn)在我們準(zhǔn)備好了。剩下的就是去綁定端口號,然后啟動服務(wù)器。這里,我們綁定8080.你現(xiàn)在可以調(diào)用bind()方法隨意多的次數(shù)(使用不同的綁定地址)。
祝賀!你已經(jīng)完成了你的第一個Netty服務(wù)器。
Looking into the Received Data 查看收到的數(shù)據(jù)
現(xiàn)在我們已經(jīng)寫了我們的第一個服務(wù)器,我們需要測試它是不是真的能工作。最簡單的方法就是用telnet命令。例如,你可以輸入telnet localhost 8080,然后輸入一些東西。
然而,這樣我們就能說這個服務(wù)器能正常工作了嗎?我們不知道,因為他是個丟棄數(shù)據(jù)的服務(wù)器。你得不到任何的響應(yīng)。為了證明它真的可以正常工作,然我們修改一下服務(wù)器,讓他打印出他收到的消息。
我們已經(jīng)知道channelRead()方法會在接收到數(shù)據(jù)的時候被調(diào)用。讓我們在channelRead()方法里加一點代碼:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
try {
while (in.isReadable()) { // (1)
System.out.print((char) in.readByte());
System.out.flush();
}
} finally {
ReferenceCountUtil.release(msg); // (2)
}
}
1.這個低效率的循環(huán)可以被簡化成System.out.println(in.toString(io.netty.util.CharsetUtil.US_ASCII))
2.你可以用in.release替換這里
如果你再次運行telnet命令,你就會看到服務(wù)器打印出了他剛收到的消息。