Netty5用戶手冊之二:使用netty實現Discard服務器程序

編寫一個discard服務器程序

        世界上最簡單的協議不是‘hello world’而是discard。他是一個丟棄任何接收到的數據並且無任何響應的協議。
        爲了實現dicard協議,我們只需要做一件事就是忽略所有接收到的數據。讓我們從handler處理器的實現開始,handler在netty中是一個用來處理io事件的的處理器。
<span style="font-family:Microsoft YaHei;font-size:14px;"><span style="font-family:Microsoft YaHei;">import io.netty.buffer.ByteBuf;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelHandlerAdapter;

/**
</span></span>
<span style="font-family:Microsoft YaHei;font-size:14px;"><span style="font-family:Microsoft YaHei;"> * 處理一個服務器通道
 */
public class DiscardServerHandler extends ChannelHandlerAdapter { // (1)

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // 直接調用release來釋放接收到的數據
        ((ByteBuf) msg).release(); // (3)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // 當異常發生時,關閉處理器
        cause.printStackTrace();
        ctx.close();
    }
}</span></span>
      1.DiscardServerHandler繼承了ChannelHandlerAdapter類,這個類是ChannelHandler的一個實現類。其中ChannelHandler提供了各類事件的處理器方法方便我們來覆蓋。但是,現在繼承ChannelHandlerAdapter類比實現ChannelHandler更方便。
     2.代碼中覆蓋了channelRead()事件處理器方法。這個方法在無論何時客戶端傳送給送服務器都會被調用而用來接收客戶端
的數據。這個例子中,接收到的數據消息就是ByteBuf對象。
  3.爲了實現discard協議,這個處理器對象必須忽略接收到的數據。bytebuf是一個引用計數對象,因此它必須通過realeas方法
進行釋放。請記住,處理器的職責就是釋放所傳遞處理器的引用計數對象,通常,channelRead方法的實現就像下面的代碼:
<span style="font-family:Microsoft YaHei;font-size:14px;"><span style="font-family:Microsoft YaHei;">@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
   try {
        // Do something with msg
    } finally {
        ReferenceCountUtil.release(msg);
    }
}
</span></span>
          4.exceptionCaught()事件處理器方法主要適用於一個異常發生的時候,這個異常可能是在netty中被一個io錯誤引起或者一個
handler處理器實現邏輯時發生了異常引起的。在大部分情況下在這個方法中,被捕捉的異常需要被記錄到日誌中,並且它關聯的
channel需要在此處被關閉,然而這個方法的處理方式會在遇到不同異常的情況下有不同的實現,比如你可能想在關閉連接之
前發送一個錯誤碼的響應消息。
        OK,到目前爲止,我們已經實現了discard服務器的一半了,下面爲剩下的main方法。
<span style="font-family:Microsoft YaHei;font-size:14px;">package com.zzj.nio.netty;

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;

/**
 * 拋棄消息服務端
 * @author zzj
 * @date Oct 19, 2016 1:24:05 PM
 */
public class DiscardServer {

	/**端口號**/
	private int port;
	
	/**
	 * 構造函數
	 * @param port
	 */
	public DiscardServer(int port){
		this.port = port;
	}
	
	public static void main(String[] args) {
		int port = 9999;
		try {
			//開啓服務端接受消息
			new DiscardServer(port).run();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 開啓服務器
	 * @author zzj
	 * @throws InterruptedException 
	 */
	public void run() throws InterruptedException{
		EventLoopGroup bossGroup = new NioEventLoopGroup();//(1)
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap bootstrap = new ServerBootstrap();//(2)
			bootstrap.group(bossGroup, workerGroup)
			.channel(NioServerSocketChannel.class)//(3)
			.childHandler(new ChannelInitializer<SocketChannel>() {//(4)

				@Override
				protected void initChannel(SocketChannel channel) throws Exception {
					channel.pipeline().addLast(new DiscardServerHandler());
				}
			});
			bootstrap.option(ChannelOption.SO_BACKLOG, 128)//(5)
			.childOption(ChannelOption.SO_KEEPALIVE, true);//(6)
			
			//緊接着,綁定並且開始接受請求的連接
			ChannelFuture future = bootstrap.bind(port).sync();
			
			//等待直到服務器socket被關閉,在這裏例子中,這個不會發生,但是你可以這樣寫
			future.channel().closeFuture().sync();
			
		}finally{
			
			//優雅的關閉
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}
}
</span>
1.NioEventLoopGroup是一個用戶處理io操作的多線程event loop.Netty對於不同種類的傳輸提供了多種EventLoopGroup實現方式。在這個例
子中我們實現了一個server端的應用程序,並且用到了兩個NioEventLoopGroup對象,其中,第一個通常被稱爲boss,它主要用於接收連接過來的連
接。而第二個通常稱爲worker,用來處理已經被接受的連接,一旦boss接收到連接,就會把連接信息註冊到worker上。如何知道多少個線程已經被
使用,如何映射到已經創建的Channels上都需要依賴於EventLoopGroup的實現,並且可以通過構造函數來配置他們的關係
2.ServerBootStrap是一個幫助的工具類用於配置一個服務器。你可以通過一個Channel直接來配置服務器。然而,請注意這是一個複雜的過程,
而且很多情況下沒有必要這麼做。
3.這裏,我們給出了使用NioServerSocketChannel類用戶實例化一個Channel來接收鏈接過來的連接。
4.這裏的處理器類用來處理最近被接受過來的Channel。ChannelInitializer是一個專門的處理器是用於幫助用戶配置一個新的Channel。 
也許你想通過增加一些處理類比如DiscardServerHandle來配置一個新的Channel或者其對應的ChannelPipeline來實現你的網絡程序。隨着應用程序
變得複雜,你可能需要爲pipeline添加更多的處理器,然後提取這些匿名類到最頂層的類上。
5. 你可以設置這裏指定的通道實現的配置參數。我們正在寫一個TCP/IP的服務端,因此我們被允許設置socket的參數選項比如tcpNoDelay和
keepAlive。請參考ChannelOption和詳細的ChannelConfig實現的接口文檔以此可以對ChannelOptions的有一個大概的認識.
6. 你關注過option()和childOption()嗎?option()是提供給NioServerSocketChannel用來接收進來的連接。childOption()是提供給由父管道
ServerChannel接收到的連接,在這個例子中也是NioServerSocketChannel。
 7. 我們繼續,剩下的就是綁定端口然後啓動服務。這裏我們在機器上綁定了機器所有網卡上的8080端口。當然現在你可以多次調用bind()方法(基於不同綁定地址)。

如果,想要在服務器中打印出客戶端輸入的消息,那麼可以使用一下代碼:
@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)
    }
}
OK,到此爲止discard服務端程序就完成了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章