<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>panjf2000 (潘少)</title>
    <link>https://ruby-china.org/panjf2000</link>
    <description/>
    <language>en-us</language>
    <item>
      <title>[开源] gnet: 一个轻量级且高性能、基于事件驱动的 Go 网络库</title>
      <description>&lt;p&gt;
&lt;img src="https://raw.githubusercontent.com/panjf2000/gnet/master/logo.png" alt="gnet"&gt;
&lt;br&gt;
&lt;a title="Build Status" target="_blank" href="https://travis-ci.com/panjf2000/gnet"&gt;&lt;img src="https://img.shields.io/travis/com/panjf2000/gnet?style=flat-square"&gt;&lt;/a&gt;
&lt;a title="Codecov" target="_blank" href="https://codecov.io/gh/panjf2000/gnet"&gt;&lt;img src="https://img.shields.io/codecov/c/github/panjf2000/gnet?style=flat-square"&gt;&lt;/a&gt;
&lt;a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/panjf2000/gnet"&gt;&lt;img src="https://goreportcard.com/badge/github.com/panjf2000/gnet?style=flat-square"&gt;&lt;/a&gt;
&lt;br&gt;
&lt;a title="" target="_blank" href="https://golangci.com/r/github.com/panjf2000/gnet"&gt;&lt;img src="https://golangci.com/badges/github.com/panjf2000/gnet.svg"&gt;&lt;/a&gt;
&lt;a title="Doc for gnet" target="_blank" href="https://gowalker.org/github.com/panjf2000/gnet?lang=zh-CN"&gt;&lt;img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square"&gt;&lt;/a&gt;
&lt;a title="Release" target="_blank" href="https://github.com/panjf2000/gnet/releases"&gt;&lt;img src="https://img.shields.io/github/release/panjf2000/gnet.svg?style=flat-square"&gt;&lt;/a&gt;
&lt;a title="Mentioned in Awesome Go" target="_blank" href="https://github.com/avelino/awesome-go"&gt;&lt;img src="https://awesome.re/mentioned-badge-flat.svg"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Github 主页：
&lt;a href="https://github.com/panjf2000/gnet" rel="nofollow" target="_blank"&gt;https://github.com/panjf2000/gnet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gnet&lt;/code&gt; 是一个基于事件驱动的高性能和轻量级网络框架。它直接使用 &lt;a href="https://en.wikipedia.org/wiki/Epoll" rel="nofollow" target="_blank" title=""&gt;epoll&lt;/a&gt; 和 &lt;a href="https://en.wikipedia.org/wiki/Kqueue" rel="nofollow" target="_blank" title=""&gt;kqueue&lt;/a&gt; 系统调用而非标准 Golang 网络包：&lt;a href="https://golang.org/pkg/net/" rel="nofollow" target="_blank" title=""&gt;net&lt;/a&gt; 来构建网络应用，它的工作原理类似两个开源的网络库：&lt;a href="https://github.com/netty/netty" rel="nofollow" target="_blank" title=""&gt;netty&lt;/a&gt; 和 &lt;a href="https://github.com/libuv/libuv" rel="nofollow" target="_blank" title=""&gt;libuv&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;这个项目存在的价值是提供一个在网络包处理方面能和 &lt;a href="http://redis.io" rel="nofollow" target="_blank" title=""&gt;Redis&lt;/a&gt;、&lt;a href="http://www.haproxy.org" rel="nofollow" target="_blank" title=""&gt;Haproxy&lt;/a&gt; 这两个项目具有相近性能的 Go 语言网络服务器框架。&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;code&gt;gnet&lt;/code&gt; 衍生自另一个项目：&lt;code&gt;evio&lt;/code&gt;，但性能远胜之。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="功能"&gt;功能&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="#%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95" title=""&gt;高性能&lt;/a&gt; 的基于多线程/Go 程模型的 event-loop 事件驱动&lt;/li&gt;
&lt;li&gt;内置 Round-Robin 轮询负载均衡算法&lt;/li&gt;
&lt;li&gt;内置 goroutine 池，由开源库 &lt;a href="https://github.com/panjf2000/ants" rel="nofollow" target="_blank" title=""&gt;ants&lt;/a&gt; 提供支持&lt;/li&gt;
&lt;li&gt;内置 bytes 内存池，由开源库 &lt;a href="https://github.com/gobwas/pool/" rel="nofollow" target="_blank" title=""&gt;pool&lt;/a&gt; 提供支持&lt;/li&gt;
&lt;li&gt;简洁的 APIs&lt;/li&gt;
&lt;li&gt;基于 Ring-Buffer 的高效内存利用&lt;/li&gt;
&lt;li&gt;支持多种网络协议：TCP、UDP、Unix Sockets&lt;/li&gt;
&lt;li&gt;支持两种事件驱动机制：Linux 里的 epoll 以及 FreeBSD 里的 kqueue&lt;/li&gt;
&lt;li&gt;支持异步写操作&lt;/li&gt;
&lt;li&gt;灵活的事件定时器&lt;/li&gt;
&lt;li&gt;SO_REUSEPORT 端口重用&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="核心设计"&gt;核心设计&lt;/h2&gt;&lt;h2 id="多线程/Go程模型"&gt;多线程/Go 程模型&lt;/h2&gt;&lt;h3 id="主从多 Reactors 模型"&gt;主从多 Reactors 模型&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;gnet&lt;/code&gt; 重新设计开发了一个新内置的多线程/Go 程模型：『主从多 Reactors』，这也是 &lt;code&gt;netty&lt;/code&gt; 默认的线程模型，下面是这个模型的原理图：&lt;/p&gt;

