我们知道, Go语言为并发编程提供了简洁的编程方式, 你可以以"同步"的编程风格来并发执行代码, 比如使用
关键字新开一个goroutine。 对于网络编程,Go标准库和运行时内部采用
- go
来实现基于
- epoll/kqueue/IoCompletionPort
的网络异步处理,但是通过
- event-loop
的方式对外提供同步的访问。具体代码可以参考 runtime/netpoll、 net和internal/poll。
- netpoll
Package poll supports non-blocking I/O on file descriptors with polling.
This supports I/O operations that block only a goroutine, not a thread.
This is used by the net and os packages.
It uses a poller built into the runtime, with support from the
runtime scheduler.
当然,我们平常不会设计到这些封装的细节,正常使用
包就很方便的开发网络程序了, 但是,如果我们想自己实现基于
- net
的
- epoll
网络程序呢?
- event-loop
可以查看epoll的相关介绍。下面这个例子来自tevino, 采用
- man epoll
方式处理事件。
- edge-triggered
它采用
、
- syscall.Socket
、
- syscall.SetNonblock
、
- syscall.Bind
系统调用来监听端口,然后采用
- syscall.Listen
、
- syscall.EpollCreate1
、
- syscall.EpollCtl
来关联这个监听的file descriptor, 一旦有新的连接的事件过来,使用
- syscall.EpollWait
接收连接请求,并对这个连接file descriptor调用
- syscall.Accept
监听数据事件。一旦连接有数据ready, 调用
- syscall.EpollCtl
读数据,调用
- syscall.Read
写数据。
- syscall.Write
- package main
- import (
- "fmt"
- "net"
- "os"
- "syscall"
- )
- const (
- EPOLLET = 1 << 31
- MaxEpollEvents = 32
- )
- func echo(fd int) {
- defer syscall.Close(fd)
- var buf [32 * 1024]byte
- for {
- nbytes, e := syscall.Read(fd, buf[:])
- if nbytes > 0 {
- fmt.Printf(">>> %s", buf)
- syscall.Write(fd, buf[:nbytes])
- fmt.Printf("<<< %s", buf)
- }
- if e != nil {
- break
- }
- }
- }
- func main() {
- var event syscall.EpollEvent
- var events [MaxEpollEvents]syscall.EpollEvent
- fd, err := syscall.Socket(syscall.AF_INET, syscall.O_NONBLOCK|syscall.SOCK_STREAM, 0)
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- defer syscall.Close(fd)
- if err = syscall.SetNonblock(fd, true); err != nil {
- fmt.Println("setnonblock1: ", err)
- os.Exit(1)
- }
- addr := syscall.SockaddrInet4{Port: 2000}
- copy(addr.Addr[:], net.ParseIP("0.0.0.0").To4())
- syscall.Bind(fd, &addr)
- syscall.Listen(fd, 10)
- epfd, e := syscall.EpollCreate1(0)
- if e != nil {
- fmt.Println("epoll_create1: ", e)
- os.Exit(1)
- }
- defer syscall.Close(epfd)
- event.Events = syscall.EPOLLIN
- event.Fd = int32(fd)
- if e = syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, fd, &event); e != nil {
- fmt.Println("epoll_ctl: ", e)
- os.Exit(1)
- }
- for {
- nevents, e := syscall.EpollWait(epfd, events[:], -1)
- if e != nil {
- fmt.Println("epoll_wait: ", e)
- break
- }
- for ev := 0; ev < nevents; ev++ {
- if int(events[ev].Fd) == fd {
- connFd, _, err := syscall.Accept(fd)
- if err != nil {
- fmt.Println("accept: ", err)
- continue
- }
- syscall.SetNonblock(fd, true)
- event.Events = syscall.EPOLLIN | EPOLLET
- event.Fd = int32(connFd)
- if err := syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, connFd, &event); err != nil {
- fmt.Print("epoll_ctl: ", connFd, err)
- os.Exit(1)
- }
- } else {
- go echo(int(events[ev].Fd))
- }
- }
- }
- }
上面的基于
只是一个简单的
- epoll
处理原型,而且在有些平台下(MAC OS)也不能执行,事件的处理也很粗糙,如果你想实现一个完整的
- event-loop
的网络程序, 可以参考下节的库。
- event-loop
evio是一个性能很高的event-loop网络库,代码简单,功能强大。它直接使用
和
- epoll
系统调用,除了Go标准net库提供了另外一种思路, 类似libuv和libevent。
- kqueue
这个库实现redis和haproxy等同的包处理机制,但并不想完全替代标准的net包。对于一个需要长时间运行的请求(大于1毫秒), 比如数据库访问、身份验证等,建议还是使用Go net/http库。
你可能知道, 由很多基于event-loop的程序, 比如Nginx、Haproxy、redis、memcached等,性能都非常不错,而且它们都是单线程运行的,非常快。
这个库还有一个好处, 你可以在一个event-loop中处理多个network binding。
一个简单的例子:
- package main
- import "github.com/tidwall/evio"
- func main() {
- var events evio.Events
- events.Data = func(id int, in []byte) (out []byte, action evio.Action) {
- out = in
- return
- }
- if err := evio.Serve(events, "tcp://localhost:5000", "tcp://192.168.0.10:5001", "tcp://192.168.0.10:5002","unix://socket"); err != nil {
- panic(err.Error())
- }
- }
作者对性能做了对比,性能非常不错。
简单的echo例子
http对比
pipeline
为1
pipeline
为8
来源: https://juejin.im/entry/5a275f4e6fb9a0450e75fcbf