lesismal / nbio

Pure Go 1000k+ connections solution, support tls/http1.x/websocket and basically compatible with net/http, with high-performance and low memory cost, non-blocking, event-driven, easy-to-use.
MIT License
2.17k stars 153 forks source link

你好大佬,看了你的方法,有一点建议,不知道合理不合理,希望你能看到,给个回复 #376

Closed xuehuizhang closed 9 months ago

xuehuizhang commented 9 months ago

关于如下代码中addRead方法,方法的名字是否合理?

func (p *poller) addConn(c *Conn) {
    c.g = p.g
    p.g.onOpen(c)
    fd := c.fd
    p.g.connsUnix[fd] = c
    err := p.addRead(fd)
    if err != nil {
        p.g.connsUnix[fd] = nil
        c.closeWithError(err)
        logging.Error("[%v] add read event failed: %v", c.fd, err)
        return
    }
}

针对代码中的addRead方法,这个名字是否合理,我下午在读代码时一直在纠结accept后的fd如何和worker poller建立关联关系,当读到这个方法的时候,自动忽略了addRead这个方法,所以一直忽略关键代码,没想到在这个方法中epoll_ctl的,也许是自己粗心大意。

func (p *poller) addRead(fd int) error {
    return syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_ADD, fd, &syscall.EpollEvent{Fd: int32(fd), Events: epoollEventsRead})
}
lesismal commented 9 months ago

感谢关注和反馈。 但是抱歉,能不能具体说下是怎么不合理,我没get到你的点。如果想改、改成什么比较适合?

xuehuizhang commented 9 months ago

感谢关注和反馈。 但是抱歉,能不能具体说下是怎么不合理,我没get到你的点。如果想改、改成什么比较适合?

我感觉addRead这个函数的名字不太让首次读代码的人Get到函数的意图,比如 addConn这个函数,我看到函数名就能立刻知道干什么的,我下午在读代码时,一直没找到 “accept后的fd如何和worker poller建立关联关系”的地方,其实我也看到了addRead这个函数,但是没有往syscall.EpollCtl 方向考虑。

或者大佬您可以说下设计这个函数的意图么?

例如可以添加一个新函数 addFd,可以名字也不太合理,只是举个例子

xuehuizhang commented 9 months ago

感谢关注和反馈。 但是抱歉,能不能具体说下是怎么不合理,我没get到你的点。如果想改、改成什么比较适合?

或者类似与deleteEvent方法,修改为addEvent

func (p *poller) addEvent(fd int) error {
    return syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_ADD, fd, &syscall.EpollEvent{Fd: int32(fd), Events: epoollEventsRead})
}
lesismal commented 9 months ago

这个理由不够充分,兄弟你适应一下、看仔细点别漏掉就可以了 :joy: 另外epoll相关的,可以直接代码里搜相关系统调用的关键字、抓住关键点一层层看就清晰了

xuehuizhang commented 9 months ago

这个理由不够充分,兄弟你适应一下、看仔细点别漏掉就可以了 😂 另外epoll相关的,可以直接代码里搜相关系统调用的关键字、抓住关键点一层层看就清晰了

但是为啥delete是deleteEvent,添加却是addRead呢?哈哈,好吧,我能看懂就好了,谢谢回复

lesismal commented 9 months ago

但是为啥delete是deleteEvent,添加却是addRead呢?哈哈,好吧,我能看懂就好了,谢谢回复

事件分几种情况:

  1. 增加读(addRead):新连接刚被accept时,只添加读事件,因为此时没有需要写的数据
  2. 增加写(modWrite):fd并不需要先等待可写后再写,fd通常是可写的,先写、写失败后说明需要等待,此时再添加写事件、其实是EPOLL_CTL_MOD修改为fd同时等待可读可写事件并把写入失败的数据缓存起来,可写后再把缓存的数据写入
  3. 删除写(resetRead):2之后,fd可写,则把缓存数据写入,如果都写完了没有err,则说明此时fd又恢复到良好可写状态了,不需要继续监听写事件,避免非ONESHOT重复触发可写(因为ONESHOT每次都需要重新添加事件、syscall成本高性能差,所以通常不使用ONESHOT),此时EPOLL_CTL_MOD修改为fd只等待可读
  4. 删除(deleteEvent):close时删除全部事件、只要一个删除操作。其实deleteEvent不执行应该也没什么问题,因为syscall close后也会自动从epoll中删除,在epoll wait之外的其他协程/线程并发close也没有副作用

几个命名也是这个逻辑

xuehuizhang commented 9 months ago

但是为啥delete是deleteEvent,添加却是addRead呢?哈哈,好吧,我能看懂就好了,谢谢回复

