Trouble understanding Enet tutorial

Started by
16 comments, last by hplus0603 10 years, 6 months ago

Hi

I have read the Enet tutorial http://enet.bespin.org/Tutorial.html and I can't it figure out.

In the section Creating an ENet server the server is created without specifying an IP adress, instead ENET_HOST_ANY is used. I assume this is because the server does not need to know its own address, only the port it needs to recieve from.

But, in the Creating an ENet client section, a NULL is passed instead of an address... I thought the client needed to know where to send its packets?

Another concept I don't get is the client->server pair, is this bi-directional or not? Do I need a server and a client on both ends of the connection?

My understanding of an network API is that a player connects to a host, so that the client application will need to create a client object only and then plug in an IP and Port and start sending and recieving packets with the server.

I would like to learn how to use Enet becuase I think using UDP packets in Quake3 style is a way to go for RPG and FPS games.

Does anyone here have full examples of client code and server code (C++) I can use to understand Enet?

Cheers

Advertisement

ENET_HOST_ANY means that the server will accept incoming connections from any address. The other way would be to specify a specific IP, but then you'd only be able to receive traffic from that specific IP. So this address parameter is only used for specifying the address of allowed incoming connections.

A client won't be listening for new connections so it uses NULL in that case (it will only connect to other servers, it won't receive connections).

It's true that both server and client needs the ENetHost/enet_host_create() in order to function, I view this as the network system backend basically.

In addition to the ENetHost, the client also needs an ENetPeer as described in the Connecting to an ENet Host section at the bottom. In this section you specify the address and port to the server and then initiate the connection with enet_host_connect().

An ENetPeer is essentially a connection, the client has a single ENetPeer which is the connection to the server.

The server however receives a new ENetPeer in the ENET_EVENT_TYPE_CONNECT, i.e when a new client connects. So when this event happens, it makes sense to save the peer in a list (and somehow associate it with a player id in your game).

These ENetPeer structures are used as recipients/targets when sending packets.

The documention is a bit lacking and the way things are named makes it a bit confusing, but after you get used to it and have managed to get a decent framework around it the small and simple interface is really nice to work with!

@mikaelbauer

Super Sportmatchen - A retro multiplayer competitive sports game project!

So?

1. So one enet_host_create() for the server, and one enet_host_create() for the client. This will setup the endpoints.

2. Then the client will enet_host_connect() to the server

3. The server will recieve ENET_EVENT_TYPE_CONNECT event and act accordingly.

4. Now the connection is established and work in both directions?

I have a few more questions I hope you don't mind answering.

The function


enet_packet_create (const void *data, size_t dataLength, enet_uint32 flags)

Creates a packet that may be sent to a peer. The tutorial states "Optionally, initial data may be specified to copy into the packet." Does that mean that this function copy the data pointed to by void *data to its own allocated space, such that I can deallocate void *data after this function call?

Do these functions


enet_host_check_events (ENetHost *host, ENetEvent *event)
enet_host_service (ENetHost *host, ENetEvent *event, enet_uint32 timeout)

perform the same thing, only the latter blocks until timeout. Meaning it will act exactly the same if timeout = 0 is passed? So I should use the first if I don't want to block? Is it more optimized?

In this code


