服務端:
public class TimeServer {
public void bind(int port) throws Exception{
//配置服務端的NIO執行緒組
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new ChildChannelHandler());
//繫結埠,同步等待成功
ChannelFuture f= b.bind(port).sync();
//等待伺服器監聽埠關閉
f.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
//優雅退出,釋放執行緒池資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeServerHandler());
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if(args!=null&&args.length>0){
try{
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
}
}
new TimeServer().bind(port);
}
}
複製程式碼
首先建立了兩個NioEventLoopGroup例項。NioEventLoopGroup是個執行緒組,它包含了一組NIO執行緒,專門用於網路時間的處理,實際上它們就是Reactor執行緒組。這裡建立兩個的原因是一個用於服務端接受客戶端的連結,另一個用於進行SocketChannel的網路讀寫。 然後建立了ServerBootstrap物件,它是Netty用於啟動NIO服務端的輔助啟動類,目的是降低服務端開發的複雜度。接著呼叫ServerBootstrap的group方法,將兩個NIO執行緒組當做引數傳遞到ServerBootstrap中。 接著設定建立的Channel為NioServerChannel,它的功能對於與JDK NIO類庫中的ServerSocketChannel類。然後配置NIOServerSocketChannel的TCP引數,此處將它的backlog設定為1024,最後繫結IO事件的處理類ChildChannelHandler,它的作用類似於Reactor模式中的Handler類,主要用於處理網路IO事件,例如記錄日誌,對訊息進行編碼解碼等。 服務端啟動輔助類配置完成之後,呼叫它的bind方法繫結監聽埠,隨後,呼叫它的同步阻塞方法sync等待繫結操作完成。完成時候Netty返回一個ChannelFuture,它的功能類似於JDK的java.util.concurrent.Future,主要用於非同步操作的通知回撥。 使用f.channel().closeFuture.sync()方法進行阻塞,等待伺服器鏈路關閉之後main函式才退出。 呼叫NIO執行緒組的shutdownGracefully進行優雅退出,它會釋放跟shutdownGracefuly相關聯的資源。 服務端處理器
public class TimeServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"utf-8");
System.out.println("伺服器收到命令:"+body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString():"BAD ORDER";
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
複製程式碼
TimeServerHandler繼承自 ChannelHandlerAdapter,它用於對網路事件進行讀寫操作,通常我們只需關注channelRead和exceptionCaught方法。 將msg轉換 成Netty的ByteBuf物件。ByteBuf類似於JDK中的java.nio.ByteBuffer物件,不過它提供了更加強大和靈活的功能。通過ByteBuf的readableBytes方法可以獲取緩衝區可讀的位元組數,根據可讀的位元組數建立byte陣列,通過ByteBuf的readBytes方法將緩衝區中的位元組陣列複製到新建的byte陣列中,最後通過new String建構函式獲取請求訊息。這是對請求訊息進行判斷,如果是"QUERY TIME ORDER"則建立應答訊息,通過ChannelHandlerContext的write方法非同步傳送應答訊息個客戶端。 呼叫ChannelHandlerContext的flush方法,它的作用是將訊息傳送佇列中的訊息寫入到SockChannel中傳送給對方。從效能角度考慮,為了防止頻繁地喚醒Selector進行訊息傳送,Netty的write方法並不直接將訊息寫入SocketChannel中,呼叫write方法只是把待傳送的訊息放到傳送緩衝陣列中,再通過呼叫flush方法,將傳送緩衝區中的訊息全部寫入到SocketChannel中。
客戶端
public class TimeClient {
public void connect(int port,String host)throws Exception{
//配置客戶端NIO執行緒組
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap b = new Bootstrap();
b.group(group).channel(NioServerSocketChannel.class).option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});
//發起非同步連線操作
ChannelFuture f= b.connect(host,port).sync();
//等待非同步連線操作
f.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if(args!=null&&args.length>0){
try{
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
}
}
new TimeClient().connect(port,"127.0.0.1");
}
}
複製程式碼
首先建立客戶端處理IO讀寫的NioEventLoopGroup執行緒組,然後繼續建立客戶端輔助啟動類Bootstrap,隨後需要對其進行配置。與服務端不同的是,它的Channel需要設定為NioSocketChannel,然後為其新增Handler。此處為了簡單直接建立匿名內部類,實現initChannel方法,其作用是當建立NioSocketChannel成功之後,在進行初始化時,將它的ChannelHandler設定到ChannelPipeline中,用於處理網路IO事件。 客戶端啟動輔助類設定完成之後,呼叫connect方法發起非同步連線,然後呼叫同步方法等待連線成功。 最後當客戶端連線關閉之後,客戶端主函式退出,退出之前的釋放NIO執行緒組的字眼。
客戶端處理器
public class TimeClientHandler extends ChannelHandlerAdapter {
private final ByteBuf firstMessage;
public TimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("異常:"+cause.getMessage());
ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(firstMessage);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String (req,"utf-8");
System.out.println("訊息:"+body);
}
}
複製程式碼
當客戶端和服務端TCP鏈路建立成功之後,Netty的NIO執行緒會呼叫channelActive方法,傳送查詢時間的指定給服務端,呼叫ChannelHandlerContext的writeAndFlush方法將請求訊息傳送給服務端。 當服務端返回應答訊息時,channelRead被呼叫,從Netty的ByteBuf中讀取並列印應答訊息 當發生異常時,列印異常日誌,釋放客戶端資源。