用 Go 基于 epoll 实现一个最小化 IO 库

2023-06-04 10:22:40 +08:00
 RememberCurry

目前 Go 圈有很多款异步的网络框架:

排名不分先后。

这里面最早的实现是 evio 。evio 也存在一些问题,之前也写过evio文章介绍过。 其他比如 nbio 和 gnet 也写过一些源码分析。

为什么会出现这些框架?之前也提到过,由于标准库 netpoll 的一些特性:

这些框架在应用层上做了很多优化,比如:Worker Pool,Buffer,Ring Buffer,NoCopy......。

都分析了好几篇的代码了,那么咋么说也得自己动手搞一个来达成学习目的。

没错,这就是easyio的由来。

它是一个最小化的 IO 框架,只实现最核心的部分,加起来不超过 500 行代码。

也没有用户端上层应用的优化,且目前只实现了 linux 的 epoll ,以及只能运行 tcp 协议。

简单的 demo ,

服务端:


package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"

	"github.com/wuqinqiang/easyio"
)

var _ easyio.EventHandler = (*Handler)(nil)

type Handler struct{}

type EasyioKey struct{}

type Message struct{ Msg string }

var CtxKey EasyioKey

func (h Handler) OnOpen(c easyio.Conn) context.Context {
	return context.WithValue(context.Background(), CtxKey, Message{Msg: "helloword"})
}

func (h Handler) OnRead(ctx context.Context, c easyio.Conn) {
	_, ok := ctx.Value(CtxKey).(Message)
	if !ok {
		return
	}
	var b = make([]byte, 100)
	_, err := c.Read(b)
	if err != nil {
		fmt.Println("err:", err)
	}
	fmt.Println("[Handler] read data:", string(b))

	if _, err = c.Write(b); err != nil {
		panic(err)
	}
}

func (h Handler) OnClose(_ context.Context, c easyio.Conn) {
	fmt.Println("[Handler] closed", c.Fd())
}

func main() {
	e := easyio.New("tcp", ":8090",easyio.WithNumPoller(4), easyio.WithEventHandler(Handler{}))

	if err := e.Start(); err != nil {
		panic(err)
	}

	defer e.Stop()

	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)
	<-c
}

上面的代码,初始化一个 easyio ,启动一个 tcp 服务,监听端口 8090 ,options 里面设置 epoll 的数量,以及设置事件处理器。

当一个新连接到来时会回调 OnOpen 函数,此时你可以设置自定义的 ctx ,那么当对应连接读事件到来 OnRead 回调,你可以拿到之前设置的 ctx ,调用 conn.Read 读取数据,且通过 Write 向对端写数据。

这里需要注意的是,一个连接如果数据没读完,当 OnRead 执行结束,下一轮会继续触发回调代码,因为底层 epoll 采用的是 LT 触发方式。

简单的客户端

package main

import (
	"fmt"
	"net"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	conn, err := net.Dial("tcp", ":8090")
	if err != nil {
		panic(err)
	}
	n, err := conn.Write([]byte("hello world"))
	if err != nil {
		panic(err)
	}

	go func() {
		b := make([]byte, 100)
		if n, err = conn.Read(b); err != nil {
			panic(err)
		}
		fmt.Println("read data:", n, string(b))
	}()

	defer conn.Close()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)

	<-ch
}

源码地址:https://github.com/wuqinqiang/easyio

4559 次点击
所在节点    Go 编程语言
38 条回复
midknight
2023-06-04 10:26:45 +08:00
有点意思,mark 一下
julyclyde
2023-06-04 10:41:29 +08:00
那为啥不去改进标准库呢?

这些年,各种“yet another”已经太多了。绝大多数都没长久的生命力
hankai17
2023-06-04 10:43:32 +08:00
go 用的少 有些疑问

"一个 conn 一个 goroutine 导致利用率低"
这个应该是优点吧 写起来方便 没看过 go 的协程 按理说性能不会太低吧

"用户无法感知 conn 状态"
同上 回调用起来不方便
RememberCurry
2023-06-04 10:47:41 +08:00
@julyclyde
我上面说了,
> 都分析了好几篇的代码了,那么咋么说也得自己动手搞一个来达成学习目的。
RememberCurry
2023-06-04 10:48:59 +08:00
@hankai17 海量连接情况下一个连接一个 g 并不划算
julyclyde
2023-06-04 10:49:10 +08:00
@RememberCurry 学习是目的,“单独搞一个”是手段,这完全两码事
如果贡献到标准库,你的作品还能多活几年
RememberCurry
2023-06-04 10:50:29 +08:00
@julyclyde 你说的都对。
lindt99cocoa
2023-06-04 11:00:20 +08:00
@hankai17 中国特色 go 协程问题
codehz
2023-06-04 11:23:53 +08:00
3202 年是不是可以用上 io uring 了(
RememberCurry
2023-06-04 11:28:12 +08:00
@codehz 俺不会,等俺学学😭
z3phyr
2023-06-04 11:51:40 +08:00
2023 年还在写回调,无异于开历史倒车
matrix1010
2023-06-04 12:11:18 +08:00
io 圈好卷。有没有人写 cache 框架跟我 pk 一下
lesismal
2023-06-04 12:22:44 +08:00
支持 OP 一下!
lesismal
2023-06-04 12:23:18 +08:00
> 2023 年还在写回调,无异于开历史倒车

@z3phyr
试试我这个,http 基本兼容标准库,http 和 websocket 来消息时都是可以写同步代码的。
https://github.com/lesismal/nbio
lesismal
2023-06-04 12:31:29 +08:00
> 3202 年是不是可以用上 io uring 了(

@codehz 单就网络 io 这块,不同场景下 io uring 、epoll 性能好像是各有优劣,并不是所有情况都一边倒,所以综合网络库 epoll 有历史加持、足够了。特定场景的话倒是可以考虑定向优化
fgwmlhdkkkw
2023-06-04 13:08:43 +08:00
脱裤子放屁,多此一举。
Nazz
2023-06-04 13:10:41 +08:00
@julyclyde 标准库你想改就能改吗
Nazz
2023-06-04 13:12:00 +08:00
sunny1688
2023-06-04 13:27:20 +08:00
这样说的话,那我也来发一下我实现的轮子吧~~
netman: https://github.com/ikilobyte/netman

支持 TCP 、UDP 、WebSocket 、同时 TCP 支持路由模式
tyrantZhao
2023-06-04 14:14:56 +08:00
上 iouring

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/945616

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX