For example, suppose that we want to write jtelnet. This is a program that works just like "telnet" (for the most part). The user specifies a hostname and a port, and jtelnet performs a request_connection() to that hostname and port.
Once the connection is made, jtelnet will take any bytes that come from the socket connection and print them out on standard output, and it will take any input from standard input, and write it onto the socket connection.
Jtelnet needs select() because it needs to probe the operating system to discern whether there are bytes to be read from stdin or from the socket connection.
Read the book's treatment of select(). This is a very nice description.
The arguments to select() are as follows:
int select (int width, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)The data types fd_set and struct timeval are defined in sys/types.h and sys/time.h. In this class, we won't care about selecting for writable fd's or for exceptional conditions, so we will always use select() as follows:
select(width, readfds, NULL, NULL, NULL);Readfds is a bitmap of file descriptors. Select() will return when any of the file descriptors in the bitmap has something to read. When it returns, readfds will be a new bitmap containing all the file descriptors that have something read. In other words, a read() call on that file descriptor is guaranteed not to block. It may return with a value of zero (i.e. end of file), but it is guaranteed to return.
We manipulate the bitmaps with the macros:
FD_SET (fd, &fdset) FD_CLR (fd, &fdset) FD_ISSET (fd, &fdset) FD_ZERO (&fdset)where fd is an int and fdset is a fd_set. So, to make a bitmap that has file descriptors 0 and 3 set, we would do:
fd_set fds; FD_ZERO(&fds); FD_SET(0, &fds); FD_SET(3, &fds);We use FD_CLR() to clear bits from the bitmap, and FD_ISSET() to test to see if a particular bit is set.
Now, jtelnet (in jtelnet.c):
First, we mess with argc and argv and do the requisite request_connection:
#include "socketfun.h" #include#include #include main(int argc, char **argv) { int port; int fd; char *hostname, buf[100]; fd_set readfds, masterfds; int nopen, n; if (argc != 3) { fprintf(stderr, "usage: jtelnet host port\n"); exit(1); } hostname = argv[1]; port = atoi(argv[2]); fd = request_connection(hostname, port);
FD_ZERO(&masterfds); FD_SET(0, &masterfds); FD_SET(fd, &masterfds);
while (1) { memcpy(&readfds, &masterfds, sizeof(fd_set)); if (select(fd+1, &readfds, NULL, NULL, NULL) < 0) { perror("on select"); exit(1); } if (FD_ISSET(0, &readfds)) { n = read(0, buf, 100); if (n == 0) { close(0); close(fd); exit(1); } else { write(fd, buf, n); } } if (FD_ISSET(fd, &readfds)) { n = read(fd, buf, 100); if (n == 0) { close(0); close(fd); exit(1); } else { write(1, buf, n); } } } }
Telnet is a nice program because it is a generalized client. Many server programs exist on the net that serve connections to which anyone may connect via telnet. For example, there is a weather server at Michigan which is at host blackbird.wunderground.com and port 3000. If you telnet to it (or jtelnet), you can get current weather conditions. This is a very powerful paradigm. Telnet is nice because it allows the people at Michigan to set up a server without providing client code -- we already have the client: telnet. Other examples of general client programs are Netscape and ftp.