Jump to content

  • Log In with Google      Sign In   
  • Create Account


Trouble understanding Enet tutorial


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
17 replies to this topic

#1 Tispe   Members   -  Reputation: 878

Like
0Likes
Like

Posted 25 June 2013 - 06:46 AM

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



Sponsor:

#2 bauer   Members   -  Reputation: 288

Like
0Likes
Like

Posted 25 June 2013 - 01:09 PM

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! 


----------------------------
BauAir Studios - Music & Sfx
www.bauair.se

#3 Tispe   Members   -  Reputation: 878

Like
0Likes
Like

Posted 25 June 2013 - 04:23 PM

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?



#4 Tispe   Members   -  Reputation: 878

Like
0Likes
Like

Posted 26 June 2013 - 09:17 AM

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?

Edited by Tispe, 26 June 2013 - 09:23 AM.


#5 Tispe   Members   -  Reputation: 878

Like
0Likes
Like

Posted 27 June 2013 - 01:12 PM

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....



#6 savail   Members   -  Reputation: 314

Like
0Likes
Like

Posted 30 June 2013 - 03:56 AM

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!



#7 bauer   Members   -  Reputation: 288

Like
1Likes
Like

Posted 09 August 2013 - 06:17 AM

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. :)


----------------------------
BauAir Studios - Music & Sfx
www.bauair.se

#8 hplus0603   Moderators   -  Reputation: 4517

Like
1Likes
Like

Posted 09 August 2013 - 09:53 AM

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 };

#9 bauer   Members   -  Reputation: 288

Like
0Likes
Like

Posted 11 August 2013 - 05:17 AM

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. :)
----------------------------
BauAir Studios - Music & Sfx
www.bauair.se

#10 zfvesoljc   Members   -  Reputation: 393

Like
0Likes
Like

Posted 09 September 2013 - 06:59 AM

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



#11 Tispe   Members   -  Reputation: 878

Like
0Likes
Like

Posted 27 September 2013 - 02:37 AM

Apparently people report that they are not accurate.



#12 Tispe   Members   -  Reputation: 878

Like
0Likes
Like

Posted 02 October 2013 - 11:53 AM

Ok, I need some help. On the server side, the event->peer->data member gets overwritten such that all packets looks like they come from the same source. If I connect 3 clients to the server, all packets seems to come from the last ip:port connected!? I thought that Enet filled all event->peer fields during enet_host_service (server, &event, 1000)?

	//Main Loop
	bool loop = true;
	while(loop){
		ENetEvent event;
		/* Wait up to 1000 milliseconds for an event. */
		while (enet_host_service (server, &event, 1000) > 0){
			switch (event.type)    {    
			case ENET_EVENT_TYPE_CONNECT:        
				printf("A new client connected from %x:%u.\n", event.peer->address.host, event.peer->address.port);        
				/* Store any relevant client information here. */
				long long ipandport;
				char buffer[64];
				in_addr addr;
				addr.S_un.S_addr = event.peer->address.host;
				sprintf_s(buffer, "%s:%d", inet_ntoa(addr), event.peer->address.port);
				event.peer->data = buffer;	//Overwritten for all peers????

				ipandport = *((long long*) (&event.peer->address));
				cout << "Registered user: " << ipandport << endl;
				break;    
			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,	//All packets are from same IP:Port???
					event.channelID);        
				/* Clean up the packet now that we're done using it. */
				if(event.packet != NULL){
					enet_packet_destroy (event.packet);
				}
				break;           
			case ENET_EVENT_TYPE_DISCONNECT:        
				printf("%s disconected.\n", event.peer->data);        
				/* Reset the peer's client information. */        
				event.peer -> data = NULL;    
			}
		}
	}

Edited by Tispe, 02 October 2013 - 11:56 AM.


#13 hplus0603   Moderators   -  Reputation: 4517

Like
0Likes
Like

Posted 03 October 2013 - 09:22 AM

Your problem is that you are assigning the "data" member a value that is a pointer to a buffer on the stack.


enum Bool { True, False, FileNotFound };

#14 Tispe   Members   -  Reputation: 878

Like
0Likes
Like

Posted 03 October 2013 - 11:12 AM

Right. The tutorial is very misleading here. It sets event.peer->data = "Client information";. Where this string is constant inside the binary...

