golang - epoll 예제 코드

Golang에서 epoll API를 호출해서 서버 프로그램을 만드는 예제 코드이다.
출처

epoll.go

package main

import (
    "syscall"
    "fmt"
)

const (
    EPOLLET        = 1 << 31
    MaxEpollEvents = 32
)

type epoll struct {
    fd int
}

func initEpoll() (epoll, error) {
    epfd, err := syscall.EpollCreate1(0)
    if err != nil {
        return epoll{}, err
    }
    return epoll{fd: epfd}, nil
}

func (ep *epoll) close() {
    syscall.Close(ep.fd)
}

func (ep *epoll) wait() ([]syscall.EpollEvent, error) {
    var events [MaxEpollEvents]syscall.EpollEvent
    nevents, err := syscall.EpollWait(ep.fd, events[:], -1)
    if err != nil {
        return []syscall.EpollEvent{}, err
    }

    return events[:nevents], nil
}

func (ep *epoll) add(fd int, eventOperations uint32, edgeMode bool) error {
    fmt.Println("epoll add:", fd)
    var event syscall.EpollEvent
    event.Events = eventOperations
    if edgeMode {
        event.Events |= EPOLLET
    }
    event.Fd = int32(fd)
    if err := syscall.EpollCtl(ep.fd, syscall.EPOLL_CTL_ADD, fd, &event); err != nil {
        return err
    }
    return nil
}

main.go 일부

// 이벤트 핸들러
// syscall.EpollEvent의 events에 의해서 실행하는 처리를 구분한다
// 먼저 echo 동작을 위한 구현
func handleConnectedEvent(event syscall.EpollEvent)  {
    fmt.Println("event:", event)
    switch event.Events {
    case syscall.EPOLLIN:
        echo(int(event.Fd))
    case syscall.EPOLLIN | syscall.EPOLLRDHUP:
        fmt.Println("connection close: ", event.Fd)
        syscall.Close(int(event.Fd))
    }
}

// 소켓에서 read 한 내용을 그대로 보낸다
func echo(fd int) {
    var buf [32 * 1024]byte
    nbytes, _ := syscall.Read(fd, buf[:])
    if nbytes > 0 {
        fmt.Printf(">>> %s", buf)
        syscall.Write(fd, buf[:nbytes])
    }
}

func main() {
    var listenFd int
    var err error

    listenFd, err = initListenFd("0.0.0.0", 3000)
    if err != nil {
        exit(err)
    }
    defer syscall.Close(listenFd)

    var ep epoll
    ep, err = initEpoll()
    if err != nil {
        exit(err)
    }
    defer ep.close()

    add := ep.add(listenFd, syscall.EPOLLIN,false)
    if err = add; err != nil {
        exit(err)
    }

    var events []syscall.EpollEvent
    for {
        // 이벤트 통지 대기
        events, err = ep.wait()
        if err != nil {
            exit(err)
        }

        for _, event := range events {
            // 파일 디스크립터가 신규 접속 대기를 하는 것이라면 신규 접속 처리 
            if int(event.Fd) == listenFd {
                connFd, _, err := syscall.Accept(listenFd)
                if err != nil {
                    exit(err)
                }
                defer syscall.Close(connFd)

                // 연결 확립 후의 통신 용 소켓의 파일 디스크립터를 epoll에 등록한다
                if e := ep.add(connFd, syscall.EPOLLIN | syscall.EPOLLRDHUP,true); e != nil {
                    exit(err)
                }
            } else {
                // コネクション確率済みの処理は handleConnectedEvent を呼び出す
                handleConnectedEvent(event)
            }
        }
    }
}

goroutine을 사용하지 않기 때문에 모든 것을 메인 스레드에서 처리하고 있다.
그러나 이 구현은 이벤트 구동으로 블로킹이 아니다.


이 글은 2020-10-08에 작성되었습니다.