网络编程包 - Magician 的原理 与 使用

2021-04-20 09:41:21 +08:00
 Joker123456789

Magician 是一个异步非阻塞的网络编程包,用 java 语言实现,支持 Http, WebSocket, UDP 等协议

运行环境

jdk11+

简单的原理介绍

Magician 的底层使用的是 NIO,但是没有用 Selector,因为 Selector 的设计初衷就是为了用单线程来处理高并发,从而减少 因为连接太多而造成线程太多,占用过多的服务器资源。 但是这样做的坏处也很明显,就是无法充分利用 cpu 的多核性能,还有一个就是 如果业务比较耗时,会造成整个循环被堵住。

所以,考虑到这一点,Magician 决定使用 accept,代码如下:

while (true){
    SocketChannel channel = null;
    try {
        /* 这个方法会阻塞,直到有新连接进来 */
        channel = serverSocketChannel.accept();
        channel.configureBlocking(false);

        /* 将任务添加到队列里执行 */
        ParsingThreadManager.addTaskToParsingThread(channel);
    } catch (Exception e){
        logger.error("处理请求出现异常", e);
        ChannelUtil.close(channel);
    }
}

有一个 while 不断的监听 accept,当没有新请求进来的时候 accept 是阻塞的,所以不会有空轮询的问题,当有了新的请求进来,就会把 channel 丢到队列里面去,然后继续监听 accept 。

那么这个队列是什么结构的?他又是如何来执行的呢?

首先,队列的结构是这样的:他是一个 LinkedBlockingDeque,有序 且 长度无限(除非内存爆了),然后这个队列 放在了一个线程中, 线程开启后就会有一个 while 不断的从这个队列里 take 元素,如果队列为空,take 就会阻塞,队列一旦有数据 take 就会按顺序返回里面的元素。

所以,只要这个线程在运行,我们就可以不断的往队列里丢任务,让这个线程来慢慢消化。

如果这样的线程+队列有多个, 我们把收到的请求 通过轮询算法 分配到这些队列,让线程各自消化,是不是就可以实现一个,线程数量可控,同时又具有异步特性的模型?

这个模型就是 Magician 现在所使用的,如下图所示:

在使用 Magician 的时候,可以自己配置 需要几个线程来同时运行。

除了 http,udp 也是采用的这个模型。只不过 udp 是以同步的模式读数据,数据读完了 再丢到队列里 让队列去执行业务逻辑。

如何使用

说了这么多原理,我们接下来说说 如何使用 Magician 来开发各种服务。

首先我们看一下 http 的实现

Magician.createHttpServer().httpHandler("/", req -> {

                        req.getResponse()
                           .sendJson(200, "{'status':'ok'}");

                    }).bind(8080).start();

如果不想把 handler 跟这段代码窝在一起,可以单独建立 handler,在这里添加进去即可

WebSocket 实现

Magician.createHttpServer().bind(8080)
                    .httpHandler("/", new DemoHandler())
                    .webSocketHandler("/websocket", new DemoSocketHandler())
                    .start();

只需要在创建 http 服务的时候,添加一个 WebSocketHandler 即可。

UDP 实现

Magician.createUdpServer()
                .handler(outputStream -> {
                    // outputStream 是 ByteArrayOutputStream 类型的
                    // 它是客户端发过来的数据,自行解析即可
                }).bind(8088).start();

同样的,也可以单独创建 handler,在这里添加进去

了解更多

想了解更多的话,欢迎访问 Magician 官网:http://magician-io.com

2523 次点击
所在节点    推广
54 条回复
Joker123456789
2021-04-21 13:25:49 +08:00
@guyeu

又发现个不足点, 非常感谢。
GuuJiang
2021-04-21 14:58:18 +08:00
既然难得你听得进去,那我就再多啰嗦几句

