V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
xuxu555
V2EX  ›  分享创造

[开源] gev(支持 websocket 啦): Go 实现基于 Reactor 模式的非阻塞网络库

  •  
  •   xuxu555 ·
    Allenxuxu · 2019-10-24 10:39:15 +08:00 · 3324 次点击
    这是一个创建于 1892 天前的主题,其中的信息可能已经有所发展或是发生改变。

    https://github.com/Allenxuxu/gev

    gev 是一个轻量、快速、高性能的基于 Reactor 模式的非阻塞网络库,底层并不使用 golang net 库,而是使用 epoll 和 kqueue。

    现在它支持 WebSocket 啦!

    支持定时任务,延时任务!

    ⬇️⬇️⬇️

    特点

    • 基于 epoll 和 kqueue 实现的高性能事件循环
    • 支持多核多线程
    • 动态扩容 Ring Buffer 实现的读写缓冲区
    • 异步读写
    • SO_REUSEPORT 端口重用支持
    • 支持 WebSocket
    • 支持定时任务,延时任务

    性能测试

    测试环境 Ubuntu18.04

    • gev
    • gnet
    • eviop
    • evio
    • net (标准库)

    吞吐量测试

    null

    null

    evio 压测方式:

    限制 GOMAXPROCS=1,1 个 work 协程

    image.png

    限制 GOMAXPROCS=1,4 个 work 协程

    image.png

    限制 GOMAXPROCS=4,4 个 work 协程

    image.png

    安装

    go get -u github.com/Allenxuxu/gev
    

    示例

    TCP

    package main
    
    import (
    	"flag"
    	"strconv"
    	"log"
    
    	"github.com/Allenxuxu/gev"
    	"github.com/Allenxuxu/gev/connection"
    	"github.com/Allenxuxu/ringbuffer"
    )
    
    type example struct{}
    
    func (s *example) OnConnect(c *connection.Connection) {
    	log.Println(" OnConnect: ", c.PeerAddr())
    }
    func (s *example) OnMessage(c *connection.Connection, buffer *ringbuffer.RingBuffer) (out []byte) {
    	//log.Println("OnMessage")
    	first, end := buffer.PeekAll()
    	out = first
    	if len(end) > 0 {
    		out = append(out, end...)
    	}
    	buffer.RetrieveAll()
    	return
    }
    
    func (s *example) OnClose() {
    	log.Println("OnClose")
    }
    
    func main() {
    	handler := new(example)
    	var port int
    	var loops int
    
    	flag.IntVar(&port, "port", 1833, "server port")
    	flag.IntVar(&loops, "loops", -1, "num loops")
    	flag.Parse()
    
    	s, err := gev.NewServer(handler,
    		gev.Network("tcp"),
    		gev.Address(":"+strconv.Itoa(port)),
    		gev.NumLoops(loops))
    	if err != nil {
    		panic(err)
    	}
    
    	s.Start()
    }
    

    WebSocket

    package main
    
    import (
    	"flag"
    	"github.com/Allenxuxu/gev/ws"
    	"log"
    	"math/rand"
    	"strconv"
    
    	"github.com/Allenxuxu/gev"
    	"github.com/Allenxuxu/gev/connection"
    )
    
    type example struct{}
    
    func (s *example) OnConnect(c *connection.Connection) {
    	log.Println(" OnConnect: ", c.PeerAddr())
    }
    func (s *example) OnMessage(c *connection.Connection, data []byte) (messageType ws.MessageType, out []byte) {
    	log.Println("OnMessage:", string(data))
    	messageType = ws.MessageBinary
    	switch rand.Int() % 3 {
    	case 0:
    		out = data
    	case 1:
    		if err := c.SendWebsocketData(ws.MessageText, data); err != nil {
    			if e := c.CloseWebsocket(err.Error()); e != nil {
    				panic(e)
    			}
    		}
    	case 2:
    		if e := c.CloseWebsocket("close"); e != nil {
    			panic(e)
    		}
    	}
    	return
    }
    
    func (s *example) OnClose(c *connection.Connection) {
    	log.Println("OnClose")
    }
    
    func main() {
    	handler := new(example)
    	var port int
    	var loops int
    
    	flag.IntVar(&port, "port", 1833, "server port")
    	flag.IntVar(&loops, "loops", -1, "num loops")
    	flag.Parse()
    
    	s, err := gev.NewWebSocketServer(handler,
    		gev.Network("tcp"),
    		gev.Address(":"+strconv.Itoa(port)),
    		gev.NumLoops(loops))
    	if err != nil {
    		panic(err)
    	}
    
    	s.Start()
    }
    

    相关文章

    仓库地址: https://github.com/Allenxuxu/gev

    11 条回复    2019-10-26 13:10:15 +08:00
    zeromake
        1
    zeromake  
       2019-10-24 10:45:20 +08:00
    能解释一下为啥要内置定时能力,而不是让调用方使用外部库来实现呢?
    xuxu555
        2
    xuxu555  
    OP
       2019-10-24 10:49:21 +08:00
    @zeromake 其实并非内置,定时功能也是使用了外部库。 因为有小伙伴提了这个需求,所以就加上去了方便使用。
    agui2200
        3
    agui2200  
       2019-10-24 12:21:57 +08:00
    这个名字很有灵性啊
    xuxu555
        4
    xuxu555  
    OP
       2019-10-24 15:43:01 +08:00
    @agui2200 有种不好的感觉,啥灵性啊🐕
    agui2200
        5
    agui2200  
       2019-10-24 16:10:04 +08:00
    @xuxu555 看到 gev 就想到 gay,(狗头
    xuxu555
        6
    xuxu555  
    OP
       2019-10-24 18:05:02 +08:00
    @agui2200 心里 gay 看什么都 gay 😂
    codehz
        7
    codehz  
       2019-10-24 21:44:02 +08:00
    定时任务似乎没有使用 timerfd,这样就不能利用单一 epoll 的优势了(
    Aether
        8
    Aether  
       2019-10-25 12:03:30 +08:00
    定时内容都是放在内存里的,可能会丢失,是这样吗?
    xuxu555
        9
    xuxu555  
    OP
       2019-10-26 11:19:35 +08:00
    @Aether 是,丢失的情况是进程都挂了啊
    xuxu555
        10
    xuxu555  
    OP
       2019-10-26 11:21:44 +08:00
    @codehz 是的,timerfd 考虑过,但是考虑到 go 协程比线程廉价的多,开个协程搞个 timingwheel 实现定时更简洁方便。
    Aether
        11
    Aether  
       2019-10-26 13:10:15 +08:00
    @xuxu555 对。我发现类似解决方案有一个算是痛点的部分,就是有些需求是希望可以从中断恢复。之前看了一圈,比较轻量,开箱即用的解决方案似乎不多,或者说就没有……。然后这些方案肯定需要依赖外部数据储存,比如 redis 之类的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2502 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 15:32 · PVG 23:32 · LAX 07:32 · JFK 10:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.