establishing UDP/IP communication

Started by
19 comments, last by karx11erx 19 years, 1 month ago
Hi, I am trying to implement UDP/IP communication in a MS Windows based multiplayer game. I have some basic understanding of WinSock, IP addresses, sockets, and ports, but I am clueless about how to make a server and a client communicate via UPD, so I'd like to ask for some basic explanations. 1. How does the server listen for an incoming join request from a client? 2. How does the client send a join request? I guess it needs the servers IP address and the port it is using. 3. Once a connection is established, does the server need the clients IP address to send data to it? I guess yes, but ... What I have currently done is to bind both server and client to IP INADDR_ANY, and have the client send data to the servers IP address, using a given port. The server never receives data though (checked with listen() on the bound socket).
_________karx11erxVisit my Descent site or see my case mod.
Advertisement
UDP doesn't use bound sockets. Use a socket of type SOCKET_DGRAM.

This shows how to create a simple server/client using UDP. It basically keeps firing data from the client to the server.

In listen mode, the their_addr struct is filled in with the sender's address during recvfrom. This is how to know what the client's address is.

If you can figure this source out, look into ioctlsocket. It can be used to set a socket to non-blocking, and also to detect if there is any data waiting in the incoming buffer -- this will help you avoid a recvfrom call that waits for a long time before giving up and returning (due to no incoming data).


