Netty實例

Netty做爲一款性能優秀的NIO框架,現在的版本是4.1.13,之前出到netty5了,但都是內測版本,可能作者覺得netty5並不是很好,於是官網和git上都沒有了。所以本實例,還是採用netty4.1.13正式版,做爲開發基礎包。

這裏就直接引用了一個完整包:netty-all-4.1.13.Final.jar

Netty需要客戶端和服務端進行交互,

下面是代碼,服務端類:AppServer.java

package com.zyujie.app;

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

/**
 * Netty測試服務端類
 * @author zhouyujie
 */
public class AppServer {

	/**
	 * 綁定端口
	 * @param port
	 * @throws Exception
	 */
	public void bing(int port) throws Exception {
		//服務器線程組 用於網絡事件的處理一個用於服務器接收客戶端的連接
		//另一個線程組用於處理SocketChannel的網絡讀寫
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workGroup = new NioEventLoopGroup();
		try {
			//NIO服務器端的輔助啓動類,及配置
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workGroup);
			b.channel(NioServerSocketChannel.class);
			b.option(ChannelOption.SO_BACKLOG, 1024);
			//Server需要一個渠道
			b.childHandler(new channelHandler());
			//服務器啓動後 綁定監聽端口,同步等待成功主要用於異步操作的通知回調,回調處理用的ChildChannelHandler
			ChannelFuture f = b.bind(port).sync();
			//等待服務端監聽端口關閉
			f.channel().closeFuture().sync();
		} finally {
			//釋放線程池資源
			bossGroup.shutdownGracefully();
			workGroup.shutdownGracefully();
		}
	}

	public static void main(String[] args) {
		try {
			System.out.println("服務端已經開啓......");
			new AppServer().bing(8888);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

這裏定義了一個基礎渠道類,相當於一個BASE渠道類:channelHandler.java,用於初始化一些參數,心跳檢測的類可以用,但是具體還需要改進,先這樣吧。

package com.zyujie.app;

import java.util.concurrent.TimeUnit;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * Netty測試,渠道基礎類
 * @author zhouyujie
 */
public class channelHandler extends ChannelInitializer<SocketChannel> {

	/**
	 * 初始化渠道及參數,Netty5的自帶心跳檢測可用,Netty可以通過交互來模擬心跳
	 */
	protected void initChannel(SocketChannel e) throws Exception {

		System.out.println("有一客戶端鏈接到本服務端");
		System.out.println("IP:" + e.localAddress().getHostName());
		System.out.println("Port:" + e.localAddress().getPort());

		/**
		 * 心跳包 1、readerIdleTimeSeconds 讀超時時間 2、writerIdleTimeSeconds 寫超時時間
		 * 3、allIdleTimeSeconds 讀寫超時時間 4、TimeUnit.SECONDS 秒[默認爲秒,可以指定]
		 */
		e.pipeline().addLast(new IdleStateHandler(2, 2, 2, TimeUnit.SECONDS));
//		// 基於換行符號解碼器
//		e.pipeline().addLast(new LineBasedFrameDecoder(1024));
//		// 解碼轉String
//		e.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));
//		// 編碼器 String
//		e.pipeline().addLast(new StringEncoder(Charset.forName("UTF-8")));
		
		// 處理心跳 【放在編碼解碼的下面,因爲這個是通道有處理順序】
		e.pipeline().addLast(new HeartBeatIdleHandler());
		
		// 在管道中添加我們自己的接收數據實現方法
		e.pipeline().addLast(new AppServerHandler());
	}
	
}

基礎渠道類裏面定義了兩個渠道監聽類,一個是心跳,一個是接收數據的業務類,

HeartBeatIdleHandler.java

package com.zyujie.app;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

/**
 * Netty測試,心跳檢測類
 * @author zhouyujie
 */
public class HeartBeatIdleHandler extends ChannelInboundHandlerAdapter {
	
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            if (e.state() == IdleState.READER_IDLE) {
                System.out.println("--- Reader Idle ---");
                ctx.writeAndFlush("讀取等待:客戶端你在嗎... ...\r\n");
                // ctx.close();
            } else if (e.state() == IdleState.WRITER_IDLE) {
                System.out.println("--- Write Idle ---");
                ctx.writeAndFlush("寫入等待:客戶端你在嗎... ...\r\n");
                // ctx.close();
            } else if (e.state() == IdleState.ALL_IDLE) {
                System.out.println("--- All_IDLE ---");
                ctx.writeAndFlush("全部時間:客戶端你在嗎... ...\r\n");
            }
        }
    }

}

服務端業務渠道監聽類:AppServerHandler.java

package com.zyujie.app;

import java.util.Date;

import com.zyujie.app.pojo.BaseMsg;
import com.zyujie.app.util.AppTool;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * Netty測試,渠道監聽類
 * @author zhouyujie
 */
public class AppServerHandler extends ChannelInboundHandlerAdapter {

