Golang 高性能网络库 evio 源码解析

新博客链接: 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
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值