你这个东西不是基于现在已经指出的问题修修补补就能完成的,只能推倒重写,因为你的根本思路就是反的
作为一个网络框架,它的职责是什么,是负责网络 IO,所以要做的事情就是用尽量少的资源管理尽量多的连接,负责网络数据的读写,对于上层协议应该一无所知,也就是只实现 TCP/UDP 层,有数据可读就读,读到的数据直接丢给上层协议,至于协议数据什么时候完整,如何划分边界,得到业务数据后怎么进行业务处理,用什么线程模型,这些通通都是上层协议的事情,已经和网络层无关了,而你现在的思路是由协议层来主导读,我是一个 http 协议,所以我主动调 read 方法直到读到一个完整的 http 请求,我是一个 websocket 协议,那我就定时读取数据,这种思路首先只适用于 bio,其次也不推荐,这也就是我一开始说的,从 bio 到 nio 并不只是一个简简单单的 configureBlocking(false)就完事了,而是思路的转变

你真的想要实现一个网络框架,那最基本的能力之一就是使用者能自由扩充应用层协议,你确实可以内置一部分常用协议,但这种扩充应该是无侵入性的,更大的意义在于让使用者能开箱即用,同时对扩展协议提供参考
再回过头来看你图里的后半部分,我为什么管它叫线程池的雏形,因为它确实跟线程池的基本原理有点类似,但是很可惜实现也是错误的,你的队列和线程是一一对应的,这就导致线程无法得到充分利用,会出现有的线程空闲,而有的任务又被堵塞得不到处理,你可以去看看正确的线程池实现方式,给你点提示,看看队列和线程之间到底是什么关系

综上所述,你的这个东西,网络层和协议层之间的关系是错的,对 NIO 的使用方式是错的,线程池实现也是错的,并且这些错误都属于非常基础非常低级但是在初学者身上又很容易出现的,说实话,我发完第一条回复后好奇点了下你的发帖历史,我就已经后悔了,因为我看得出来你对自己的开源项目是非常自信的,但是很可惜你实际写出来的东西和你的自我认知之间存在非常巨大的差距,这样说也许很残忍,但是如果你继续在这条路上越走越偏直到哪天摔下来的时候,在隔壁帖子里对你表示过支持的所有人都有责任
D3EP
2021-04-21 16:14:12 +08:00
while (this.channel.read(readBuffer) > -1) {
readBuffer = parsingByByteBuffer(readBuffer);
if(readBuffer == null){
break;
}
}

read 是个非阻塞的,这儿会一直空转。你的框架真的是自娱自乐...
Joker123456789
2021-04-22 13:40:16 +08:00
@D3EP 空转?? 除非 channel 里没数据。都这怎么会空转? parsingByByteBuffer 这个方法被你吃了?
Joker123456789
2021-04-22 13:41:50 +08:00
@D3EP

空转?? 除非 channel 里没数据。否则怎么会空转? parsingByByteBuffer 这个方法被你吃了?

每次读出来的数据 不会超过 readBuffer 的长度, 你发送一个 http 请求 数据大小如果超过缓冲区长度,你一次能读完吗? 不得多读几次? 连这个 while 的意思都看不懂????
Joker123456789
2021-04-22 13:43:39 +08:00
@D3EP 一个 http 请求至少 有第一行的三个符号 以及部分请求头,channel 会没数据吗? 你这个空转怎么得来的?

喷也要 有点技术含量好吧?
Joker123456789
2021-04-22 13:45:16 +08:00
@D3EP 然后你再看一看 parsingByByteBuffer 这个方法里 是怎么判断 该不该停止的,去看看吧。好吗?

求你认真看完 再喷。 不要为了喷去看代码,而是看了代码发现了槽点 再来提。

你现在的这种心态就是 找茬,根本不是真心了解这个项目。

我是哪得罪你了吗? 要你如此大费周章的 找我茬 来喷我?
Joker123456789
2021-04-22 14:09:43 +08:00
@GuuJiang

有问题就改嘛,直到成熟为止。什么东西不是一点点积累起来的呢,至于你说越走越偏 那基本是不可能的,因为错的东西总会有暴露的时候,暴露出来后 我就会立刻去改。 我想不会有人来捧我这个 素不相识的人吧? 所以那种温水煮青蛙的情况 你大可不必担心。

我发出来就是为了 有你这样的人可以给我指点, 所以你不必为我听得进去而感到惊讶。

