Douglas E. Comer


Computer Science Department
Purdue University
West Lafayette, IN 47907

webmaster: W. David Laverell


Companion Topics

Home

Book Information

Purpose of Site

Getting Started

Students

Faculty

What's New

Coming Attractions

C Pointers

Topics

Acknowledgements

 

 
Hands-On Networking: C Pointers

C Pointers

A search on "C tutorial" yields many sites of interest. As it happens, the site that has been the most helpful to me as I worked on solutions is that of Dave Marshall. At the moment that site has some broken links including one to "Java Algorithm Animations (C related)" that sounds very interesting and also the "Keyword Search". Another helpful site is Beej's Guide to Network Programming.

This page will contain a list of all the C functions that were used in developing solutions for the programming experiments; a section on the function select, which turns out to be extremely important in Chapter 8; a slightly different take on pointers; tips, mainly specific to network programming; examples of C Traps and Pitfalls into which I have fallen while writing solutions; and a list of FAQ.

A list of C functions appearing in solutions

accept, address, atoi, bcopy, bind, bzero, close, closedir, connect, ctime, drand48, dup, echo, exec, execl, exit, fclose, feof, fflush, fgets, fileno, fopen, fork, fprintf, fread, free, fseek, ftell, fwrite, getchar, gethostbyname, getpeername, getprotobyname, getrusage, gets, gettimeofday, htonl, htons, inet_addr, inet_ntoa, isprint, listen, malloc, memcpy, memset, nanosleep, ntohl, ntohs, opendir, pclose, perror, popen, printf, protobyname, pthread_attr_init, pthread_attr_setdetachstate, pthread_create, pthread_mutex_init, putchar, putenv, qsort, rand, read, readdir, realloc, recv, recvfrom, return, scandir, scanf, select, send, sendto, setsockopt, signal, sizeof, sleep, snprintf, socket, sprintf, srand, sscanf, stat, strcat, strchr, strcmp, strcoll, strcpy, string, strlen, strncasecmp, strncmp, strncpy, strrchr, strtok, system, times, ualarm, wait3, waitpid, write

The select function

