本文我們來寫第一個Netty的入門實例,一個TCP服務案例。
Netty入門案例
1. 案例需求
- 創建Netty 服務器在 6668 端口監聽
- 創建Netty客戶端,客戶端能發送消息給服務器 “hello, 服務器~”
- 服務器可以回覆消息給客戶端 “hello, 客戶端~”
目的:對Netty 線程模型 有一個初步認識, 便於理解Netty 模型理論
2.創建maven項目
通過eclipse或者IDEA創建一個普通的maven項目即可
3.導入依賴
引入相關的maven座標
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.20.Final</version>
</dependency>
4.創建服務端Handler
創建服務端的handler,主要是處理客戶端提交的請求的。
package com.dpb.netty.tcp;
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;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-23 11:24
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 讀取客戶端發送來的數據
* @param ctx ChannelHandler的上下文對象 有管道 pipeline 通道 channel 和 請求地址 等信息
* @param msg 客戶端發送的具體數據
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("客戶端請求到了..." + ctx.channel().remoteAddress());
ByteBuf buf = (ByteBuf) msg;
System.out.println("客戶端發送的數據是:" +buf.toString(CharsetUtil.UTF_8));
}
/**
* 讀取客戶端發送數據完成後的方法
* 在本方法中可以發送返回的數據
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// writeAndFlush 是組合方法
ctx.writeAndFlush(Unpooled.copiedBuffer("你好啊,客戶端....^_^",CharsetUtil.UTF_8));
}
}
5.創建服務端
創建服務端
package com.dpb.netty.simple;
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;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-23 11:15
*/
public class NettyServerDemo {
public static void main(String[] args) {
// 創建對應的 線程池
// 創建Boss group
EventLoopGroup boosGroup = new NioEventLoopGroup(1);
// 創建 workgroup
EventLoopGroup workGroup = new NioEventLoopGroup();
// 創建對應的啓動類
ServerBootstrap bootstrap = new ServerBootstrap();
try{
// 設置相關的配置信息
bootstrap.group(boosGroup,workGroup) // 設置對應的線程組
.channel(NioServerSocketChannel.class) // 設置對應的通道
.option(ChannelOption.SO_BACKLOG,1024) // 設置線程的連接個數
.childHandler(new ChannelInitializer<SocketChannel>() { // 設置
/**
* 給pipeline 設置處理器
* @param socketChannel
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("服務啓動了....");
// 綁定端口 啓動服務
ChannelFuture channelFuture = bootstrap.bind(6668).sync();
// 對關閉通道進行監聽
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
}finally {
// 優雅停服
boosGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
6.創建客戶端Handler
客戶端handler主要是發送請求給客戶端及處理服務端返回的信息。
package com.dpb.netty.simple;
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;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-23 11:36
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 連接上服務的回調方法
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 發送數據
System.out.println("連接上了 服務器....");
ctx.writeAndFlush(Unpooled.copiedBuffer("哈哈 你好呀!!!", CharsetUtil.UTF_8));
}
/**
* 讀取服務端返回的信息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("服務端返回的信息:" + buf.toString(CharsetUtil.UTF_8));
}
}
7.創建客戶端
客戶端用來連接服務器
package com.dpb.netty.simple;
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;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-23 11:31
*/
public class NettyClientDemo {
public static void main(String[] args) {
// 客戶端就只需要創建一個 線程組了
EventLoopGroup loopGroup = new NioEventLoopGroup();
// 創建 啓動器
Bootstrap bootstrap = new Bootstrap();
try{
// 設置相關的參數
bootstrap.group(loopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
// 連接服務
ChannelFuture future = bootstrap.connect("localhost",6668).sync();
// 對服務關閉 監聽
future.channel().closeFuture().sync();
}catch (Exception e){
}finally {
loopGroup.shutdownGracefully();
}
}
}
8.測試
先啓動服務器,然後再啓動客戶端,然後看下面的效果,實現了我們的要求
方案再說明
- Netty 抽象出兩組線程池,BossGroup 專門負責接收客戶端連接,WorkerGroup 專門負責網絡讀寫操作。
- NioEventLoop 表示一個不斷循環執行處理任務的線程,每個 NioEventLoop 都有一個 selector,用於監聽綁定在其上的 socket 網絡通道。
- NioEventLoop 內部採用串行化設計,從消息的讀取->解碼->處理->編碼->發送,始終由 IO 線程 NioEventLoop 負責
NioEventLoopGroup 下包含多個 NioEventLoop
每個 NioEventLoop 中包含有一個 Selector,一個 taskQueue
每個 NioEventLoop 的 Selector 上可以註冊監聽多個 NioChannel
每個 NioChannel 只會綁定在唯一的 NioEventLoop 上
每個 NioChannel 都綁定有一個自己的 ChannelPipeline
好了netty的第一個入門案例就到此了,具體的代碼解釋請多看代碼中的註釋,有不清楚的歡迎留言交流 O(∩_∩)O哈哈~