ENetEvent event;
    while (enet_host_service (client, & event, 1000) > 0)
    {
        switch (event.type)
        {
        case ENET_EVENT_TYPE_RECEIVE:
            printf ("A packet of length %u containing %s was received from %s on channel %u.\n",
                    event.packet -> dataLength,
                    event.packet -> data,
                    event.peer -> data,
                    event.channelID); 

The packet source is known from the .peer member, does the library retain the data internally?

When connecting to a host


/* Initiate the connection, allocating the two channels 0 and 1. */
ENetPeer *peer;
peer = enet_host_connect (client, & address, 2, 0); 
if (peer == NULL)
    {
       fprintf (stderr, 
                "No available peers for initiating an ENet connection.\n");
       exit (EXIT_FAILURE);
    }
The "peer" object/pointer, cannot possibly know if a connection has been established that fast? What happens here? Does this function block until it get a response? Is it necessary to keep this return value when I can always get it back when "event.type == ENET_EVENT_TYPE_CONNECT" ? Heck, why even get the return value when I get the peer anyway from "ENET_EVENT_TYPE_RECEIVE"?
From the website:
"ENet aggregates all protocol commands, including acknowledgements and packet transfer, into larger protocol packets to ensure the proper utilization of the connection and to limit the opportunities for packet loss that might otherwise result in further delivery latency."
Will ENet merge packets up until MTU? What if the last packet to be merged exceed MTU, can it be fragmented onto the next aggregated packet? (I know this is transparent to the programmer I just want to know).
Regarding Sequencing and Channels, If I were to send two packets on the same channel they would each get each get an unique sequencing number. And if the latest packet arrives first, then the other packet would be discarded when not in reliable mode. This is good for when you constantly send your character position. But what if I were to send two different data structs, one containing position and the other containing something else, that something else which may take slighly longer time will never get to the server. So, is it wise to have one packet type per channel?
Does the library start any threads? Does it have any internal buffers that store packets, or does it only get them from the OS when the library functions are called? I don't see any "Tick()" functions....
I read somewhere to get the ping I should write my own layer to send a timestamp to my peer who will echo it back, then I compare the timestamp packet with the current time to deterime ping, is this right or does have the library have this feature under the hood?
Finally, how do I get the DataRate(Kbps, Mbps etc) and can I get them per channel?

If anyone can please reply I would appreciate it.

Some additional questions :)

I am testing some code from this site http://neppramod.wordpress.com/2012/08/12/enet-tutorial

I got it working but something seems wrong. When the client executes "enet_host_connect(client, &address, 2, 0);" in the begining, nothing happens server side. Only after a data packet is sent will the server trigger the event.type == ENET_EVENT_TYPE_CONNECT... And since I am sending unreliable packets only the second data packet will reach the server.

Why won't the connection complete before the first packet is sent? Am I doing anything wrong?

Client:


#include <enet/enet.h>
#include <stdio.h>
#include <iostream>
#include <string.h>

#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib,"enet.lib")


int main(int argc, char **argv)
{
    ENetAddress address;
    ENetHost *client;
    ENetPeer *peer;
    char message[1024];
    ENetEvent netevent;
    int eventStatus;

    // a. Initialize enet
    if (enet_initialize() != 0) {
        fprintf(stderr, "An error occured while initializing ENet.\n");
        return EXIT_FAILURE;
    }

    atexit(enet_deinitialize);

    // b. Create a host using enet_host_create
    client = enet_host_create(NULL, 1, 2, 57600/8, 14400/8);

    if (client == NULL) {
        fprintf(stderr, "An error occured while trying to create an ENet server host\n");
        exit(EXIT_FAILURE);
    }

    enet_address_set_host(&address, "localhost");
    address.port = 1234;

    // c. Connect and user service
    peer = enet_host_connect(client, &address, 2, 0);

    if (peer == NULL) {
        fprintf(stderr, "No available peers for initializing an ENet connection");
        exit(EXIT_FAILURE);
    }

    eventStatus = 1;

    while (1) {
		message[0] = '\0';

		printf("Say > ");
        gets(message);

		
        if (strlen(message) > 0) {
            ENetPacket *packet = enet_packet_create(message, strlen(message) + 1, ENET_PACKET_FLAG_RELIABLE);
            enet_peer_send(peer, 0, packet);
        }

        while(enet_host_service(client, &netevent, 0)>0)
		{
        // If we had some event that interested us
            switch(netevent.type) {
                case ENET_EVENT_TYPE_CONNECT:
                    printf("(Client) We got a new connection from %x\n",
                            netevent.peer->address.host);
                    break;

                case ENET_EVENT_TYPE_RECEIVE:
                    printf("(Client) Message from server : %s\n", netevent.packet->data);

                    enet_packet_destroy(netevent.packet);
                    break;

                case ENET_EVENT_TYPE_DISCONNECT:
                    printf("(Client) %s disconnected.\n", netevent.peer->data);

                    // Reset client's information
                    netevent.peer->data = NULL;
                    break;
		}

       

    }
	}
}

Server:


#include <enet\enet.h>
#include <stdio.h>

#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib,"enet.lib")

int main(int argc, char **argv)
{
	ENetAddress address;
	ENetHost *server;
	ENetEvent netevent;
	int eventStatus;

	// a. Initialize enet
	if (enet_initialize() != 0) {
		fprintf(stderr, "An error occured while initializing ENet.\n");
		return EXIT_FAILURE;
	}

	atexit(enet_deinitialize);

	// b. Create a host using enet_host_create
	address.host = ENET_HOST_ANY;
	address.port = 1234;

	server = enet_host_create(&address, 32, 2, 0, 0);

	if (server == NULL) {
		fprintf(stderr, "An error occured while trying to create an ENet server host\n");
		exit(EXIT_FAILURE);
	}

	// c. Connect and user service
	eventStatus = 1;
	while(true){
		while (enet_host_service (server, & netevent, 1000) > 0)
		{
			switch (netevent.type)
			{
			case ENET_EVENT_TYPE_CONNECT:
				printf ("A new client connected from %x:%u.\n", 
					netevent.peer -> address.host,
					netevent.peer -> address.port);

				/* Store any relevant client information here. */
				netevent.peer -> data = "Client 1";

				break;

			case ENET_EVENT_TYPE_RECEIVE:
				printf ("%s > %s\n",
					netevent.peer -> data,
					netevent.packet -> data);

				enet_host_broadcast(server, 0, netevent.packet);
				/* Clean up the packet now that we're done using it. */
				//enet_packet_destroy (netevent.packet);

				break;

			case ENET_EVENT_TYPE_DISCONNECT:
				printf ("%s disconected.\n", netevent.peer -> data);

				/* Reset the peer's client information. */

				netevent.peer -> data = NULL;
			}
		}
	}

}

Also: enet_packet_destroy() server side crash the application on a second packet so I commented it out....

ENET_HOST_ANY means that the server will accept incoming connections from any address. The other way would be to specify a specific IP, but then you'd only be able to receive traffic from that specific IP. So this address parameter is only used for specifying the address of allowed incoming connections.

A client won't be listening for new connections so it uses NULL in that case (it will only connect to other servers, it won't receive connections).