然后可能我对这个东西的定位 跟 真正的网络包 产生了冲突, 这个应该是用词不当带来的问题。 我对这个项目的定位就是一个 支持 http,udp,webwocket 的 服务包, 我从来就不是走的 netty 的那条路,netty 应该是 二次封装后的底层,可以比喻成 BIO,NIO,AIO 之后的又一个 IO (虽然那他是基于 NIO 的)。 但是我做的这个是偏应用层的,你可以看做是一个 极其微小的 tomcat,jboss 等。 所以他不是你印象中的那种网络包,只是我用词不当导致的。

最后,你说线程池 是错的,我有点不敢苟同, 如果你把每个线程看作一台服务器,吧往队列里丢任务的那个进程看做是 nginx,你就大概明白这个思路了。 如果这个算错误,那负载均衡不也是错的? 有些服务器可能收到的请求比较轻松他执行的很快,有些服务器收到请求比较大 执行缓慢,不也会导致部分服务器被堵死吗? 所以负载均衡这种架构也是错的吗?

这个问题,我觉得加一个超时控制,或者把线程和队列分开? 让空余线程来轮询或者随机消费其中一个队列? 优化一下就好了嘛,只是一个有 bug 的东西,不至于彻底否认吧。

有问题就改问题,所以 你如此的语重心长,苦口婆心 ,我是听进去了,但你不至于这样,这只是一个有 bug 和设计缺陷的不成熟项目而已, 改就是了。

而且版本号你也应该看过吧,1.1.5, 这才哪到哪,现在基本只是处于一个能跑的状态,你对他的期望值过高 也是我们产生矛盾的 一个点。
Joker123456789
2021-04-22 14:12:26 +08:00
@GuuJiang 而且你也看一下这个项目的创建时间,这才写了几天哦。
Joker123456789
2021-04-22 14:32:26 +08:00
@GuuJiang

还有一点,对于你说的 网络层和协议层, 为什么你不这么看待呢? NIO 就是网络层啊,而我写的这个东西就是协议层啊。

可能我在对他的定位 用词不当,导致你把他当做一个网络层来看待了,所以觉得这个架构 很荒谬。

你不妨就把他当做你口中的协议层, 他就是一个协议层, 基于 NIO 开发的一个服务器,支持 http,websocket,UDP 三种请求方式,仅此而已。 就是 NIO 到应用层的 一个中间的 数据解析工具。
GuuJiang
2021-04-22 16:03:29 +08:00
@Joker123456789 #43 指出的问题和我一直在说的是同一个问题,如果这个弯转不过来那你永远不可能写出正确的 NIO,你自己 debug 看一下是不是客户端没有发数据的时候也在读,然后读到 0 字节?
试图用协议层去驱动网络层就是导致你所有这些错误的根源,换句话说你可能用了下 NIO,但是以一种错误的姿势在使用,然后觉得是框架自身的问题,于是准备解决这个“问题”,你有没有想过如果真的存在如此低级的问题,别人就不会发现吗?难道所有人都在默默忍受?
正确认识自己确实不是一件容易的事,言尽于此,我也不会再继续回复了
D3EP
2021-04-23 13:56:52 +08:00
@GuuJiang 老哥你还给苦口婆心地帮他分析...自傲且不自知的人,让他自娱自乐去吧
Joker123456789
2021-04-23 15:04:36 +08:00
@GuuJiang 我知道是同一个问题, 我反驳他是因为他 出言不逊, 我反驳的是他的不尊重,而不是 他指出的问题。

如果他后面不加那句“自娱自乐” 我是不会反击他的。 反倒是他自己,连续三次 在最后 说出了“自娱自乐”这三个字,及其的不尊重人。 就这样 还指望我能用好态度对他啊。

但是你指出的问题,我已经在修复中了,甚至有可能会彻底改变架构,对你我是表示感激的。
Joker123456789
2021-04-23 15:26:29 +08:00
@GuuJiang 你作为一个旁观者,可能 会忽略 那四个字,因为他不是在喷你。 所以你的注意力会在 他指出的问题上,因为这个问题跟你产生了一定的共鸣。

但是,我连续熬夜 2 个多星期 做出来的一个项目,他说我自娱自乐,你觉得我能忍吗?

我跟你聊了那么多个来回,甚至我都明确回复了 [没数据确实是个问题,非常感谢] 我都这样回你了,你居然还以为我不知道这个问题。 我心里苦啊。

你最后那句 [正确认识自己确实不是一件容易的事] 让我有点寒心。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/771813

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX