Introduction
This project consists of a simple UDP server and client programs. If you've never written a program that uses UDP, this is an ideal starting project. The server runs on a local computer, waiting for a datagram request from a remote computer asking for the server's current time. The server then returns its current time to the client, which in turn displays it.
Background
UDP stands for User Datagram Protocol. The client sends a datagram to the server, which then processes the information and returns a response. This article demonstrates how to use the sendto
and recvfrom
functions.
The server program
The server program is a simple UDP server that waits for the datagram from clients. When it receives a datagram containing the text "GET TIME\r\n", it returns the current server's time to the client.
Opening the Windows connection
Before calling any socket functions, it is necessary to first open the Windows connection. This is done with the WSAStartup
function.
if (WSAStartup(0x0101, &w) != 0)
{
fprintf(stderr, "Could not open Windows connection.\n");
exit(0);
}
The hexadecimal number 0x0101
is the version of WinSock to use, and the variable w
is a structure of WSADATA
form.
Opening a datagram socket
The next step is to open a datagram socket for UDP. This is done with the socket
function, which returns a socket descriptor.
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd == INVALID_SOCKET)
{
fprintf(stderr, "Could not create socket.\n");
WSACleanup();
exit(0);
}
AF_INET
specifies the family to use, and SOCK_DGRAM
tells the function we want to use UDP instead of TCP/IP.
Setting up the server information
After this, it is necessary to fill in some information about the server in a struct
of type sockaddr_in
. First, the memory in the struct
is cleared out. Then the family of the server is set, which is always AF_INET
. Then the port number is set using the htons
function. Depending on the command line parameters entered, the server will either try to get its own IP address, which is the preferred method of operation, or if that doesn't work, it can be specified manually. To automatically get the address of the server computer, the gethostname
function is called, and then the function gethostbyname
returns a pointer to a struct
of type hostent
. Finally, each component of the address in xxx.xxx.xxx.xxx form is copied to the server
struct
.
memset((void *)&server, '\0', sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(port_number);
if (argc == 2)
{
gethostname(host_name, sizeof(host_name));
hp = gethostbyname(host_name);
if (hp == NULL)
{
fprintf(stderr, "Could not get host name.\n");
closesocket(sd);
WSACleanup();
exit(0);
}
server.sin_addr.S_un.S_un_b.s_b1 = hp->h_addr_list[0][0];
server.sin_addr.S_un.S_un_b.s_b2 = hp->h_addr_list[0][1];
server.sin_addr.S_un.S_un_b.s_b3 = hp->h_addr_list[0][2];
server.sin_addr.S_un.S_un_b.s_b4 = hp->h_addr_list[0][3];
}
else
{
server.sin_addr.S_un.S_un_b.s_b1 = (unsigned char)a1;
server.sin_addr.S_un.S_un_b.s_b2 = (unsigned char)a2;
server.sin_addr.S_un.S_un_b.s_b3 = (unsigned char)a3;
server.sin_addr.S_un.S_un_b.s_b4 = (unsigned char)a4;
}
The variables a1
, a2
, a3
and a4
are the components of the server's address in xxx.xxx.xxx.xxx form as typed on the command line.
Binding the address to the socket
The next step is to bind the server's address to the socket created by the socket
function. This is done using the bind
function, which returns -1 if there is an error.
if (bind(sd, (struct sockaddr *)&server,
sizeof(struct sockaddr_in)) == -1)
{
fprintf(stderr, "Could not bind name to socket.\n");
closesocket(sd);
WSACleanup();
exit(0);
}
Getting the datagram from the client
The server is now ready to listen for datagrams from clients. This is done using the recvfrom
function. buffer
is the buffer to store the datagram received, and BUFFER_SIZE
is the maximum number of bytes to receive. client
is a struct
of type sockaddr_in
that contains information about the client sending the datagram, including the client's address.
client_length = (int)sizeof(struct sockaddr_in);
bytes_received = recvfrom(sd, buffer, BUFFER_SIZE, 0,
(struct sockaddr *)&client, &client_length);
if (bytes_received < 0)
{
fprintf(stderr, "Could not receive datagram.\n");
closesocket(sd);
WSACleanup();
exit(0);
}
Sending back a response
Once the datagram has been received by the server, the server compares the information in the datagram to the string "GET TIME\r\n". If these strings match, then the server returns the time. Otherwise, the request is discarded as an invalid request. The time is sent back to the client using the sendto
function.
if (strcmp(buffer, "GET TIME\r\n") == 0)
{
current_time = time(NULL);
if (sendto(sd, (char *)¤t_time,
(int)sizeof(current_time), 0,
(struct sockaddr *)&client, client_length) !=
(int)sizeof(current_time))
{
fprintf(stderr, "Error sending datagram.\n");
closesocket(sd);
WSACleanup();
exit(0);
}
}
After this, the server returns in an infinite loop back to the recvfrom
function.
The client program
The client program is a simple UDP client that sends a request to the server to get the current time and receives the time back. First, the Windows connection is opened. Then a socket is opened. Next the address of the server is copied into the server
struct
. This code is very similar to the code for the server.
Getting the address of the client
In a UDP client, it is necessary to know the IP address of the client computer. This can be done automatically, or manually assigned by a command line switch. The code is almost identical to the code to assign the address to the server, except that the port number for the client is set to zero client.sin_port = htons(0);
, where client
is a struct
of type sockaddr_in
.
Binding the client address to the socket
The next step is to bind the client address to the socket. This code is almost identical to the code for the server.
Transmitting the request to get the time
Now it is time to send the request to the server for the current time. The sendto
function is used to do this:
server_length = sizeof(struct sockaddr_in);
if (sendto(sd, send_buffer, (int)strlen(send_buffer) + 1,
0, (struct sockaddr *)&server, server_length) == -1)
{
fprintf(stderr, "Error transmitting data.\n");
closesocket(sd);
WSACleanup();
exit(0);
}
send_buffer
is a string containing the text "GET TIME\r\n"
terminated by a null terminator.
Receiving the time
After the request for the time has been sent, a response from the server will be sent back. This is done with the recvfrom
function:
if (recvfrom(sd, (char *)¤t_time,
(int)sizeof(current_time), 0,
(struct sockaddr *)&server,
&server_length) < 0)
{
fprintf(stderr, "Error receiving data.\n");
closesocket(sd);
WSACleanup();
exit(0);
}
current_time
is a time_t
variable that stores the time.
Closing the socket and cleaning Up
The program is almost finished. The last step is to close the socket and Windows connection. The socket is closed with the closesocket
function, and WSACleanup
is called to close the Windows connection:
closesocket(sd);
WSACleanup();
Using the server program
The syntax for the server program is timeserv [server_address] port
. The port
is the port number to run the server on. It is recommended that you choose a port number above 1023, as the lower port numbers may be assigned to other protocols. The optional server_address
parameter is the local IP address of the server computer entered in xxx.xxx.xxx.xxx form. If this parameter is omitted, the program will attempt to automatically get the address. Most of the time this works fine, but if you have more than one network connection, such as an Ethernet card and wireless networking card, the program may choose the wrong one. In this case, type ipconfig
at the command prompt to determine your address and enter it on the command line. For example, to run the program with automatic local address generation on port 5000, you would type: timeserv 5000
, and to run it on a computer with a local address of 192.168.1.102, you would type timeserv 192.168.1.102 5000
. To quit the server program, hold down the CTRL key and press C.
Using the client program
Before starting the client program, first make sure the server program is running on the server computer. The syntax for the client program is timecli server_address port [client_address]
. The server_address
is the address in xxx.xxx.xxx.xxx form that the server computer is running on. The port parameter is the port that the server is running on. The optional client_address
parameter overrides the automatic local address generation for the client computer and is similar in operation to the server program listed above. For example, to connect to a server with an address of 192.168.1.102 running on port 5000, you would type timecli 192.168.1.102 5000
.
Points of interest
Firewall and anti-virus software
Some firewall and anti-virus programs may not allow you to run the server and/or client program on your computer. This is just their attempt to protect you from spyware. These programs may by mistake identify this project as some sort of spyware. If your firewall or anti-virus program blocks either one of these programs, you may have to temporarily disable them. Be sure, however, to turn them back on when you're done using these programs.
Linking the code
When you compile the source code files, be sure to link them with the library file wsock32.lib. Otherwise, the linker will generate a bunch of errors saying that there are undefined functions.
Why did I choose to write this program in C?
A question I will probably be asked is, "Why did you write this program in C instead of C++?". The answer is simple. The point of this program is to show the basic framework of WinSock programming, not to give the smallest and most up-to-date code. That's why I chose not to implement the program with the MFC CSocket
class, for example. Don't worry, however, the source code will still compile with a C++ compiler. I wanted to demonstrate the use of basic functions such as socket
, bind
, and sendto
. I originally learned socket programming on a UNIX system and want programmers from that environment to see how similar functions can be used in Windows programming. This is a basic example program for beginners in WinSock programming, not a program intended for advanced programmers, who probably already know all this stuff anyway.
Running the programs on a computer with no network connection
It is possible to test out this server and client on a computer that doesn't have any network connections by using the loopback address. The loopback address is 127.0.0.1 and is not associated with any network hardware. To run the server on the loopback address, type timeserv 127.0.0.1 5000
, and to run the client, type timecli 127.0.0.1 5000 127.0.0.1
on the same computer.
History