Sign in to follow this  

establishing UDP/IP communication

This topic is 4658 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
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[i]))
{
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;
}

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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".

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I am not getting that far. The server doesn't get any data from the client, and I don't know why.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
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).

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
Quote:
Original post by karx11erx
WI 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.


Ethereal is a GUI program with a comprehensive and helpful GUI. That's why we use it.

WinPCap is just a component required before its (Ethereal's) installation.

Quote:

Honestly, this typical for the stuff Linux ppl do. It's complicated, it's cryptic, it's hard to use....


You just haven't read the Ethereal info.

Installation instructions

and to quote them,

" Both steps are extremely simply, as you only have to download and install the two exe files."

I'll ignore the rest of your rant about Linux users :)

In reply to your second bit about "netstat"...

Use "netstat -a" to display non-connected sockets too. "netstat" only displays connected ones, and seeing as UDP sockets are never connected, they generally aren't displayed.

Mark

Share this post


Link to post
Share on other sites
Oh yes one thing:

On win32, Ethereal seems unable to sniff traffic on the loopback interface (127.0.0.1).

So if you've been using that for testing, change it to the real IP address of your machine on the LAN.

It's also possible that there are some other interface types that Ethereal doesn't work with on win32 - but it works fine on Ethernet (as its name implies) and that's what nearly everyone uses.

If you're having trouble finding the Ethereal win32 installer, it's in a subdirectory called "all-versions", check the timestamps, as they are not listed in chronological order (look for 0.10 versions).

Although Ethereal has an apparently low version number (< 1.0), it's actually been around for a long time and is extremely comprehensive with a lot of features.

I use it on Win32 and Linux for debugging all sorts of network problems - especially network / web applications.

Mark

Share this post


Link to post
Share on other sites
Thanks. In the meantime I have been able to get and run Ethereal with some help from another forum.

There doesn't seem to be any UDP traffic generated by my program.

Currently it works like this:

(a) Server

The server gets a UDP socket, binds it to INADDR_ANY:<server port>, sets it to non blocking, listens with recvfrom().

(b) Client

The client gets a UDP socket, binds it to INADDR_ANY:<client port>, sets it to non blocking, sends data with sendto(). sendto() returns a function result indicating successful operation (i.e. the amount [bytes] of data sent). The data is sent to <server IP>:<server port>. As the server IP I have taken the IP returned by gethostbyaddr() and queryhost() (which is 192.168.0.1).

Well, Ethereal doesn't show any UDP traffic to <server IP>:<server port>, and I don't know enough about UDP to understand what's going wrong.

As a reminder: Both server and client run on the same machine.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I didn't make it clear enough when to use ioctlsocket. Look for it twice in this version:



#include <winsock2.h>

#include <iostream>
using std::cout;
using std::endl;

#include <string>
using std::string;

#pragma comment(lib, "ws2_32.lib")

bool Init(void)
{
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2,2);

if(WSAStartup(wVersionRequested, &wsaData))
return false;

if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
return false;

return true;
}

void Cleanup(SOCKET &Socket)
{
if(INVALID_SOCKET != Socket)
closesocket(Socket);

WSACleanup();
}


int main(void)
{
// init winsock
if(!Init())
return 1;

// create socket
SOCKET UDPSocket = INVALID_SOCKET;

if(INVALID_SOCKET == (UDPSocket = socket(AF_INET, SOCK_DGRAM, 0)))
{
cout << " Could not allocate a new socket." << endl;
Cleanup(UDPSocket);
return 1;
}

// set non-blocking mode
unsigned long int non_blocking = 1;

if(0 != ioctlsocket(UDPSocket, FIONBIO, &non_blocking))
{
cout << "Could not set non-blocking mode.";
Cleanup(UDPSocket);
return 1;
}

// toggle this for client/server
bool send_mode = true;

if(send_mode)
{
struct sockaddr_in dest_addr;
struct hostent *he = gethostbyname("127.0.0.1");

if(0 == he)
{
cout << "Could not resolve target host.";
Cleanup(UDPSocket);
return 1;
}

dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(55555);
dest_addr.sin_addr = *((struct in_addr *)he->h_addr);
memset(&(dest_addr.sin_zero), '\0', 8);

char *TXBuffer = "Hello World";

while(1)
{
cout << "Sending data..." << endl;

if(SOCKET_ERROR == (sendto(UDPSocket, TXBuffer, (int)strlen(TXBuffer) + 1, 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))))
{
cout << "sendto error";
break;
}

Sleep(1000);
}
}
else
{
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
int addr_len = sizeof(struct sockaddr);

my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(55555);
my_addr.sin_addr.s_addr = INADDR_ANY;
memset(&(my_addr.sin_zero), '\0', 8);

if(SOCKET_ERROR == bind(UDPSocket, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)))
{
cout << " Could not bind socket to port 55555";
Cleanup(UDPSocket);
return 1;
}

