netty搭建Tcp伺服器實踐

lanyu發表於2021-09-09

在中,我們大致瞭解了netty的一些基本元件,今天我們來搭建一個基於netty的Tcp服務端程式,透過程式碼來了解和熟悉這些元件的功能和使用方法。

首先我們自己建立一個Server類,命名為TCPServer

第一步初始化ServerBootstrap,ServerBootstrap是netty中的一個伺服器引導類,對ServerBootstrap的例項化就是建立netty伺服器的入口

圖片描述

public class TCPServer {    private Logger log = LoggerFactory.getLogger(getClass());    //埠號
    private int port=5080;    //伺服器執行狀態
    private volatile boolean isRunning = false; 
    //處理Accept連線事件的執行緒,這裡執行緒數設定為1即可,netty處理連結事件預設為單執行緒,過度設定反而浪費cpu資源
    private final EventLoopGroup bossGroup = new NioEventLoopGroup(1);    //處理hadnler的工作執行緒,其實也就是處理IO讀寫 。執行緒資料預設為 CPU 核心數乘以2
    private final EventLoopGroup workerGroup = new NioEventLoopGroup();     
    public void init() throws Exception{        //建立ServerBootstrap例項
        ServerBootstrap serverBootstrap=new ServerBootstrap();        //初始化ServerBootstrap的執行緒模型
        serverBootstrap.group(workerGroup,workerGroup);//
        //設定將要被例項化的ServerChannel類
        serverBootstrap.channel(NioServerSocketChannel.class);//
        //在ServerChannelInitializer中初始化ChannelPipeline責任鏈,並新增到serverBootstrap中
        serverBootstrap.childHandler(new ServerChannelInitializer());        //標識當伺服器請求處理執行緒全滿時,用於臨時存放已完成三次握手的請求的佇列的最大長度
        serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);        // 是否啟用心跳保活機機制
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);    
        //繫結埠後,開啟監聽
        ChannelFuture channelFuture = serverBootstrap.bind(port).sync();        if(channelFuture.isSuccess()){
            System.out.println("TCP服務啟動 成功---------------");
        }
    }    
    /**
     * 服務啟動     */
    public synchronized void startServer() {        try {              this.init();
        }catch(Exception ex) {
            
        }
    }    
    /**
     * 服務關閉     */
    public synchronized void stopServer() {        if (!this.isRunning) {            throw new IllegalStateException(this.getName() + " 未啟動 .");
        }        this.isRunning = false;        try {
            Future<?> future = this.workerGroup.shutdownGracefully().await();            if (!future.isSuccess()) {
                log.error("workerGroup 無法正常停止:{}", future.cause());
            }

            future = this.bossGroup.shutdownGracefully().await();            if (!future.isSuccess()) {
                log.error("bossGroup 無法正常停止:{}", future.cause());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        this.log.info("TCP服務已經停止...");
    }    
    private String getName() {        return "TCP-Server";
    }
}

圖片描述

上面的程式碼中主要使用到的ServerBootstrap類的方法有以下這些:

group  :設定SeverBootstrap要用到的EventLoopGroup,也就是定義netty服務的執行緒模型,處理Acceptor連結的主"執行緒池"以及用於I/O工作的從"執行緒池";

channel:設定將要被例項化的SeverChannel類;

option :指定要應用到新建立SeverChannel的ChannelConfig的ChannelOption.其實也就是服務本身的一些配置;

chidOption:子channel的ChannelConfig的ChannelOption。也就是與客戶端建立的連線的一些配置;

childHandler:設定將被新增到已被接收的子Channel的ChannelPipeline中的ChannelHandler,其實就是讓你在裡面定義處理連線收發資料,需要哪些ChannelHandler按什麼順序去處理;

第二步接下來我們實現ServerChannelInitializer類,這個類繼承實現自netty的ChannelInitializer抽象類,這個類的作用就是對channel(連線)的ChannelPipeline進行初始化工作,說白了就是你要把處理資料的方法新增到這個任務鏈中去,netty才知道每一步拿著socket連線和資料去做什麼。

圖片描述

@ChannelHandler.Sharablepublic class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {    static final EventExecutorGroup group = new DefaultEventExecutorGroup(2);     
    public ServerChannelInitializer() throws InterruptedException {
    }
    
    @Override    protected void initChannel(SocketChannel socketChannel) throws Exception {    
        ChannelPipeline pipeline = socketChannel.pipeline();        //IdleStateHandler心跳機制,如果超時觸發Handle中userEventTrigger()方法
        pipeline.addLast("idleStateHandler",                new IdleStateHandler(15, 0, 0, TimeUnit.MINUTES));        // netty基於分割符的自帶解碼器,根據提供的分隔符解析報文,這裡是0x7e;1024表示單條訊息的最大長度,解碼器在查詢分隔符的時候,達到該長度還沒找到的話會拋異常//        pipeline.addLast(//                new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer(new byte[] { 0x7e }),//                        Unpooled.copiedBuffer(new byte[] { 0x7e })));         //自定義編解碼器         pipeline.addLast(                 new MessagePacketDecoder(),                 new MessagePacketEncoder()
                );        //自定義Hadler
        pipeline.addLast("handler",new TCPServerHandler());        //自定義Hander,可用於處理耗時操作,不阻塞IO處理執行緒
        pipeline.addLast(group,"BussinessHandler",new BussinessHandler());
    }
}