事件分几种情况:

  1. 增加读(addRead):新连接刚被accept时,只添加读事件,因为此时没有需要写的数据
  2. 增加写(modWrite):fd并不需要先等待可写后再写,fd通常是可写的,先写、写失败后说明需要等待,此时再添加写事件、其实是EPOLL_CTL_MOD修改为fd同时等待可读可写事件并把写入失败的数据缓存起来,可写后再把缓存的数据写入
  3. 删除写(resetRead):2之后,fd可写,则把缓存数据写入,如果都写完了没有err,则说明此时fd又恢复到良好可写状态了,不需要继续监听写事件,避免非ONESHOT重复触发可写(因为ONESHOT每次都需要重新添加事件、syscall成本高性能差,所以通常不使用ONESHOT),此时EPOLL_CTL_MOD修改为fd只等待可读
  4. 删除(deleteEvent):close时删除全部事件、只要一个删除操作。其实deleteEvent不执行应该也没什么问题,因为syscall close后也会自动从epoll中删除,在epoll wait之外的其他协程/线程并发close也没有副作用

几个命名也是这个逻辑

好的,我看看,谢谢了

lesismal commented 9 months ago

如果只是命名为 addEvent 之类的,不同的地方调用的时候、传入不同的OP、事件组合,其实看起来更麻烦、每个地方都要去看是什么事件和分析这样做有什么效果、没有跟场景挂钩那么明显、可能更不容易理解; 现在这个相当于是按使用场景命名、如果熟悉了机制、理解起来可能更容易。

lesismal commented 9 months ago

另外一个原因就是,nbio默认LT,但也支持ET、ET+ONESHOT,对epoll模式支持算是最全面的了。 通过配置EpollMod、EPOLLONESHOT的值即可实现。如果用户使用了EPOLLONESHOT则需要自己决定何时nbio.Conn.ResetPollerEvent()重新监听事件、nbio不会自动重置事件,因为如果自动重置了、跟LT和ET就类似了,都是在epoll wait的循环内处理,但EPOLLONESHOT的syscall更多性能更差,所以不如不用。用EPOLLONESHOT主要是为了让用户自己决策什么时候去读写、可以控制fd上的io速率、对应的buffer消耗的内存等。

因为支持的模式多,所以如果只是按照addEvent的命名、各个地方不同的参数调用addEvent这种,不同模式下代码的情况会更复杂、更难理解

xuehuizhang commented 9 months ago

另外一个原因就是,nbio默认LT,但也支持ET、ET+ONESHOT,对epoll模式支持算是最全面的了。 通过配置EpollMod、EPOLLONESHOT的值即可实现。如果用户使用了EPOLLONESHOT则需要自己决定何时nbio.Conn.ResetPollerEvent()重新监听事件、nbio不会自动重置事件,因为如果自动重置了、跟LT和ET就类似了,都是在epoll wait的循环内处理,但EPOLLONESHOT的syscall更多性能更差,所以不如不用。用EPOLLONESHOT主要是为了让用户自己决策什么时候去读写、可以控制fd上的io速率、对应的buffer消耗的内存等。

因为支持的模式多,所以如果只是按照addEvent的命名、各个地方不同的参数调用addEvent这种,不同模式下代码的情况会更复杂、更难理解

好的,谢谢了,我在梳理下代码,体会下实现逻辑,我刚开始学习网络编程,可能理解的不太透彻

lesismal commented 9 months ago

不客气,欢迎来交流

xuehuizhang commented 9 months ago

不客气,欢迎来交流

好的好的,看完你写的这个框架,我才真正弄明白Reactor模型的设计理念以及运行原理,十分感谢

lesismal commented 9 months ago

不怕你们笑话,我到现在都记不住Reactor、Proactor是什么设计理念,每次聊到这两个模型我都得先搜索一下看看它俩到底是啥,以前搜过很多次了,仍然记不住 :joy: 。。。

我只知道就是个事件机制+回调,然后封装起来给下游使用就可以了,至于是谁读数据、谁提供buffer那些,都是小事情了。。。

xuehuizhang commented 9 months ago

不怕你们笑话,我到现在都记不住Reactor、Proactor是什么设计理念,每次聊到这两个模型我都得先搜索一下看看它俩到底是啥,以前搜过很多次了,仍然记不住 😂 。。。

我只知道就是个事件机制+回调,然后封装起来给下游使用就可以了,至于是谁读数据、谁提供buffer那些,都是小事情了。。。

你对网络编程这块理解的太透彻了,你之前是做c或者c++开发的吧?

lesismal commented 9 months ago

嗯嗯,c/c++写了不少

xuehuizhang commented 9 months ago

嗯嗯,c/c++写了不少

一猜就是,对系统编程理解这么透彻的的大佬一般都是搞过c/c++的,大佬这个框架我会继续研究研究,后续有问题在向你咨询,感谢大佬的回复

lesismal commented 9 months ago

好的,加油!