[Previous] [Contents] [Next]

Lesson 2:
A Winsock TCP client

Introduction

This TCP client will create a socket, connect it to a server, send some data, receive a response and close the connection. You can use it as a template for writing other client applications. The code involves basic standard I/O, string functions and command-line arguments. For the ones of you who don't know what command-line arguments are, I'll explain what it is, and how to use it in the program.

Initialization

First, we need to know what the server's name is and which port we're supposed to connect to, these two data are obtained through command-line arguments. For example if we want to execute a program called arg with two command-line arguments, "tcp" and "winsock", we write

arg tcp winsock

to execute the program. To connect wsclient to the address www.xyz.org at port 80, write

wsclient www.xyz.org 80

If you want to obtain the command-line arguments in a program, you simply replace

int main()

with

int main(int argc, char **argv)

where argc is the number of command-line arguments supplied plus one and argv is a dynamic, two-dimensional array containing the arguments supplied. argv[0] is always the name or the full name of the program. So, in our previous example, argv[1] would be "www.xyz.org" and argv[2] would be "80".

Now that we know about command-line arguments, we can take a look at the first part of the program:

 

// author: frenchwhale (http://frenchwhales_site.tripod.com)
// for the WinSock tutorial
//
// file name: wsclient.c
//
// description: A TCP client that uses Winsock
// to connect to a server and
// sends a message, then, it waits
// for an answer from the server.
// Copyright 2002 frenchwale

 

#include <winsock.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

#define MESSAGE         "Hello, Server!\n"

 

int main(int argc, int **argv)

