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