I am trying to use a std::shared_ptr and attach it to event.peer->data but I have some difficulties. Mainly casting between void* and

std::shared_pr<mystruct>. Got any input?

 

I want to stay away from raw pointers, don't want to risk leaks if a disconnect does not get recieved such that the delete operator does not get called or something buggy like that.

case ENET_EVENT_TYPE_CONNECT:
	{
		char buffer[64];
		in_addr addr;
		long long ipandport = (netevent.peer->address.host << 16) | netevent.peer->address.port;		//Unique ID
		std::shared_ptr<PeerData> spNewPeer(new PeerData);
		spNewPeer->ipandport = ipandport;
		spNewPeer->peer = netevent.peer;
		addr.S_un.S_addr = netevent.peer->address.host;
		sprintf_s(buffer, "%s:%d", inet_ntoa(addr), netevent.peer->address.port);
		printf("A new client connected from %s.\n", buffer);
		spNewPeer->sipandport = string(buffer);
		netevent.peer->data = &(*spNewPeer);						//assigning, might be bad....
		cout << "Registered user: " << ipandport << endl;
		break;
	}
case ENET_EVENT_TYPE_RECEIVE:
	{
		std::shared_ptr<PeerData> spPeer(netevent.peer->data);
		cout << "A packet of length " << netevent.packet->dataLength 
			<< " containing " << netevent.packet->data 
			<< " was received from " << spPeer->sipandport			//causes compile errors, cannot convert parameter 1 from 'void *' to 'PeerData *'
			<< " on channel" << netevent.channelID << "." << endl;

		if(netevent.packet != NULL){
			enet_packet_destroy (netevent.packet);
		}
		break;
	}

Edited by Tispe, 03 October 2013 - 11:16 AM.


#15 zfvesoljc   Members   -  Reputation: 393

Like
0Likes
Like

Posted 04 October 2013 - 02:02 AM

In the end, you will probably want to have the network code completely independent of the game code - logically (game code) and technically (update rate ie.). I'd just take the data pointer from packet, copy the data into your structure and release the event. Then pass your structure into "higher levels" (session, game).



#16 hplus0603   Moderators   -  Reputation: 4517

Like
2Likes
Like

Posted 04 October 2013 - 10:55 AM

The tutorial is very misleading here. It sets event.peer->data = "Client information";. Where this string is constant inside the binary...
I am trying to use a std::shared_ptr and attach it to event.peer->data but I have some difficulties. Mainly casting between void* and
std::shared_pr<mystruct>. Got any input?


I don't think the tutorial is misleading at all. The semantics of pointsrs and the lifetime of the data they point to is very well known to C and C++ programmers and a fundamental concept of the language.

A void* is not a shared pointer, and cannot be a shared pointer. Sizeof(shared_ptr) is larger than sizeof(void*).

I suggest you make the "data" point to some object you allocate yourself, and when you are notified about the peer object going away, properly delete that object.
enum Bool { True, False, FileNotFound };

#17 Tispe   Members   -  Reputation: 878

Like
0Likes
Like

Posted 08 October 2013 - 01:44 PM

Thanks all for the input. I have encapsulated Enet in my own working class with this interface.

	bool Connect(char* Name, char* HostName, unsigned short port);
	void Disconnect();
	DWORD GetStatus();
	bool Tick(ENetEvent* RecvPacket);
	void Send(EnetBaseMessage *message);
	void FlushPackets();
	const char* GetUserName();
	ENetPeer* GetPeer();

Tick() returns true if there is a packet, this is how I "while(tick(&packet))" the incoming packets from. I can also inject to the packet information such as "disconnected".

 

The ping info inside the peer is 15ms higher then I get from normal pinging the server. The incomingDataTotal and outgoingDataTotal makes no sense.


Edited by Tispe, 08 October 2013 - 01:50 PM.


#18 hplus0603   Moderators   -  Reputation: 4517

Like
1Likes
Like

Posted 09 October 2013 - 10:16 AM

The ping info inside the peer is 15ms higher then I get from normal pinging the server


That probably reflects the difference between just sending ICMP packets that are returned by the kernel, and measuring actual application messaging latency that involves the entire messaging stack.
enum Bool { True, False, FileNotFound };




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS