[ENet-discuss] discerning disconnect reasons

Kevin Wasserman kwasserman at maddocsoftware.com
Tue Oct 5 11:52:50 PDT 2004


Nick Hodapp wrote:
>Is it possible to determine how a peer was disconnected?
That is, when
>net_host_service() raises a Disconnect event, I want to
know whether the
>peer disconnected on its own, whether it timed out, or
whether I
>disconnected it earlier with a call to
enet_peer_disconnect().

I had the same desire; it was not too difficult to 
implement.  I added the following enum to enet.h:

typedef enum
{
   ENET_DISCONNECT_ABNORMAL = 0, // disconnect due to
abnormal behaviour
   ENET_DISCONNECT_REMOTE   = 1, // 'normal' gentle
disconnect resulting from a remote call to
enet_peer_disconnect() 
   ENET_DISCONNECT_HARD     = 2, // 'hard' disconnect
resulting from peer's call to enet_peer_disconnect_now()
} ENetPeerDisconnectReason;

and to the ENetPeer struct I added (after the mtu, so the
size is unchanged assuming your structs are 4-byte aligned)

   enet_uint16   disconnectReason;         /**< when a peer
is disconnected, this is the reason; it is used to generate
the appropriate event before enet_peer_reset() is called*/

in ENetEventType I replaced ENET_EVENT_TYPE_DISCONNECT with
the following:

   /** a peer has disconnected.  This event is generated on
a successful 
     * completion of a disconnect initiated by a local call
to 
     * enet_peer_disconnect.  The peer field contains the
peer which 
     * disconnected, which will already have been reset.
     */
   ENET_EVENT_TYPE_LOCAL_DISCONNECT = 2,  

   /** remote peer disconnected via enet_peer_disconnect()
     */
   ENET_EVENT_TYPE_REMOTE_DISCONNECT   = 4,

   /** a remote peer has disconnected via
enet_peer_disconnect_now()
     */
   ENET_EVENT_TYPE_HARD_DISCONNECT     = 5,

   /** a peer has been disconnected due to a reliable packet
timeout
     */
   ENET_EVENT_TYPE_TIMEOUT_DISCONNECT  = 6,

   /** a peer has been disconnected due to abnormal
behaviour
     */
   ENET_EVENT_TYPE_ABNORMAL_DISCONNECT = 7,


in peer.c I initialize disconnectReason to
ENET_DISCONNECT_ABNORMAL in enet_peer_reset():

   peer -> disconnectReason = ENET_DISCONNECT_ABNORMAL;

there are numerous places in ENet where unexpected behaviour
by 
the peer results in the peer state being switched to ZOMBIE;
if 
this happens we will generate an abnormal disconnect event 
in enet_protocol_dispatch_incoming_commands() (see below).

in enet_peer_disconnect() I made the following changes:

    command.header.flags = 0;
    command.header.commandLength = sizeof
(ENetProtocolDisconnect);
    if (peer -> state == ENET_PEER_STATE_CONNECTED)
      command.header.flags |=
ENET_PROTOCOL_FLAG_ACKNOWLEDGE;

becomes
    	/* Ask for an ack even if we're not connected; it'll
just be thrown away in that case. */
	/* If we don't ask for an ack, the receiver will
flag it as a 'hard' disconnect a la
enet_peer_disconnect_now(). */
    command.header.flags = ENET_PROTOCOL_FLAG_ACKNOWLEDGE;
    command.header.commandLength = sizeof
(ENetProtocolDisconnect);



btw, while your in peer.c, there's a typo in
enet_peer_disconnect_now() that I should really mention in a
separate post:

    if (peer -> state != ENET_PEER_STATE_DISCONNECTED)
        return;

should be

    if (peer -> state == ENET_PEER_STATE_DISCONNECTED)
        return;

I generally only ever call enet_peer_disconnect_now() in my
exception handler.  
This way, when a peer gets a 'hard' disconnect, it knows the
other peer crashed.