	/*
	 * channelAction channel 通道 action 活躍的
	 * 當客戶端主動鏈接服務端的鏈接後,這個通道就是活躍的了。也就是客戶端與服務端建立了通信通道並且可以傳輸數據
	 */
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println(ctx.channel().localAddress().toString() + " channelActive");
		// 通知您已經鏈接上客戶端
		String str = "您已經開啓與服務端鏈接" + " " + ctx.channel().id() + new Date() + " " + ctx.channel().localAddress();
		ctx.writeAndFlush(str);
	}

	/*
	 * channelInactive channel 通道 Inactive 不活躍的
	 * 當客戶端主動斷開服務端的鏈接後,這個通道就是不活躍的。也就是說客戶端與服務端的關閉了通信通道並且不可以傳輸數據
	 */
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		System.out.println(ctx.channel().localAddress().toString() + " channelInactive");
	}

	/*
	 * channelRead channel 通道 Read 讀 簡而言之就是從通道中讀取數據,也就是服務端接收客戶端發來的數據
	 * 但是這個數據在不進行解碼時它是ByteBuf類型的後面例子我們在介紹
	 */
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//		System.out.println("服務器讀取到客戶端請求...");
//		ByteBuf buf = (ByteBuf) msg;
//		byte[] req = new byte[buf.readableBytes()];
//		buf.readBytes(req);
//		String body = new String(req, "UTF-8");
//		System.out.println("the time server receive order:" + body);
		
		ByteBuf buf = (ByteBuf) msg;
		byte[] req = new byte[buf.readableBytes()];
		buf.readBytes(req);
		BaseMsg bm = (BaseMsg) AppTool.ByteToObject(req);
		
		System.out.println(bm.getMsgId());
		System.out.println(bm.getMsgType());
		System.out.println(bm.getMsgContent());

		String str = "我收到了。";
		ByteBuf resp = Unpooled.copiedBuffer(str.getBytes("UTF-8"));
		ctx.write(resp);
		System.out.println("服務器做出了響應");
	}

	/*
	 * channelReadComplete channel 通道 Read 讀取 Complete 完成
	 * 在通道讀取完成後會在這個方法裏通知,對應可以做刷新操作 ctx.flush()
	 */
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}

	/*
	 * exceptionCaught exception 異常 Caught 抓住
	 * 抓住異常,當發生異常的時候,可以做一些相應的處理,比如打印日誌、關閉鏈接
	 */
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		ctx.close();
		System.out.println("異常信息:\r\n" + cause.getMessage());
	}
}

服務端弄好了,下面是客戶端,這裏只寫了一個:AppClient.java客戶端配置類和渠道監聽類:AppClientHandler.java

package com.zyujie.app;

import io.netty.bootstrap.Bootstrap;
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.NioSocketChannel;

public class AppClient {

	/**
	 * 連接服務器
	 * @param port
	 * @param host
	 * @throws Exception
	 */
	public void connect(int port, String host) throws Exception {
		// 配置客戶端NIO線程組
		EventLoopGroup group = new NioEventLoopGroup();
		try {
			// 客戶端輔助啓動類 對客戶端配置
			Bootstrap b = new Bootstrap();
			b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
					.handler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ch.pipeline().addLast(new AppClientHandler());
						}
					});
			// 異步鏈接服務器 同步等待鏈接成功
			ChannelFuture f = b.connect(host, port).sync();
			// 等待鏈接關閉
			f.channel().closeFuture().sync();

		} finally {
			group.shutdownGracefully();
			System.out.println("釋放了線程資源...");
		}

	}

	public static void main(String[] args) throws Exception {
		new AppClient().connect(8888, "127.0.0.1");
	}
	
}

package com.zyujie.app;

import java.util.logging.Logger;

import com.zyujie.app.pojo.BaseMsg;
import com.zyujie.app.util.AppTool;
import com.zyujie.heartbeat.MyHeartBeatClientHandler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class AppClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
	
	private static final Logger logger = Logger.getLogger(MyHeartBeatClientHandler.class.getName());

	/**
	 * 客戶端連接
	 */
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("客戶端active");
		BaseMsg bm = new BaseMsg();
        bm.setMsgId("123");
        bm.setMsgType("測試");
        bm.setMsgContent("測試的內容");
        byte[] reqs = AppTool.ObjectToByte(bm);
        ctx.writeAndFlush(Unpooled.buffer(reqs.length).writeBytes(reqs));
	}

	/**
	 * 客戶端讀取服務端返回數據
	 */
	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
		System.out.println("客戶端收到服務器響應數據");
		ByteBuf buf = (ByteBuf) msg;
		byte[] req = new byte[buf.readableBytes()];
		buf.readBytes(req);
		String body = new String(req, "UTF-8");
		System.out.println("======>" + body);
	}

	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
		System.out.println("客戶端收到服務器響應數據處理完成");
		
		//讀取服務端返回後,再發送,再返回這樣一直循環,長連接
		Thread.sleep(2000);
		
		BaseMsg bm = new BaseMsg();
        bm.setMsgId("123");
        bm.setMsgType("測試");
        bm.setMsgContent("測試的內容");
        byte[] reqs = AppTool.ObjectToByte(bm);
        ctx.writeAndFlush(Unpooled.buffer(reqs.length).writeBytes(reqs));
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		logger.warning("Unexpected exception from downstream:" + cause.getMessage());
		ctx.close();
		System.out.println("客戶端異常退出");
	}
	
}

這樣就建立了服務端和客戶端的長連接了,如果把數據庫連接,或者redis這些做好,客戶端就可以一直髮送數據給服務端了。當然這只是個簡易的,具體的還需要改進。

注:netty5,我也寫了一個例子,netty5的handler,不管是服務端還是客戶端都是繼承:ChannelHandlerAdapter,而netty4的服務端handler是繼承:ChannelInboundHandlerAdapter,客戶端handler是繼承:SimpleChannelInboundHandler<ByteBuf>,這只是一種寫法,具體還有其它的,希望大家指正哈。

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