ENet Hello World

A server listens on a port; a client connects to it; both call EnetHost.service() regularly to move packets and receive events. The following is the whole of it, in one process for the sake of the example.

from acron.core import Datagram, DatagramIterator
from acron.enet import EnetHost, EnetEvent

server = EnetHost.create_server(5000, peer_count=32, channel_limit=2)
if server is None:
    raise RuntimeError("could not bind port 5000")

client = EnetHost.create_client(peer_count=4, channel_limit=2)
peer = client.connect("127.0.0.1", 5000)

def pump(host):
    event = host.service(0)
    while event is not None:
        handle(host, event)
        event = host.service(0)

def handle(host, event):
    if event.type == EnetEvent.T_connect:
        print(host, "connected to", event.peer.address)
        if host is client:
            dg = Datagram()
            dg.add_string("hello")
            dg.add_uint32(42)
            event.peer.send_datagram(0, dg)
    elif event.type == EnetEvent.T_receive:
        dg = event.get_datagram()
        it = DatagramIterator(dg)
        print(host, "received", it.get_string(), it.get_uint32(),
              "on channel", event.channel)
    elif event.type == EnetEvent.T_disconnect:
        print(host, "lost", event.peer.address)

# In a real program this is a task; here, a loop.
for _ in range(100):
    pump(server)
    pump(client)

Walking through it:

  • EnetHost.create_server() binds a UDP port. It returns None if the port cannot be bound, so a server should check and report. peer_count is the number of simultaneous connections it will accept; channel_limit the number of channels per connection.

  • EnetHost.create_client() makes a host that does not listen. Give it more than one peer slot even if it will only ever hold one connection: a connection that is being closed keeps its slot until the other side acknowledges, and a client with a single slot cannot reconnect until then.

  • EnetHost.connect() starts the handshake and returns the EnetPeer immediately. The connection exists once both hosts have produced a T_connect event for it; until then EnetPeer.connected is False.

  • EnetHost.service() sends what is queued, receives what has arrived, and returns one EnetEvent or None. Call it until it returns None every time you service the host; that is what keeps the connection alive.

  • Messages travel as bytes or as a Datagram (EnetPeer.send_datagram(), EnetEvent.get_datagram()), so the engine’s DatagramIterator reads them back.

In an application the two pump calls become one task each on the client and on the server:

def network_task(task):
    pump(client)
    return task.cont

taskMgr.add(network_task, "network")

Closing a connection is EnetPeer.disconnect(); the other side receives a T_disconnect event once it has been told. See The Service Loop for the variants and for what a robust loop looks like.