Added Packet-loss prevention code for Netgames

This commit is contained in:
zicodxx 2009-01-31 23:47:15 +00:00
parent 28b1ccbb52
commit 4d59723cc2
10 changed files with 323 additions and 9 deletions

View file

@ -1,5 +1,10 @@
D2X-Rebirth Changelog
20090201
--------
main/netdrv_udp.c: Fixing bug with incorrect/missing UDP port termination
main/network.c, main/network.h, main/multi.h, main/netdrv_udp.c, main/netdrv_udp.h, main/noloss.c, main/noloss.h, SConstruct: Added Packet-loss prevention code for Netgames
20090130
--------
arch/ogl/ogl.c, main/render.c: New level render order for better seperation and blending between transculent level geometry and sprites

View file

@ -141,6 +141,7 @@ common_sources = [
'main/network.c',
'main/newdemo.c',
'main/newmenu.c',
'main/noloss.c',
'main/object.c',
'main/paging.c',
'main/physics.c',

View file

@ -2708,7 +2708,7 @@ multi_send_remobj(int objnum)
multibuf[3] = obj_owner;
multi_send_data(multibuf, 4, 0);
multi_send_data(multibuf, 4, 1);
if (Network_send_objects && network_objnum_is_past(objnum))
{

View file

@ -35,7 +35,7 @@ COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
#else
#define MULTI_PROTO_VERSION 4
#endif
#define MULTI_PROTO_D2X_VER 6 // Increment everytime we change networking features
#define MULTI_PROTO_D2X_VER 7 // Increment everytime we change networking features - based on ubyte, must never be > 255
// Protocol versions:
// 1 Descent Shareware

View file

@ -271,6 +271,16 @@ void udp_packet_relay(char *text, int len, struct _sockaddr *sAddr)
// Never relay PING packets
if (text[0] == PID_PING_SEND || text[0] == PID_PING_RETURN)
return;
// We got an PID_PDATA_ACK packet - check if it is for me (host). If not, transfer it to designated client if sender or receiver is relay client.
if (text[0] == PID_PDATA_ACK)
{
if ((int)text[2] != Player_num && (UDP_Peers[NetPlayers.players[(int)text[1]].network.ipx.node[0]].relay || UDP_Peers[NetPlayers.players[(int)text[2]].network.ipx.node[0]].relay))
{
sendto (UDP_sock, text, len, 0, (struct sockaddr *) &UDP_Peers[NetPlayers.players[(int)text[2]].network.ipx.node[0]].addr, sizeof(struct _sockaddr));
}
return;
}
// Check if sender is a relay client and store ID if so
for (i = 1; i < MAX_CONNECTIONS; i++)
@ -567,7 +577,14 @@ static int udp_send_packet(socket_t *unused, IPXPacket_t *IPXHeader, ubyte *text
return 0;
if (!UDP_Peers[IPXHeader->Destination.Node[0]].valid)
return 0;
{
// PID_PDATA_ACK needs to be delivered to designated peer. If there's a relay player, Host cannot relay, as PID_PDATA_ACK is sent specifically.
// So in this case, if we encounter invalid ID while PID_PDATA_ACK, wrap this packet to Host so it can be relayed.
if (text[0] == PID_PDATA_ACK)
return sendto (UDP_sock, text, len, 0, (struct sockaddr *) &UDP_Peers[0].addr, sizeof(struct _sockaddr));
else
return 0;
}
return sendto (UDP_sock, text, len, 0, (struct sockaddr *) &UDP_Peers[IPXHeader->Destination.Node[0]].addr, sizeof(struct _sockaddr));
}

View file

@ -38,7 +38,7 @@
#endif
#define DXXcfgid "D2Rc" // identification string for UDP/IP configuration packets
#define MAX_CONNECTIONS 32 // maximum connections that can be stored in UDPPeers
#define MAX_CONNECTIONS 32 // maximum connections that can be stored in UDPPeers - as this currently bases on player node[0] this must NOT exceed ubyte!
#define LEN_SERVERNAME 41
#define LEN_PORT 6
#define UDP_BASEPORT 31017
@ -54,11 +54,11 @@
typedef struct peer_list
{
struct _sockaddr addr; // real address information about this peer
int valid; // 1 = client connected / 2 = client ready for handshaking / 3 = client done with handshake and fully joined
int valid; // 1 = client connected / 2 = client ready for handshaking / 3 = client done with handshake and fully joined / 0 between clients = no connection -> relay
fix timestamp; // time of received packet - used for timeout
char hs_list[MAX_CONNECTIONS]; // list to store all handshake results from clients assigned to this peer
int hstimeout; // counts the number of tries the client tried to connect - if reached 10, client put to relay if allowed
int relay; // relay packets by this clients over host
int relay; // relay packets by/to this clients over host
} __pack__ peer_list;
extern sequence_packet My_Seq;

View file

@ -69,6 +69,7 @@ COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
#include "gamefont.h"
#include "songs.h"
#include "netdrv.h"
#include "noloss.h"
// MWA -- these structures are aligned -- please save me sanity and
// headaches by keeping alignment if these are changed!!!! Contact
@ -1955,11 +1956,16 @@ void network_process_packet(ubyte *data, int length )
network_process_naked_pdata((char *)data,length);
break;
case PID_PDATA_ACK:
if (Network_status == NETSTAT_PLAYING)
noloss_got_ack(data);
break;
case PID_OBJECT_DATA:
if (Network_status == NETSTAT_WAITING)
network_read_object_packet(data);
break;
case PID_ENDLEVEL:
if ((Network_status == NETSTAT_ENDLEVEL) || (Network_status == NETSTAT_PLAYING))
network_read_endlevel_packet(data);
@ -3094,6 +3100,8 @@ network_start_game()
d_srand( timer_get_fixed_seconds() );
Netgame.Security=d_rand(); // For syncing Netgames with player packets
noloss_init_queue();
if(network_select_players())
{
@ -3409,6 +3417,7 @@ network_level_sync(void)
MySyncPackInitialized = 0;
network_flush(); // Flush any old packets
noloss_init_queue();
if (N_players == 0)
result = network_wait_for_sync();
@ -3524,8 +3533,10 @@ int network_do_join_game(int choice)
network_set_game_mode(Netgame.gamemode);
StartNewLevel(Netgame.levelnum, 0);
noloss_init_queue();
StartNewLevel(Netgame.levelnum, 0);
return 1; // look ma, we're in a game!!!
}
@ -4025,9 +4036,9 @@ void network_do_frame(int force, int listen)
// Send out packet PacksPerSec times per second maximum... unless they fire, then send more often...
if ( (last_send_time>F1_0/Netgame.PacketsPerSec) || (Network_laser_fired) || force || PacketUrgent )
{
int to_queue = (Network_laser_fired || force || PacketUrgent);
if ( Players[Player_num].connected ) {
int objnum = Players[Player_num].objnum;
PacketUrgent = 0;
if (listen) {
multi_send_robot_frame(0);
@ -4097,8 +4108,14 @@ void network_do_frame(int force, int listen)
}
}
}
noloss_process_queue(); // Check queued packets, remove or re-send if necessery
noloss_add_packet_to_queue(to_queue, MySyncPack.numpackets, MySyncPack.data, MySyncPack.data_size); // add the current packet to queue
PacketUrgent = 0;
MySyncPack.data_size = 0; // Start data over at 0 length.
if (Control_center_destroyed)
{
if (Player_is_dead)
@ -4204,6 +4221,8 @@ void network_read_pdata_packet(ubyte *data )
TheirPlayernum = pd->playernum;
TheirObjnum = Players[pd->playernum].objnum;
noloss_send_ack(pd->numpackets, pd->playernum);
if (TheirPlayernum < 0) {
Int3(); // This packet is bogus!!
return;
@ -4359,6 +4378,8 @@ void network_read_pdata_short_packet(short_frame_info *pd )
TheirPlayernum = new_pd.playernum;
TheirObjnum = Players[new_pd.playernum].objnum;
noloss_send_ack(new_pd.numpackets, new_pd.playernum);
if (TheirPlayernum < 0) {
Int3(); // This packet is bogus!!

View file

@ -74,6 +74,7 @@ COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
// new packet types to get a little bit more information about the netgame so we can show up some rules/flags - uses netgame_info instead of lite_info
#define PID_LITE_INFO_D2X 65 // like PID_LITE_INFO
#define PID_GAME_LIST_D2X 66 // like PID_GAME_LIST
#define PID_PDATA_ACK 75 // ACK message for a PDATA packet
#define NETGAME_ANARCHY 0
#define NETGAME_TEAM_ANARCHY 1

227
main/noloss.c Normal file
View file

@ -0,0 +1,227 @@
/*
* Packet-loss-prevention code for DXX-Rebirth
* This code will save the data field of a netgame packet to a queue.
* Saving is only done for urgent packets.
* Each PDATA packet will be ACK'd by the receiver.
* If all receivers submitted their ACK packets to initial sender, the packet is removed from the queue.
* If a player has not ACK'd a PDATA packet within a second, it will resend to him.
* Timeout, disconnects, or changes in player count are handled as well.
*/
#include "noloss.h"
struct pdata_noloss_store noloss_queue[NOLOSS_QUEUE_SIZE];
extern frame_info MySyncPack;
extern int MaxXDataSize;
extern int N_players;
// Adds a packet to our queue
// Should be called when an IMPORTANT frameinfo packet is created
void noloss_add_packet_to_queue(int urgent, int pkt_num, char *data, ushort data_size)
{
int i;
// Only proceed if this is a version-checked game
if (Netgame.protocol_version != MULTI_PROTO_D2X_VER)
return;
// Only add urgent packets
if (!urgent)
return;
for (i = 0; i < NOLOSS_QUEUE_SIZE; i++)
{
if (noloss_queue[i].used)
continue;
noloss_queue[i].used = 1;
noloss_queue[i].pkt_initial_timestamp = GameTime;
noloss_queue[i].pkt_timestamp = GameTime;
noloss_queue[i].pkt_num = pkt_num;
memset( &noloss_queue[i].player_ack, 0, sizeof(ubyte)*MAX_PLAYERS );
noloss_queue[i].N_players = N_players;
memcpy( &noloss_queue[i].data[0], data, data_size );
noloss_queue[i].data_size = data_size;
return;
}
con_printf(CON_DEBUG, "Noloss queue is full!\n");
}
// Send the packet stored in queue list at index to given receiver_pnum
// Called from inside noloss_process_queue()
void noloss_send_queued_packet(int queue_index)
{
short_frame_info ShortSyncPack;
int objnum = Players[Player_num].objnum;
// Update Timestamp
noloss_queue[queue_index].pkt_timestamp = GameTime;
// Copy the multibuf data to MySyncPack
memcpy( &MySyncPack.data[0],&noloss_queue[queue_index].data[0], noloss_queue[queue_index].data_size );
MySyncPack.data_size = noloss_queue[queue_index].data_size;
// The following code HEAVILY borrows from network_do_frame()
// Create a frameinfo packet
if (Netgame.ShortPackets)
{
#ifdef WORDS_BIGENDIAN
ubyte send_data[MAX_DATA_SIZE];
#endif
int i;
memset(&ShortSyncPack,0,sizeof(short_frame_info));
create_shortpos(&ShortSyncPack.thepos, Objects+objnum, 0);
ShortSyncPack.type = PID_PDATA;
ShortSyncPack.playernum = Player_num;
ShortSyncPack.obj_render_type = Objects[objnum].render_type;
ShortSyncPack.level_num = Current_level_num;
ShortSyncPack.data_size = MySyncPack.data_size;
memcpy (&ShortSyncPack.data[0],&MySyncPack.data[0],MySyncPack.data_size);
MySyncPack.numpackets = INTEL_INT(noloss_queue[queue_index].pkt_num);
ShortSyncPack.numpackets = MySyncPack.numpackets;
#ifndef WORDS_BIGENDIAN
for(i=0; i<N_players; i++)
{
// Check if a player is not connected anymore so we won't send a packet and set positive
if (!noloss_queue[queue_index].player_ack[i] && !Players[i].connected)
noloss_queue[queue_index].player_ack[i] = 1;
if(Players[i].connected && (i != Player_num))
netdrv_send_packet_data((ubyte*)&ShortSyncPack, sizeof(short_frame_info) - MaxXDataSize + MySyncPack.data_size, NetPlayers.players[i].network.ipx.server, NetPlayers.players[i].network.ipx.node,Players[i].net_address);
}
#else
squish_short_frame_info(ShortSyncPack, send_data);
for(i=0; i<N_players; i++) {
// Check if a player is not connected anymore so we won't send a packet and set positive
if (!noloss_queue[queue_index].player_ack[i] && !Players[i].connected)
noloss_queue[queue_index].player_ack[i] = 1;
if(!noloss_queue[queue_index].player_ack[i] && Players[i].connected && (i != Player_num))
netdrv_send_packet_data((ubyte*)send_data, IPX_SHORT_INFO_SIZE-MaxXDataSize+MySyncPack.data_size, NetPlayers.players[i].network.ipx.server, NetPlayers.players[i].network.ipx.node,Players[i].net_address);
}
#endif
}
else // If long packets
{
int i;
MySyncPack.numpackets = noloss_queue[queue_index].pkt_num;
MySyncPack.type = PID_PDATA;
MySyncPack.playernum = Player_num;
MySyncPack.obj_render_type = Objects[objnum].render_type;
MySyncPack.level_num = Current_level_num;
MySyncPack.obj_segnum = Objects[objnum].segnum;
MySyncPack.obj_pos = Objects[objnum].pos;
MySyncPack.obj_orient = Objects[objnum].orient;
MySyncPack.phys_velocity = Objects[objnum].mtype.phys_info.velocity;
MySyncPack.phys_rotvel = Objects[objnum].mtype.phys_info.rotvel;
for(i=0; i<N_players; i++)
{
// Check if a player is not connected anymore so we won't send a packet and set positive
if (!noloss_queue[queue_index].player_ack[i] && !Players[i].connected)
noloss_queue[queue_index].player_ack[i] = 1;
if(!noloss_queue[queue_index].player_ack[i] && Players[i].connected && (i != Player_num))
{
send_frameinfo_packet(&MySyncPack, NetPlayers.players[i].network.ipx.server, NetPlayers.players[i].network.ipx.node,Players[i].net_address);
}
}
}
MySyncPack.data_size = 0; // Start data over at 0 length.
}
// We have received a PDATA packet. Send ACK response to sender!
// ACK packet needs to contain: packet num, sender player num, receiver player num
// Call in network_process_packet() at case PID_PDATA
void noloss_send_ack(int pkt_num, ubyte receiver_pnum)
{
noloss_ack ack;
memset(&ack,0,sizeof(noloss_ack));
ack.type = PID_PDATA_ACK;
ack.sender_pnum = Player_num;
ack.receiver_pnum = receiver_pnum;
ack.pkt_num = pkt_num;
netdrv_send_packet_data( (ubyte *)&ack, sizeof(noloss_ack), NetPlayers.players[receiver_pnum].network.ipx.server, NetPlayers.players[receiver_pnum].network.ipx.node,Players[receiver_pnum].net_address );
}
// We got an ACK by a player. Set this player slot to positive!
void noloss_got_ack(ubyte *data)
{
int i;
noloss_ack *gotack = (noloss_ack *)data;
if (gotack->receiver_pnum != Player_num)
return;
for (i = 0; i < NOLOSS_QUEUE_SIZE; i++)
{
if (gotack->pkt_num == noloss_queue[i].pkt_num)
{
noloss_queue[i].player_ack[gotack->sender_pnum] = 1;
break;
}
}
}
// Init/Free the queue. Call at start and end of a game or level.
void noloss_init_queue(void)
{
memset(&noloss_queue,0,sizeof(pdata_noloss_store)*NOLOSS_QUEUE_SIZE);
}
// The main queue-process function.
// 1) Check if we can remove a packet from queue (all players positive or packet too old)
// 2) Check if there are packets in queue which we need to re-send to player(s) (if packet is older than one second)
void noloss_process_queue(void)
{
int i;
for (i = 0; i < NOLOSS_QUEUE_SIZE; i++)
{
int j, resend = 0;
if (!noloss_queue[i].used)
continue;
// If N_players differs (because of disconnet), we resend to all to make sure we we get the right ACK packets for the right players
if (N_players != noloss_queue[i].N_players)
{
memset( noloss_queue[i].player_ack, 0, sizeof(ubyte)*8 );
noloss_queue[i].N_players = N_players;
resend = 1;
}
else
{
// Check if at least one connected player has not ACK'd the packet
for (j = 0; j < N_players; j++)
{
if (!noloss_queue[i].player_ack[j] && Players[j].connected && j != Player_num)
{
resend = 1;
}
}
}
// Check if we can remove a packet...
if (!resend ||
((noloss_queue[i].pkt_initial_timestamp + (F1_0*15) <= GameTime) || (GameTime < noloss_queue[i].pkt_initial_timestamp)))
{
memset(&noloss_queue[i],0,sizeof(pdata_noloss_store));
}
// ... otherwise resend if a second has passed
else if ((noloss_queue[i].pkt_timestamp + F1_0 <= GameTime) || (GameTime < noloss_queue[i].pkt_timestamp))
{
con_printf(CON_DEBUG, "Re-Sending queued packet %i\n",i);
noloss_send_queued_packet(i);
}
}
}

42
main/noloss.h Normal file
View file

@ -0,0 +1,42 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "strutil.h"
#include "pstypes.h"
#include "netpkt.h"
#include "network.h"
#include "game.h"
#include "multi.h"
#include "netpkt.h"
#include "netdrv.h"
#include "byteswap.h"
#define NOLOSS_QUEUE_SIZE 512 // Store up to 512 packets
// the structure list keeping the data we may want to resend
// this does only contain the extra data field of a PDATA packet (if that isn't enough, the whole PDATA struct info still could be added later)
typedef struct pdata_noloss_store {
int used;
fix pkt_initial_timestamp; // initial timestamp to see if packet is outdated
fix pkt_timestamp; // Packet timestamp
int pkt_num; // Packet number
ubyte player_ack[MAX_PLAYERS]; // 0 if player has not ACK'd this packet, 1 if ACK'd or not connected
int N_players; // Save N_players, too - so if a player disconnects we will resend to make sure all acks are related to the right players
char data[NET_XDATA_SIZE]; // extra data of a packet - contains all multibuf data we don't want to loose
ushort data_size;
} __pack__ pdata_noloss_store;
// ACK signal packet
typedef struct noloss_ack {
ubyte type;
ubyte sender_pnum;
ubyte receiver_pnum;
int pkt_num;
} __pack__ noloss_ack;
void noloss_add_packet_to_queue(int urgent, int pkt_num, char *data, ushort data_size);
void noloss_send_ack(int pkt_num, ubyte receiver_pnum);
void noloss_got_ack(ubyte *data);
void noloss_init_queue(void);
void noloss_process_queue(void);