Giter Club home page Giter Club logo

evio's Introduction

evio
GoDoc

evio is an event loop networking framework that is fast and small. It makes direct epoll and kqueue syscalls rather than using the standard Go net package, and works in a similar manner as libuv and libevent.

The goal of this project is to create a server framework for Go that performs on par with Redis and Haproxy for packet handling. It was built to be the foundation for Tile38 and a future L7 proxy for Go.

Please note: Evio should not be considered as a drop-in replacement for the standard Go net or net/http packages.

Features

Getting Started

Installing

To start using evio, install Go and run go get:

$ go get -u github.com/tidwall/evio

This will retrieve the library.

Usage

Starting a server is easy with evio. Just set up your events and pass them to the Serve function along with the binding address(es). Each connections is represented as an evio.Conn object that is passed to various events to differentiate the clients. At any point you can close a client or shutdown the server by return a Close or Shutdown action from an event.

Example echo server that binds to port 5000:

package main

import "github.com/tidwall/evio"

func main() {
	var events evio.Events
	events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) {
		out = in
		return
	}
	if err := evio.Serve(events, "tcp://localhost:5000"); err != nil {
		panic(err.Error())
	}
}

Here the only event being used is Data, which fires when the server receives input data from a client. The exact same input data is then passed through the output return value, which is then sent back to the client.

Connect to the echo server:

$ telnet localhost 5000

Events

The event type has a bunch of handy events:

  • Serving fires when the server is ready to accept new connections.
  • Opened fires when a connection has opened.
  • Closed fires when a connection has closed.
  • Detach fires when a connection has been detached using the Detach return action.
  • Data fires when the server receives new data from a connection.
  • Tick fires immediately after the server starts and will fire again after a specified interval.

Multiple addresses

A server can bind to multiple addresses and share the same event loop.

evio.Serve(events, "tcp://192.168.0.10:5000", "unix://socket")

Ticker

The Tick event fires ticks at a specified interval. The first tick fires immediately after the Serving events.

events.Tick = func() (delay time.Duration, action Action){
	log.Printf("tick")
	delay = time.Second
	return
}

UDP

The Serve function can bind to UDP addresses.

  • All incoming and outgoing packets are not buffered and sent individually.
  • The Opened and Closed events are not availble for UDP sockets, only the Data event.

Multithreaded

The events.NumLoops options sets the number of loops to use for the server. A value greater than 1 will effectively make the server multithreaded for multi-core machines. Which means you must take care when synchonizing memory between event callbacks. Setting to 0 or 1 will run the server as single-threaded. Setting to -1 will automatically assign this value equal to runtime.NumProcs().

Load balancing

The events.LoadBalance options sets the load balancing method. Load balancing is always a best effort to attempt to distribute the incoming connections between multiple loops. This option is only available when events.NumLoops is set.

  • Random requests that connections are randomly distributed.
  • RoundRobin requests that connections are distributed to a loop in a round-robin fashion.
  • LeastConnections assigns the next accepted connection to the loop with the least number of active connections.

SO_REUSEPORT

Servers can utilize the SO_REUSEPORT option which allows multiple sockets on the same host to bind to the same port.

Just provide reuseport=true to an address:

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

More examples

Please check out the examples subdirectory for a simplified redis clone, an echo server, and a very basic http server.

To run an example:

$ go run examples/http-server/main.go
$ go run examples/redis-server/main.go
$ go run examples/echo-server/main.go

Performance

Benchmarks

These benchmarks were run on an ec2 c4.xlarge instance in single-threaded mode (GOMAXPROC=1) over Ipv4 localhost. Check out benchmarks for more info.

echo benchmarkhttp benchmarkredis 1 benchmarkredis 8 benchmark

Contact

Josh Baker @tidwall

License

evio source code is available under the MIT License.

evio's People

Contributors

corerman avatar coyove avatar kevwan avatar tidwall avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

evio's Issues

Write errors report

Hi,
There is no way to make sure that no error has been occurred during the "write": I suggest adding a PostWrite event with an error as a parameter!
Thanks :)

Panic will occur when accepting

panic: no such file or directory

