原创分享 golang epoll 库

wida · 2020年06月10日 · 1144 次阅读

go 原生的 net 库(linux)本身就是基于 epoll 的,在大多数场景下,go 原生的 net 库能很好满足性能要求。

在某些需要大量连接数的场景,原生的 net 库可能会比较吃力。

相信很多人都看过《百万 Go TCP 连接的思考: epoll 方式减少资源占用》(https://colobu.com/2019/02/23/1m-go-tcp-connection/)这个博文。

后来 go 代码中直接使用 epoll 开始变得流行,evio(https://github.com/tidwall/eviognet(https://github.com/panjf2000/gnet)作为代表的新的 go 网络开始出现。),

本人在阅读 rust mio(rust 生态中异步 io 的基石)的代码时候,突然想到是不是可以移植到 golang,于是有了新的一个 epoll 库

https://github.com/widaT/poller, 以及基于 poller 的 websocket 服务 https://github.com/widaT/crabpr。,希望大家多多给意见,甚至提

下面是个 poller 的 demo

package main

import (
    "fmt"
    "log"
    "net"

    "github.com/widaT/poller"
    "github.com/widaT/poller/interest"
    "github.com/widaT/poller/pollopt"
    "golang.org/x/sys/unix"
)

const SERVER poller.Token = poller.Token(0)

// run it
// then nc localhost 9999
func main() {
    poll, err := poller.New()
    if err != nil {
        log.Fatal(err)
    }

    ln, err := net.Listen("tcp", ":9999")
    if err != nil {
        log.Fatal(err)
    }

    fd, err := poller.Listener2Fd(ln, true)
    if err != nil {
        log.Fatal(err)
    }
    err = poll.Register(fd, SERVER, interest.READABLE, pollopt.Edge)
    if err != nil {
        log.Fatal(err)
    }

    uniqueToken := 1
    events := poller.MakeEvents(128)
    connections := make(map[poller.Token]int)
    for {
        n, err := poll.Select(events, -1)
        if err != nil {
            if err == unix.EINTR {
                continue
            }
            log.Fatal(err)
        }
        for i := 0; i < n; i++ {
            ev := events[i]
            switch ev.Token() {
            case SERVER:
                for {
                    cfd, _, err := unix.Accept(fd)
                    if err != nil {
                        //WouldBlock
                        if err == unix.EAGAIN {
                            //  fmt.Println(err)
                            break
                        }
                        log.Fatal(err)
                    }
                    if err := poller.Nonblock(cfd); err != nil {
                        log.Fatal(err)
                    }

                    uniqueToken++
                    err = poll.Register(cfd, poller.Token(uniqueToken), interest.READABLE.Add(interest.WRITABLE), pollopt.Edge)
                    if err != nil {
                        log.Fatal(err)
                    }
                    connections[poller.Token(uniqueToken)] = cfd
                }

            default:
                if fd, found := connections[ev.Token()]; found {
                    err := handleEvent(poll, fd, ev)
                    if err != nil {
                        delete(connections, poller.Token(uniqueToken))
                    }
                }
            }
        }
    }
}

func handleEvent(s *poller.Selector, fd int, event poller.Event) error {
    switch {
    case event.IsReadable():
        connectionClosed := false
        receivedData := make([]byte, 4096)
        for {
            buf := make([]byte, 256)
            n, err := unix.Read(fd, buf)
            if n == 0 {
                connectionClosed = true
            }
            if err != nil {
                //WouldBlock
                if err == unix.EAGAIN {
                    break
                }
                //Interrupted
                if err == unix.EINTR {
                    continue
                }
                return err
            }
            receivedData = append(receivedData, buf[:n]...)
        }

        fmt.Println(string(receivedData))
        if connectionClosed {
            fmt.Println("Connection closed")
            return nil
        }
        unix.Write(fd, receivedData)
    }
    return nil
}
更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册