[source = "cpp"]#define UNICODE#define WIN32_LEAN_AND_MEAN#define STRICT#include <iostream>#include <string>#include <new>#include <cstring>using namespace std;#include <winsock2.h>#include <windows.h>bool Stop=false;SOCKET UDPSocket = INVALID_SOCKET;enum ProgramMode { Talk, Listen };void PrintUsage(void){	cout << "  USAGE:" << endl;		cout << "    Listen mode:" << endl;	cout << "      udpspeed PORT_NUMBER" << endl;	cout << endl;	cout << "    Talk mode:" << endl;	cout << "      updspeed TARGET_HOST PORT_NUMBER" << endl;	cout << endl;	cout << "    ie:" << endl;	cout << "      Listen Mode:   udpspeed www 342" << endl;	cout << "      Listen Mode:   udpspeed 10.200.67.1 950" << endl;	cout << "      Send Mode:     udpspeed 1920" << endl;	cout << endl;}bool VerifyPort(const string &PortString, unsigned long int &PortNumber){	for(size_t i = 0; i < PortString.length() ; i++)	{		if(!isdigit(PortString))		{			cout << "  Invalid port: " << PortString << endl;			cout << "  Ports are specified by numerals only." << endl;			return false;		}	}	PortNumber = atol(PortString.c_str());	if(PortString.length() > 5 || PortNumber > 65535 || PortNumber == 0)	{		cout << "  Invalid port: " << PortString << endl;		cout << "  Port must be in the range of 1-65535" << endl;		return false;	}	return true;}bool InitializeWinsock(void){	WSADATA wsaData;	WORD wVersionRequested = MAKEWORD(2,2);		if(WSAStartup(wVersionRequested, &wsaData))	{		cout << "Could not initialize Winsock 2.2.";		return false;	}	if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)	{		cout << "Required version of Winsock (2.2) not available.";		return false;	}	return true;}BOOL ControlHandler(DWORD dwCtrlType){	Stop = true;	closesocket(UDPSocket);	return TRUE;}bool InitializeOptions(const int &argc, char **argv, enum ProgramMode &Mode, string &TargetHostString, long unsigned int &PortNumber){	if(!SetConsoleCtrlHandler((PHANDLER_ROUTINE)ControlHandler,TRUE))	{		cout << "  Could not add control handler." << endl;		return false;	}	if(!InitializeWinsock())		return false;	string PortString = "";	if(2 == argc)	{		Mode = Listen;		PortString = argv[1];	}	else if(3 == argc)	{		Mode = Talk;		TargetHostString = argv[1];		PortString = argv[2];	}	else	{		PrintUsage();		return false;	}	if(!VerifyPort(PortString,PortNumber))		return false;	cout.setf(ios::fixed,ios::floatfield);	cout.precision(0);	return true;}void Cleanup(void){	// if the program was aborted, flush cout and print a final goodbye	if(Stop)	{		cout.flush();		cout << endl << "  Stopping." << endl;	}	// if the socket is still open, close it	if(INVALID_SOCKET != UDPSocket)		closesocket(UDPSocket);	// shut down winsock	WSACleanup();	// remove the console control handler	SetConsoleCtrlHandler((PHANDLER_ROUTINE)ControlHandler,FALSE);}int main(int argc, char **argv){	cout << endl << "udpspeed 1.0 - UDP speed tester" << endl << "Copyright (c) 2002, Shawn Halayka" << endl << endl;	// set up a mode variable to store whether we are in talk or listen mode	ProgramMode Mode;	// a string to hold the target host's name / IP string	string TargetHostString = "";	// the port number to talk or listen on	long unsigned int PortNumber = 0;	// buffer to hold the message being passed to the other end	const long unsigned int TXBufferSize = 1450; // 1400-1450 magic #?	char TXBuffer[1450];	memset(TXBuffer,'\0',TXBufferSize);	// buffer to hold the message being received from the other end	const long unsigned int RXBufferSize = 8196;	char RXBuffer[8196];	// initialize winsock and all of the program's options	if(!InitializeOptions(argc, argv, Mode, TargetHostString, PortNumber))	{		Cleanup();		return 0;	}	// if talk mode, go into talk mode	if(Talk == Mode)	{		struct sockaddr_in their_addr;		struct hostent *he;		he=gethostbyname(TargetHostString.c_str());		if(NULL == he)		{			cout << "Could not resolve target host." << endl;			Cleanup();			return 0;		}		their_addr.sin_family = AF_INET;		their_addr.sin_port = htons((unsigned short int)PortNumber);		their_addr.sin_addr = *((struct in_addr *)he->h_addr);		memset(&(their_addr.sin_zero), '\0', 8);		if(INVALID_SOCKET == (UDPSocket = socket(AF_INET, SOCK_DGRAM, 0)))		{			cout << "  Could not allocate a new socket." << endl;			Cleanup();			return 0;		}		cout << "  Sending on port " << PortNumber << " - CTRL+C to exit." << endl << endl;		while(!Stop)		{			if(SOCKET_ERROR == (sendto(UDPSocket, TXBuffer, TXBufferSize, 0, (struct sockaddr *)&their_addr, sizeof(struct sockaddr))))			{				if(!Stop) cout << " (TX ERR)" << endl;				break;			}		}	}	// else, go into listen mode	else if(Listen == Mode)	{		struct sockaddr_in my_addr;		struct sockaddr_in their_addr;		int addr_len;		my_addr.sin_family = AF_INET;		my_addr.sin_port = htons((unsigned short int)PortNumber);		my_addr.sin_addr.s_addr = INADDR_ANY;		memset(&(my_addr.sin_zero), '\0', 8);		addr_len = sizeof(struct sockaddr);		if(INVALID_SOCKET == (UDPSocket = socket(AF_INET, SOCK_DGRAM, 0)))		{			cout << "  Could not allocate a new socket." << endl;			Stop=true;		}		if(SOCKET_ERROR == bind(UDPSocket, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)))		{			cout << "  Could not bind socket to port " << PortNumber << "." << endl;			Stop=true;		}		cout << "  Listening on UDP port " << PortNumber << " - CTRL+C to exit." << endl << endl;        DWORD StartLoopTicks = 0;		DWORD EndLoopTicks = 0;		DWORD ElapsedLoopTicks = 0;		__int64 TotalElapsedTicks = 0;		__int64 TotalBytesReceived = 0;		__int64 LastReportedAtTicks = 0;		__int64 LastReportedTotalBytesReceived = 0;		double RecordBPS = 0;		DWORD TempBytesReceived = 0;		while(!Stop)		{			StartLoopTicks = GetTickCount();			if(SOCKET_ERROR == (TempBytesReceived = recvfrom(UDPSocket, RXBuffer, RXBufferSize, 0, (struct sockaddr *) &their_addr,&addr_len)))			{				if(!Stop) cout << endl << "  Listening on UDP port " << PortNumber << " - CTRL+C to exit." << endl;			}			else			{				TotalBytesReceived += TempBytesReceived;			}			EndLoopTicks = GetTickCount();			if(EndLoopTicks < StartLoopTicks)				ElapsedLoopTicks = MAXDWORD - StartLoopTicks + EndLoopTicks;			else				ElapsedLoopTicks = EndLoopTicks - StartLoopTicks;			TotalElapsedTicks+=ElapsedLoopTicks;			if(TotalElapsedTicks>=LastReportedAtTicks+1000)			{				__int64 BytesSentReceivedBetweenReports = TotalBytesReceived - LastReportedTotalBytesReceived;				double BytesPerSecond = (double)BytesSentReceivedBetweenReports /  (  ( (double)TotalElapsedTicks - (double)LastReportedAtTicks ) / (double)1000.0f  );				if(BytesPerSecond > RecordBPS)					RecordBPS = BytesPerSecond;				LastReportedAtTicks = TotalElapsedTicks;				LastReportedTotalBytesReceived = TotalBytesReceived;				cout << "  " << BytesPerSecond << " BPS, Record: " << RecordBPS << " BPS" << endl;			}		}		cout << endl;		cout << "  Record: " << RecordBPS << " BPS" << endl;	}	Cleanup();	return 0;}
Essentially, UDP is a message-based protocal. For example, consider UDP as sending out a letter (mail). You give it an address and hope that the person receives the letter.