{

   WSADATA wsda;        // Structure to store info

                   // returned from WSAStartup

 

   struct hostent *host;     // Used to store information

                   // retreived about the server

 

   char szMessage[80];

   int iMessageLen;

   int ret;

 

   char szInBuffer[128];

   int iBufferLen;

 

   char szAddress[64];

   int iPort;

 

   SOCKET s;       // Our TCP socket handle

   SOCKADDR_IN addr;    // The host's address

 

   // Check arguments

   if(argc != 3 ||

      (argc==2 && strcmp((char *) &argv[1][0], "/?")==0))

   {

      printf("wsclient server port\n");

      printf("   server: the server to connect to\n");

      printf("   port: the port on the remote server \n");

      exit(1);

   }

 

   // Copy the IP address

   strcpy(szAddress, (char *) &argv[1][0]);

 

   // Get the remote port

   iPort = atoi((char *) &argv[2][0]);

 

   if(iPort<0 || iPort>65563)

   {

      printf("Invalid port number! (%s)\n", argv[2]);

      exit(1);

   }

 

   // Copy the pre-defined message into a buffer

   strcpy(szMessage, MESSAGE);

 

   iMessageLen = strlen(szMessage);

 

 

We are using four libraries:

ˇ        winsock.h, obviously

ˇ        stdio.h for the standard I/O

ˇ        stdlib.h for string-to-integer conversions

ˇ        string.h for the string manipulation functions

The WSADATA structure and the hostent are used by the WSACleanup and gethostbyname functions, respectively, I'll talk about them later. Then we've got three strings, one for the outgoing message, one for the incoming response and another one for the server's address. Each of the strings has an integer to keep track of the length of the strings.

The socket is stored in a SOCKET variable, or more exactly, a socket handle. When you connect to a server, you have to specify the server's address and the port number in a SOCKADDR_IN structure, therefore, I included one SOCKADDR_IN variable in my program.

When we check the arguments, we check how many arguments there are, in our program, it has to be 3 (name, address and port), if it's two, we check whether the argument is "/?", if one of these expressions is true, we display a help message indicating how to execute the program.

After that we copy the address into a variable, convert the port number (a string) into an integer using the stdlib.h function atoi, the restrictions on a port number is that it has to be larger than 0 and smaller than 65563, so we check that too. Then we copy the pre-defined message into the outgoing buffer and append "\r\n" to it.

Byte-ordering and resolving techniques

The next part of our code will be a little trickier, it will involve byte-ordering functions and resolving, so we'll cover them now.

When variables are stored in the computer, their bytes are often reversed, for example, if we assign the number 300 to an long, the number 300 is represented as

00 00 01 2C

when assigned to the variable. This is known as the most-significant-byte order. However, in the memory, the value is stored in the least-significant-byte order as shown below

2C 01 00 00

Least-significant-byte order is the method used by most platforms, but some platforms store variables in most-significant-byte order.

What does this have to do with networking? On the host, the IP-address and the port number are stored in least-significant-byte order, but the network only accepts most-significant-byte ordered addresses and port numbers. From now on, we are going to refer to the least-significant-byte ordering as host-byte ordering and most-significant-byte ordering as network-byte ordering. In Winsock, there are two byte-ordering conversion functions for shorts:

u_short ntohs(u_short netshort);  // Network To Host Short

u_short htons(u_short hostshort); // Host To Network Short

and two byte-ordering conversion functions for longs:

u_long ntohl(u_long netlong);     // Network To Host Long

u_long htonl(u_long hostlong);    // Host To Network Long

So, when you specify IP-addresses or post numbers, remember to convert them to network byte order first (IP-addresses are longs and port numbers are shorts).

When browsing the Internet, most people don't come into contact with IP-addresses, since IP-addresses are harder to memorize, they are often mapped to a Fully Qualified Domain Name (FQDN for short, often called domain name or host name), an address consiting of letters instead of numbers, if you want to connect to a computer and you have its domain name, you have to resolve the domain name first. The domain-name-to-IP-address resolving function is called gethostbyname and is declared as follows:

struct hostent FAR *gethostbyname(const char FAR *name);

You only have to pass a string containing the address, then, a structure filled with information about the different IP-addresses belonging to the name is returned. If the name couldn't be resolved, the function returns a pointer to NULL. (Note: If you pass an IP-address to gethostbyname, it will be treated as if it was a unknown domain name). Here is the hostent stucture:

struct hostent {

   char FAR *      h_name;

   char FAR * FAR * h_aliases;

   short           h_addrtype;

   short           h_length;

   char FAR * FAR * h_addr_list;

};

h_name is a string representing the main name of the domain name (often, this is the name passed to gethostbyname). h_aliases is a null-terminated array of domain names (the last element is equal to NULL) associated with this host. h_addrtype is quite self-explaining, its value indicates which type of address the domain name is, h_length is the length of the addresses in h_addr_list. These addresses are stored as longs (network-byte-order) in null-terminated array, they can be converted into IP-addresses by the function inet_ntoa, although some type conversion is needed since the function requires an in_addr structure (which is defined as a long). To help you understand the concept of resolving, I've written a resolving program called resolver. It initializes Winsock, resolves the domain name and cleans up. RESOLVER.C.

Creating and resolving code

Here's the next part of our code:

 

   // Load version 1.1 of Winsock

 

   WSAStartup(MAKEWORD(1,1), &wsda);

 

   // Create a TCP socket

   printf("Creating socket...");

   s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

 

   // Error?

   if(s == SOCKET_ERROR)

   {

      printf("Error\nCall to socket(AF_INET, SOCK_STREAM, IPPROTO_IP); failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

 

   printf("OK\n");

 

   // Fill in the host information

   addr.sin_family = AF_INET;

   addr.sin_port = htons(iPort);

   addr.sin_addr.s_addr = inet_addr(szAddress);

 

   if(addr.sin_addr.s_addr == INADDR_NONE) // The address wasn't in numeric

                             // form, resolve it

   {

      host = NULL;

      printf("Resolving host...");

      host = gethostbyname(szAddress); // Get the IP address of the server

                        // and store it in host

      if(host == NULL)

      {

         printf("Error\nUnknown host: %s\n", szAddress);

         exit(1);

      }

      memcpy(&addr.sin_addr, host->h_addr_list[0],

            host->h_length);

      printf("OK\n");

   }

 

 

Our first Winsock function call is to WSAStartup, the initialization function. Its first argument is a word containing the Winsock version that you want to load (in our case Winsock 1.1). The second is a pointer to a WSADATA structure, upon return this structure is filled with information about the Winsock version loaded, we won't use this structure in our programs.

[Fig. 2-1, The dependance of the arguments of the socket function]

The next thing we do is to create a socket using the socket function. This function returns a SOCKET handle, which is used to refer to our socket when using other functions, the three arguments supplied are the address family, the socket type and the protocol used. The address family is AF_INET which means that we will use TCP or UDP, protocols of the AF_INET (or IP) address family. For AF_INET, there are three socket types, SOCK_STREAM, SOCK_DGRAM and SOCK_RAW, for this particular address family, SOCK_STREAM is TCP, SOCK_DGRAM is UDP and SOCK_RAW is used to send raw ip packets. For each of these socket types, you can choose which protocol you want to use for the socket (although you have to choose IPPROTO_UDP for SOCK_DGRAM and IPPROTO_IP for SOCK_STREAM).

After that we have a basic error-handling procedure. In Winsock, if a function fails, it usually returns SOCKET_ERROR. If this happens, you can call WSAGetLastError to get the error code. Every function has its error codes listed in the MSDN library, avaliable at http://library.msdn.com. When our program encounters an error, it prints the error code and exits, as you can see.

In order to conect to a host on a network, you need to fill out a SOCKADDR_IN structure:

struct sockaddr_in

{

   short           sin_family;        // Address family, use AF_INET

   unsigned short  sin_port;          // Port to connect to

   IN_ADDR         sin_addr;          // Address of the host

   char            sin_zero[8];       // Padding bytes

};

The sin_family field is the address family. It should be the same as the one used when we called socket. sin_port is the port we want to connect to. Remember, this has to be in network-byte order, so we use the htons function described earlier to convert the port number. The sin_addr field is a IN_ADDR structure which consists of a union, we can choose to specify the address as four chars (s_un_b), two shorts (s_un_w) or one long (s_addr). We will always specify the address as a long, so we use sin_addr.s_addr when assigning addresses. When we assign the address, we first assume that it's in the a.b.c.d form and pass it to inet_addr (an IP-address-to-long conversion function which takes s string as an argument and returns a long containing the address in network-byte-order). If it isn't in this form, the function returns INADDR_NONE. If this is the case, we pass the address on to our resolving procedure (see resolver.c) that copies the first IP-address returned from gethostbyname into the sin_addr field. The last field is eight padding bytes, we never use them.

Connecting, sending and receiving

Here is the core part of our code, the conencting, sending and receiving part:

 

   // Connect to the server

   printf("Connecting to %s:%d...",szAddress, iPort);

   ret = connect(s, (struct sockaddr *) &addr, sizeof(addr));

 

   if(ret == SOCKET_ERROR)

   {

      printf("Error\nCall to connect(s, (SOCKADDR) addr, sizeof(addr)); failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

 

   printf("OK\n");

 

   // Ready to send data

 

   printf("Sending data...");

   ret = send(s, szMessage, iMessageLen, 0);

 

   if(ret == SOCKET_ERROR)

   {

      printf("Error\nCall to send(s, szMessage, iMessageLen, 0) failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

   printf("OK\n");

 

   printf("\"%s\" sent to %s\n", szMessage, szAddress);

 

   // Receive data

 

   printf("Waiting for a response...");

   ret = recv(s, szInBuffer, sizeof(szInBuffer), 0);

 

   if(ret == SOCKET_ERROR)

   {

      printf("Error\nCall to recv(s, szInBuffer, sizeof(szInBuffer), 0); failed with:\n%d\n", WSAGetLastError());

      exit(1);

   }

   printf("Response recieved\n");

 

   iBufferLen = ret;         // recv() returns the number of bytes read

 

   szInBuffer[iBufferLen] = '\0'; // convert to cstring

 

   printf("Response recieved from %s:\n\"%s\"\n", szAddress, szInBuffer);

 

We connect to a host using the connect function, declared in winsock.h as:

int connect(SOCKET s, const struct sockaddr FAR *name, int namelen);

The first argument is the socket handle that we want to connect. As you can see, the address structure is the more universal sockaddr structure rather than the address-family specific SOCKADDR_IN structure, so we cast SOCKADDR_IN variable to a sockaddr structure. The last argument is simply the length of the SOCKADDR_IN structure passed to the function.

The function used to send data through the socket is named send and to receive data, you use recv. Here's the declaration of send:

int send(SOCKET s, const char FAR *buf, int len, int flags);

The first argument is the socket handle, the second is a pointer to the data you wish to send, the third is the length of the data buffer and the last one specifies which flags you want to use. Since we won't use any flags, we set this to 0. The function returns the number of bytes actually sent. If you want an error-proof application, you should check to see whether the return value equals the length of the buffer.

The receiving function, recv, is declared as:

int recv(SOCKET s, char FAR *buf, int len, int flags);

This function is similar to the send function except that the buffer supplied is filled with the data that is received and that len specifies the maximum length of the data (the length of the buffer). The function returns the number of bytes received, in order to print the received data, we use the return value to insert a '\0' at the end of the buffer.

Cleanup

The cleanup procedure is very simple:

 

   closesocket(s);

 

   WSACleanup();

 

   return 0;

}

 

To disconnect a socket, use closesocket. When your program has finished using Winsock, just call WSACleanup.

That's all! If you need more information about Winsock functions or structures, you can go to http://msdn.microsoft.com/library, where you'll find a complete Winsock documentation.

Source code for Lesson 2:

n      WSCLIENT.C - Connects to a host at a specified port and sends a pre-specified string, then it prints out the response.

[Previous] [Contents] [Next]