The select function offers an easy way to do some event-driven programming, to handle a situation in which you have no way of knowing which of several events will next take place, or in the simplest case, whether a given event will or will not take place at all. In Chapter 8 you are asked to use UDP to provide a realiable file transfer service through an emulation gateway that randomly drops or corrupts packets. The easiest way to do this is with the stop-and-wait protocol. When the server sends a datagram, it waits for that datagram to be acknowledged. When the acknowledgement is received, a new datagram is sent, but the datagram may have been dropped or corrupted, or the acknowledgement itself may have been dropped. So you need to set a timer upon whose expiration, the current datagram must be resent. The gateway itself is of interest in light of the fact that it is really a server communicating with two clients which are "pretending" to be a client and a server. One way to do this would be to form UDP connections with the "server" and the "client" and then forward all datagrams received from the "client" to the "server" and vice versa. You should certainly not make assumptions as to the source of the next datagram that arrives. You must take into account the possibility that it will come from either the "client" or the "server". As an example, suppose that we decide to have our gateway wait for a datagram to be received from one of two sockets, sd_server and sd_client. Suppose that in addition we decide to allow only a certain interval of time in which the next event can take place. (When debugging our code, we might have the gateway shut down in the absence of traffic.) This is what we do to provide this functionality:
  1. Include the files sys/time.h (for the required time structure), sys/types.h (which itself includes sys/select.h, and unistd.h.
  2. Define a variable of type struct timeval, say mytime.
  3. Define a variable of type fd_set, say rfds. This is actually an array of bits which you set or clear to indicate events of interest.
  4. Initialize the bits in your array using the macro FD_ZERO, FD_ZERO(&rfds).
  5. Initialize a variable sd_max to have the larger value of the two sockets.
  6. Presumably you have a loop in which you are waiting for a datagram to arrive on one of your two sockets. In this loop
    1. code FD_SET(sd_client,&rfds)
    2. code FD_SET(sd_server,&rfds)
    3. code mytime.tv_sec=10
    4. code mytime.tv_usec=0
    5. test the return value of select(sd_max+1,&rfds,(fd_set *)0,(fd_set *)0,&mytime)
      1. if negative, you have an error condition
      2. if zero, you have a timeout
      3. otherwise you have the number of sockets on which an event took place
    6. assuming that a datagram has arrived, you use FD_ISSET to determine the source. For example, FD_ISSET(sd_server,&rfds) will be true if the datagram arrived from the server. You can then use FD_CLR to clear the appropriate bit.
Easy enough, but do not forget the +1 in the first parameter slot on the select. One well known author admits to spending two hours finding this mistake in a server program.

Pointers on pointers

I hesitate to offer advice in this area, but a quick introduction to an important feature of C that gives many students trouble may be helpful Fortunately for me, I came to C after a great deal of experience in assembly language so I had little trouble grasping and applauding the basic idea. A pointer is an address, it is not the thing, it is the address of the thing. I still had to master the syntax and would make three points:
  1. the phrase int *p simply means that what p points to is an int.

  2. if p is a pointer whose value is the address of q, ie, if p=&q, then coding q=2 or *p=2 will accomplish exactly the same thing.

  3. pointers to different types of data differ in several ways, most notably with respect to pointer arithmetic. *p++ will add 1 to a pointer, but 1 what? The sizeof the data type to which p is a pointer.

When do you use pointers? The packet sniffing exercises provide a good example. You are reading from a file that has been produced by packet sniffing software. The bytes in that file have an organization which is not fixed, but depends heavily on the type and, therefore, the size of the packets. You can begin by reading the ethernet header into a data area whose length wants to match exactly the length of the ethernet header. You may define a struct of type ether_header and call the fread with sizeof(ether_header) as one of the arguments. This is not a pointer. You actually have defined space to hold the data. The ethernet header contains information concerning the length of the ethernet frame so, once you have the header, you can read the rest of the frame into some data area. Now you start looking at that data which you have already read into memory. It has an ip header which, conveniently, begins at the beginning of your data area, but, depending on the type, it might be a udp datagram, say. Now you need a pointer to a udp header, and you need to get it pointing at just the right place. Enter pointer arithmetic to get you there, and then, a struct to let you refer to the data in that udp header. Now you have a pointer to a struct, and you need to refer to specific items. It actually is rather simple. If field1 is a member of a struct p, then p.field1 is the way you refer to it. However, if p is a pointer to a struct, this is not correct. You could always code (*p).field1, and it is important to realize that you could do so. However, you do not code it this way because there is an alternative, namely, p->field1 that means the same thing.

Tips

  • This tip comes from a student, David Vos, who alerted me to the value of perror. Just yesterday I was getting some help from a colleague, Joel Adams, on a concurrent server in which an accept was failing. The error message simply said, "Accept failed." Not helpful. I changed the code to use perror, and the error message was so clear and descriptive that Joel immediately knew what was wrong. David's approach involves defining a macro (#define P_ERROR(mesg) {perror(mesg);exit(-1);}. In the body of the program he then simply codes P_ERROR("accept failed").

Traps and Pitfalls

  • Never use strncpy to move data in packets. Use memcpy!

  • Everyone has coded = instead of == in an if statement. Probably not too many have have coded < instead of <<. (...)&(0xf<2) did not give me what I wanted. At least it was easy to find.

  • You code something like this: if(recv(...)<0). Then you realize that you need to keep track of the number of bytes received. Easy! You code if(no_bytes=recv(...)<0). Wrong, wrong, wrong! Precedence, precedence, precedence! This one at least was not too hard to find.

  • In building your gateway you use sizeof(buffer) in a recv call, buffer being defined as char buffer[1000]. Everything is working fine, but you then decide to move the receiving of the message into a function passing the address of buffer as a parameter. In your function buffer is defined as char *buffer. Now you discover that your gateway doesn't seem to be sending all the information from one host to the other. You debug and discover that the receiving host is only getting 4 bytes. Actually, the gateway is sending more but you told the recv function to accept only 4 since sizeof(buffer) is now 4. It's an address! Dumb, dumb, dumb! And it took me 3 hours to track down.

Frequently Asked Questions

  1. How do I call C functions in a C++ program?
  2. How do I put an int into a string?


How do I call C functions in a C++ program?

To include "C" functions in your C++ program you have to declare them as:

	 extern "C" 

For example type in your C++ program at the beginning.
 extern "C" int passiveTCP(const char *service, int qlen);
 extern "C" int errexit(const char *format, ...);


How do I put an int into a string?

You can use "snprintf()" to convert integers to string and to format the output. int snprintf(char *s, size_t n, const char *format, /* args */ ... ); snprintf works like printf, however instead of sending the output to stdout, it sends it to the buffer whose address is the first argument. The variable n specifies the size of the buffer and prevents overflow. snprintf is safer than the similar sprintf function. that "s" has enough space to store the output of sprintf. The format string is a combination of characters to be printed and descriptors. For example, suppose we have a character array called number and an integer called answer. We wish to form an English statement in a buffer, character array, buff. We could code snprintf(buff,sizeof(buff), "\nThe answer to problem %s is %d",number,answer);.



This site is maintained by
W. David Laverell of the Computer Science Department at Calvin College. For assistance or corrections, please contact him at lave@calvin.edu.