V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Joker123456789
V2EX  ›  推广

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

  •  
  •   Joker123456789 · 2021-04-20 09:41:21 +08:00 · 2680 次点击
    这是一个创建于 1316 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 现在所使用的,如下图所示:

    avatar

    在使用 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

    54 条回复    2021-04-23 15:26:29 +08:00
    zifangsky
        1
    zifangsky  
       2021-04-20 10:36:31 +08:00
    看起来不错,后面有机会可以试用一下
    guyeu
        2
    guyeu  
       2021-04-20 10:46:35 +08:00
    1. 推广就发推广;
    2. 看来看去看不出来比 netty 高在哪儿
    Joker123456789
        3
    Joker123456789  
    OP
       2021-04-20 10:52:56 +08:00
    @guyeu

    1. 推广当然要发在人多的地方,推广节点有人看吗? 你会去一个全是广告的节点翻帖子??? 如果你有意见可以举报一波,让管理员来删帖。轮不到你喷我。
    2. 我好像全文都没跟 netty 对比吧?你突然冒出一句 看不出来比 netty 高级在哪,我很懵逼啊。
    Joker123456789
        4
    Joker123456789  
    OP
       2021-04-20 10:54:19 +08:00
    @guyeu 而且,这个节点是 java,我发的也是 java 项目,而且是开源非商业, 我不觉得有啥大问题。 如果真的冒犯到了这里的规矩,我接受管理员的删帖 甚至封号。
    GuuJiang
        5
    GuuJiang  
       2021-04-20 12:09:27 +08:00   ❤️ 1
    折腾的精神可嘉,但是。。。容我给估计还沉浸在造完轮子的喜悦中的你泼点冷水,相信我,再过几年,你会希望自己没有留下过这段黑历史的
    首先,你所谓的 Selector 的“缺点”,证明你并没有真正理解 Selector 的意义,并不是说 configureBlocking(false)就可以称为 NIO 了,恰恰相反,Selector 才是 NIO 的灵魂,让我们来看下你是怎么做的吧,用一个每 100ms 执行的定时任务,循环对每一个 channel 进行 read,这点实在是槽点太多无从吐起,建议你自己先去看看 select 、epoll 、iocp 等的基本原理,如果看完还有疑问的话欢迎回来讨论
    至于“如果业务比较耗时,会造成整个循环被堵住”,这个跟是否用 Selector 根本没关系,而是使用者自己应该保证不在 IO 线程里处理耗时业务,再看看你是怎么解决这个所谓的“问题”的,我猜这个 BlockingQueue 应该是你最引以为傲的一部分了吧,恭喜你,你重新发明了线程池……的雏形版
    总的来说,这个东西作为一个关于 NIO 以及线程池的概念验证的课后实验还是可以的,但是实际应用的话,价值为零
    D3EP
        6
    D3EP  
       2021-04-20 12:14:01 +08:00
    同意楼上。看上去解决了一个不存在的问题。
    learningman
        7
    learningman  
       2021-04-20 12:15:01 +08:00 via Android   ❤️ 2
    @livid 推广
    learningman
        8
    learningman  
       2021-04-20 12:15:39 +08:00 via Android
    Http, WebSocket, UDP
    这三个玩意儿是应该并列的吗?
    Livid
        9
    Livid  
    MOD
       2021-04-20 12:26:51 +08:00
    @learningman 谢谢。这个主题已经已经被移动。

    @Joker123456789 请阅读 V2EX 的节点使用说明:

    https://www.v2ex.com/help/node
    Joker123456789
        10
    Joker123456789  
    OP
       2021-04-20 12:51:02 +08:00
    @GuuJiang

    100 毫秒的 那个你是不是看错了? 那个是 websocket 。 连接上了以后 需要实时监控 channel 里有没有消息。
    而且这个一看就知道 不是 http 了啊,跟我文章是描述的原理完全不搭噶,你是怎么理解到一起的?

    建议你重新看一下 http 的实现部分。

    还有这句: [至于“如果业务比较耗时,会造成整个循环被堵住”,这个跟是否用 Selector 根本没关系,而是使用者自己应该保证不在 IO 线程里处理耗时业务]

    你可以试一下 在 select 的 while 里开线程试试,你去试一下就知道了。

    第三线程池确实是这个原理,但是这有什么问题?
    Joker123456789
        11
    Joker123456789  
    OP
       2021-04-20 12:51:40 +08:00
    @learningman

    来,说出你的观点。 不要直接一句反问,因为我不懂你的意思。
    Joker123456789
        12
    Joker123456789  
    OP
       2021-04-20 12:58:01 +08:00
    @GuuJiang
    哦对了,还有一点,我分享原理 是想让大家对这个东西了解多一些,从而决定自己要不要使用。并不是拿来炫耀的。

    你所谓的没价值,只是你认为,有没有价值取决于 每个人的 实际需求。

    最后呢,建议你看代码的时候仔细一点,你点的那个 package 明明就是 websocket 而不是 http,这都没看出来吗?
    Joker123456789
        13
    Joker123456789  
    OP
       2021-04-20 13:06:38 +08:00
    @GuuJiang selector 确实是精髓,但是精髓 就在于 并发高了不会造成线程太多啊,因为他是用一个线程在消费 selectionkey,我不是在文章里说过了吗?

    如果你觉得可以在 while 里开线程,那你就去试试,试完了再说话。

    这句代码会把当前的连接 放到 selectionkeys 里
    ```java
    int select = selector.select();
    ```

    而 http 需要将连接保持到响应结束,如果你开线程去做别的事,这件事做完之前 连接是不会关闭的,会导致这里将 channel 再一次的取出来,造成重复消费。不信可以自己尝试。

    不过话说回来,while 里开线程,和我这个模型有啥区别呢? 不都是把 channel 丢给线程去处理,有区别吗?
    GuuJiang
        14
    GuuJiang  
       2021-04-20 13:11:27 +08:00
    @Joker123456789
    这个包结构本身就是另一个槽点了,证明了你对 tcp/http/websocket 三者关系的理解都是混乱的,这个暂且不表
    你只需要回答一个问题,“在一个循环里依次对一堆 channel 进行 read”这个做法,相比起被你否定掉的 NIO,优势在哪里?
    Joker123456789
        15
    Joker123456789  
    OP
       2021-04-20 13:15:11 +08:00
    @GuuJiang 优势在于可以控制线程数量啊。 使用者可以自己配置 要几个线程来进行消费。 而不是只有一个 while 在那消费 selectionskey 。

    还有,http,websocket 都是给予 tcp 的吧,websocket 甚至需要先发一个 http 来建立连接,所以 http 和 websocket 都放在 tcp 的包里 没什么问题吧?

    upd 是另一种协议,我也是放到另一个单独的包里了。而且 udp 我就是用的 selector,因为他不需要保持连接到响应结束,因为他不需要响应,所以我可以在数据读完以后 再开线程去消费,这样就不存在重复操作 channel 的问题了。
    GuuJiang
        16
    GuuJiang  
       2021-04-20 13:19:19 +08:00
    你开心就好
    Joker123456789
        17
    Joker123456789  
    OP
       2021-04-20 13:25:41 +08:00
    @GuuJiang 找了个 槽点出来,吐槽了一番,后来发现自己看错了代码, 然后 就吐槽我对协议的理解有问题。

    当我解释了优势 以及协议的关系后, 来一句“你开心就好”。

    哎~,真的是。。。一言难尽。
    Joker123456789
        18
    Joker123456789  
    OP
       2021-04-20 13:36:11 +08:00   ❤️ 3
    @Livid 如果真的要按规矩来,这个帖子应该在 分享创造 节点。 谢谢。
    D3EP
        19
    D3EP  
       2021-04-20 14:27:29 +08:00
    一般异步非阻塞的网络编程框架起码能 C10K 。但当前框架一个线程同时只能处理一个 TCP 连接,而且在没有数据时一直空转,非阻塞 IO 不和多路复用一起使用,CPU 打满的几率非常高。设计上就存在问题,只能说自娱自乐了...
    D3EP
        20
    D3EP  
       2021-04-20 14:32:27 +08:00
    此外,IO 线程和任务处理线程不做隔离,那更容易出问题了。可以参考 Tomcat 、Netty 的设计。
    guyeu
        21
    guyeu  
       2021-04-20 14:38:37 +08:00
    @Joker123456789 #3 推广的事就不提了。其实上午我怀着相当大的期待阅读了你的部分源码,因为是把它当作 Netty 的替代去看待的,所以不自觉地把它和 Netty 做了一些对比,如果有冒犯的地方向你道歉。emmmmm,那就不提 Netty 说几个有可能提升你这个项目质量的点

    1. 对协议的抽象不够,假如某个业务想把 http 换成 udp,看不出有什么平滑切换的可能性;
    2. 和线程模型绑得太死,市面上鲜少有这种自带线程模型的网络库;
    3. 没有性能测试数据,也无从得知你这个网络库的性能怎么样;
    4. 代码风格值得优化,起码把 JavaDoc 按规范写了吧;
    5. 缺乏面向对象设计,似乎从未考虑过一个服务的两个组件同时使用你这个网络库的任何可能性;
    learningman
        22
    learningman  
       2021-04-20 14:58:14 +08:00 via Android
    @Joker123456789 自己去看看 7 层网络模型,这三个分别应该在哪一层
    Joker123456789
        23
    Joker123456789  
    OP
       2021-04-20 15:50:03 +08:00
    @D3EP

    1. 队列的 take 方法 会自动检查队列是否为空,如果为空是不返回的 ,直接阻塞在那,所以不存在空转。

    2. 多路复用,那是系统层面的,到了应用层还是一个线程在消费。Selector 确实是一个线程 可以处理多个 TCP 连接,但是他是在 while 里排队一个个处理的。 这跟我的模型区别不大吧? 我也是每一个线程 都在处理排着队的多个 tcp 。

    3. 配置几个线程 就是几个线程同时跑,不会因为请求多了就线程暴增,怎么会打满 CPU ?

    4. 你说的线程没有隔离 可否详细一点? 对于这一条,我是真的想虚心请教的。
    Joker123456789
        24
    Joker123456789  
    OP
       2021-04-20 15:50:51 +08:00
    @learningman 我只是做了一个支持 这三个协议的包,你跟我扯 网络模型干嘛? 没东西喷了 就来吐槽我的目录结构吗?
    Joker123456789
        25
    Joker123456789  
    OP
       2021-04-20 15:55:14 +08:00
    @guyeu

    说实话,你这段话有点感动到我了。真的。

    我会认真考虑你的建议,并在后面尽可能优化上。
    Joker123456789
        26
    Joker123456789  
    OP
       2021-04-20 16:12:06 +08:00
    @guyeu 不过,第 5 点 是支持的, 监听两个端口即可。
    huang119412
        27
    huang119412  
       2021-04-20 17:17:16 +08:00
    感觉这个线程模型很像 tomcat 的 nio,tomcat 的 nio 的 ServerSocketChannel 就是用的 accept 。netty boss 线程只处理连接,worker 处理其他事件应该算是经典了。netty 兼容性,稳定性,安全性,便捷性久经考验。性能早就不少 netty 强项。是个轻量级的 nio,aio 都能和 netty 性能媲美。但是生产中敢随便用吗?
    blackboom
        28
    blackboom  
       2021-04-20 17:28:24 +08:00
    建议换个头像
    GuuJiang
        29
    GuuJiang  
       2021-04-20 19:04:35 +08:00
    估计你今天情绪上很难接受,为此还专门另开一个贴抱怨下,以你现在对网络 IO 的认知,我很难跟你一一讲明白你的错误在哪里,但是明眼人都看得出来,真心建议你静下心来好好找点资料看看网络基础,各种多路复用方案到底在解决什么问题,等你看完了如果还是不服气,欢迎你回来对线
    你可以先带着这个问题去看
    “当客户端没有发数据时,你的程序在干什么,而其他正确实现了的网络框架,包括( BIO 、NIO 、netty 等)又在干什么”

    到时候你就会明白,你这个东西称之为“一个异步非阻塞的网络编程包”,是多么的荒谬
    Joker123456789
        30
    Joker123456789  
    OP
       2021-04-20 21:41:44 +08:00 via iPhone
    @GuuJiang 好的, 我会去看的。

    然后回答一下你的问题, 当没有数据进来的时候,我这个程序是等待状态,accept 自动阻塞住了 不会返回任何东西 就停在那。 队列的 take 方法也阻塞住了,就停在那。 整个程序除了主线程挂在那,就没任何动作了。

    然后我问你一个问题, 消费 selectionkey 的那个 while 是不是有被业务阻塞的可能? 你可能会说 在 while 里开线程 让业务线程去处理。 但问题是 在业务线程跑完之前 channel 是不能关的。 因为 http 客户端需要等待响应, 必须等业务跑完 并把响应写入 channel 才能关吧。 而 channel 不关的话,while 会进入下一次循环,select 方法会再次为这个连接生成 selectkey, 这样一来就出现重复处理了。 这个问题是我真实遇到的问题。 如果你有办法解决, 那你可以说出来。

    多路复用器 是系统层面的, 到了应用层 就是单线程在一个个消费 selectkey, 如果这个你觉得不对也欢迎 指正。并说出具体哪里不对。

    最后你可以去看一下 nio 的 Reator 的分发模型, 是不是跟我这个有点像。
    Joker123456789
        31
    Joker123456789  
    OP
       2021-04-20 22:02:28 +08:00 via iPhone
    @GuuJiang 接着上一条回复,

    我重新回答一下这个问题吧: [在一个循环里依次对一堆 channel 进行 read”这个做法,相比起被你否定掉的 NIO,优势在哪里?]

    你可以把我的模型 理解成,可以用配置来 决定同时有几个循环在 以此消费 selectionkey 。 把单线程变成了 可以指定数量的多线程。

    说白了,NIO 的 while 相当于单机, 而我这个相当于负载均衡。 这个理解吧?

    虽然 没有彻底异步, 也不可能彻底异步,否则就会出现一个请求一个线程 带来上下午切换的问题。 但他确实 解决了一些问题吧?

    当然了,如果 NIO 有自带的 api 来做这件事, 你可以说出来反驳我。。

    我有情绪是针对喷子的, 对于你这种正确交流的人 我是很友好的,你可以放心说话。
    GuuJiang
        32
    GuuJiang  
       2021-04-20 22:09:46 +08:00
    @Joker123456789
    第一句话就错了,你再好好看看 tcp 里 accept 到底是在哪个阶段
    至于你说的 select 重复处理的问题,在看到代码之前不好下结论,不过我猜十有八九是没有调 remove,因为这个是个很典型的错误
    早期版本的 jdk 安装后有个 samples.zip ,里面就有一个用 nio 实现的 echo server 的例子,建议你多读几遍,而实际应用到真实项目中时,只需要把直接 reply 的那一行代码换成投递到工作线程池即可,这也是业界通用的做法
    我在 16 楼的那句话可能刺伤了你,对此我道歉,但是等你弄明白所有这些问题之后相信你能体会当时的心情,换作你也会无话可说的
    Joker123456789
        33
    Joker123456789  
    OP
       2021-04-20 22:35:42 +08:00 via iPhone
    @GuuJiang

    remove 是有的, 不是 selectionkey 这个集合里 有重复数据, 而是再次执行 selector.select () 方法后 重新获取的 selectionkey 集合里 有正在被处理的 channel,

    while ( true ){
    selector.select ();

    while (){
    如果这里开了线程,内部这个 while 没问题, 但是等内部 while 结束后, 外部 while 开始下一次循环后, 获取到的那个新的 selectionkey 里会有正在处理的 channel



    一开始我就是用的 选择器, 如果在 while 里开线程能解决问题,我还这么大费周章干嘛呢。

    而且 UDP 我也是用的选择器, 因为不用响应,我可以同步读完后,用线程去处理 handler 。

    accept 是在有连接进来的阶段。 选择器里如果发现了 这个状态的 key 不就会立刻注册成 op_read 状态吗? 代表可以读了

    然后你也不用道歉,我那个帖子是怼那两个喷子的, 并不是针对你,对于正确讨论的人 我很少闹情绪。 今天白天 主要是你一上来看错了代码,我才那样的。
    GuuJiang
        34
    GuuJiang  
       2021-04-20 22:51:11 +08:00 via iPhone
    “selector 里有正在处理的 channel”这个没有任何问题啊,你可能误解了 selector 的含义,每一次 select 操作返回的是处于就绪状态的 channel,所谓的就绪对于 read 操作来说就是有数据可读,那你读就行啊,内层 while 是错误的,你还停留在 bio 的思想,有数据可读不代表能完整地读到一个上层协议所需要的结构,你能读到多少读多少,至于什么时候得到上层所需要的完整数据这又是另一个话题了,暂时按下不表,所以一个 channel 多次出现在 select 的结果中是再正常不过的行为
    你画个时序图,模拟下多个连接同时存在,然后以随机的间隔发送下一个数据片段,然后对比下在这种情况下你的程序和 nio 的区别
    要知道,一个 channel 不是你想读就能读到数据的,而 select 的存在就是保证了你只在真正有数据可读时才去读
    Joker123456789
        35
    Joker123456789  
    OP
       2021-04-20 22:54:57 +08:00 via iPhone
    @GuuJiang

    accept 确实是阻塞住的, 对于防止空转 我还是很重视的, 自测过好几次。

    因为 block 设置为 false 我是设置在 socketChannel 上的,

    而我监听的是 serverSocketChannel 的 accept 方法 这个的 block 我设置的是 true 。
    Joker123456789
        36
    Joker123456789  
    OP
       2021-04-20 23:05:22 +08:00 via iPhone
    @GuuJiang 问题是 已经有个业务线程在读写他了, 如果再读写一次直接抛异常, 而且也不该 被多个线程读写吧。

    请求来了,业务线程工作了, 结果你又叫别的业务线程来处理这个 channel, 有点不合逻辑吧。

    不过这个思路我可以研究一下。

    然后,http 的读 我是根据 content-length 来判断读没读完的,所以一旦开始了 read 就会读到结束。 否则就会一直读。

    websocket 也是根据长度判断的,但是同一个通道 会有很多数据进来,如果发送太快会发生粘包问题, 这个我也在想办法处理。
    GuuJiang
        37
    GuuJiang  
       2021-04-20 23:06:41 +08:00 via iPhone   ❤️ 1
    accept 阻塞的是 connect 阶段,我说的是客户端 connect 后没有发数据,假设你总共有 n 个线程,现在有 n 个客户端连接上但是只发了一个完整 http 请求的一部分数据,这时候第 n+1 个以后的客户端是不是永远被卡住得不到处理了?你这个完全就是同时抛弃了 bio 和 nio 的优势,结合了二者的劣势,bio 虽说占用线程数多点吧,好歹还能继续处理后续的客户端,并且没数据可读时是在阻塞,而你这个本质上还是一个线程服务一个连接,并且没数据时也在空转,而且线程数还是有上限的,所以连最原始的 bio 都不如
    Joker123456789
        38
    Joker123456789  
    OP
       2021-04-20 23:19:11 +08:00 via iPhone
    @GuuJiang 对, 如果数据没有发送确实是个问题。

    我再来优化一下吧。 非常感谢。
    bengol
        39
    bengol  
       2021-04-21 01:08:48 +08:00 via Android
    说个题外话 bio/nio 这些东西都没啥意思 模型都很成熟了,聊来聊去就那些知识点

    基于不太稳定的 io uring 实现 aio 以及附加的 syscall batch 可能还有点意思

    配合网络智能卡或者 RDMA 做底层网络方面优化比较好玩

    我们当前在追求一个完全的 zerocopy network
    programming framework
    guyeu
        40
    guyeu  
       2021-04-21 10:17:01 +08:00
    @Joker123456789 #26 然后两个组件的网络消息共用一个线程池。。。
    Joker123456789
        41
    Joker123456789  
    OP
       2021-04-21 13:25:49 +08:00
    @guyeu

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

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

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

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

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

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

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

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

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

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

    我是哪得罪你了吗? 要你如此大费周章的 找我茬 来喷我?
    Joker123456789
        48
    Joker123456789  
    OP
       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
        49
    Joker123456789  
    OP
       2021-04-22 14:12:26 +08:00
    @GuuJiang 而且你也看一下这个项目的创建时间,这才写了几天哦。
    Joker123456789
        50
    Joker123456789  
    OP
       2021-04-22 14:32:26 +08:00
    @GuuJiang

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

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

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

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

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

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

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

    你最后那句 [正确认识自己确实不是一件容易的事] 让我有点寒心。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3360 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 12:20 · PVG 20:20 · LAX 04:20 · JFK 07:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.