Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

A simple UDP time server and client for beginners

4.78/5 (41 votes)
23 Sep 20058 min read 2   35.5K  
How to implement a simple UDP time server and client using WinSock.

Image 1

Image 2

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.

/* Open windows connection */
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.

/* Open a datagram socket */
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.

/* Clear out server struct */
memset((void *)&server, '\0', sizeof(struct sockaddr_in));

/* Set family and port */
server.sin_family = AF_INET;
server.sin_port = htons(port_number);

/* Set address automatically if desired */
if (argc == 2)
{
    /* Get host name of this computer */
    gethostname(host_name, sizeof(host_name));
    hp = gethostbyname(host_name);

    /* Check for NULL pointer */
    if (hp == NULL)
    {
        fprintf(stderr, "Could not get host name.\n");
        closesocket(sd);
        WSACleanup();
        exit(0);
    }

    /* Assign the address */
    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];
}
/* Otherwise assign it manually */
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.

/* Bind address to socket */
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);

/* Receive bytes from client */
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.

/* Check for time request */
if (strcmp(buffer, "GET TIME\r\n") == 0)
{
    /* Get current time */
    current_time = time(NULL);
            
    /* Send data back */
    if (sendto(sd, (char *)&current_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:

/* Tranmsit data to get time */
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:

/* Receive time */
if (recvfrom(sd, (char *)&current_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

  • No history yet.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here