Skip to content

Commit

Permalink
SO_REUSEPORT
Browse files Browse the repository at this point in the history
Just add "reuseport=true" to an address:

evio.Serve(events, "tcp://0.0.0.0:1234?reuseport=true")
  • Loading branch information
tidwall committed Mar 19, 2018
1 parent ac89b56 commit cf82b88
Show file tree
Hide file tree
Showing 19 changed files with 947 additions and 11 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The reason I wrote this framework is so I can build certain network services tha
- Fallback for non-epoll/kqueue operating systems by simulating events with the [net](https://golang.org/pkg/net/) package
- Ability to [wake up](#wake-up) connections from long running background operations
- [Dial](#dial-out) an outbound connection and process/proxy on the event loop
- [SO_REUSEPORT](#so_reuseport) socket option

## Getting Started

Expand Down Expand Up @@ -205,7 +206,7 @@ events = evio.Translate(events, nil,
},
)

log.Fatal(Serve(events, "tcp://0.0.0.0:443"))
log.Fatal(evio.Serve(events, "tcp://0.0.0.0:443"))
```

Here we wrapped the event with a TLS translator. The `evio.NopConn` function is used to converts the `ReadWriter` a `net.Conn` so the `tls.Server()` call will work.
Expand Down Expand Up @@ -235,6 +236,16 @@ The `Serve` function can bind to UDP addresses.
- The `Wake` and `Dial` operations are not available to UDP connections.
- All incoming and outgoing packets are not buffered and sent individually.

## SO_REUSEPORT

Servers can utilize the [SO_REUSEPORT](https://lwn.net/Articles/542629/) option which allows multiple sockets on the same host to bind to the same port.

Just provide `reuseport=true` to an address:

```go
evio.Serve(events, "tcp://0.0.0.0:1234?reuseport=true"))
```

## More examples

Please check out the [examples](examples) subdirectory for a simplified [redis](examples/redis-server/main.go) clone, an [echo](examples/echo-server/main.go) server, and a very basic [http](examples/http-server/main.go) server with TLS support.
Expand Down
40 changes: 36 additions & 4 deletions evio.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"os"
"strings"
"time"

"github.com/kavu/go_reuseport"
)

// Action is an action that occurs after the completion of an event.
Expand Down Expand Up @@ -140,7 +142,7 @@ func Serve(events Events, addr ...string) error {
for _, addr := range addr {
var ln listener
var stdlibt bool
ln.network, ln.addr, stdlibt = parseAddr(addr)
ln.network, ln.addr, ln.opts, stdlibt = parseAddr(addr)
if stdlibt {
stdlib = true
}
Expand All @@ -149,9 +151,17 @@ func Serve(events Events, addr ...string) error {
}
var err error
if ln.network == "udp" {
ln.pconn, err = net.ListenPacket(ln.network, ln.addr)
if ln.opts.reusePort() {
ln.pconn, err = reuseport.ListenPacket(ln.network, ln.addr)
} else {
ln.pconn, err = net.ListenPacket(ln.network, ln.addr)
}
} else {
ln.ln, err = net.Listen(ln.network, ln.addr)
if ln.opts.reusePort() {
ln.ln, err = reuseport.Listen(ln.network, ln.addr)
} else {
ln.ln, err = net.Listen(ln.network, ln.addr)
}
}
if err != nil {
return err
Expand Down Expand Up @@ -204,15 +214,27 @@ type listener struct {
ln net.Listener
lnaddr net.Addr
pconn net.PacketConn
opts addrOpts
f *os.File
fd int
network string
addr string
}

func parseAddr(addr string) (network, address string, stdlib bool) {
type addrOpts map[string]string

func (opts addrOpts) reusePort() bool {
switch opts["reuseport"] {
case "yes", "true", "1":
return true
}
return false
}

func parseAddr(addr string) (network, address string, opts addrOpts, stdlib bool) {
network = "tcp"
address = addr
opts = make(map[string]string)
if strings.Contains(address, "://") {
network = strings.Split(address, "://")[0]
address = strings.Split(address, "://")[1]
Expand All @@ -221,5 +243,15 @@ func parseAddr(addr string) (network, address string, stdlib bool) {
stdlib = true
network = network[:len(network)-4]
}
q := strings.Index(address, "?")
if q != -1 {
for _, part := range strings.Split(address[q+1:], "&") {
kv := strings.Split(part, "=")
if len(kv) == 2 {
opts[kv[0]] = kv[1]
}
}
address = address[:q]
}
return
}
23 changes: 20 additions & 3 deletions evio_loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ func serve(events Events, lns []*listener) error {
}()
var rsa syscall.Sockaddr
var sa6 syscall.SockaddrInet6

var detached []int
var packet [0xFFFF]byte
var evs = internal.MakeEvents(64)
nextTicker := time.Now()
Expand Down Expand Up @@ -394,6 +394,7 @@ func serve(events Events, lns []*listener) error {
continue
}
}
detached = detached[:0]
lock()
for i := 0; i < pn; i++ {
var in []byte
Expand All @@ -416,7 +417,16 @@ func serve(events Events, lns []*listener) error {
ln = nil
c = fdconn[fd]
if c == nil {
syscall.Close(fd)
var found bool
for _, dfd := range detached {
if dfd == fd {
found = true
break
}
}
if !found {
syscall.Close(fd)
}
goto next
}
if c.opening {
Expand Down Expand Up @@ -665,6 +675,13 @@ func serve(events Events, lns []*listener) error {
delete(idconn, c.id)
if c.action == Detach {
if events.Detached != nil {
if err = internal.DelRead(p, c.fd, &c.readon, &c.writeon); err != nil {
goto fail
}
if err = internal.DelWrite(p, c.fd, &c.readon, &c.writeon); err != nil {
goto fail
}
detached = append(detached, c.fd)
c.detached = true
if len(c.outbuf)-c.outpos > 0 {
c.outbuf = append(c.outbuf[:0], c.outbuf[c.outpos:]...)
Expand Down Expand Up @@ -708,7 +725,7 @@ func serve(events Events, lns []*listener) error {
// resolve resolves an evio address and retuns a sockaddr for socket
// connection to external servers.
func resolve(addr string) (sa syscall.Sockaddr, err error) {
network, address, _ := parseAddr(addr)
network, address, _, _ := parseAddr(addr)
var taddr net.Addr
switch network {
default:
Expand Down
2 changes: 1 addition & 1 deletion evio_net.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ func servenet(events Events, lns []*listener) error {
}
id := int(atomic.AddInt64(&idc, 1))
go func() {
network, address, _ := parseAddr(addr)
network, address, _, _ := parseAddr(addr)
var conn net.Conn
var err error
if timeout > 0 {
Expand Down
2 changes: 1 addition & 1 deletion evio_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (ln *listener) close() {
}
}

func (ln *listener) system() error {
func (ln *listener) system(opts map[string]string) error {
return nil
}

Expand Down
13 changes: 12 additions & 1 deletion examples/echo-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,28 @@ import (
"flag"
"fmt"
"log"
"strings"

"github.com/tidwall/evio"
)

func main() {
var port int
var udp bool
var trace bool
var reuseport bool
flag.IntVar(&port, "port", 5000, "server port")
flag.BoolVar(&udp, "udp", false, "listen on udp")
flag.BoolVar(&reuseport, "reuseport", false, "reuseport (SO_REUSEPORT)")
flag.BoolVar(&trace, "trace", false, "print packets to console")
flag.Parse()

var events evio.Events
events.Serving = func(srv evio.Server) (action evio.Action) {
log.Printf("echo server started on port %d", port)
if reuseport {
log.Printf("reuseport")
}
return
}
events.Opened = func(id int, info evio.Info) (out []byte, opts evio.Options, action evio.Action) {
Expand All @@ -33,12 +41,15 @@ func main() {
return
}
events.Data = func(id int, in []byte) (out []byte, action evio.Action) {
if trace {
log.Printf("%s", strings.TrimSpace(string(in)))
}
out = in
return
}
scheme := "tcp"
if udp {
scheme = "udp"
}
log.Fatal(evio.Serve(events, fmt.Sprintf("%s://:%d", scheme, port)))
log.Fatal(evio.Serve(events, fmt.Sprintf("%s://:%d?reuseport=%t", scheme, port, reuseport)))
}
1 change: 1 addition & 0 deletions vendor/.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stub
21 changes: 21 additions & 0 deletions vendor/github.com/kavu/go_reuseport/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions vendor/github.com/kavu/go_reuseport/Makefile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions vendor/github.com/kavu/go_reuseport/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions vendor/github.com/kavu/go_reuseport/reuseport.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit cf82b88

Please sign in to comment.