Go语言中实现基于 event-loop 网络处理

https://colobu.com/2017/11/29/event-loop-networking-in-Go/

 

我们知道, Go语言为并发编程提供了简洁的编程方式, 你可以以"同步"的编程风格来并发执行代码, 比如使用go关键字新开一个goroutine。 对于网络编程,Go标准库和运行时内部采用 epoll/kqueue/IoCompletionPort来实现基于 event-loop的网络异步处理,但是通过netpoll的方式对外提供同步的访问。具体代码可以参考 runtime/netpoll、net和internal/poll。

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包就很方便的开发网络程序了, 但是,如果我们想自己实现基于epollevent-loop网络程序呢?

基于epoll的简单程序

man epoll可以查看epoll的相关介绍。下面这个例子来自tevino, 采用edge-triggered方式处理事件。

它采用 syscall.Socketsyscall.SetNonblocksyscall.Bindsyscall.Listen系统调用来监听端口,然后采用syscall.EpollCreate1syscall.EpollCtlsyscall.EpollWait来关联这个监听的file descriptor, 一旦有新的连接的事件过来,使用syscall.Accept接收连接请求,并对这个连接file descriptor调用syscall.EpollCtl监听数据事件。一旦连接有数据ready, 调用syscall.Read读数据,调用syscall.Write写数据。

来自https://gist.github.com/tevino/3a4f4ec4ea9d0ca66d4f

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

 

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只是一个简单的event-loop处理原型,而且在有些平台下(MAC OS)也不能执行,事件的处理也很粗糙,如果你想实现一个完整的event-loop的网络程序, 可以参考下节的库。

evio

evio是一个性能很高的event-loop网络库,代码简单,功能强大。它直接使用 epollkqueue系统调用,除了Go标准net库提供了另外一种思路, 类似libuv和libevent。

这个库实现redis和haproxy等同的包处理机制,但并不想完全替代标准的net包。对于一个需要长时间运行的请求(大于1毫秒), 比如数据库访问、身份验证等,建议还是使用Go net/http库。

你可能知道, 由很多基于event-loop的程序, 比如Nginx、Haproxy、redis、memcached等,性能都非常不错,而且它们都是单线程运行的,非常快。

这个库还有一个好处, 你可以在一个event-loop中处理多个network binding。

一个简单的例子:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

 

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())

}

}

作者对性能做了对比,性能非常不错。

Go语言中实现基于 event-loop 网络处理_第1张图片简单的echo例子
Go语言中实现基于 event-loop 网络处理_第2张图片http对比
Go语言中实现基于 event-loop 网络处理_第3张图片pipeline 为1
Go语言中实现基于 event-loop 网络处理_第4张图片

你可能感兴趣的:(Go语言中实现基于 event-loop 网络处理)