|
|
A server process normally listens at a well-known address for service requests. ``Well-known'' means that port assignments for services are usually stable and can be seen in /etc/services. Alternative schemes which use a service server can be used to eliminate a number of server processes clogging the system while remaining dormant most of the time. Such a scheme is used in the inetd program of SCO TCP/IP. See inetd(ADMN).
When accessing one of the standard network services provided by SCO TCP/IP, a client program such as ftp(TC), telnet(TC), or rlogin(TC) contacts the corresponding server at the remote host. However, it is inetd that selectively determines and processes the incoming request from the client by examining the port number and protocol (either TCP or UDP) used by the requesting client program. inetd then creates the appropriate server process based on a database (/etc/inetd.conf) and ``splices'' the client and server together, voiding its part in the transaction. This scheme is attractive in that the inetd server process can provide a single contact point for all services, as well as carrying out the initial steps of connection establishment.
While this is an attractive possibility for standardizing access to services, it does introduce a certain amount of overhead due to the intermediate process involved. Implementations which provide this type of service should try to minimize the cost of client server rendezvous.
In SCO TCP/IP, most services are accessed at well-known Internet addresses. When inetd is started at boot time, it advertises the services configured in /etc/inetd.conf by setting up a separate socket for each of the configured services, and listening at well-known port numbers.
A much simpler approach, used in older BSD TCP/IP implementations, is for each server program to be started separately at system boot time and to listen for requests for their own services, as shown in ``Main loop of a remote login server''.
For example, a simple remote login server's main loop might be:
Main loop of a remote login server
/* include header files */ main(argc, argv) int argc; char **argv; { int f; struct sockaddr_in from; struct servent *sp;/* disassociate server from controlling terminal */
sp = getservbyname("login", "tcp"); if (sp == NULL) { fprintf(stderr, "rlogind: tcp/login: unknown service\n"); exit(1); } sin.sin_port = sp->s_port; /* ... */ f = socket(AF_INET, SOCK_STREAM, 0); /* ... */ if (bind(f, (struct sockaddr *) &sin, sizeof (sin)) < 0) { /* ... */ } /* ... */ listen(f, 5); for (;;) { int g, len = sizeof(struct sockaddr_in); g = accept(f, (struct sockaddr *) &from, &len); if (g < 0) { if (errno != EINTR) perror("rlogind: accept"); continue; } if (fork() == 0) { close(f); doit(g, &from); } close(g); } }
Once a server has established a pristine environment, it creates a socket and begins accepting service requests. The bind call is required to ensure that the server listens at its expected location. The main body of the loop is fairly simple:
Main body of the loop
for (;;) { int g, len = sizeof(struct sockaddr_in); g = accept(f, (struct sockaddr *) &from, &len); if (g < 0) { if (errno != EINTR) perror("rlogind: accept"); continue; } if (fork() == 0) { close(f); doit(g, &from); } close(g); }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. Therefore, the return value from accept is checked to ensure that a connection has actually been established. With a connection in hand, the server then forks a child process and invokes the main body of the remote login protocol processing. The socket used by the parent for queueing 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 the address is required to authenticate clients.