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>,這只是一種寫法,具體還有其它的,希望大家指正哈。