&lt;p&gt;
&lt;img width="820" alt="multi_reactor" src="https://user-images.githubusercontent.com/7496278/64916634-8f038080-d7b3-11e9-82c8-f77e9791df86.png"&gt;
&lt;/p&gt;

&lt;p&gt;它的运行流程如下面的时序图：&lt;/p&gt;

&lt;p&gt;
&lt;img width="869" alt="reactor" src="https://user-images.githubusercontent.com/7496278/64918644-a5213900-d7d3-11e9-88d6-1ec1ec72c1cd.png"&gt;
&lt;/p&gt;
&lt;h3 id="主从多 Reactors + 线程/Go程池"&gt;主从多 Reactors + 线程/Go 程池&lt;/h3&gt;
&lt;p&gt;你可能会问一个问题：如果我的业务逻辑是阻塞的，那么在 &lt;code&gt;EventHandler.React&lt;/code&gt; 注册方法里的逻辑也会阻塞，从而导致阻塞 event-loop 线程，这时候怎么办？&lt;/p&gt;

&lt;p&gt;正如你所知，基于 &lt;code&gt;gnet&lt;/code&gt; 编写你的网络服务器有一条最重要的原则：永远不能让你业务逻辑（一般写在 &lt;code&gt;EventHandler.React&lt;/code&gt; 里）阻塞 event-loop 线程，否则的话将会极大地降低服务器的吞吐量，这也是 &lt;code&gt;netty&lt;/code&gt; 的一条最重要的原则。&lt;/p&gt;

&lt;p&gt;我的回答是，基于&lt;code&gt;gnet&lt;/code&gt; 的另一种多线程/Go 程模型：『带线程/Go 程池的主从多 Reactors』可以解决阻塞问题，这个新网络模型通过引入一个 worker pool 来解决业务逻辑阻塞的问题：它会在启动的时候初始化一个 worker pool，然后在把 &lt;code&gt;EventHandler.React&lt;/code&gt;里面的阻塞代码放到 worker pool 里执行，从而避免阻塞 event-loop 线程，&lt;/p&gt;

&lt;p&gt;模型的架构图如下所示：&lt;/p&gt;

&lt;p&gt;
&lt;img width="854" alt="multi_reactor_thread_pool" src="https://user-images.githubusercontent.com/7496278/64918783-90de3b80-d7d5-11e9-9190-ff8277c95db1.png"&gt;
&lt;/p&gt;

&lt;p&gt;它的运行流程如下面的时序图：&lt;/p&gt;

