libgo高性能网络服务器,【开源】gnet: 一个轻量级且高性能的 Golang 网络库

![](https://ask.qcloudimg.com/http-save/1303222/sipe2g9n9h.png)

# Github 主页

[https://github.com/panjf2000/gnet](https://github.com/panjf2000/gnet)

欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦。

# 原文博客

[gnet: 一个轻量级且高性能的 Golang 网络库 ](https://taohuawu.club/go-event-loop-networking-library-gnet)

# 简介

`gnet` 是一个基于 Event-Loop 事件驱动的高性能和轻量级网络库。这个库直接使用 [epoll](https://en.wikipedia.org/wiki/Epoll) 和 [kqueue](https://en.wikipedia.org/wiki/Kqueue) 系统调用而非标准 Golang 网络包:[net](https://golang.org/pkg/net/) 来构建网络应用,它的工作原理类似于两个开源的网络库:[libuv](https://github.com/libuv/libuv) 和 [libevent](https://github.com/libevent/libevent)。

这个项目存在的价值是提供一个在网络包处理方面能和 [Redis](http://redis.io)、[Haproxy](http://www.haproxy.org) 这两个项目具有相近性能的Go 语言网络服务器框架。

`gnet` 的亮点在于它是一个高性能、轻量级、非阻塞的纯 Go 实现的传输层(TCP/UDP/Unix-Socket)网络库,开发者可以使用 `gnet` 来实现自己的应用层网络协议,从而构建出自己的应用层网络应用:比如在 `gnet` 上实现 HTTP 协议就可以创建出一个 HTTP 服务器 或者 Web 开发框架,实现 Redis 协议就可以创建出自己的 Redis 服务器等等。

**`gnet`** **衍生自另一个项目:\*\***`evio`\***\*,但是性能更好。**

# 功能

- 高性能 的基于多线程模型的 Event-Loop 事件驱动

- 内置 Round-Robin 轮询负载均衡算法

- 简洁的 APIs

- 基于 Ring-Buffer 的高效内存利用

- 支持多种网络协议:TCP、UDP、Unix Sockets

- 支持两种事件驱动机制:Linux 里的 epoll 以及 FreeBSD 里的 kqueue

- 支持异步写操作

- 允许多个网络监听地址绑定在一个 Event-Loop 上

- 灵活的事件定时器

- SO\_REUSEPORT 端口重用

# 核心设计

## 多线程/Go程模型

### 主从多 Reactors 模型

`gnet` 重新设计开发了一个新内置的多线程/Go程模型:『主从多 Reactors』,这也是 `netty` 默认的线程模型,下面是这个模型的原理图:

![](https://ask.qcloudimg.com/http-save/1303222/f1kmbmqgq3.png)

它的运行流程如下面的时序图:

![](https://ask.qcloudimg.com/http-save/1303222/md4p172zmi.png)

### 主从多 Reactors + 线程/Go程池

你可能会问一个问题:如果我的业务逻辑是阻塞的,那么在 `Event.React()` 注册方法里的逻辑也会阻塞,从而导致阻塞 event-loop 线程,这时候怎么办?

正如你所知,基于 `gnet` 编写你的网络服务器有一条最重要的原则:永远不能让你业务逻辑(一般写在 `Event.React()` 里)阻塞 event-loop 线程,否则的话将会极大地降低服务器的吞吐量,这也是 `netty` 的一条最重要的原则。

我的回答是,现在我正在为 `gnet` 开发一个新的多线程/Go程模型:『带线程/Go程池的主从多 Reactors』,这个新网络模型将通过引入一个 worker pool 来解决业务逻辑阻塞的问题:它会在启动的时候初始化一个 worker pool,然后在把 `Event.React()`里面的阻塞代码放到 worker pool 里执行,从而避免阻塞 event-loop 线程,

这个模型还在持续开发中并且很快就能完成,模型的架构图如下所示:

![64918783-90de3b80-d7d5-11e9-9190-ff8277c95db1.png](https://ask.qcloudimg.com/draft/1303222/w9448bjpjz.png)

它的运行流程如下面的时序图:

![](https://ask.qcloudimg.com/http-save/1303222/nk3tnisthu.png)

不过,在这个新的网络模型开发完成之前,你依然可以通过一些其他的外部开源 goroutine pool 来处理你的阻塞业务逻辑,在这里我推荐个人开发的一个开源 goroutine pool:[ants](https://github.com/panjf2000/ants),它是一个基于 Go 开发的高性能的 goroutine pool ,实现了对大规模 goroutine 的调度管理、goroutine 复用。

你可以在开发 `gnet` 网络应用的时候集成 `ants` 库,然后把那些阻塞业务逻辑提交到 `ants` 池里去执行,从而避免阻塞 event-loop 线程。

## 通信机制

`gnet` 的『主从 Reactors 多线程』模型是基于 Golang 里的 Goroutines的,一个 Reactor 挂载在一个 Goroutine 上,所以在 `gnet` 的这个网络模型里主 Reactor/Goroutine 与从 Reactors/Goroutines 有海量通信的需求,因此 `gnet` 里必须要有一个能在 Goroutines 之间进行高效率的通信的机制,我没有选择 Golang 里的主流方案:基于 Channel 的 CSP 模型,而是选择了性能更好、基于 Ring-Buffer 的 Disruptor 方案。

所以我最终选择了 [go-disruptor](https://github.com/smartystreets-prototypes/go-disruptor):高性能消息分发队列 LMAX Disruptor 的 Golang 实现。

## 自动扩容的 Ring-Buffer

`gnet` 利用 Ring-Buffer 来缓存 TCP 流数据以及管理内存使用。

![](https://ask.qcloudimg.com/http-save/1303222/405gs3dnfi.gif)

# 开始使用

## 安装

```

$ go get -u github.com/panjf2000/gnet

```

## 使用示例

用 `gnet` 来构建网络服务器是非常简单的,只需要把你关心的事件注册到 `gnet.Events` 里面,然后把它和绑定的监听地址一起传递给 `gnet.Serve` 方法就完成了。在服务器开始工作之后,每一条到来的网络连接会在各个事件之间传递,如果你想在某个事件中关闭某条连接或者关掉整个服务器的话,直接把 `gnet.Action` 设置成 `Cosed` 或者 `Shutdown`就行了。

Echo 服务器是一种最简单网络服务器,把它作为 `gnet` 的入门例子在再合适不过了,下面是一个最简单的 echo server,它监听了 9000 端口:

### 不带阻塞逻辑的 echo 服务器

```

package main

import (

"log"

"strings"

"github.com/panjf2000/gnet"

)

func main() {

var trace bool

var events gnet.Events

events.React = func(c gnet.Conn) (out []byte, action gnet.Action) {

top, tail := c.ReadPair()

out = append(top, tail...)

c.ResetBuffer()

if trace {

log.Printf("%s", strings.TrimSpace(string(top)+string(tail)))

}

return

}

log.Fatal(gnet.Serve(events, "tcp://:9000", gnet.WithMulticore(true)))

}

```

正如你所见,上面的例子里 `gnet` 实例只注册了一个 `React` 事件。一般来说,主要的业务逻辑代码会写在这个事件方法里,这个方法会在服务器接收到客户端写过来的数据之时被调用,然后处理输入数据(这里只是把数据 echo 回去)并且在处理完之后把需要输出的数据赋值给 `out` 变量然后返回,之后你就不用管了,`gnet` 会帮你把数据写回客户端的。

### 带阻塞逻辑的 echo 服务器

```

package main

import (

"log"

"time"

"github.com/panjf2000/ants"

"github.com/panjf2000/gnet"

)

func main() {

var events gnet.Events

// Create a goroutine pool.

poolSize := 256 * 1024

pool, _ := ants.NewPool(poolSize, ants.WithNonblocking(true))

defer pool.Release()

events.React = func(c gnet.Conn) (out []byte, action gnet.Action) {

data := c.ReadBytes()

c.ResetBuffer()

action = DataRead

// Use ants pool to unblock the event-loop.

_ = pool.Submit(func() {

time.Sleep(1 * time.Second)

c.AsyncWrite(data)

})

return

}

log.Fatal(gnet.Serve(events, "tcp://:9000", gnet.WithMulticore(true)))

}

```

正如我在『主从多 Reactors + 线程/Go程池』那一节所说的那样,如果你的业务逻辑里包含阻塞代码,那么你应该把这些阻塞代码变成非阻塞的,比如通过把这部分代码通过 goroutine 去运行,但是要注意一点,如果你的服务器处理的流量足够的大,那么这种做法将会导致创建大量的 goroutines 极大地消耗系统资源,所以我一般建议你用 goroutine pool 来做 goroutines 的复用和管理,以及节省系统资源。

## I/O 事件

`gnet` 目前支持的 I/O 事件如下:

- `OnInitComplete` 当 server 初始化完成之后调用。

- `OnOpened` 当连接被打开的时候调用。

- `OnClosed` 当连接被关闭的时候调用。

- `React` 当 server 端接收到从 client 端发送来的数据的时候调用。(你的核心业务代码一般是写在这个方法里)

- `Tick` 服务器启动的时候会调用一次,之后就以给定的时间间隔定时调用一次,是一个定时器方法。

- `PreWrite` 预先写数据方法,在 server 端写数据回 client 端之前调用。

### 定时器

`Tick` 会每隔一段时间触发一次,间隔时间你可以自己控制,设定返回的 `delay` 变量就行。

定时器的第一次触发是在 `gnet.Serving` 事件之后。

```

events.Tick = func() (delay time.Duration, action Action){

log.Printf("tick")

delay = time.Second

return

}

```

## UDP 支持

`gnet` 支持 UDP 协议,在 `gnet.Serve` 里绑定 UDP 地址即可,`gnet` 的 UDP 支持有如下的特性:

- 数据进入服务器之后立刻写回客户端,不做缓存。

- `OnOpened` 和 `OnClosed` 这两个事件在 UDP 下不可用,唯一可用的事件是 `React`。

## 使用多核

`gnet.WithMulticore(true)` 参数指定了 `gnet` 是否会使用多核来进行服务,如果是 `true` 的话就会使用多核,否则就是单核运行,利用的核心数一般是机器的 CPU 数量。

## 负载均衡

`gnet` 目前内置的负载均衡算法是轮询调度 Round-Robin,暂时不支持自定制。

## SO\_REUSEPORT 端口复用

服务器支持 [SO\_REUSEPORT](https://lwn.net/Articles/542629/) 端口复用特性,允许多个 sockets 监听同一个端口,然后内核会帮你做好负载均衡,每次只唤醒一个 socket 来处理 accept 请求,避免惊群效应。

开启这个功能也很简单,设置 options 参数即可:

```

gnet.Serve(events, "tcp://:9000", gnet.WithMulticore(true)))

```

# 性能测试

## Linux (epoll)

### 系统参数

```

# Machine information

OS : Ubuntu 18.04/x86_64

CPU : 8 Virtual CPUs

Memory : 16.0 GiB

# Go version and configurations

Go Version : go1.12.9 linux/amd64

GOMAXPROCS=8

```

### 同类型的网络库性能对比:

#### Echo Server

![echolinux.png](https://ask.qcloudimg.com/http-save/1303222/g3c6hz902p.png)

#### HTTP Server

![httplinux.png](https://ask.qcloudimg.com/http-save/1303222/1jq6mtct5j.png)

## FreeBSD (kqueue)

### 系统参数

```

# Machine information

OS : macOS Mojave 10.14.6/x86_64

CPU : 4 CPUs

Memory : 8.0 GiB

# Go version and configurations

Go Version : go version go1.12.9 darwin/amd64

GOMAXPROCS=4

```

#### Echo Server

![echomac.png](https://ask.qcloudimg.com/http-save/1303222/dkn8yx6nwv.png)

#### HTTP Server

![httpmac.png](https://ask.qcloudimg.com/http-save/1303222/lrtzlusmka.png)

# 证书

`gnet` 的源码允许用户在遵循 MIT [开源证书](https://github.com/panjf2000/gnet/blob/master/LICENSE) 规则的前提下使用。

# 相关文章

- [A Million WebSockets and Go](https://www.freecodecamp.org/news/million-websockets-and-go-cc58418460bb/)

- [Going Infinite, handling 1M websockets connections in Go](https://speakerdeck.com/eranyanay/going-infinite-handling-1m-websockets-connections-in-go)

- [gnet: 一个轻量级且高性能的 Golang 网络库](https://taohuawu.club/go-event-loop-networking-library-gnet)

# 待做事项

> gnet 还在持续开发的过程中,所以这个仓库的代码和文档会一直持续更新,如果你对 gnet 感兴趣的话,欢迎给这个开源库贡献你的代码,还有你要是喜欢 gnet 的话,可以给个星星鼓励一下哦 ~~

有疑问加站长微信联系(非本文作者))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/339519.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

拆分js文件_2021入门Webpack,看这篇就够了:Webpack.config.js 解析

这是优妈成长记的第63篇原创这是一个webpack配置说明本文是发布在github上webpack-demo的README文件内容。主要对webpack.config.js每一条的注释说明。github项目地址:https://github.com/hourong88/webpack-demo可以点击文章最下方【阅读原文】跳转github链接查看&…

orcad自上而下_开发自上而下的Web服务项目

orcad自上而下这是从Alessio Soldano编辑的Advanced JAX-WS Web Services手册中摘录的示例章节。 第一章介绍了自底向上创建Web服务端点的方法。 它允许非常快地将现有bean作为Web Service端点公开:在大多数情况下,将类转换为端点只需在代码中添加少量注…

安装win7系统不能开机启动服务器,win7系统开机启动项不能加载的原因分析及解决...

开机启动项是每台电脑都有的东西,就是多和少的问题的,很多人开机的时候喜欢加载很多的启动项,其实这也没什么不好的。现在的电脑为了受到更好的保护,往往在开机的时候就加载了一些启动项,如:杀毒软件&#…

七大罪过与如何避免

在整个本文中,我将在代码片段中使用Java,同时还将使用JUnit和Mockito 。 本文旨在提供示例测试代码,这些示例可以是: 难以阅读 难以维护 在这些示例之后,本文将尝试提供替代方法,这些替代方法可用于增强…

4 指针运算_C++用指针访问数组元素(学习笔记:第6章 08)

用指针访问数组元素[1]数组是一组连续存储的同类型数据,可以通过指针的算术运算,使指针依次指向数组的各个元素,进而可以遍历数组。定义指向数组元素的指针定义与赋值例:int a[10], *pa; pa&a[0]; 或 paa;等效的形式经过上述定…

asyncexec_如何安全使用SWT的显示器asyncExec

asyncexec大多数用户界面(UI)工具箱都是单线程的, SWT也不例外。 这意味着必须仅从单个线程(即所谓的UI线程)访问UI对象。 另一方面,应在后台线程中执行长时间运行的任务,以使UI保持响应。 这使…

属性面板 脚本_3.1 创建和使用脚本

在unity中,游戏物体的行为是通过组件来驱动的,我们可以通过内建的组件来给我们的游戏物体组合各种能力,尽管如此,要知道我们的需求永远是动态的,很快我们就会发现,内建的组件功能已经无法满足我们的需求&am…

新的JMetro JavaFX 11兼容版本

你好,我们又见面了! 这次是一个新版本,该版本与JavaFX 11兼容。 继续阅读以获取详细信息。 JMetro 8.5.7和11.5.7版本 JMetro代码已分为2个分支。 master分支具有Java 8兼容的JMetro版本,“ 11”分支具有Java 11兼容的版本。 以…

vue加跨域代理静态文件404_解决vue本地环境跨域请求正常,版本打包后跨域代理不起作用,请求不到数据的方法——针对vue2.0...

问题:在本地使用了proxyTable代理可以正常跨域请求后台数据,打包上传后就无法获得后台的json文件。查看了相关资料可以用nginx进行解决。还可以使用命名环境变量,请求的时候进行判断,话不多说上干货module.exports merge(prodEnv…

tomee_使用Vysper,TomEE和PrimeFaces将XMPP服务器嵌入JSF Web应用程序内部

tomee我有一个需要在完成某些工作时通知用户的应用程序。 它使用JSF和Primefaces,因此可以使用大气 (也称为Push)来实现这种通知。 但是另一个有趣的方法是使用嵌入在Java Web应用程序中的XMPP服务器。 好的,好的,您不…

板框导入_板框结构导入有问题?这几个问题最常见,附解决方法!

对于一些比较复杂的结构,Altium的处理能力有限,通常采用AutoCAD来进行设计,然后在Altium中执行菜单栏中“文件”→“导入”→DWG/DXF命令,选择需要导入的DXF文件即可。如果导入过程中出现了乱码,报错等问题要如何解决呢…

您如何使用硒来计算自动化测试的投资回报率?

跨浏览器测试是一种测试,需要大量的精力和时间。 通过不同的浏览器,操作系统,设备,屏幕分辨率测试Web应用程序,以评估针对各种受众的Web内容呈现的过程是一项活动。 特别是如果手动处理。 使用Selenium进行的自动跨浏览…

流量复制_详解Linux系统流量复制--gor、tcpcopy、nginx模块流量复制等

概述对于一些有并发要求的业务,特别是对接外部流量时,产品上线前一定要做的就是压力测试,但是常规的压力测试并不能覆盖所有情况。以gemeter、ab,、webbench、http_load为例,这些通过模拟请求的压测工具,只能发送特定的…

mongodb+java_Java EE + MongoDb与Apache TomEE和Jongo Starter项目

mongodbjava知道MongoDB和Java EE ,但是您不确切地知道如何将它们集成在一起? 您是否阅读了很多有关该主题的内容,但没有找到适合该目的的解决方案? 这个入门项目适合您: 您将学习如何以一种时尚的方式使用MongoDB和J…

C语言天才!想法奇异?还是逼格满满?一份国外C语言写的传奇简历

C语言天才!想法奇异?还是逼格满满?一份国外C语言写的传奇简历作者用代码更新了自己的简历,是不是很接地气,特符合程序员的逼格。这是一份可读可执行的语言源文件,也是作者编码风格的体现。C语言源码&#x…

hash值 更改git_Git切换版本

Git切换版本有三种方式:1.基于哈希值切换》基于哈希值切换(推荐),命令:git reset --hard 哈希值,哈希值从哪来,git reflog查看下就知道了,切换版本后,git reflog会发现有两个HEAD,别…

devc++ 文件未编译问题

点击 文件 然后点击关闭全部文件, 重新打开一下软件件就好了

候选JEP:记录和密封类型

马克赖因霍尔德(Mark Reinhold )本周在OpenJDK琥珀色开发者邮件列表上宣布了两个新的紧密相关的候选 JDK增强提案( JEP) ,其帖子分别为“ 新候选JEP:359:记录(预览) ”和…

fedora mysql_Fedora server 安装Mysql8

导读MySQL是一种关系数据库管理系统(RDBMS),作为服务器运行,提供对多个数据库的多用户访问。 这是指导,如何在Fedora 28/27/26,CentOS 7.5 / 6.10和Red Hat(RHEL)7.5 / 6.10上安装或升级MySQL社区服务器最新版本8.0(8.0.12)/5.7(5…

lombok 生成代码_使用Project Lombok减少Java应用程序中的样板代码

lombok 生成代码对Java编程语言最常提出的批评之一是它需要大量的样板代码 。 对于简单的类尤其如此,该类只需要存储一些值就可以。 您需要这些值的getter和setter方法,也许您还需要一个构造函数,覆盖equals()和 hash…