in protocol.c I made the following change to
enet_protocol_dispatch_incoming_commands():

       if (currentPeer -> state == ENET_PEER_STATE_ZOMBIE)
       {
           host -> recalculateBandwidthLimits = 1;
           event -> type = ENET_EVENT_TYPE_DISCONNECT;

becomes

       if (currentPeer -> state == ENET_PEER_STATE_ZOMBIE)
       {
           // Disconnect zombies at this point, generating
the appropriate event
           host -> recalculateBandwidthLimits = 1;

           switch ( currentPeer->disconnectReason )
           {
           case ENET_DISCONNECT_ABNORMAL:
               event->type =
ENET_EVENT_TYPE_ABNORMAL_DISCONNECT;
               break;
           case ENET_DISCONNECT_REMOTE:
               event->type =
ENET_EVENT_TYPE_REMOTE_DISCONNECT;
               break;
           case ENET_DISCONNECT_HARD:
               event->type =
ENET_EVENT_TYPE_HARD_DISCONNECT;
               break;
           }

in enet_protocol_handle_disconnect():

    if (command -> header.flags &
ENET_PROTOCOL_FLAG_ACKNOWLEDGE)
        peer -> state =
ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT;
    else
        peer -> state = ENET_PEER_STATE_ZOMBIE;

becomes

    if (command -> header.flags &
ENET_PROTOCOL_FLAG_ACKNOWLEDGE)
        peer -> state =
ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT;
    else
    {
        peer -> disconnectReason = ENET_DISCONNECT_HARD;
        peer -> state = ENET_PEER_STATE_ZOMBIE;
    }

in enet_protocol_handle_acknowlegement():
    case ENET_PEER_STATE_DISCONNECTING:
       if (commandNumber !=
ENET_PROTOCOL_COMMAND_DISCONNECT)
         return 0;

       host -> recalculateBandwidthLimits = 1;
       event -> type = ENET_EVENT_TYPE_DISCONNECT;
       event -> peer = peer;
       enet_peer_reset (peer);
       return 1;
becomes
    case ENET_PEER_STATE_DISCONNECTING:
       if (commandNumber !=
ENET_PROTOCOL_COMMAND_DISCONNECT)
           return 0;

       host -> recalculateBandwidthLimits = 1;
       event -> type = ENET_EVENT_TYPE_LOCAL_DISCONNECT;
       event -> peer = peer;
       enet_peer_reset (peer);
       return 1;

in enet_protocol_send_acknowlegement():
       if (acknowledgement -> command.header.command ==
ENET_PROTOCOL_COMMAND_DISCONNECT)
         peer -> state = ENET_PEER_STATE_ZOMBIE;
becomes
       if (acknowledgement -> command.header.command ==
ENET_PROTOCOL_COMMAND_DISCONNECT)
	 {
           peer -> disconnectReason =
ENET_DISCONNECT_REMOTE;
           peer -> state = ENET_PEER_STATE_ZOMBIE;
	 }

and finally, in enet_protocol_check_timeouts():
       if (outgoingCommand -> roundTripTimeout >=
outgoingCommand -> roundTripTimeoutLimit)
       {
          event -> type = ENET_EVENT_TYPE_DISCONNECT;
          event -> peer = peer;
          enet_peer_reset (peer);
          return 1;
       }
becomes
       if (outgoingCommand -> roundTripTimeout >=
outgoingCommand -> roundTripTimeoutLimit)
       {
            event -> type =
ENET_EVENT_TYPE_TIMEOUT_DISCONNECT;
            event -> peer = peer;
            enet_peer_reset (peer);
            return 1;
       }

OK, so it was a little more involved than I remembered.  
I'm also not really happy with having five separate 
disconnect events; it would probably be better to
have a separate field in ENetEvent for the disconnect
reason or something.

-Kevin Wasserman




More information about the ENet-discuss mailing list