Peer to Peer networking

Started by
28 comments, last by nivlekio 16 years, 1 month ago
Not sure if im being stupid here sorry about that if i am and sorry if we are going in circles.

But there wont be a centralized server, it will be peers, looking for other unknown peers to connect to. So say three people run the game ( each game application wont know the other two users ip numbers) and they click on "play lan game" it will run code which will accpet on coming connections (if a peer frind them) and code to look for other unkown players( so if they find the other two players before they are found they will connect to them).

Once connected they will be able to talk to each other, see if some one has hosted a game, and if some one has hosted a they will be able to see who is in that game.

Say if a 4th person runs the game and clicks on "play lan game" they will do also do the same searching proccess and connect to the other players.

The reason why im coding the lan lobby like that is because I have seen games (starcraft, C&C Generals, WarCraft wtc) where you click on "play lan game" then you enter a lobby and you can see other people who are in this lobby and see if any one has hosted a game.

Is that not how they go about coding such a situation?

You can't have a centrlized server in this case (so i think) because every network has a diffrent ip formats and pool sizes from each other. One might be from 192.168.1.66 to 192.168.1.125 and another like 192.168.1.1 to 192.168.1.255. And seeing that no two networks have the same ip format the central server will have keep changing its ip and how will the other apllications know what it is if it has to keep on compansating on diffrent lan networks?

Also you will require one machine be a cental server is this not a bit pointless in th lan case becuase say if you have two people who want to play agaisnt each other on lan you will need a 3rd machine to be a central server, what happens if there is not 3rd machine?

Once again im trying to look for unknown peers who are also running the application and once found they are connected to each other. Im just stuck on that bit i pretty much have an idea on what to do after searching and connecting peers on the lan lobby.

Quote:
Q21) and Q24) in the Forum FAQ answer your question.


I have looked at those questions but they dont really help in my situation I think. Ill be using TCP/IP so i wont be able to do the UDP broadcasting and there will be no Central/local/master server it will be just peers looking for other peers to connect to form a peer to peer network with out a server just in the lobby stage.

Quote:
Lobby works like this:

1) centralized server to act as matchmaker
2) client hosts game
3) in hosting a game, the engine contacts the central server which throws your IP and game into the shared lobby space (database)

4) now connecting clients, entering the lobby have access to the hosted games.

You can't do a lobby without a centralized, known server.


