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

简介: Go 语言社区中存在多个异步网络框架,如 evio、nbio、gnet 和 netpoll 等。这些框架旨在解决标准库 netpoll 的低效问题,如一个连接占用一个 goroutine 导致的资源浪费。easyio 是一个最小化的 IO 框架,核心代码不超过 500 行,仅实现 Linux 下的 epoll 和 TCP 协议。它通过 Worker Pool、Buffer 等优化提高了性能,并提供了简单的事件处理机制。

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

排名不分先后。

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

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

  • 一个 conn 一个 goroutine 导致利用率低
  • 用户无法感知 conn 状态
  • .....

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

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

没错,这就是easyio的由来。

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

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

简单的 demo ,

服务端:

go

代码解读

复制代码


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 触发方式。

简单的客户端

go

代码解读

复制代码

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://juejin.cn/post/7240750927052701751

相关文章
|
6月前
|
Shell Go API
Go语言grequests库并发请求的实战案例
Go语言grequests库并发请求的实战案例
|
7月前
|
Rust 安全 算法
Go标准库的新 math/rand
Go标准库的新 math/rand
|
4月前
|
JSON Go 开发者
go-carbon v2.5.0 发布,轻量级、语义化、对开发者友好的 golang 时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 Golang 时间处理库,提供了对时间穿越、时间差值、时间极值、时间判断、星座、星座、农历、儒略日 / 简化儒略日、波斯历 / 伊朗历的支持。
99 4
|
4月前
|
缓存 监控 前端开发
Go 语言中如何集成 WebSocket 与 Socket.IO,实现高效、灵活的实时通信
本文探讨了在 Go 语言中如何集成 WebSocket 与 Socket.IO,实现高效、灵活的实时通信。首先介绍了 WebSocket 和 Socket.IO 的基本概念及其优势,接着详细讲解了 Go 语言中 WebSocket 的实现方法,以及二者集成的重要意义和具体步骤。文章还讨论了集成过程中需要注意的问题,如协议兼容性、消息格式、并发处理等,并提供了实时聊天、数据监控和在线协作工具等应用案例,最后提出了性能优化策略,包括数据压缩、缓存策略和连接管理优化。旨在帮助开发者更好地理解并应用这些技术。
188 3
|
4月前
|
存储 Cloud Native Shell
go库介绍:Golang中的Viper库
Viper 是 Golang 中的一个强大配置管理库,支持环境变量、命令行参数、远程配置等多种配置来源。本文详细介绍了 Viper 的核心特点、应用场景及使用方法,并通过示例展示了其强大功能。无论是简单的 CLI 工具还是复杂的分布式系统,Viper 都能提供优雅的配置管理方案。
121 6
|
4月前
|
JSON 安全 网络协议
go语言使用内置函数和标准库
【10月更文挑战第18天】
39 3
|
4月前
|
JSON 监控 安全
go语言选择合适的工具和库
【10月更文挑战第17天】
35 2
|
4月前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
228 0
|
5月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
115 0
Linux C/C++之IO多路复用(poll,epoll)
|
5月前
|
Linux 编译器 Go
cgo--在Go中链接外部C库
cgo--在Go中链接外部C库

热门文章

最新文章