Kuphryn
kuphryn,

more than I ever wanted to know ... :P

anon,

Actually I am trying to fix some UDP/IP someone else had been coding and that doesn't work. I know a little about TCP/IP, but not about UDP/IP. I will look into the source code and come back here if I have any more questions. Thank you so far.

Edit:

I have changed my UDP code according to your example. The server socket is now bound to INADDR_ANY, and the client socket remains unbound.

Both server and client sockets are set to non-blocking with ioctlsocket().

Since I am running client and server on the same machine, I am using the IP address returned by gethostname() and queryhost() as server IP address.

Nevertheless if the client sends data to that IP address and the port I've chosen, the server doesn't get any data (recvfrom() returns -1).

Same result if I log into the inet and use my network adapter's client IP.

And now?

[Edited by - karx11erx on March 9, 2005 5:38:35 PM]
_________karx11erxVisit my Descent site or see my case mod.
Yeah. In my game, the UDP "listen" port just waits for a packet that includes the client's IP and port. The server then assumes the client is connected for about 12 seconds (any lag over that will get the player kicked).

Since the newcomer client will send the connection request multiple times, the server should just check to see if that IP and port are occupied by a client. If so, just forget the packet and move on, as the client is happily "connected".
I am not getting that far. The server doesn't get any data from the client, and I don't know why.
Any ideas anybody ?
Ok, you have a client and server - two independent pieces of kit, either or both of them could be faulty.

So get a sniffer such as Ethereal, and a packet generator of some sort.

Use the sniffer to determine whether sending is happening correctly, and use the packet generator to test the receiving function.

You will definitely need to

- Keep a data structure holding the ip address /port number (typicaly struct sockaddr_in) for each connected player
- Have a handshake for connection

You will probably need to
- Have some kind of magic number in packets (ideally allocated randomly by the server) to know that they DEFINITELY came from the client (i.e. weren't spoofed). Source IP / port is no guarantee of identity. Spoofing UDP is very easy.

If you have two independent bits of software which could both be faulty, you won't know where to start.

---

Re-reading your original message:

listen() is not relevant for UDP sockets and won't work. It's for TCP only.

To receive packets, use recvfrom().

Check that the server is bound on the right port number (use netstat). Be sure to use htons / ntohs as necessary to ensure the port numbers are in "network order".

The client should not bind to a specific port number. It should always bind to port 0 to get an arbritary port. You can find out which port has locally be bound with getsockname()

Mark

Mark
The initial post is not relevant anymore. I have changed the code according the example the anonymous poster placed here.

Yes, I am afraid I need to work into a packet sniffer (sniff).
Well, forgot to login ...

The client doesn't bind its socket to an address any more.

The server binds its socket to INADDR_ANY:<port>, where <port> is some port number I've chosen.

Both client and server use non-blocking connections and are "listening" with recvfrom(). The client is sending data, but the server has always -1 returned from recvfrom().

Edit:

I have been looking on the ethereal web site for a packet sniffer. All I could find was a program named winpcap. It seems to be a command shell (DOS box) application, no documentation, no GUI, you aren't even asked by the installer which folder you'd like to install this too.

/rant on

Honestly, this typical for the stuff Linux ppl do. It's complicated, it's cryptic, it's hard to use. I have encountered this before several times when trying to use some software from the open source community. Absolutely bollox. I wonder where these ppl take the arrogance from they look down at MS Windows and Windows users. I really hate messes like these.

If it hadn't been for Microsoft, we'd still be stuck with horrible, command line controlled debuggers. Even SUN only made a GUI controlled debugger long after MS had built theirs.

Heck. Darn Linux geeks. Most of then seem to have messy minds.

Well ... I don't hate Linux, or Unix, have been working with it, but that's just my common experience whenever I try to use some open source software (with a few noble exceptions targetted at the main stream, like OpenOffice, Firefox, Thunderbird).

/rant off

If I look at connections with netstat, I cannot see any UDP connections. D'oh.

[Edited by - karx11erx on March 10, 2005 5:57:00 AM]
_________karx11erxVisit my Descent site or see my case mod.

This topic is closed to new replies.

Advertisement