&lt;p&gt;
&lt;img width="916" alt="multi-reactors" src="https://user-images.githubusercontent.com/7496278/64918646-a7839300-d7d3-11e9-804a-d021ddd23ca3.png"&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gnet&lt;/code&gt; 通过利用 &lt;a href="https://github.com/panjf2000/ants" rel="nofollow" target="_blank" title=""&gt;ants&lt;/a&gt; goroutine 池（一个基于 Go 开发的高性能的 goroutine 池，实现了对大规模 goroutines 的调度管理、goroutines 复用）来实现『主从多 Reactors + 线程/Go 程池』网络模型。关于 &lt;code&gt;ants&lt;/code&gt; 的全部功能和使用，可以在 &lt;a href="https://gowalker.org/github.com/panjf2000/ants?lang=zh-CN" rel="nofollow" target="_blank" title=""&gt;ants 文档&lt;/a&gt; 里找到。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gnet&lt;/code&gt; 内部集成了 &lt;code&gt;ants&lt;/code&gt; 以及提供了 &lt;code&gt;pool.NewWorkerPool&lt;/code&gt; 方法来初始化一个 &lt;code&gt;ants&lt;/code&gt; goroutine 池，然后你可以把 &lt;code&gt;EventHandler.React&lt;/code&gt; 中阻塞的业务逻辑提交到 goroutine 池里执行，最后在 goroutine 池里的代码调用 &lt;code&gt;gnet.Conn.AsyncWrite&lt;/code&gt; 方法把处理完阻塞逻辑之后得到的输出数据异步写回客户端，这样就可以避免阻塞 event-loop 线程。&lt;/p&gt;

&lt;p&gt;有关在 &lt;code&gt;gnet&lt;/code&gt; 里使用 &lt;code&gt;ants&lt;/code&gt; goroutine 池的细节可以到&lt;a href="#echo-server-with-blocking-logic" title=""&gt;这里&lt;/a&gt;进一步了解。&lt;/p&gt;
&lt;h2 id="自动扩容的 Ring-Buffer"&gt;自动扩容的 Ring-Buffer&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;gnet&lt;/code&gt; 利用 Ring-Buffer 来缓冲网络数据以及管理内存。&lt;/p&gt;

&lt;p&gt;
&lt;img src="https://user-images.githubusercontent.com/7496278/64916810-4f8b6300-d7b8-11e9-9459-5517760da738.gif"&gt;
&lt;/p&gt;
&lt;h2 id="开始使用"&gt;开始使用&lt;/h2&gt;&lt;h2 id="安装"&gt;安装&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; github.com/panjf2000/gnet
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="使用示例"&gt;使用示例&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;详细的文档在这里：&lt;a href="https://gowalker.org/github.com/panjf2000/gnet?lang=zh-CN" rel="nofollow" target="_blank" title=""&gt;gnet 接口文档&lt;/a&gt;，不过下面我们先来了解下使用 &lt;code&gt;gnet&lt;/code&gt; 的简略方法。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;用 &lt;code&gt;gnet&lt;/code&gt; 来构建网络服务器是非常简单的，只需要实现 &lt;code&gt;gnet.EventHandler&lt;/code&gt;接口然后把你关心的事件函数注册到里面，最后把它连同监听地址一起传递给 &lt;code&gt;gnet.Serve&lt;/code&gt; 函数就完成了。在服务器开始工作之后，每一条到来的网络连接会在各个事件之间传递，如果你想在某个事件中关闭某条连接或者关掉整个服务器的话，直接把 &lt;code&gt;gnet.Action&lt;/code&gt; 设置成 &lt;code&gt;Cosed&lt;/code&gt; 或者 &lt;code&gt;Shutdown&lt;/code&gt;就行了。&lt;/p&gt;

