本文是个人的一些笔记整理,粗略看了一遍netty源码之后,进行的二次总结,可能有不到位的地方,请多多见谅。
Netty是 一个异步事件驱动的网络应用程序框架, 用于快速开发可维护的高性能协议服务器和客户端。(来自官网的简介)
此处不再重复介绍netty,具体介绍请看官网https://netty.io/
这里,我就用一个小小的案例来引入netty,先看看结果
先看看服务端:
再来看看客户端:
我们下面就来看看我是怎么使用netty来完成客户端与服务端的交互。
引入netty依赖
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.50.Finalversion>
dependency>
编写WSServer(启动类)
/**
* 启动类
*/
public class WSServer {
public static void main (String[] args) throws InterruptedException {
// 定义一对线程组
// 主线程组,用于接收客户端的连接,不做任何处理
EventLoopGroup mainGroup = new NioEventLoopGroup ();
// 从线程组,主线程组会把任务丢给他,让手下线程组去做任务
EventLoopGroup subGroup = new NioEventLoopGroup ();
try {
// netty服务器的创建,serverBootstrap 是一个启动类
ServerBootstrap server = new ServerBootstrap ();
server.group (mainGroup, subGroup) // 设置主从线程组
.channel (NioServerSocketChannel.class) // 设置nio的双向通道
.childHandler (new WSServerInitialzer ()); // 子处理器,用来处理subGroup
// 启动server,并且设置8088为启动的端口号,同时启动方式为同步
ChannelFuture channelFuture = server.bind (8088).sync ();
// 监听关闭的channel,设置为同步方式
channelFuture.channel ().closeFuture ().sync ();
} finally {
mainGroup.shutdownGracefully ();
subGroup.shutdownGracefully ();
}
}
}
编写WSServerInitialzer来完成childHandler的初始化
public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {
protected void initChannel (SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline ();
// websocket基于http协议,所以要有http编解码器
pipeline.addLast (new HttpServerCodec ());
// 对写大数据流的支持
pipeline.addLast (new ChunkedWriteHandler ());
// 对httpMessage进行聚合,聚合成FullHttpRequest或FullHttpResponse
// 几乎再netty中的编程,都会使用到hanler
pipeline.addLast (new HttpObjectAggregator (1024*64));
// websocket 服务器处理的协议,用于指定给客户端连接访问的路由 /ws
// 本handler会帮你处理一些繁重的复杂的事
// 会帮你处理握手动作: handshaking
// 对于websocket来讲,都是以frames进行传输的,不同的数据类型对应的frames也不同
pipeline.addLast (new WebSocketServerProtocolHandler ("/ws"));
// 自定义的handler
pipeline.addLast (new ChatHandler ());
}
}
编写一个自定义handler来处理数据
/**
* 处理消息的handler
* TextWebSocketFrame:在netty中,是用于为websocket专门处理文本的对象,frame是消息的载体
*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
// 用于记录和管理所有的客户端的channel
private static ChannelGroup clients = new DefaultChannelGroup (GlobalEventExecutor.INSTANCE);
protected void channelRead0 (ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 获取客户端传输过来的消息
String content = msg.text ();
System.out.println ("接收到的数据"+ content);
for (Channel channel : clients){
channel.writeAndFlush (new TextWebSocketFrame ("[服务器在]" + LocalDateTime.now ()
+ "接收到消息, 消息为:" + content));
}
}
/**
* 当客户端连接服务端之后,
* 获取客户端的channel,并且放到ChannelGroup中进行管理
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded (ChannelHandlerContext ctx) throws Exception {
clients.add (ctx.channel ());
}
@Override
public void handlerRemoved (ChannelHandlerContext ctx) throws Exception {
// 当触发handlerRemove,ChannelGroup会自动移除对应客户端的channel
// clients.remove (ctx.channel ());
System.out.println ("客户端断开,channel对应的长id:" + ctx.channel ().id ().asLongText ());
System.out.println ("客户端断开,channel对应的短id:" + ctx.channel ().id ().asShortText ());
}
}
前端代码这里就不再过多介绍了。
代码地址:https://gitee.com/oreo_team/netty-study-week01
看完这几段代码,是不是觉得很懵逼,我就用一张图(注意,此图只是一个简陋的图,里面还涉及到很多细节没有画出来),总结一下我刚刚写的代码吧
这种模式,就是我们最常用的主从多线程模式.主 reactor 只负责连接建立的处理,而其他处理事件的逻辑分给子reactor 来处理,最后得到的结果异步返回。
那这里也顺带提一下什么是Reactor线程模式呢?简单来说就是将原来一个人做的工作,分给多个人来做,这多个人分别负责不同的模块,每个模块都可以以集群的方式来做。
Reactor是一种开发模式,模式的核心流程:
注册感兴趣的事件-> 扫描是否有感兴趣的事件发生-> 事件发生后做出 相应的处理。
下面就来看看剩下的两种线程模式分别是:
**单线程,**这种单线程模式是指一个线程去处理一个事件,是不可取的,当处理的事件足够多的时候,很多事件都会处于阻塞状态,会导致事件的响应变慢,最终导致系统不可用。
多线程,可能针对第一个问题你可能会提出要引入线程池机制来避免浪费,但是本质上是没有变的,如果连接数量急剧上升,这种实现方式就无法很好地工作了,因为线程上下文切换开销会在高并发时变得很明显,这是同步阻塞方式的低扩展性劣势。(这也说明,多线程不一定就是好的)
Channel—Socket;
EventLoop—控制流、多线程处理、并发;
ChannelFuture—异步通知。
Channel、EventLoop、Thread 以及EventLoopGroup 之间的关系
ChannelFuture 接口
Netty 中所有的I/O 操作都是异步的,可以将ChannelFuture 看作是将来要执行的操作的结果的占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯定的是它将会被执行。此外,所有属于同一个Channel 的操作都被保证其将以它们被调用的顺序被执行。
ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器
ChannelPipeline,提供了ChannelHandler 链的容器,并定义了用于在该链上传播入站和出站事件流的API。当Channel 被创建时,它会被自动地分配到它专属的ChannelPipeline。
ChannelHandler和ChannelPipeline的关系,这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的。实际上,被我们称为ChannelPipeline 的是这些ChannelHandler 的编排顺序。
运动方向是从客户端到服务器端,那么我们称这些事件为出站(从tail->head)的,反之则称为入站的。
6月也接近尾声了,很少做总结的我,也该说说近况了,这篇文章也算是作为奥利奥团队的一员发布的一篇文章,之前虽然一直都有做笔记的习惯,但是没有像今天这样,老老实实把零碎的知识点,整理成一篇完整的文章(虽然也不算太完整,感觉思路还是有点乱的,大家没有基础的话,可能不知道我在说啥),其实,我一直有这么一个梦想,就是拥有属于自己的一个团队,所以,我之前也试过去找人,但是,最终以失败告终,可能我不太擅长说话吧,导致最后整个团队还没找齐人就决定放弃解散了,但是,在6月初,有幸被邀请到奥利奥团队,作为一个成员,我其实是挺激动的,因为也终于找到一群人,一起学习,一起成长了。
再来说说最近的一些学习状况吧,最近学源码真的学得脑袋疼,这细节扣得死死的,感觉也不知道学了啥,不过,也总算意识到这种学习方式的错误,好像一开始真的不能这样子抠细节,还是边应用边抠细节吧,所以,下周的计划还是继续以项目为驱动来学习源码,重点把期末大项目搞出来了先,文档也进来在这周出个初稿了,所以,这周任务比较重,加油!!
上次说到我在疫情中干了什么,趁今天做总结,再来说一下疫情期间,我是如何做到自律的,先谈谈我对自律的观点吧,我认为的自律是,你有目的,有规律地去干一件事,而不是三天打鱼两天晒网,这里很多人可能会说,自律就是不玩游戏,不玩手机,天天呆在电脑前面学习,我是不太建议这种自律的方法的,我比较推崇的是,该玩的时候放开玩,该学习的时候就要尽心学习。
好,接下来就说说我的日常:
也说得差不多了,今天的总结就到这吧,接下来可能会每隔3天左右,就会总结一篇笔记发上来,嘻嘻,喜欢的朋友记得点个赞!
14:30-17:30,重复早上的操作,一般这个时候,如果感觉状态还不错,就会继续早上的学习
5. 17:30-18:30,打球运动时间
6. 19:00-20:00,吃饭洗澡时间
7. 20:00-23:00,如果感觉状态还不错,就会继续下午的学习,如果状态比较差,就会整理一下笔记
8. 23:45之前就要睡觉了
也说得差不多了,今天的总结就到这吧,接下来可能会每隔3天左右,就会总结一篇笔记发上来,嘻嘻,喜欢的朋友记得点个赞!