新博客链接: https://note.mogutou.xyz/articles/2019/08/06/1565053139105.html
阅读前提:了解 epoll
evio 是一个基于事件驱动的网络框架,它非常轻量而且相比 Go net 标准库更快。其底层使用epoll 和 kqueue 系统调度实现。
原理
evio 是 Reactor 模式的简单实现。Reactor 本质就是“non-blocking IO + IO multiplexing”,通过非阻塞IO+ IO 多路复用来处理并发。程序运行一个或者多个事件循环,通过在事件循环中注册回调的方式实现业务逻辑。
evio 将所有文件描述符设为非阻塞,并注册到事件循环( epoll / kqueue )中。相较于传统的 per thread per connection 的处理方法,线程使用更少,线程资源利用率更高。
evio 需要在服务启动前,注册回调函数,当事件循环中有事件到来时,会调用回调函数处理。
使用示例
先从一个简单的 echo server 的例子来了解 evio 。
package main
import (
"flag"
"fmt"
"log"
"strings"
"github.com/tidwall/evio"
)
func main() {
var port int
var loops int
var udp bool
var trace bool
var reuseport bool
var stdlib bool
flag.IntVar(&port, "port", 5000, "server port")
flag.BoolVar(&udp, "udp", false, "listen on udp")
flag.BoolVar(&reuseport, "reuseport", false, "reuseport (SO_REUSEPORT)")
flag.BoolVar(&trace, "trace", false, "print packets to console")
flag.IntVar(&loops, "loops", 0, "num loops")
flag.BoolVar(&stdlib, "stdlib", false, "use stdlib")
flag.Parse()
var events evio.Events
events.NumLoops = loops
events.Serving = func(srv evio.Server) (action evio.Action) {
log.Printf("echo server started on port %d (loops: %d)", port, srv.NumLoops)
if reuseport {
log.Printf("reuseport")
}
if stdlib {
log.Printf("stdlib")
}
return
}
events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) {
if trace {
log.Printf("%s", strings.TrimSpace(string(in)))
}
out = in
return
}
scheme := "tcp"
if udp {
scheme = "udp"
}
if stdlib {
scheme += "-net"
}
log.Fatal(evio.Serve(events, fmt.Sprintf("%s://:%d?reuseport=%t", scheme, port, reuseport)))
}
上面的例子主要就是注册了两个回调函数: events.Serving 和 events.Data 。
当 server 启动时,会来执行注册的 events.Serving 回调函数;
当有数据到来时,执行 events.Data 回调函数。
程序最后调用 evio.Serve 方法开启事件循环,程序在此处不断循环检测是否有事件发生并处理(有数据到来,有数据要发送…)。
evio 都是通过回调函数来执行业务逻辑的。 当客户端有数据发送过来时,调用用户注册的 events.Data 函数。
需要发送数据给客户端时,只可以通过注册的回调函数的返回值来返回,evio 框架来负责发送(有bug)。
回调函数的返回值主要有两个 out []byte, action evio.Action
, out 就是需要发送给客户端的, Action 就是返回一些状态,用来关闭连接,或者服务器退出啥的操作。主要状态如下:
const (
// None indicates that no action should occur following an event.
None Action = iota
// Detach detaches a connection. Not available for UDP connections.
Detach
// Close closes the connection.
Close
// Shutdown shutdowns the server.
Shutdown
)
evio 的事件循环
evio.Serve
我们先来看下 evio.Serve 方法的实现
func Serve(events Events, addr ...string) error {
var lns []*listener
defer func() {
// 这个函数如果推出,需要关闭所有 listener
for _, ln := range lns {
ln.close()
}
}()
var stdlib bool
// 可以选择使用 stdlib(stdlib 主要是为了支持 非 *unix 平台)
for _, addr := range addr {
// 生成 listener
var ln listener
var stdlibt bool
ln.network, ln.addr, ln.opts, stdlibt = parseAddr(addr)
if stdlibt {
stdlib = true
}
if ln.network == "unix" {
os.RemoveAll(ln.addr)
}
var err error
if ln.network == "udp" {
if ln.opts.reusePort {
ln.pconn, err = reuseportListenPacket(ln.network, ln.addr)
} else {
ln.pconn, err = net.ListenPacket(ln.network, ln.addr)
}
} else {
if ln.opts.reusePort {
ln.ln, err = reuseportListen(ln.network, ln.addr)
} else {
ln.ln, err = net.Listen(ln.network, ln.addr)
}
}
if err != nil {
return err
}
if ln.pconn != nil {
ln.lnaddr = ln.pconn.LocalAddr()
} else {
ln.lnaddr = ln.ln.Addr()
}
if !stdlib {
if err := ln.system(); err != nil {
return err
}
}
lns = append(lns, &ln)
}
if stdlib {
return stdserve(events, lns)
// 使用 std net 库 启动server
}
return serve(events, lns)
// 使用 epoll or kqueue 启动server