I have seen games where if you click on "play lan game" you enter the lobby and you can see other peers in the network (if they have click on "play lan game". From what i think after the user clicks on "play lan game" there will be code that will accept on coming conenctions and code to search for unknown peers who are also in this lobby stage. I dont think there is a cental server in this case becuase when you play lan games no one sets one machine to become a central server in thse games.

But when a person hosts a room the hosting peer will become the server (for the game) and the other peers will be clients to the hosting peer. The clients will only talk to the host and the host will do the job of sending packets about the place.

Quote:
whoever initiates the connection with host (the machine responsible for accepting new players) gets his public address (address behind his NAT, local IP address for LAN, ...) sent to other peers by the host. Then the peers can talk to each other.

That's pretty much it. Why cant you have a dedicated server anyway?


If look above you will see why i want to have a dedicated/central server for the lobby stage. If can't get round my problem ill just have a dedicated server.
Advertisement
Check out this article on multicasting. This will let you make a LAN game lobby without a centralized server. The basic idea is, each instance of your game joins a multicast group, which is just a class D IP address from 224.0.1.0 to 239.255.255.255. Whenever you want to find other peers, just send a UDP datagram to the multicast group's IP, and every instance of your game on the network will receive it. Multicast doesn't work over the internet however, so I would design your game so that Multicast is only used to find peers, but the actual game still uses regular sending and receiving. If you want to find peers over the entire internet, the only way I'm afraid is a centralized lobby server.
Quote:Ill be using TCP/IP so i wont be able to do the UDP broadcasting


There is no way you can do discovery with TCP. It's not what that protocol is for.

Multicast is a fairly broken idea, and only works in small, well defined, isolated networks. It does not work for the general internet, and it is typically not supported by ISPs or gateways.
enum Bool { True, False, FileNotFound };
For a simple project, simplify your life and use a broadcast UDP socket, at least for the host discovery.

you just send a advertise request broadcast on a specific port your game host will listen to. When they receive a advertise request, then they send back a reply packet to the sender, to make the host known.

You just have to reserve a port for your application for its host advertising.

here's a quick example I made for some test code. nothing fancy.

#include <winsock2.h>#include <stdio.h>#include <memory.h>void debugBreak(){	__asm int 3;}#define assert(condition) if(!(condition)) debugBreak();#pragma comment (lib, "wsock32.lib")bool sendTo(SOCKET s, const char* data, int len, SOCKADDR_IN addr){	int result = ::sendto(s, data, len, 0, (const sockaddr*)&addr, sizeof(addr));	return (result == len); }bool sendMessage(SOCKET s, const char* msg, const char* data, int datalen, SOCKADDR_IN addr){	// send message with data	printf("sending \"%s\" to [%s:%d]...\n", msg, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));	char packet[1288];	int msglen = (int)strlen(msg)+1;	// sanity check	if(msglen + datalen > sizeof(packet))	{		assert(false);		return false;	}		// copy message header and data	int packetlen = 0;	memcpy(packet + packetlen,  msg,  msglen); packetlen += msglen;	memcpy(packet + packetlen, data, datalen); packetlen += datalen;		// send packet	return sendTo(s, packet, packetlen, addr);}bool sendMessage(SOCKET s, const char* msg, SOCKADDR_IN addr){	printf("sending \"%s\" to [%s:%d]...\n", msg, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));				return sendTo(s, msg, (int)strlen(msg)+1, addr);}bool broadcast(SOCKET s, const char* data, int len, int port){	SOCKADDR_IN addr;	ZeroMemory(&addr, sizeof(addr));	addr.sin_family			= AF_INET;	addr.sin_addr.s_addr	= INADDR_BROADCAST;	addr.sin_port			= htons(port);	return sendTo(s, data, len, addr);}bool receive(SOCKET s, char* data, int& len, int maxLen, SOCKADDR_IN& from){	// read file descriptors	fd_set readfds;	FD_ZERO(&readfds);	FD_SET(s, &readfds);	// time interval	timeval tv;	tv.tv_sec = 0;	tv.tv_usec = 0;	// check if data waiting	int result = ::select(1, &readfds, NULL, NULL, &tv);	assert(result != SOCKET_ERROR);	// no data pending in the socket	if(!FD_ISSET(s, &readfds))		return false;	int fromlen = sizeof(from);	len = ::recvfrom(s, data, maxLen, 0, (sockaddr*)&from, &fromlen);	return (len > 0);}const char* receiveMessage(SOCKET s, SOCKADDR_IN& from){	static char data[1288];	int len;	if(!receive(s, data, len, sizeof(data), from))		return NULL;	printf("received \"%s\" from [%s:%d]...\n", data, inet_ntoa(from.sin_addr), ntohs(from.sin_port));	return data;}const int GAME_PORT = 1000;int findAddr(SOCKADDR_IN list[], int addrCount, SOCKADDR_IN addr){	for(int i = 0; i < addrCount; i ++)	{		if(	list.sin_addr.s_addr == addr.sin_addr.s_addr &&			list.sin_port == addr.sin_port)		{			return i;		}	}	return -1;}bool bind(SOCKET s, int port){	SOCKADDR_IN addr;	ZeroMemory(&addr, sizeof(addr));	addr.sin_family			= AF_INET;	addr.sin_addr.s_addr	= INADDR_ANY;	addr.sin_port			= htons(port);	int result = ::bind(s, (const sockaddr*)(&addr), sizeof(SOCKADDR_IN));	return (result != SOCKET_ERROR);}int searchHosts(SOCKADDR_IN hosts[], int maxHosts, int milliseconds){	int hostCount = 0;	// search for game	bool val = true;	SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);	int result	= setsockopt(s, SOL_SOCKET, SO_BROADCAST, (const char*)&val, sizeof(val));	assert(s != NULL);	assert(result != SOCKET_ERROR);	int frame = 0;	int start = GetTickCount();	int send_tick = 0;	while (1)	{		if(((int)GetTickCount() - send_tick) > 1000)		{			send_tick = GetTickCount();			const char* request = "ADVERTISE_REQUEST";			printf("broadcasting %s, %d...\n", request, frame++);			broadcast(s, request, (int)strlen(request)+1, GAME_PORT);		}		if(((int)GetTickCount() - start) > milliseconds)			break;		// Poll socket for information		SOCKADDR_IN addr;		const char* msg = receiveMessage(s, addr);		if(msg != NULL)		{			// received advertise reply message			if (strcmp(msg, "ADVERTISE_REPLY") == 0)			{				// add host to the list				int index = findAddr(hosts, hostCount, addr);				if(index == -1)				{					printf("found host [%s:%d].\n", inet_ntoa(addr.sin_addr), addr.sin_port);					hosts[hostCount] = addr;					hostCount++;					if(hostCount >= maxHosts)						break;				}			}		}		Sleep(1);	}	closesocket(s);					return hostCount;}bool host(){	printf("Hosting\n");	// clients in the game	const int MAX_CLIENTS = 16;	SOCKADDR_IN clients[MAX_CLIENTS];	memset(clients, 0, sizeof(clients));	// game socket	SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);	if(!bind(s, GAME_PORT))	{		closesocket(s);						return false;	}	int frame = 0;	int send_tick = 0;		while (1)	{		// send heartbeat		if(((int)GetTickCount() - send_tick) > 1000)		{			send_tick = GetTickCount();			printf("sending heartbeat %d...\n", frame++);			// send tick to clients			for(int i = 0; i < MAX_CLIENTS; i ++)			{				// client valid. send heartbeat				if(clients.sin_port != 0)				{					sendMessage(s, "HOST_TICK", clients);				}			}		}		// read messages		SOCKADDR_IN addr;		const char* msg = receiveMessage(s, addr);		if(msg != NULL)		{			// advertise request			if(strcmp(msg, "ADVERTISE_REQUEST") == 0)			{				// send reply back				sendMessage(s, "ADVERTISE_REPLY", addr);			}			// join request			if(strcmp(msg, "JOIN_REQUEST") == 0)			{				// chek if client in the list				int slot = findAddr(clients, MAX_CLIENTS, addr);								// nope, add him to the list				if(slot == -1)				{					// find free slot					SOCKADDR_IN zero;					memset(&zero, 0, sizeof(zero));					slot = findAddr(clients, MAX_CLIENTS, zero);										// list is full. refuse the client					if(slot == -1)					{						sendMessage(s, "JOIN_REFUSE", addr);					}					else					{						// add him. send accept message. add slot info as parameters						clients[slot] = addr;						sendMessage(s, "JOIN_ACCEPTED", (const char*)&slot, sizeof(slot), addr);					}				}				else				{					sendMessage(s, "JOIN_ACCEPTED", (const char*)&slot, sizeof(slot), addr);				}			}			// a client tick			else if(strcmp(msg, "CLIENT_TICK") == 0)			{				// client should be in our list				int slot = findAddr(clients, MAX_CLIENTS, addr);							// do stuff....			}		}		Sleep(1);	}	closesocket(s);					return false;}bool join(SOCKADDR_IN host){	printf("Peering\n");	// game socket	SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);	int portCount = 100;	int i;	for(i = 0; i < portCount; i ++)	{		// try to bind to a port		if(bind(s, GAME_PORT + i))			break;	}	// failed to bind port.	if(i == portCount) 	{		closesocket(s);						return false;	}			// state.	bool accepted = false;	int frame = 0;	int send_tick = 0;		while (1)	{		// not acepted yet. send request		if(!accepted)		{			if(((int)GetTickCount() - send_tick) > 1000)			{				send_tick = GetTickCount();								// send reply back				sendMessage(s, "JOIN_REQUEST", host);			}		}		else		{			if(((int)GetTickCount() - send_tick) > 1000)			{				send_tick = GetTickCount();								printf("sending heartbeat %d...\n", frame++);				sendMessage(s, "CLIENT_TICK", host);			}		}		// read messages		SOCKADDR_IN addr; 		const char* msg = receiveMessage(s, addr);		if(msg != NULL)		{			// join game refused			if(strcmp(msg, "JOIN_REFUSE") == 0)			{				// quit				closesocket(s);								return false;			}			// join accepted			if(strcmp(msg, "JOIN_ACCEPTED") == 0)			{				// extract slot info				int slot;				memcpy(&slot, msg + strlen("JOIN_ACCEPTED")+1, sizeof(slot));				printf("accepted slot = (%d)\n", slot);				accepted = true;			}			// the host tick			else if(strcmp(msg, "HOST_TICK") == 0)			{				if(accepted)				{					// do stuff....				} 			}		}		Sleep(1);	}	closesocket(s);					return false;}int main(void){	WSADATA wsa;	memset(&wsa,0,sizeof(wsa));	WSAStartup(WINSOCK_VERSION,&wsa);	SOCKADDR_IN hosts[16];	int hostCount = searchHosts(hosts, sizeof(hosts) / sizeof(*hosts), 6000);	printf("found %d hosts.\n", hostCount);		// no host found, host a game ourselves	if(hostCount == 0)	{		host();	}	// we found some hosts, join the first one	else	{		join(hosts[0]);	}	return 0;}


[Edited by - oliii on February 24, 2008 3:10:18 PM]

Everything is better with Metal.

Quote:simplify your life and use a multicast UDP socket


You mean broadcast, not multicast. At least, that's what you implemented :-)
enum Bool { True, False, FileNotFound };
;)