goroutine 50 [running]:
github.com/tidwall/evio/internal.(*Poll).ModReadWrite(0xc4202ea060, 0x507)
/xagiot/home/yangxin/iot-proxy/src/github.com/tidwall/evio/internal/internal_linux.go:116 +0x8c
github.com/tidwall/evio.loopRead(0xc42030a000, 0xc4202d0100, 0xc4209c9050, 0xc4208ffb70, 0x0)
/xagiot/home/yangxin/iot-proxy/src/github.com/tidwall/evio/evio_unix.go:445 +0x17d
github.com/tidwall/evio.loopRun.func2(0x507, 0x0, 0x0, 0x0, 0x0)
/xagiot/home/yangxin/iot-proxy/src/github.com/tidwall/evio/evio_unix.go:245 +0xe6
github.com/tidwall/evio/internal.(*Poll).Wait(0xc4202ea060, 0xc420a93fa8, 0xc42030a000, 0x0)
/xagiot/home/yangxin/iot-proxy/src/github.com/tidwall/evio/internal/internal_linux.go:66 +0xb3
github.com/tidwall/evio.loopRun(0xc42030a000, 0xc4202d0100)
/xagiot/home/yangxin/iot-proxy/src/github.com/tidwall/evio/evio_unix.go:230 +0x93
created by github.com/tidwall/evio.serve
/xagiot/home/yangxin/iot-proxy/src/github.com/tidwall/evio/evio_unix.go:157 +0x483

I thought there have some exception in listen unsolve, so sometimes evio will panic without any recover.
I‘m looking forward to hearing from you, best wishs to U.

Using channels

I need to use a goroutine pool to execute blocking tasks. Almost all goroutine pool libraries use channels to pass tasks to worker goroutines or at-least use channels for goroutine wake-up. Does using channels in main io loops goroutines (without blocking on channel write) degrade performance of io loops too much ?

How to limit the maximum number of connections ?

I want to implement a proxy program.

A = evio
B = backend service

The client will connect to A , and A read data from B then out data to A

If B's max connections is 10. and clients is more than 10
In event.Opened , I use time.Sleep to wait B finish the work , but evio looks like blocked.

So how do I implement it ? or any options can set max connections and backlog ?

how to write to Conn from another goroutine?

since evio run in it's event loop, when Data event happen ,we may run another goroutine for long time processing, when the result is ready, how to write the result to the Conn?
is Conn.Wake safe for calling from another goroutine?

what is the best way for this type operation?
thanks for any help.

TLS Example

Is there an example using TLS? The docs mention it but I didn't see anything in the code.

Calling Wake() on UDP conn does nothing

We are trying to echo messages received from one UDP connection to other connected UDP clients. However, it looks like the UDP connection doesn't have an associated loop, so calling Wake() on the connection does nothing because loop is nil.
Should the connection created in func loopUDPRead(s *server, l *loop, lnidx, fd int) have a reference to the loop?

No write function?

I can only use evio to do echo...

ps: maybe be you can expose fd to user directly, no need to wrap by an id and map.

build error on mac

when I build on mac ,it warn undefined
./../evio_loop.go:269: undefined: sort.Slice
../../evio_net.go:332: undefined: sort.Slice

How to write UDP?

#13 shows how to use Wake to write something, but comment of Wake says:

Not available for UDP connections.

Like i receive a tcp connection, how can i write to another udp conn?

Drop-in

Is there a plan that features drop-in replacement for net package? For example to host a net/http server or similar?

To be honest I would like to see it in the standard library.

Saw this... but why not?

You would not want to use this framework if you need to handle long-running requests (milliseconds or more). For example, a web api that needs to connect to a mongo database, authenticate, and respond; just use the Go net/http package instead.

Why not? What will be the problem if I do so? Most request will be sub milliseconds but just one or two will be in the milliseconds... but I'm curious what will the problem be... can you elaborate?

Serve from a specific net.PacketConn

Hey there,

What do you think about adding a function that can serve events from a specified net.PacketConn or do you have a will to accept a PR with this feature?

Listen FD register twice?

For tcp server, evio uses net.Listen to start listen, then use netln.File() (evio_unix.go line 505) dup listen fd. Evio's epoll use the dup fd listen events. When call net.Listen, golang also register listen fd to netpoll, evio has not operation on this fd. So, the same file (fd not equal) register in seperate epoll.

My question is :

  1. why evio not use syscall.Socket to get listen fd.
fd, err := syscall.Socket(syscall.AF_INET, syscall.O_NONBLOCK|syscall.SOCK_STREAM, 0)
  1. do they have impact on server performance?
    As, file event will trigger two epoll.

Thanks.

About the Reactor pattern

hi Josh
Can you explain where the Reactor pattern is used in this project? I understand that the Reactor pattern and the Proactor pattern are implemented by the kernel, not the things that the application layer needs to worry about. Maybe I don't understand. I'm looking forward to your explanation.