char RXBuffer[4096];

while(1)
{
long unsigned int NumBytesToBeRead = 0;
int bytes_received = 0;

if(0 != ioctlsocket(UDPSocket, FIONREAD, &NumBytesToBeRead))
{
cout << "ioctlsocket error";
break;
}
else if(0 != NumBytesToBeRead) // ioctl socket worked, and didn't report 0 bytes in the buffer
{
bytes_received = recvfrom(UDPSocket, RXBuffer, sizeof(RXBuffer), 0, (struct sockaddr *)&their_addr, &addr_len);

if(SOCKET_ERROR == bytes_received)
{
cout << "recvfrom error";
break;
}

cout << "Received: " << RXBuffer << endl;
}
}
}

Cleanup(UDPSocket);

return 0;
}

Share this post


Link to post
Share on other sites
Ok, you are checking for available data with ioctlsocket(), too.

What I don't understand is that you don't bind an address to the socket when sending data.

What do I have to use instead of 127.0.0.1 if I want to send data to some other computer? That computer's IP address?

Do I have to use 127.0.0.1 when sending to the same computer the sender is running on? Can't I use that computer's IP address as returned by gethostname() and gethostbyname() with the result of gethostname()?

Edit:

Well, whatever I try, Ethereal doesn't show me any UDP packets from my program. The last thing I have tried was to specify my network adapter's IP address as destination address. Didn't work.

[Edited by - karx11erx on March 10, 2005 3:29:08 PM]

Share this post


Link to post
Share on other sites
Rather than busy-waiting, try just setting the sockets into blocking mode. That way you can keep your receiver (server) running as you want.

Confirm with netstat (with -a option or whatever required to show open sockets) that the socket is really there.

If at all possible, obtain a packet-generation program to send some messages to it and confirm with Ethereal that they're really there.

Obviously test Ethereal with something which generates local UDP traffic first to ensure that it really works (hint: using nslookup to the local host as a nameserver (even if a DNS is not running locally) will generate UDP traffic).

There really is no point in checking the number of bytes to be read - you may as well go ahead and try to read them - if the socket is nonblocking, it will fail with EWOULDBLOCK.

If any socket errors occur be sure to print out and look up the error code you get (not sure how to do that on win32, it's not too tricky though).

Mark

Share this post


Link to post
Share on other sites
I've gotten further.

First of all, both server and client need to bind INADDR_ANY to their sockets.

As both are on the same machine, they must not both use the same port, so I chose <client port> = <server port> + 1.

Now the server is receiving something, and it has the right "fingerprint", but the rest of the data doesn't seem to fit.

At least I have the communication going, so I can get into that.

Edit: I am having first positive results now. Things had gotten complicated because server and client were running on the same machine, so I had to use different ports and take that into regard at various spots in the code.

Thanks for all the help. :)

[Edited by - karx11erx on March 10, 2005 7:06:34 PM]

Share this post


Link to post
Share on other sites
Look at what I said before - the client SHOULD NOT bind to a specific port - rather it should bind to port "0" and let the OS choose an unused one.

Failure to observe this rule will cause your game to fail on multiuser machines, over NAT etc.

The server should store the port number of the client too in its user list and always send back to the same port the requests came from - regardless of the port that the client *thinks* it's using.

Mark

Share this post


Link to post
Share on other sites
The client has to call bind() (with whatever port). That's my point, and that's what is *not* happening in the above code examples.

I will deal with client port handling when I have the communication completely working with a fixed port number.

Share this post


Link to post
Share on other sites
The client doesn't have to call bind(). If a UDP socket is not bound, it will be auto-bound to whatever port at the point of the first send().

Anyway, if you're binding to a specific port, I'd suggest checking for error, and re-trying with port+1 (and repeating up to about port+10 or so) before giving up and telling the user about the problem. That way, a collision with some other program, or a collision with another instance of the same program, won't shut you out. Also, a command-line option or configuration that lets the user choose the port is also useful to work around certain corporate firewall policies (where admins will only open ports where they know the port numbers on both sides).

Share this post


Link to post
Share on other sites
As the code is now, I have to use a specific port, as only join game requests from that port are accepted. I didn't write that code though (and the guy who did mustn't have had much knowledge of UDP), and I will change this once the general stuff is working. Thanks for the hint.

There were numerous reasons why that code didn't work, and it took me a while to figure them all out.

Share this post


Link to post
Share on other sites

This topic is 4658 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this