第一個netty程序——編寫Echo服務器

所有的Netty服務器都需要一下兩部分

①至少一個ChannelHandler——該組件實現了服務器對從客戶端接收的數據的處理,即它的業務邏輯。

②引導——這是配置服務器的啓動代碼。至少,它會將服務器綁定到連接請求的端口上。

因爲Echo服務器會影響傳入的消息,所以它需要ChannelInboundHandler接口,用來定義響應入站事件的方法。這個簡單的應用程序只需要用到少量的這些方法,所以繼承CHannelInboundHandlerAdapter類也就足夠了,它提供了ChannelInboundHandler的默認實現。

package netty.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

@Sharable //標示一個Channel-Handler可以被多個Channel安全的共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		ByteBuf in = (ByteBuf) msg;
		System.out.println("Server received:"+in.toString(CharsetUtil.UTF_8));
		ctx.write(in);//接收到消息寫給發送者,而不沖刷出站消息
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		//將未決消息沖刷到遠程節點,並且關閉該channel
		ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}

ChannelInboundHandlerAdapter有一個直觀的API,並且它的每個方法都可以被重寫以掛鉤到事件生命週期的恰當點上。因爲需要處理所有接收到的數據,所以重寫了channelRead()方法。在這個服務器應用程序中,你將數據簡單地送給了遠程節點。

重寫exceptionCaught()允許你對Throwable的任何子類型做出反應。

如果不捕獲異常,會發生什麼呢?

每個channel都擁有一個與之相關聯的ChannelPipeline,其持有一個ChannelHandler的實例鏈。在默認情況下,ChannelHandler會把對它的方法的調用轉發給鏈接中的下一個ChannelHandler。因此,如果exceptionCaught()方法沒有被該鏈中的某處實現,那麼所接收的異常將會被傳遞到ChannelPipeline的尾端並被記錄。爲此,你的應用該程序應該提供至少有一個實現了exceptionCaught()方法的ChannelHandler。

引導服務器

①綁定到服務器將在其上監聽並接受傳入連接請求的端口;

②配置channel,並將有關的入站消息通知給EchoServerHandler實例。

package netty.server;

import java.net.InetSocketAddress;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class EchoServer {
	private final int port;
	
	public EchoServer(int port) {
		this.port = port;
	}
	
	public static void main(String[] args) throws Exception {
		if(args.length != 1) {
			System.err.println("Usage:" + EchoServer.class.getSimpleName()+"<port>");
			return;
		}
		//設置端口值(如果端口參數的格式不正確,則拋出一個NumberFormatException)
		int port = Integer.parseInt(args[0]);
		//調用服務器的start()方法
		new EchoServer(port).start();
	}
	
	public void start() throws Exception {
		final EchoServerHandler serverHandler = new EchoServerHandler();
		//創建Event-LoopGroup
		EventLoopGroup group = new NioEventLoopGroup();
		try {
			//創建ServerBootstrap
			ServerBootstrap b = new ServerBootstrap();
			b.group(group)
				//指定所使用的NIO傳輸Channel
				.channel(NioServerSocketChannel.class)
				//使用制定的端口設置套接字地址
				.localAddress(new InetSocketAddress(port))
				//添加一個EchoServer-Handler到子Channel的ChannelPipeline
				.childHandler(new ChannelInitializer<SocketChannel>() {
					@Override
					protected void initChannel(SocketChannel ch) throws Exception {
						//EchoServerHandler被標註爲@Shareable,所以我們可以總是使用同樣的實例
						ch.pipeline().addLast(serverHandler);
					}
				});
			//異步的綁定服務器;調用sync()方法阻塞等待直到綁定完成
			ChannelFuture f = b.bind().sync();
			//獲取Channel的CloseFuture,並且阻塞當前線程直到它完成
			f.channel().closeFuture().sync();
		}finally {
			//關閉EventLoopGroup,釋放所有的資源
			group.shutdownGracefully().sync();
		}
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章