&lt;p&gt;Echo 服务器是一种最简单网络服务器，把它作为 &lt;code&gt;gnet&lt;/code&gt; 的入门例子在再合适不过了，下面是一个最简单的 echo server，它监听了 9000 端口：&lt;/p&gt;
&lt;h3 id="不带阻塞逻辑的 echo 服务器"&gt;不带阻塞逻辑的 echo 服务器&lt;/h3&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/panjf2000/gnet"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;echoServer&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventServer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;es&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;echoServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;React&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResetBuffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;echoServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tcp://:9000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMulticore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正如你所见，上面的例子里 &lt;code&gt;gnet&lt;/code&gt; 实例只注册了一个 &lt;code&gt;EventHandler.React&lt;/code&gt; 事件。一般来说，主要的业务逻辑代码会写在这个事件方法里，这个方法会在服务器接收到客户端写过来的数据之时被调用，然后处理输入数据（这里只是把数据 echo 回去）并且在处理完之后把需要输出的数据赋值给 &lt;code&gt;out&lt;/code&gt; 变量然后返回，之后你就不用管了，&lt;code&gt;gnet&lt;/code&gt; 会帮你把数据写回客户端的。&lt;/p&gt;
&lt;h3 id="带阻塞逻辑的 echo 服务器"&gt;带阻塞逻辑的 echo 服务器&lt;/h3&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/panjf2000/gnet"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/panjf2000/gnet/pool"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;echoServer&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventServer&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;es&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;echoServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;React&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResetBuffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Use ants pool to unblock the event-loop.&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;es&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsyncWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWorkerPool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;echoServer&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tcp://:9000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMulticore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正如我在『主从多 Reactors + 线程/Go 程池』那一节所说的那样，如果你的业务逻辑里包含阻塞代码，那么你应该把这些阻塞代码变成非阻塞的，比如通过把这部分代码通过 goroutine 去运行，但是要注意一点，如果你的服务器处理的流量足够的大，那么这种做法将会导致创建大量的 goroutines 极大地消耗系统资源，所以我一般建议你用 goroutine pool 来做 goroutines 的复用和管理，以及节省系统资源。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;更多的例子可以在这里查看：&lt;a href="https://github.com/panjf2000/gnet/tree/master/examples" rel="nofollow" target="_blank" title=""&gt;gnet 示例&lt;/a&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="I/O 事件"&gt;I/O 事件&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;gnet&lt;/code&gt; 目前支持的 I/O 事件如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EventHandler.OnInitComplete&lt;/code&gt; 当 server 初始化完成之后调用。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EventHandler.OnOpened&lt;/code&gt; 当连接被打开的时候调用。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EventHandler.OnClosed&lt;/code&gt; 当连接被关闭的时候调用。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EventHandler.React&lt;/code&gt; 当 server 端接收到从 client 端发送来的数据的时候调用。（你的核心业务代码一般是写在这个方法里）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EventHandler.Tick&lt;/code&gt; 服务器启动的时候会调用一次，之后就以给定的时间间隔定时调用一次，是一个定时器方法。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EventHandler.PreWrite&lt;/code&gt; 预先写数据方法，在 server 端写数据回 client 端之前调用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="定时器"&gt;定时器&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;EventHandler.Tick&lt;/code&gt; 会每隔一段时间触发一次，间隔时间你可以自己控制，设定返回的 &lt;code&gt;delay&lt;/code&gt; 变量就行。&lt;/p&gt;