圖片描述

這裡我們注意下

pipeline.addLast(group,"BussinessHandler",new BussinessHandler());

在這裡我們可以把一些比較耗時的操作(如儲存、入庫)等操作放在BussinessHandler中進行,因為我們為它單獨分配了EventExecutorGroup 執行緒池執行,所以說即使這裡發生阻塞,也不會影響TCPServerHandler中資料的接收。

最後就是各個部分的具體實現

解碼器的實現:

圖片描述

public class MessagePacketDecoder extends ByteToMessageDecoder
{    public MessagePacketDecoder() throws Exception
    {
    }

    @Override    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception
    {        try {            if (buffer.readableBytes() > 0) {                // 待處理的訊息包
                byte[] bytesReady = new byte[buffer.readableBytes()];
                buffer.readBytes(bytesReady);                //這之間可以進行報文的解析處理                out.add(bytesReady );
            }
        }finally {
            
        }
    }


}

圖片描述

編碼器的實現

圖片描述

public class MessagePacketEncoder extends MessageToByteEncoder<Object>{    public MessagePacketEncoder()
    {
    }

    @Override    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception
    {        try {            //在這之前可以實現編碼工作。
            out.writeBytes((byte[])msg); 
        }finally {
            
        }  
    }
}

圖片描述

TCPServerHandler的實現

圖片描述

public class TCPServerHandler extends ChannelInboundHandlerAdapter {    
    public TCPServerHandler() {
    }
    
    @Override  
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
       //拿到傳過來的msg資料,開始處理    }    
    //檢測到空閒連線,觸發    @Override    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {       //這裡可做一些斷開連線的處理       }
}

圖片描述

BussinessHandler的實現與TCPServerHandler基本類似,它可以處理一些相對比較耗時的操作,我們這裡就不實現了。

透過以上的程式碼我們可以看到,一個基於netty的TCP服務的搭建基本就是三大塊:

1、對引導伺服器類ServerBootstrap的初始化;

2、對ChannelPipeline的定義,也就是把多個ChannelHandler組成一條任務鏈;

3、對 ChannelHandler的具體實現,其中可以有編解碼器,可以有對收發資料的業務處理邏輯;

以上程式碼只是在基於netty框架搭建一個最基本的TCP服務,其中包含了一些netty基本的特性和功能,當然這只是netty運用的一個簡單的介紹,如有不正確的地方還望指出與海涵。

原文出處:https://www.cnblogs.com/dafanjoy/p/9729400.html  

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2035/viewspace-2815272/,如需轉載,請註明出處,否則將追究法律責任。

相關文章