Skip to content

Instantly share code, notes, and snippets.

@tevino
Last active January 20, 2024 22:50
Show Gist options
  • Star 85 You must be signed in to star a gist
  • Fork 20 You must be signed in to fork a gist
  • Save tevino/3a4f4ec4ea9d0ca66d4f to your computer and use it in GitHub Desktop.
Save tevino/3a4f4ec4ea9d0ca66d4f to your computer and use it in GitHub Desktop.
An example of using epoll in Go
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))
}
}
}
}
@JoveYu
Copy link

JoveYu commented Jan 25, 2019

use so many syscall, why not use C instead of GO

@praglody
Copy link

praglody commented Jun 15, 2019

为什么 EPOLLET 是一个负数,我这里会报错,请问下是什么原因呢?

EpollCtl(epfd, EPOLL_CTL_ADD, ufd, &EpollEvent{
        Fd:     int32(ufd),
	Events: EPOLLIN | EPOLLERR | EPOLLET,
})

command-line-arguments

./server.go:73:23: constant -2147483647 overflows uint32

@tevino
Copy link
Author

tevino commented Jun 15, 2019

@praglody
Try EPOLLET = 0x80000000

FYI golang/go#832

@praglody
Copy link

It has been solved,thinks!

@alanxone
Copy link

use so many syscall, why not use C instead of GO

Using C inside Go is okay but it causes performance issue at the moment

@deardeng
Copy link

客户端关闭,echo 函数不能退出,goroutine死循环,要添加

	if nbytes == 0{
		if err := syscall.EpollCtl(epfd, syscall.EPOLL_CTL_DEL, fd, nil); err != nil{
			fmt.Println("epoll_ctl: ", err)
			os.Exit(1)
		}
		break
	}

@tevino
Copy link
Author

tevino commented Sep 16, 2019

客户端关闭,echo 函数不能退出,goroutine死循环,要添加

	if nbytes == 0{
		if err := syscall.EpollCtl(epfd, syscall.EPOLL_CTL_DEL, fd, nil); err != nil{
			fmt.Println("epoll_ctl: ", err)
			os.Exit(1)
		}
		break
	}

man 7 epoll:

 6.  Will closing a file descriptor cause it to be removed from all epoll interest lists?

           Yes,  but  be  aware of the following point.  A file descriptor is a reference to an open file description (see open(2)).  Whenever a file descriptor is duplicated via dup(2), dup2(2), fcntl(2) F_DUPFD, or fork(2), a new
           file descriptor referring to the same open file description is created.  An open file description continues to exist until all file descriptors referring to it have been closed.

@lasselj
Copy link

lasselj commented Apr 22, 2020

use so many syscall, why not use C instead of GO

If the compiler is a C compiler or a GO compiler what difference does it make? It is not the case that the binary produced by one is inherently "slower" than the other.

@vvilaplana
Copy link

If the compiler is a C compiler or a GO compiler what difference does it make? It is not the case that the binary produced by one is inherently "slower" than the other.

Yes, of course there's a huge difference.
GO compilers compiles your code to run inside a runtime, which is essentially an abstraction layer doing all the dirty work that C programmers manually do (memory management, process scheduling, and all of that).

But then again, this is the most important reason in my opinion:
epoll() is normally used because it provides a performance orders of magnitude bigger than a normal poll() or a select().
When you require very high performance, you don't want to ruin in because the garbage collector kicks in.

@Togou98
Copy link

Togou98 commented Jun 26, 2020

package syscall define the EPOLLET = -0x8000000 ,but epoll_event field "Events" need uint32 I'm confused 🤣

@lasselj
Copy link

lasselj commented Jun 26, 2020

package syscall define the EPOLLET = -0x8000000 ,but epoll_event field "Events" need uint32 I'm confused 🤣

There's more than one way to skin a cat. Try something like: const EPOLLET uint32 = 1<<31

@cgyim
Copy link

cgyim commented Sep 30, 2020

I guess the fd created for new connection needs to be removed from epollfd before go echo(int(events[ev].Fd)) , and then all data of the detached connection could be handled independently by a goroutine.

@yarbelk
Copy link

yarbelk commented Aug 9, 2021

If the compiler is a C compiler or a GO compiler what difference does it make? It is not the case that the binary produced by one is inherently "slower" than the other.

Yes, of course there's a huge difference.
GO compilers compiles your code to run inside a runtime, which is essentially an abstraction layer doing all the dirty work that C programmers manually do (memory management, process scheduling, and all of that).

But then again, this is the most important reason in my opinion:
epoll() is normally used because it provides a performance orders of magnitude bigger than a normal poll() or a select().
When you require very high performance, you don't want to ruin in because the garbage collector kicks in.

Or you want to do edge detection and sleep to save battery. in this case; you may be triggering code that itself doesn't need optimization and can run happily in a go with GC; but you want to sleep as much as possible until the triggering event.

@lasselj
Copy link

lasselj commented Aug 9, 2021

If the compiler is a C compiler or a GO compiler what difference does it make? It is not the case that the binary produced by one is inherently "slower" than the other.

Yes, of course there's a huge difference.
GO compilers compiles your code to run inside a runtime, which is essentially an abstraction layer doing all the dirty work that C programmers manually do (memory management, process scheduling, and all of that).

But then again, this is the most important reason in my opinion:
epoll() is normally used because it provides a performance orders of magnitude bigger than a normal poll() or a select().
When you require very high performance, you don't want to ruin in because the garbage collector kicks in.

Go is not Java. Go code does not get compiled into platform independent byte code which is then "interpreted" at runtime "inside" anything. Narrowly as it relates to epoll you are wrong in your assessment that cgo is faster at performing a syscall operation, it just isn't. More broadly as it relates to the runtime libraries used by different languages you are also wrong. For fun, you could look up how in C main() gets called by the c runtime library or in C++ you could investigate what happens when someone calls new or free..... In any event, the fact that a language and its runtime library has memory allocation and garbage collection functionality does not infer that it is by definition "slower" than a language that doesn't. Similarly, a language that doesn't, does not have determinism built-in to its memory allocation allocation/deallocation system, which is - believe it or not - a part of its runtime library functions. Lastly, you are not wrong that there are instances where you could write something in C/C++ that would perform better than the equivalent written in Go. But as it relates to using cgo for epoll in this case you are wrong to assume that syscall() used in cgo is by definition faster than syscall.Syscall in go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment