|
|
In the UNIX system, most servers are accessed at well known Internet addresses or UNIX domain names. The form of their main loop is illustrated by the following code from a remote-login server:
main(argc, argv)
int argc;
char *argv[];
{
int f;
struct sockaddr_in from;
struct sockaddr_in sin;
struct servent *sp;
sp = getservbyname("login", "tcp");
if (sp == NULL) {
fprintf(stderr,
"rlogind: tcp/login: unknown service\n");
exit(1);
}
...
#ifndef DEBUG
/* Disassociate server from controlling terminal. */
...
#endif
sin.sin_len = sizeof(sin);
sin.sin_port = sp->s_port; /* Restricted port */
sin.sin_addr.s_addr = INADDR_ANY;
...
f = socket(AF_INET, SOCK_STREAM, 0);
...
if (bind(f, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
...
}
...
listen(f, 5);
for (;;) {
int g, len = sizeof(from);
g = accept(f, (struct sockaddr *) &from, &len);
if (g < 0) {
if (errno != EINTR)
syslog(LOG_ERR, "rlogind: accept: %m");
continue;
}
if (fork() == 0) {
close(f);
doit(g, &from);
}
close(g);
}
exit(0);
}
The first step taken by the server is look up its service
definition:
sp = getservbyname("login", "tcp");
if (sp == NULL) {
fprintf(stderr,
"rlogind: tcp/login: unknown service\n");
exit(1);
}
The result of the
getservbyname
call is used in later portions
of the code to define the Internet port at which it listens for service
requests (indicated by a connection).
Some standard port numbers are
given in the file
/usr/include/netinet/in.h
for backward compatibility purposes.
Step two is to disassociate the server from the controlling terminal of its invoker:
for (i = getdtablesize()-1; i >= 0; --i) close(i);This step is important as the server will probably not want to receive signals delivered to the process group of the controlling terminal. Note, however, that once a server has disassociated itself it can no longer send reports of errors to a terminal, and must log errors via syslog.open("/dev/null", O_RDONLY); dup2(0, 1); dup2(0, 2);
i = open("/dev/tty", O_RDWR); if (i >= 0) { ioctl(i, TIOCNOTTY, 0); close(i); }
Once a server has established a pristine environment, it creates a socket and begins accepting service requests. The bind call is required to insure the server listens at its expected location. Note that the remote login server listens at a restricted port number, and must therefore be run with a user-id of root. This concept of a ``restricted port number'' is covered in the ``Advanced topics''.
The main body of the loop is simple:
for (;;) {
int g, len = sizeof(from);
g = accept(f, (struct sockaddr *)&from, &len);
if (g < 0) {
if (errno != EINTR)
syslog(LOG_ERR, "rlogind: accept: %m");
continue;
}
if (fork() == 0) { /* Child */
close(f);
doit(g, &from);
}
close(g); /* Parent */
}
An
accept
call blocks the server until
a client requests service.
This call could return a
failure status if the call is interrupted by a signal
such as
SIGCHLD
(to be discussed in
``Advanced topics'').
Therefore,
the return value from
accept
is checked to insure
a connection has been established, and
an error report is logged via
syslog
if an error
has occurred.
With a connection in hand, the server then forks a child process and invokes the main body of the remote login protocol processing. Note how the socket used by the parent for queuing connection requests is closed in the child, while the socket created as a result of the accept is closed in the parent. The address of the client is also handed to the doit routine because it requires it in authenticating clients.