Everything is better with Metal.

Hey guys thanks for your help!

I pretty much went down the UDP brodcasting route it seems to be working fine looked at your code oliii and Beej's to keep me on the right track :).

I run two instances if the program I made one to send and one to recive and the one that recives get the message :) (YAY!).

But when I run a 3rd prog its does not work, I know this is because the 1st reciving prog bounded that port it requested first. Will need more than pc to properly test out the prog tho.

I was wondering if this won't cause any unforseen problems, but can I use UDP on one port and TCP on another without any problems?

Also about packet collions say if I was reciving a TCP packet from one peer, then another peer broadcasted their info and both packets came at the same time would this cause a collision even tho they are diffrent socks on diffrent ports?

Once again thanks for your help guys.
Oh yeah almost forgot to say once again going back to the games thing, if they use UDP broadcasting in the Lan Lobby to help peers find each other.

They boardcast on a certain port, what happens if on some one's pc that port is already taken? How do they solve that problem? Do they just broadcast on multiple ports?
If there are multiple programs using the same port, it won't work (unless all programs use SO_REUSEADDR).
Also, if some other program uses the same port for broadcasting, chances are that the data format of that program is not the same as yours. You should put some recognizable data at the beginning of the packet to easily filter out data that's not yours.
enum Bool { True, False, FileNotFound };
Yeah, you need to allocate a specific port to your host, and make sure no one uses it. It would be good to tell your project supervisor about it. Any port should do but you never know with university networks. Also, firewalls can block your traffic.

Like HPlus says, for extra security, you can add a randomly generated number (that is unique to your application) at the start of the packet, so that you wont receive garbage from another program that would accidently broadcast data to that port (it happened to me, I received some packets from a network printer :)).

The random number (aka a cryptogarphic nonce) can also help you out if you have several versions of the network code running (increment the lowest byte of the number for each version of the code you run).

As for the clients, it wont matter what port they are using. In the example, I do a port scan (I scan a 100 ports after the host port) to find an unused port. When bind() succeed, here is my port for my client :) That allows me to run several instance of the program on one machine. One host, and as many clients as I want.

Also, if you want to use a TCP socket for your game, then the host will have to run one TCP socket per client, plus a UDP socket to listen to broadcasts. using only UDP here, I run everything through that socket. This is also paricularly useful for online games and NAT punchthrough. One socket, no mess.

my $0.02.

Everything is better with Metal.

This topic is closed to new replies.

Advertisement