netty實戰之二 helloNetty

洛城鐵匠發表於2018-07-21

我們下面編寫四個類

      1.用於接收資料的伺服器端Socket

      2.用於接收客戶端的訊息,用於接收和反饋客戶端發出的訊息類ServertHandler

      3.用於傳送資料的伺服器端Client

      4.用於傳送資料和接收伺服器端發出的資料處理類ClientHandler

1.server類

import io.netty.bootstrap.ServerBootstrap; 
import io.netty.buffer.ByteBuf; 
import io.netty.buffer.Unpooled; 
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; 
import io.netty.handler.codec.DelimiterBasedFrameDecoder; 
import io.netty.handler.codec.string.StringDecoder; 
  
public class Server { 
   
 public static void main(String[] args) throws InterruptedException { 
  //1.第一個執行緒組是用於接收Client端連線的 
  EventLoopGroup bossGroup = new NioEventLoopGroup(); 
  //2.第二個執行緒組是用於實際的業務處理的 
  EventLoopGroup workerGroup = new NioEventLoopGroup(); 
  ServerBootstrap b = new ServerBootstrap(); 
  b.group(bossGroup, workerGroup);//繫結兩個執行緒池 
  b.channel(NioServerSocketChannel.class);//指定NIO的模式,如果是客戶端就是NioSocketChannel 
  b.option(ChannelOption.SO_BACKLOG, 1024);//TCP的緩衝區設定 
  b.option(ChannelOption.SO_SNDBUF, 32*1024);//設定傳送緩衝的大小 
  b.option(ChannelOption.SO_RCVBUF, 32*1024);//設定接收緩衝區大小 
  b.option(ChannelOption.SO_KEEPALIVE, true);//保持連續 
  b.childHandler(new ChannelInitializer<SocketChannel>() { 
   @Override
   protected void initChannel(SocketChannel sc) throws Exception { 
    ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());//拆包粘包定義結束字串(第一種解決方案) 
    sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,buf));//在管道中加入結束字串 
   // sc.pipeline().addLast(new FixedLengthFrameDecoder(200));第二種定長 
    sc.pipeline().addLast(new StringDecoder());//定義接收型別為字串把ByteBuf轉成String 
    sc.pipeline().addLast(new ServertHandler());//在這裡配置具體資料接收方法的處理 
   } 
  }); 
  ChannelFuture future = b.bind(8765).sync();//繫結埠 
  future.channel().closeFuture().sync();//等待關閉(程式阻塞在這裡等待客戶端請求) 
  bossGroup.shutdownGracefully();//關閉執行緒 
  workerGroup.shutdownGracefully();//關閉執行緒 
 } 
}
  1. 1在上面這個Server.java中,我們都要定義兩個執行緒池,boss和worker,boss是用於管理連線到server端的client的連線數的執行緒池,而woeker是用於管理實際操作的執行緒池。
  2. ServerBootstrap用一個ServerSocketChannelFactory 來例項化。ServerSocketChannelFactory 有兩種選擇,一種是NioServerSocketChannelFactory,一種是OioServerSocketChannelFactory。 前者使用NIO,後則使用普通的阻塞式IO。它們都需要兩個執行緒池例項作為引數來初始化,一個是boss執行緒池,一個是worker執行緒池。
  3. 然後使ServerBookstrap管理boss和worker執行緒池。並且設定各個緩衝區的大小。
  4. 這裡的事件處理類經常會被用來處理一個最近的已經接收的Channel。ChannelInitializer是一個特殊的處理類,他的目的是幫助使用者配置一個新的Channel。也許你想通過增加一些處理類比如NettyServerHandler來配置一個新的Channel 或者其對應的ChannelPipeline來實現你的網路程式。 當你的程式變的複雜時,可能你會增加更多的處理類到pipline上,然後提取這些匿名類到最頂層的類上。
  5.  在使用原始的encoder、decoder的情況下,Netty傳送接收資料都是按照ByteBuf的形式,其它形式都是不合法的。 而在上面這個Socket中,我使用sc.pipeline().addLast()這個方法設定了接收為字串型別,注意:只能設定接收為字串型別,傳送還是需要傳送ByteBuf型別的資料。而且在這裡我還設定了以$_為結尾的字串就代表了本次請求字串的結束。
  6. 通過b.bind繫結埠,用於監聽的埠號。

2.ServerHandler.java

public class ServertHandler extends ChannelHandlerAdapter { 
  
 @Override
 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
  String body = (String) msg; 
  System.out.println("server"+body);//前面已經定義了接收為字串,這裡直接接收字串就可以 
  //服務端給客戶端的響應 
  String response= " hi client!$_";//傳送的資料以定義結束的字串結尾 
  ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));//傳送必須還是ByteBuf型別 
 } 
  
 @Override
 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
   cause.printStackTrace(); 
   ctx.close(); 
 } 
   
}

ServertHandler繼承自 ChannelHandlerAdapter,這個類實現了ChannelHandler介面,ChannelHandler提供了許多事件處理的介面方法,然後你可以覆蓋這些方法。現在僅僅只需要繼承ChannelHandlerAdapter類而不是你自己去實現介面方法。由於我們再server端開始的時候已經定義了接收型別為String,所以在這裡我們接收到的msg直接強轉成String就可以了。同時也要定義以什麼為一次請求的結尾。

3.Client.java

public class Client { 
  
 public static void main(String[] args) throws InterruptedException { 
  EventLoopGroup worker = new NioEventLoopGroup(); 
  Bootstrap b = new Bootstrap(); 
  b.group(worker) 
  .channel(NioSocketChannel.class) 
  .handler(new ChannelInitializer<SocketChannel>() { 
   @Override
   protected void initChannel(SocketChannel sc) throws Exception { 
    ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes()); 
    sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,buf)); 
    sc.pipeline().addLast(new StringDecoder()); 
    sc.pipeline().addLast(new ClientHandler()); 
   } 
  }); 
  ChannelFuture f=b.connect("127.0.0.1",8765).sync(); 
  f.channel().writeAndFlush(Unpooled.copiedBuffer(" hi server2$_".getBytes())); 
  f.channel().writeAndFlush(Unpooled.copiedBuffer(" hi server3$_".getBytes())); 
  f.channel().writeAndFlush(Unpooled.copiedBuffer(" hi server4$_".getBytes())); 
  f.channel().closeFuture().sync(); 
  worker.shutdownGracefully(); 
 } 
}

client端和Socket端幾乎程式碼相同,只是client端用的不是ServerBootstrap而是Bootstrap來管理連線。這裡沒什麼好說的。

public class ClientHandler extends ChannelHandlerAdapter{ 
 @Override
 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
  try { 
   System.out.println("client"+msg.toString()); 
  } finally { 
   ReferenceCountUtil.release(msg);//釋放緩衝區 
  } 
 } 
  
 @Override
 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
   cause.printStackTrace(); 
   ctx.close(); 
 } 
}

ClientHandler和ServertHandler程式碼和原理也是一樣,只是在client端我們要釋放緩衝區。為什麼在ServerHandler我們不需要釋放呢 ?因為在ServertHandler我們呼叫ctx.writeAndFlush方法的時候,這個方法預設已經幫我們釋放了緩衝區。

相關文章