What's interesting is that ENET_HOST_ANY has the same value as NULL - 0, so it actually doesn't matter I guess :P.

The function

enet_packet_create (const void *data, size_t dataLength, enet_uint32 flags)

Creates a packet that may be sent to a peer. The tutorial states "Optionally, initial data may be specified to copy into the packet." Does that mean that this function copy the data pointed to by void *data to its own allocated space, such that I can deallocate void *data after this function call?

I haven't used ENet in UDP way becouse I have been creating lobby but I guess that creating packets works similarily. So far packets that I sent in reliable mode were deleted on client side after I got acknowledgement from server. It's all made internally I guess and in UDP there's probably some kind of timeout and if you won't get acknowledgement in the specified time the packet is deleted.

When connecting to a host

/* Initiate the connection, allocating the two channels 0 and 1. */
ENetPeer *peer;
peer = enet_host_connect (client, & address, 2, 0); 
if (peer == NULL)
    {
       fprintf (stderr, 
                "No available peers for initiating an ENet connection.\n");
       exit (EXIT_FAILURE);
    }
The "peer" object/pointer, cannot possibly know if a connection has been established that fast? What happens here? Does this function block until it get a response? Is it necessary to keep this return value when I can always get it back when "event.type == ENET_EVENT_TYPE_CONNECT" ? Heck, why even get the return value when I get the peer anyway from "ENET_EVENT_TYPE_RECEIVE"?

Regarding the enet_host_connect:

The peer returned will have not completed the connection until enet_host_service() notifies of an ENET_EVENT_TYPE_CONNECT event for the peer.

I guess it only fills up ENetPeer struct with necessary data and in order to actually connect, you should use following code:


peer = enet_host_connect (client, & address, 2, 0); 
    if (peer == NULL)
    {
       fprintf (stderr, 
                "No available peers for initiating an ENet connection.\n");
       exit (EXIT_FAILURE);
    }
    if (peer == NULL)
    {
        //CreateMessageBox("Couldn't connect to server. Peer = NULL.");
        exit (EXIT_FAILURE);
    }
    if(enet_host_service (client, & netevent, 5000) > 0 && netevent.type == ENET_EVENT_TYPE_CONNECT)
    {
        //since now the connection is established on both ends of cnnection
        return true;
    }
    else
    {
        /* Either the 5 seconds are up or a disconnect event was */
        /* received. Reset the peer in the event the 5 seconds */
        /* had run out without any significant event. */
        enet_peer_reset (peer);
        peer = 0;
        //CreateMessageBox("Couldn't connect to server. Timeout.");
        return false;
    }
Also: enet_packet_destroy() server side crash the application on a second packet so I commented it out....

I had the same problem. You should check if your Packet pointer != NULL and only then use this function.

Hope this helps a bit!

Hm I'm afraid I can't answer most of your questions right now, they are probably more suited for the ENet developers. I haven't used ENet much in the last year and I've never checked through the source. I need to dig up some source code from old projects to get a better grip on how I used to do things. :)

@mikaelbauer

Super Sportmatchen - A retro multiplayer competitive sports game project!

I think the best thing you could do is to build Enet from source, and just debug/step into the source (or grep for the functions you're calling and read the source.)

The best way to learn how to program, is to learn how to use 'grep -ri' :-)

enum Bool { True, False, FileNotFound };
One thing that I can recommend btw is to use one channel for reliable msgs and at least one for non-reliable.

Some non-100%-sure answers:
Afaik Enet is a very thin layer on top of UDP, which means that youll have to do a lotofstuff yourself, such as calculating the data rate, ping, etc.

I think it maintains its own copies of the data.

About your example with lost packets when sent unreliably on the same channel, I guess this can/will happen evey now and then but in the general case they should both arrive in order. I wouldnt recommend a channel per msg type though, but it could be an idea to use different channels based on update rate. Ie you might send positions and rotations each frame on one channel, and send something like powerup status or time played updates every 100ms on a different channel.

This gotme excited to dev another mp game. :)

@mikaelbauer

Super Sportmatchen - A retro multiplayer competitive sports game project!

Some non-100%-sure answers:
Afaik Enet is a very thin layer on top of UDP, which means that youll have to do a lotofstuff yourself, such as calculating the data rate, ping, etc.

data rate & ping are already in, check ENetPeer.incomingBandwidth/outgoingBandwidth and ENetPeer.roundTripTime

This topic is closed to new replies.

Advertisement