一、什麼是Netty
Netty是一個NIO服務框架,簡化了網咯應用框架的開發難度。Netty是一個事件驅動模型,(將很多階段抽象成一個個的事件,讓後將事件映射到多個回調方法上)。主要的特點有(https://netty.io/index.html):
- 爲BIO/NIO提供統一的API
- 是一個靈活的、可擴展的事件驅動模型
- 提供高度可定製化的線程模型
- 支持真正的無連接數據報傳輸
- 提供高吞吐、低延時,並且佔用更少的資源
- 提供最小化的不必要內存拷貝
- 提供對proto Buffer的支持
二、Netty 的初級使用
public class TestServer{
public static void main(String[] args){
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, wokerGroup)
.channel(NioServerSockerChannel.class)
.childHandler(new TestServerInitializer());
ChannelFuture future = bootstrap.bind(8899).sync();
future.channel().closeFuture().sync();
}
}
// 自定義啓動初始化器
public class TestServerInitializer extens ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel ch) throws Exception{
ChannelPipeline pipeline = ch.pipiline();
// HttpServerCodec是HttpRequestDecoder和HttpResponseDecoder的組合
pipeline.addLast("httpServerCodec", new HttpServerCodec());
pipeline.addLast("serverHandler", new TestServerHandler());
}
}
// 實現自己的回調方法
public class TestServerHandler extends SimpleChannelInboundHandler<HttpObject>{
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg){
if(msg instanceof HttpObject){
ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
// response.headers().set()
ctx.writeAndFlush(response);
}
}
}
public class MyClient{
public static void main(String[] args){
EventLoopGroup loop = new NioEventLoopGroup();
try{
Bootstrap strap = new Bootstrap();
strap.group(loop).channel(NioSocketChannel.class)
.handler(new MyClientHandler());
ChannelFuture fut = strap.connect("localhost", 8899).sync();
fut.channel.closeFuture().sync();
}finally{
loop.shutdownGracefull();
}
}
}
public class MyClientInitializer extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel ch) throws Exception{
ChannelPipeline pipeline = ch.pipiline();
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast("my client handler", new MyClientHandler());
}
}
public class MyClientHandler extends SimpleChannelInboundHandler<String>{
@Overrie
protected void channelRead0(ChannelHandlerContext ctx, String msg){
// 服務端向客戶端傳遞數據時,客戶端的處理邏輯
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
cause.printStackTrace();
ctx.close();
}
@Overrie
protected void channelActive(ChannelHandlerContext ctx){
// 當與服務端建立好連接後,操作
ctx.writeAndFlush("test");
}
}
2.1 SimpleChannelInboundHandler
處理流入channel的事件處理,常見的觸發有
- channelActive() channel是活躍狀態
- channelRegisterd() 註冊channel
- channelAdded(); 添加channel
- channelInactive() 非活躍
- channelUnregistered() 卸載channel
執行順序是:channelAdded() ->registered()->active()->inactive()->unregistered
2.2 ChannelInitializer
// 自定義啓動初始化器
public class TestServerInitializer extens ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel ch) throws Exception{
ChannelPipeline pipeline = ch.pipiline();
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast("my handler", new MyHandler());
}
}
// 這裏的泛型主要依據是請求的是什麼樣的格式數據
public class MyHandler extends SimpleChannelInboundHandler<String>{
// 收到消息後的處理邏輯
@Overrie
protected void channelRead0(ChannelHandlerContext ctx, String msg){
}
}
三、聊天室實例
需求:
第一個客戶端A與服務端建立連接後,服務端打印A上線;
後續的客戶端與服務端建立好連接後,服務端打印X上線,並且廣播X上線給已經建立好的連接。
客戶端X發送一條消息後,廣播到其餘客戶端
如果客戶端下線後,服務端向所有的客戶端廣播X下線。
// 核心思想,利用channel的事件機制,將對應建立好的channel加入到一個組裏維護
public class MyChatServerHandler extends SimpleChannelInboundHandler<String>{
private static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void channelRead0(ChannelHandlerContext ctx, String msg){
Channel channel = ctx.channel();
group.forEach(t -> {
if(channel != t){
t.writeAndFlush("非自己發出的消息");
}else{
t.writeAndFlush("自己發出的消息");
}
});
}
@Override
public void handlerAdded(ChannelHandlerContext ctx){
Channel ch = ctx.channel();
group.writeAndFlush(ch.remoteAddress() +"online");
group.add(ch);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx){
Chanel channel = ctx.channel();
group.writeAndFlush(channel.remoteAddress() + "offline");
// group.remove(channel); 這個netty會自動調用,所以可以省略
}
@Override
public void channelActive(ChannelHandlerContext ctx){
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress() + "上線");
}
@Override
public void channelInactive(ChannelHandlerContext ctx){
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress() + "下線");
}
}