Netty入門之文本協議

使用Netty實現文本協議,即直接處理字符串,而不是字節,字節到字符串的編碼解碼工作都由Handler來完成,主要在於以下三個Handler:

1.DelimiterBasedFrameDecoder:基於定界符的幀解碼器,即根據指定的分隔符,來判斷協議中幀的界限,如下

new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())

第一個參數爲最大幀長度,第二個參數爲幀分隔符,這裏使用的是行分隔符

2.StringDecoder:字符串解碼器,將字節轉換爲字符串,即ByteBuf轉換爲String,可以設置需要的字符集,在接收數據時使用

3.StringEncoder:字符串編碼器,將字符串轉換爲字節,即String轉換爲ByteBuf,可以設置需要的字符串,在寫出數據時使用

 

TelnetClient

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * telnet協議客戶端
 */
@Slf4j
public final class TelnetClient {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));

    public static void main(String[] args) throws Exception {
        /* SSL上下文 */
        final SslContext sslCtx;
        if (SSL) {
            sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                .channel(NioSocketChannel.class)
                .handler(new TelnetClientInitializer(sslCtx));
            /* 啓動連接 */
            Channel ch = b.connect(HOST, PORT).sync().channel();
            log.info("客戶端啓動");
            /* 最後一次異步操作結果 */
            ChannelFuture lastWriteFuture = null;

            /* 從命令行讀取輸入 */
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            for (;;) {
                String line = in.readLine();
                if (line == null) {
                    break;
                }
                /* 發送讀取信息到服務端 */
                lastWriteFuture = ch.writeAndFlush(line + "\r\n");

                /* 如果命令爲bye,則關閉Channel */
                if ("bye".equals(line.toLowerCase())) {
                    ch.closeFuture().sync();
                    break;
                }
            }

            /* 等待最後異步操作結果完成 */
            if (lastWriteFuture != null) {
                lastWriteFuture.sync();
            }
        } finally {
            group.shutdownGracefully();
            log.info("客戶端關閉");
        }
    }
}

1.Channel寫出數據時,使用\r\n結尾,這裏\r\n作爲幀的定界符使用

2.當輸入bye命令時,客戶端關閉

 

TelnetClientInitializer

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.ssl.SslContext;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TelnetClientInitializer extends ChannelInitializer<SocketChannel> {

    /* netty字符串編碼解碼 */
    private static final StringDecoder DECODER = new StringDecoder();
    private static final StringEncoder ENCODER = new StringEncoder();

    private static final TelnetClientHandler CLIENT_HANDLER = new TelnetClientHandler();

    private final SslContext sslCtx;

    public TelnetClientInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        /* SSL處理器 */
        if (sslCtx != null) {
            pipeline.addLast(sslCtx.newHandler(ch.alloc(), TelnetClient.HOST, TelnetClient.PORT));
        }
        /* 首先添加文本行編解碼器組合 */
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast(DECODER);
        pipeline.addLast(ENCODER);
        pipeline.addLast(CLIENT_HANDLER);
    }
}

1.除了SSL,使用了三個前置Handler,分別是定界符處理器、字符串編碼器、字符串解碼器

2.最後使用的是業務Handler,保證業務Handler接收到的是String,且其寫出的String會被轉換爲字節

 

TelnetClientHander

import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;


@Slf4j
@Sharable
public class TelnetClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        log.info("客戶端讀取消息:"+msg);
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("客戶端異常",cause);
        cause.printStackTrace();
        ctx.close();
    }
}

1.業務Handler繼承了SimpleChannelnBoundHandler<String>,這裏的泛型String對應了字節的編碼解碼結果

 

TelnetServerInitializer

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.ssl.SslContext;

public class TelnetServerInitializer extends ChannelInitializer<SocketChannel> {

    private static final StringDecoder DECODER = new StringDecoder();
    private static final StringEncoder ENCODER = new StringEncoder();

    private static final TelnetServerHandler SERVER_HANDLER = new TelnetServerHandler();

    private final SslContext sslCtx;

    public TelnetServerInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();

        if (sslCtx != null) {
            pipeline.addLast(sslCtx.newHandler(ch.alloc()));
        }

        /* 首先添加文本行編解碼器組合 */
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast(DECODER);
        pipeline.addLast(ENCODER);
        pipeline.addLast(SERVER_HANDLER);
    }

1.服務端與客戶端的Handler設置相同

 

TelnetServerHandler

@Slf4j
@Sharable
public class TelnetServerHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("Channel激活,發送問候語");
        ctx.write("Welcome to " + InetAddress.getLocalHost().getHostName() + "!\r\n");
        ctx.write("It is " + new Date() + " now.\r\n");
        ctx.flush();
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, String request) throws Exception {
        String response;
        boolean close = false;

        if (request.isEmpty()) {
            log.info("客戶端信息爲空");
            response = "Please type something.\r\n";
        } else if ("bye".equals(request.toLowerCase())) {
            log.info("客戶端拜拜");
            response = "Have a good day!\r\n";
            close = true;
        } else {
            response = "Did you say '" + request + "'?\r\n";
        }

        /* 我們不需要在這裏編寫ChannelBuffer。 插入TelnetPipelineFactory的編碼器將進行轉換 */
        ChannelFuture future = ctx.write(response);

        if (close) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

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

1.服務端每次寫出數據也需要使用\r\n作爲定界符

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