文章目录
六、Netty 核心模块组件
Bootstrap、ServerBootstrap
Future、ChannelFuture
Channel
Selector
ChannelHandler 及其实现类
Pipeline 和 ChannelPipeline
ChannelHandlerContext
ChannelOption
EventLoopGroup 和其实现类 NioEventLoopGroup
Unpooled 类
API使用练习1
public class NettyByteBuf01 {
public static void main(String[] args) {
//创建一个ByteBuf
//创建对象,该对象包含一个数组arr,是一个 byte[10]
//netty的 buffer 不需要使用 flip 进行反转,因为底层维护了 readIndex 和 writeIndex
//writeIndex 可写的范围为 0 ~ capacity
//readIndex 可读的范围为 0 ~ writeIndex
//0 capacity
//0 ↑ writeIndex ↑
// ↑ readIndex ↑
ByteBuf buffer = Unpooled.buffer(10);
//赋值
for (int i = 0; i < 10; i++) {
buffer.writeByte(i);
}
System.out.println("capacity=" + buffer.capacity());
//输出
// for (int i = 0; i < buffer.capacity(); i++) {
// //指定索引读取
// System.out.println(buffer.getByte(i));
// }
for (int i = 0; i < buffer.capacity(); i++) {
//根据 readIndex 读取,每读一个,readIndex++
System.out.println(buffer.readByte());
}
}
}
API使用练习2
public class NettyByteBuf02 {
public static void main(String[] args) {
//创建ByteBuf
ByteBuf byteBuf = Unpooled.copiedBuffer("hello,world!", Charset.forName("utf-8"));
//使用相关的方法
if (byteBuf.hasArray()){
byte[] content = byteBuf.array();
System.out.println(new String(content, 0, byteBuf.writerIndex(), Charset.forName("utf-8")));
System.out.println("byteBuf=" + byteBuf);
System.out.println(byteBuf.arrayOffset()); //偏移量 0
System.out.println(byteBuf.readerIndex()); //读索引 0
System.out.println(byteBuf.writerIndex()); //写索引 12
System.out.println(byteBuf.capacity()); //总容量 36
//get方法为获取,不改变readIndex
//read方法为读取,会改变readIndex
//获取索引为0的元素
System.out.println(byteBuf.getByte(0));
//可读取的数量 12
int len = byteBuf.readableBytes();
System.out.println("len=" + len);
//读取一个元素
System.out.println(byteBuf.readByte());
//可读取的数量 11
len = byteBuf.readableBytes();
System.out.println("len=" + len);
//循环获取
for (int i = 0; i < len; i++) {
System.out.println((char) byteBuf.getByte(i));
}
//指定起始和长度的获取,起始为1,长度为2 el
System.out.println(byteBuf.getCharSequence(1, 2, Charset.forName("utf-8")));
}
}
}
Netty应用实例-群聊系统
GroupCharServerHandler
public class GroupCharServerHandler extends SimpleChannelInboundHandler<String> {
//定义一个channel组,管理所有的channel,方便消息的转发
//这个组是所有handler共享的,所以需要加上static
//GlobalEventExecutor.INSTANCE 全局事件执行器,是一个单例
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
//处理时间时使用的对象
//SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //非线程安全
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//handlerAdded 表示连接建立,连接后第一个被执行
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//当前时间
String nowTime = dtf.format(LocalDateTime.now());
Channel channel = ctx.channel();
//把当前客户端上线的消息转发到其他已登录的客户端上
//writeAndFlush 方法底层会循环遍历channelGroup并发送消息
channelGroup.writeAndFlush(nowTime + "[用户]" + channel.remoteAddress() + "加入聊天~\n");
//把连接的客户端的channel 加入到 channelGroup
channelGroup.add(channel);
}
//表示channel处于活动的状态,提示xxx上线
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//当前时间
String nowTime = dtf.format(LocalDateTime.now());
//也可能在这里对客户端上线的消息进行转发,这里只在server端显示
System.out.println(nowTime + " " + ctx.channel().remoteAddress() + "上线了~");
System.out.println("此聊天室在线的人数:" + channelGroup.size());
}
//表示channel处于不活动的状态,提示xxx离线
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//当前时间
String nowTime = dtf.format(LocalDateTime.now());
System.out.println(nowTime + " " + ctx.channel().remoteAddress() + "离线了~");
}
//断开连接,将xxx客户端离开的消息转发给当前在线的客户端
//当触发 handlerRemoved 方法时,会自动把当前客户端的channel从 channelGroup 中移除remove
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
//当前时间
String nowTime = dtf.format(LocalDateTime.now());
channelGroup.writeAndFlush(nowTime + "[用户]" + ctx.channel().remoteAddress() + " 离开了...\n");
System.out.println("此聊天室在线的人数:" + channelGroup.size());
}
//读取数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//获取当前连接的客户端的channel
Channel channel = ctx.channel();
//遍历channelGroup,根据不同情况,回送不同消息
channelGroup.forEach(ch -> {
//当前时间
String nowTime = dtf.format(LocalDateTime.now());
//不是当前channel,转发消息
if(channel != ch){
ch.writeAndFlush(nowTime + "[用户]" + channel.remoteAddress() + ": " + msg + "\n");
}else{
//用户自己
ch.writeAndFlush(nowTime + "me: " + msg + "\n");
}
});
}
//异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭通道
ctx.close();
}
}
GroupCharServer
public class GroupCharServer {
private int port; //监听端口
public GroupCharServer(int port){
this.port = port;
}
//处理客户端的请求
public void run() throws Exception {
//创建两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); //CPU核数 * 2 = 8
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
//链式配置
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) //使用 NioSocketChannel 作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG, 128) //设置线程队列得到连接的个数
.childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {
//handler 对应的是 bossGroup(指的是自己),childHandler 对应的是 workerGroup
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//获取pipeline
ChannelPipeline pipeline = ch.pipeline();
//向pipeline中添加处理器
//加入解码器
pipeline.addLast("decoder", new StringDecoder());
//加入编码器
pipeline.addLast("encoder", new StringEncoder());
//加入自定义的业务处理handler
pipeline.addLast(new GroupCharServerHandler());
}
});
System.out.println("netty 服务器启动成功~");
//绑定端口
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
//监听关闭事件
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new GroupCharServer(7000).run();
}
}
GroupCharClientHandler
public class GroupCharClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg.trim());
}
}
GroupCharClient
public class GroupCharClient {
//主机地址
private final String host;
private final int port;
public GroupCharClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//得到pipeline
ChannelPipeline pipeline = ch.pipeline();
//向pipeline中添加处理器
//加入解码器
pipeline.addLast("decoder", new StringDecoder());
//加入编码器
pipeline.addLast("encoder", new StringEncoder());
//加入自定义的业务处理handler
pipeline.addLast(new GroupCharClientHandler());
}
});
//连接
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
Channel channel = channelFuture.channel();
System.out.println("----------- " + channel.localAddress() + " -----------");
//监听关闭事件
channel.closeFuture().sync();
//客户端需要输入信息,创建一个扫描器
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String msg = scanner.nextLine();
//通过channel发送给服务器端
channel.writeAndFlush(msg + "\n");
}
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new GroupCharClient("127.0.0.1", 7000).run();
}
}
Netty心跳检测机制案例
MyServerHandler
public class MyServerHandler extends ChannelInboundHandlerAdapter {
/**
*
* @param ctx 上下文对象
* @param evt 事件对象
* @throws Exception
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
//对传过来的对象进行判断
if (evt instanceof IdleStateEvent) {
//转型
IdleStateEvent event = (IdleStateEvent) evt;
String eventType = null;
switch (event.state()) {
//读空闲
case READER_IDLE:
eventType = "读空闲";
break;
//写空闲
case WRITER_IDLE:
eventType = "写空闲";
break;
//读写空闲
case ALL_IDLE:
eventType = "读写空闲";
break;
}
//显示是哪一个客户端出现空闲
System.out.println(ctx.channel().remoteAddress() + " 超时事件:" + eventType);
System.out.println("服务器进行了相对应处理...");
//ctx.channel().close();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
MyServer
public class MyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO)) //在bossGroup里增加一个日志处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
/*
说明:
1. IdleStateHandler 是一个netty提供的处理空闲的处理器
2. 第一个参数:表示多长时间没有读,就会发送一个心跳检测包检测是否还连接(存活)
3. 第二个参数:表示多长时间没有写,就会发送一个心跳检测包检测是否还连接(存活)
4. 第三个参数:表示多长时间没有读写,就会发送一个心跳检测包检测是否还连接(存活)
5. 第四个参数:指定前三个参数的时间单位
文档说明:
* Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
* read, write, or both operation for a while.
6. 当 IdleStateEvent 触发后,就会传递给管道的下一个handler去处理,
通过调用(触发)下一个handler的 userEventTiggered 方法,我们
可以在该方法中来去处理上一个handler的IdleStateEvent(读空闲,写空闲,读写空闲)
*/
pipeline.addLast(new IdleStateHandler(
3, 5, 7, TimeUnit.SECONDS));
//加入一个对空闲检测进一步处理的自定义handler
pipeline.addLast(new MyServerHandler());
}
});
//启动服务器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
Netty 通过WebSocket编程实现服务器和客户端长连接
MyTextWebSocketFrameHandler
//TextWebSocketFrame 类型:表示一个文本帧(frame)
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("服务器收到消息:" + msg.text());
//回复消息
ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + " " + msg.text()));
}
//当web客户端连接后,触发方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//id 表示唯一的值,LongText 是唯一的,而 ShortText 不是唯一的
System.out.println("handlerAdded 被调用 " + ctx.channel().id().asLongText());
System.out.println("handlerAdded 被调用 " + ctx.channel().id().asShortText());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerRemoved 被调用 " + ctx.channel().id().asLongText());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("发生异常:" + cause.getMessage());
ctx.close();
}
}
MyServer
public class MyServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO)) //在bossGroup里增加一个日志处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//因为基于http协议,所以使用http的编码解码器
pipeline.addLast(new HttpServerCodec());
//是以块方式写,添加 ChunkedWriteHandler 处理器
pipeline.addLast(new ChunkedWriteHandler());
/*
说明:
1. http数据在传输过程中是分段HttpObjectAggregator,就是可以将多个段聚合
2. 这就是为什么,当浏器发送大量数据时,会发出多次http请求的原因
*/
pipeline.addLast(new HttpObjectAggregator(8192));
/*
说明:
1. 对应 websocket,它的数据是以 帧(frame)形式传递
2. 可以看到 WebsocketFrame下面有六个子类
3. 浏览器请求时 ws://localhost:7000/hello 表示请求的uri
4. WebSocketServerProtocolHandler 核心功能是将http协议开级为ws协议,保持长连接
5. 是通过一个状态码 101
*/
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
//自定义的handler,处理业务逻辑
pipeline.addLast(new MyTextWebSocketFrameHandler());
}
});
//启动服务器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
hello.html
<script>
var socket;
//判断当前浏览器是否支持websocket
if(window.WebSocket){
//go on
socket = new WebSocket("ws://localhost:7000/hello");
//相当于channelReado,ev 收到服务器端回送的消息
socket.onmessage = function (ev) {
var rt = document.getElementById("responseText");
rt.value += "\n" + ev.data;
}
//相当于连接开启(感知到连接开启)
socket.onopen = function (ev) {
var rt = document.getElementById("responseText");
rt.value = "连接已开启...";
}
//相当于连接关闭(感知到连接关闭)
socket.onclose = function (ev) {
var rt = document.getElementById("responseText");
rt.value += "\n连接已关闭...";
}
}else{
alert("当前浏览器不支持websocket");
}
//发送消息到服务器
function send(message) {
if(!window.socket){
//先判断socket是否创建好
return;
}
if(socket.readyState == WebSocket.OPEN){
//通过socket发送消息
socket.send(message);
}else{
alert("连接没有开启...");
}
}
</script>
<form onsubmit="return false">
<textarea name="message" style="height: 300px; width: 300px"></textarea>
<input type="button" value="发送消息" onclick="send(this.form.message.value)">
<textarea id="responseText" style="height: 300px; width: 300px"></textarea>
<input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
</form>
下一篇笔记:Netty入门学习笔记(四)
学习视频(p56-p72):https://www.bilibili.com/video/BV1DJ411m7NR?p=56