Skip to content

A Go (golang) library for managing highly concurrent independent network streams.

License

Notifications You must be signed in to change notification settings

zenhotels/astranet

Repository files navigation

Build Status

AstraNet

astranet is a package for managing highly concurrent independent network streams.

How did we get here

While dealing with everyday issues we have approached a situation when we need a huge amount of simultaneous independent data streams between two group of machines. The number was much higher than the standard TCP stack allows to have, even with sys limits tweaked in the right way.

We have checked every existing piece of software we could find to handle this issue, however they all seemed to be insufficient in some way; at the end it has been concluded that there is nothing to choose from, so we decided to roll our own.

So, astranet has been created. It does have some extra features as well:

  • Millions of independent data streams between two host machines using one tcp connection only;
  • Keep-alive frames to be sure the connections are alive;
  • NAT traversal capabilities for connecting any two machines without direct route between them using a trusted relay;
  • An embedded service discovery system;

As much as possible, the astranet package strives to look and feel just like the standard library's net package. The only thing you'd need to do is setup an astranet instance like this:

var astraNet = astranet.New()

An instance conforms this interface:

type AstraNet interface {
    Dial(network string, hp string) (net.Conn, error)
    DialTimeout(network string, hp string, t time.Duration) (net.Conn, error)
    Bind(network string, hp string) (net.Listener, error)

    Attach(conn io.ReadWriter)
    ListenAndServe(network, address string) error
    Join(network, address string) error
    Services() []ServiceId
    Routes() []ServiceId
    ServiceMap() *RegistryStorage
    RoutesMap() *RegistryStorage

    WithEnv(env ...string) AstraNet
    WithLoopBack() AstraNet
    Client() AstraNet
    Server() AstraNet
    New() AstraNet

    HttpDial(net, host string) (net.Conn, error)
}

Client

Here's how you'd initiate a new client connection to some known astranet service:

conn, err := astraNet.Dial("", "astranet_host:astranet_port")

Where astranet_host along with astranet_port are both "virtual" and are valid only inside an astranet network.

Or as a more convenient way, the same using the embedded service discovery system:

conn, err := astraNet.Dial("", "service_name")

Where service_name is the name a service used while registering on the network's registry.

Server

To bind a listener socket as a server:

l, err := astraNet.Bind("", ":astranet_port"))

Where astranet_port is a virtual port, but the behaviour is like it was a TCP listener.

Or using the embedded service discovery system:

l, err := astraNet.Bind("", "service_name"))

Where service_name is the name that will be used to register on a network's registry, so clients and other servers could do service lookups.

You can also use both service discovery and port systems together:

l, err := astraNet.Bind("", "service_name:astranet_port"))

Accepting streams

The listener returned is pretty much like the net.Listener one:

for {
    conn, err := l.Accept()
    go handleSession(conn)
}

You usually accept streams opened by the remote side as you would do with the regular net package:

stream, err := service.Accept()

Streams satisfy the net.Conn interface, so they're very familiar to work with:

n, err = stream.Write(buf)
n, err = stream.Read(buf)

buf := new(bytes.Buffer)
io.Copy(buf, stream)

Basics

To understand astranet better you should think of it as a P2P network with a flat namespace and forget about physical TCP routing stuff.

Each astranet node receives a random uint64 id that acts as a hostname. Each listener socket that binds to the astranet receives an uint32 port for itself, that's pretty much like TCP.

You should establish a connection between two astraNet instances to make them visible to each other:

var astraNet1 = astraNet.New()
var astraNet2 = astraNet.New()

astraNet1.ListenAndServe("tcp4", ":10000") // Wait for incoming astranet links on port 10000
astraNet2.Join("tcp4", "127.0.0.1:10000")  // Join the remote astranet instance
// Gz! We are connected now

In fact, an astranet instance can attach itself to any managed connection that supports the same capabilities as TCP (ordering, retransmissions, etc), or it could be just a loopback. Internally (in ListenAndServe) astranet manages TCP connections itself and does Attach. There is an example of a loopback connection:

var client, server IOLoop
server.Reader, client.Writer = io.Pipe()
client.Reader, server.Writer = io.Pipe()

// Use IOLoops as a pipe that conforms io.ReadWriter on the both ends
go astraNet1.Attach(server)
go astraNet2.Attach(client)