這是一個netty demo程序,在此不解讀原理及API的詳情,主要用於學習保留,這個demo我只對部份朋友可見,如果沒接觸過netty,又想要學習netty的朋友,建議先了解JDK的IO模型。後續有時間的話會總結netty的底層原理(如:粘包拆包、心跳機制、底層零拷貝等等),如果有什麼問題也可以留言相互學習
服務端代碼
package com.patrick.netty.demo;
import io.netty.bootstrap.ServerBootstrap;
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;
public class NettyServer {
public static void main(String[] args) throws Exception {
//創建兩個線程組bossGroup和workerGroup, 含有的子線程NioEventLoop的個數默認爲cpu核數的兩倍
// bossGroup只是處理連接請求 ,真正的和客戶端業務處理,會交給workerGroup完成
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//創建服務器端的啓動對象
ServerBootstrap bootstrap = new ServerBootstrap();
//使用鏈式編程來配置參數
bootstrap.group(bossGroup, workerGroup) //設置兩個線程組
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作爲服務器的通道實現
// 初始化服務器連接隊列大小,服務端處理客戶端連接請求是順序處理的,所以同一時間只能處理一個客戶端連接。
// 多個客戶端同時來的時候,服務端將不能處理的客戶端連接請求放在隊列中等待處理
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {//創建通道初始化對象,設置初始化參數
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//對workerGroup的SocketChannel設置處理器codec
ch.pipeline().addLast(new NettyServerHandler()); //添加自定義的操作類
}
});
System.out.println("netty server start。。");
//綁定一個端口並且同步, 生成了一個ChannelFuture異步對象,通過isDone()等方法可以判斷異步事件的執行情況
//啓動服務器(並綁定端口),bind是異步操作,sync方法是等待異步操作執行完畢
ChannelFuture cf = bootstrap.bind(9000).sync();
//給cf註冊監聽器,監聽我們關心的事件
/*cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (cf.isSuccess()) {
System.out.println("監聽端口9000成功");
} else {
System.out.println("監聽端口9000失敗");
}
}
});*/
//對通道關閉進行監聽,closeFuture是異步操作,監聽通道關閉
// 通過sync方法同步等待通道關閉處理完畢,這裏會阻塞等待通道關閉完成
cf.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.patrick.netty.demo;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* 自定義Handler需要繼承netty規定好的某個HandlerAdapter(規範)
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 讀取客戶端發送的數據
*
* @param ctx 上下文對象, 含有通道channel,管道pipeline
* @param msg 就是客戶端發送的數據
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服務器讀取線程 " + Thread.currentThread().getName());
//Channel channel = ctx.channel();
//ChannelPipeline pipeline = ctx.pipeline(); //本質是一個雙向鏈接, 出站入站
//將 msg 轉成一個 ByteBuf,類似NIO 的 ByteBuffer
ByteBuf buf = (ByteBuf) msg;
System.out.println("客戶端發送消息是:" + buf.toString(CharsetUtil.UTF_8));
}
/**
* 數據讀取完畢處理方法
*
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("HelloClient", CharsetUtil.UTF_8);
ctx.writeAndFlush(buf);
}
/**
* 處理異常, 一般是需要關閉通道
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客戶端代碼
package com.patrick.netty.demo;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 NettyClient {
public static void main(String[] args) throws Exception {
//客戶端需要一個事件循環組
EventLoopGroup group = new NioEventLoopGroup();
try {
//創建客戶端啓動對象
//注意客戶端使用的不是 ServerBootstrap 而是 Bootstrap
Bootstrap bootstrap = new Bootstrap();
//設置相關參數
bootstrap.group(group) //設置線程組
.channel(NioSocketChannel.class) // 使用 NioSocketChannel 作爲客戶端的通道實現
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
//加入處理器
channel.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("netty client start");
//啓動客戶端去連接服務器端
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();
//對關閉通道進行監聽
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
package com.patrick.netty.demo;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 當客戶端連接服務器完成就會觸發該方法
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("HelloServer", CharsetUtil.UTF_8);
ctx.writeAndFlush(buf);
}
//當通道有讀取事件時會觸發,即服務端發送數據給客戶端
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("收到服務端的消息:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("服務端的地址: " + ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}