Using splice system call for even better performance

HAProxy (and probably also Redis) is using the system call splice in Linux (http://man7.org/linux/man-pages/man2/splice.2.html) in order to copy data from one file descriptor to another.

This means that it efficiently implements proxying functionality, since it does not have to copy data from the kernel to user space and then back to the kernel.

The pattern of letting the callback evio.Events.Data always perform a copy in user space, will always be somewhat slower than what HAProxy can achieve, so is this something you have thought about for evio?

CPU consuming 100%

I'm not really sure why but after a few hours of running with minimum connection ( around 100 ). CPU usage starts growing up to 100%. here is profile.

(pprof) top5 -cum
Showing nodes accounting for 9.86hrs, 79.77% of 12.36hrs total
Dropped 348 nodes (cum <= 0.06hrs)
Showing top 5 nodes out of 23
      flat  flat%   sum%        cum   cum%
         0     0%     0%   12.02hrs 97.25%  github.com/tidwall/evio.loopRun
   0.17hrs  1.40%  1.40%   12.02hrs 97.25%  github.com/tidwall/evio/internal.(*Poll).Wait
   0.08hrs  0.61%  2.01%   11.30hrs 91.41%  syscall.EpollWait
   9.57hrs 77.45% 79.46%   11.22hrs 90.80%  syscall.Syscall6
   0.04hrs  0.31% 79.77%    0.88hrs  7.13%  runtime.entersyscall

Graph:
2020-01-03 10_19_50-Window

Do you have any ideas about why it happens?

Re-implementation of the go scheduler

The go goroutine scheduler already uses epoll/kqueue to decide when goroutines need to be scheduled.

Why is there a need to re-implement this, am I missing something?

Query

req.query = req.path[q+1 : i]

I found this code to be able to query the path

2019/01/22 15:25:40 req path:/
2019/01/22 15:25:40 req query:file=1.txt

req.Path = sdata[s:s+q]
req.Query = sdata[s+q+1:i]

build err

..\github.com\tidwall\evio\evio_other.go:25: undefined: servestdlib

Info structure in events.Opened is empty

Code:

events.Opened = func(id int, info evio.Info) (out []byte, opts evio.Options, action evio.Action) {
    log.Printf("opened: %d: %v\n", id, info.RemoteAddr)
    return
  }

Output:

opened: 1: <nil>

Conn wake write block

goroutine 8 [IO wait]:
internal/poll.runtime_pollWait(0x7faa33483bc8, 0x72, 0xffffffffffffffff)
C:/Go/src/runtime/netpoll.go:182 +0x56
internal/poll.(*pollDesc).wait(0xc0000ea218, 0x72, 0x1000, 0x1000, 0xffffffffffffffff)
C:/Go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
internal/poll.(*pollDesc).waitRead(...)
C:/Go/src/internal/poll/fd_poll_runtime.go:92
internal/poll.(*FD).Read(0xc0000ea200, 0xc0001e8000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
C:/Go/src/internal/poll/fd_unix.go:169 +0x19b
net.(*netFD).Read(0xc0000ea200, 0xc0001e8000, 0x1000, 0x1000, 0x0, 0xc006875938, 0x6ba3ea)
C:/Go/src/net/fd_unix.go:202 +0x4f
net.(*conn).Read(0xc00000e090, 0xc0001e8000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
C:/Go/src/net/net.go:177 +0x69
net/http.(*connReader).Read(0xc00007f890, 0xc0001e8000, 0x1000, 0x1000, 0x8bd6a8, 0xc006875a10, 0x3ae0042cdee)
C:/Go/src/net/http/server.go:787 +0x107
bufio.(*Reader).fill(0xc000060a20)
C:/Go/src/bufio/bufio.go:100 +0x10f
bufio.(*Reader).ReadSlice(0xc000060a20, 0xc006875a0a, 0x7faa32b2e098, 0xc006875b00, 0x40b7a9, 0xc015940400, 0x100)
C:/Go/src/bufio/bufio.go:356 +0x3d
bufio.(*Reader).ReadLine(0xc000060a20, 0xc006875b08, 0xc01c2a2a80, 0x7faa356f0d98, 0x0, 0x0, 0x89ebc0)
C:/Go/src/bufio/bufio.go:385 +0x34
net/textproto.(*Reader).readLineSlice(0xc01765f200, 0xc015940400, 0xc0000ea200, 0x0, 0x0, 0x42ba81)
C:/Go/src/net/textproto/reader.go:55 +0x6f
net/textproto.(*Reader).ReadLine(...)
C:/Go/src/net/textproto/reader.go:36
net/http.readRequest(0xc000060a20, 0x0, 0xc015940400, 0x0, 0x0)
C:/Go/src/net/http/request.go:968 +0x8d
net/http.(*conn).readRequest(0xc000099040, 0x959280, 0xc00005e840, 0x0, 0x0, 0x0)
C:/Go/src/net/http/server.go:967 +0x163
net/http.(*conn).serve(0xc000099040, 0x959280, 0xc00005e840)
C:/Go/src/net/http/server.go:1819 +0x6a8
created by net/http.(*Server).Serve
C:/Go/src/net/http/server.go:2884 +0x2f4

goroutine 24135 [syscall, 258 minutes]:
syscall.Syscall(0x1, 0xa, 0xc01090fdd8, 0x8, 0x1, 0xc01090fd58, 0xc01090fd58)
C:/Go/src/syscall/asm_linux_amd64.s:18 +0x5
syscall.write(0xa, 0xc01090fdd8, 0x8, 0x8, 0xc01090fde0, 0x8, 0x0)
C:/Go/src/syscall/zsyscall_linux_amd64.go:1005 +0x5a
syscall.Write(...)
C:/Go/src/syscall/syscall_unix.go:192
github.com/tidwall/evio/internal.(*Poll).Trigger(0xc0001054a0, 0x86d840, 0xc00012e630, 0x2, 0x2)
G:/gowork/pkg/mod/github.com/tidwall/[email protected]/internal/internal_linux.go:52 +0x3df
github.com/tidwall/evio.(*conn).Wake(0xc00012e630)
G:/gowork/pkg/mod/github.com/tidwall/[email protected]/evio_unix.go:49 +0x165
created by main.(*srv).tx
G:/gowork/src/gprs-server/server.go:231 +0x5d1

Multi-thread benchmark result(does evio support it)?

Hi,

I see the feature includes Fast single-threaded event loop, also, there is only benchmark result for a single-thread test.

Does this mean evio should run on single-thread for better performance? Is there such benchmark result in multi-thread environment?

For example, I have 4 cores and am listening on tcp://127.0.0.1:9876, then I have many guests to request this address. Does evio serve in every core with a event loop, or does it work on only one core? I'm not sure if I'm asking the right question because I'm actually not very familiar with go's concurrency control model, but I guess I make my point.

Thanks

nginx vs evio benchmark

Would love to see a performance benchmark between evio and nginx instead of go's native http/fashttp. Assuming evio is more performant than nginx, does evio have any native protection against DDoS attacks and similar attacks that more popular web servers protect against?

I think writing to the output buffer wrong in examples (http server/ redis server)

Hello. I start from simple example how to work with evio:

	events.Data = func(ec evio.Conn, in []byte) (out []byte, action evio.Action) {
		if in == nil {
			fmt.Printf("wake from %s\n", ec.RemoteAddr())
			return nil, evio.Close
		}
		c := ec.Context().(*conn)
		data := c.is.Begin(in)
		for {
			leftover, response, err := mcproto(data, b52)
			if err != nil {
				if err != ErrClose {
					// bad thing happened
					println(err.Error())
				}
				action = evio.Close
				break
			} else if len(leftover) == len(data) {
				// request not ready, yet
				break
			}
			// handle the request
			out = response//next response may overwrite previous buffer
			data = leftover
		}
		c.is.End(data)
		return
	}

But out buffer will be overrided in case of piplined requests.
I think we must write to output - like this:

                responses := make([]byte, 0)
		for {
			leftover, response, err := mcproto(data, b52)

			// handle the response
			responses = append(responses, response...)
			//out = response// don't do like this
			data = leftover
		}
		out = responses// do like this
		c.is.End(data)

You may find test for that case - here: https://github.com/recoilme/b52/blob/master/main_test.go#L19

I am not sure what's going on internally - but spent two weeks, for finding that problem)
May be you have this kind of problem in rediconn+evio+pipelined, too

Name clash

Hey, thanks for the cool project. The name is conflicting with Go experimental UI lib, though.
https://github.com/golang/exp/tree/master/shiny

I think in Go community name "shiny" is already strongly linked to this lib.

Opening this issue just to raise the concern as early as possible, because changing the name is easier at the early stage of project.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.