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:
- Include the files
sys/time.h (for the required
time structure), sys/types.h (which itself includes
sys/select.h , and
unistd.h .
- Define a variable of type
struct timeval , say
mytime .
- 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.
- Initialize the bits in your array using the macro
FD_ZERO , FD_ZERO(&rfds) .
- Initialize a variable
sd_max to have the larger
value of the two sockets.
- Presumably you have a loop in which you are waiting for a
datagram to arrive on one of your two sockets. In this loop
- code
FD_SET(sd_client,&rfds)
- code
FD_SET(sd_server,&rfds)
- code
mytime.tv_sec=10
- code
mytime.tv_usec=0
- test the return value of
select(sd_max+1,&rfds,(fd_set *)0,(fd_set
*)0,&mytime)
- if negative, you have an error condition
- if zero, you have a timeout
- otherwise you have the number of sockets on which an event
took place
- 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:
- the phrase
int *p simply means that what
p points to is an int .
- 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.
- 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
-
How do I call C
functions in a C++ program?
-
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 . |
|