&lt;p&gt;定时器的第一次触发是在 &lt;code&gt;gnet.Serving&lt;/code&gt; 事件之后，如果你要设置定时器，别忘了设置 option 选项：&lt;code&gt;WithTicker(true)&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tick"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="UDP 支持"&gt;UDP 支持&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;gnet&lt;/code&gt; 支持 UDP 协议，在 &lt;code&gt;gnet.Serve&lt;/code&gt; 里绑定 UDP 地址即可，&lt;code&gt;gnet&lt;/code&gt; 的 UDP 支持有如下的特性：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;数据进入服务器之后立刻写回客户端，不做缓存。&lt;/li&gt;
&lt;li&gt; &lt;code&gt;EventHandler.OnOpened&lt;/code&gt; 和 &lt;code&gt;EventHandler.OnClosed&lt;/code&gt; 这两个事件在 UDP 下不可用，唯一可用的事件是 &lt;code&gt;React&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="使用多核"&gt;使用多核&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;gnet.WithMulticore(true)&lt;/code&gt; 参数指定了 &lt;code&gt;gnet&lt;/code&gt; 是否会使用多核来进行服务，如果是 &lt;code&gt;true&lt;/code&gt; 的话就会使用多核，否则就是单核运行，利用的核心数一般是机器的 CPU 数量。&lt;/p&gt;
&lt;h2 id="负载均衡"&gt;负载均衡&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;gnet&lt;/code&gt; 目前内置的负载均衡算法是轮询调度 Round-Robin，暂时不支持自定制。&lt;/p&gt;
&lt;h2 id="SO_REUSEPORT 端口复用"&gt;SO_REUSEPORT 端口复用&lt;/h2&gt;
&lt;p&gt;服务器支持 &lt;a href="https://lwn.net/Articles/542629/" rel="nofollow" target="_blank" title=""&gt;SO_REUSEPORT&lt;/a&gt; 端口复用特性，允许多个 sockets 监听同一个端口，然后内核会帮你做好负载均衡，每次只唤醒一个 socket 来处理 accept 请求，避免惊群效应。&lt;/p&gt;

&lt;p&gt;开启这个功能也很简单，使用 functional options 设置一下即可：&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tcp://:9000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMulticore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;gnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReusePort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="性能测试"&gt;性能测试&lt;/h2&gt;&lt;h2 id="同类型的网络库性能对比"&gt;同类型的网络库性能对比&lt;/h2&gt;&lt;h2 id="Linux (epoll)"&gt;Linux (epoll)&lt;/h2&gt;&lt;h3 id="系统参数"&gt;系统参数&lt;/h3&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Machine information&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;OS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Ubuntu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;18.04/x86_64&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="n"&gt;CPU&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Virtual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CPUs&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;16.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GiB&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Go version and configurations&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;go1.12.9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;linux/amd64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="Echo Server"&gt;Echo Server&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_linux.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h4 id="HTTP Server"&gt;HTTP Server&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://github.com/panjf2000/gnet_benchmarks/raw/master/results/http_linux.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="FreeBSD (kqueue)"&gt;FreeBSD (kqueue)&lt;/h2&gt;&lt;h3 id="系统参数"&gt;系统参数&lt;/h3&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Machine information&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;OS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;macOS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Mojave&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10.14.6/x86_64&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="n"&gt;CPU&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CPUs&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GiB&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Go version and configurations&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;go1.12.9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;darwin/amd64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="Echo Server"&gt;Echo Server&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_mac.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h4 id="HTTP Server"&gt;HTTP Server&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://github.com/panjf2000/gnet_benchmarks/raw/master/results/http_mac.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="证书"&gt;证书&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;gnet&lt;/code&gt; 的源码允许用户在遵循 MIT &lt;a href="/LICENSE" title=""&gt;开源证书&lt;/a&gt; 规则的前提下使用。&lt;/p&gt;
&lt;h2 id="致谢"&gt;致谢&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tidwall/evio" rel="nofollow" target="_blank" title=""&gt;evio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/netty/netty" rel="nofollow" target="_blank" title=""&gt;netty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/panjf2000/ants" rel="nofollow" target="_blank" title=""&gt;ants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gobwas/pool" rel="nofollow" target="_blank" title=""&gt;pool&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="相关文章"&gt;相关文章&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.freecodecamp.org/news/million-websockets-and-go-cc58418460bb/" rel="nofollow" target="_blank" title=""&gt;A Million WebSockets and Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://speakerdeck.com/eranyanay/going-infinite-handling-1m-websockets-connections-in-go" rel="nofollow" target="_blank" title=""&gt;Going Infinite, handling 1M websockets connections in Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://taohuawu.club/go-event-loop-networking-library-gnet" rel="nofollow" target="_blank" title=""&gt;gnet: 一个轻量级且高性能的 Golang 网络库&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>panjf2000</author>
      <pubDate>Wed, 09 Oct 2019 15:21:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/39122</link>
      <guid>https://ruby-china.org/topics/39122</guid>
    </item>
  </channel>
</rss>
