|
|
1.1 root 1: .\" Copyright (c) 1986 The Regents of the University of California.
2: .\" All rights reserved.
3: .\"
4: .\" Redistribution and use in source and binary forms are permitted
5: .\" provided that the above copyright notice and this paragraph are
6: .\" duplicated in all such forms and that any documentation,
7: .\" advertising materials, and other materials related to such
8: .\" distribution and use acknowledge that the software was developed
9: .\" by the University of California, Berkeley. The name of the
10: .\" University may not be used to endorse or promote products derived
11: .\" from this software without specific prior written permission.
12: .\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
13: .\" IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
14: .\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
15: .\"
16: .\" @(#)4.t 1.4 (Berkeley) 3/7/89
17: .\"
18: .\".ds RH "Client/Server Model
19: .bp
20: .nr H1 4
21: .nr H2 0
22: .sp 8i
23: .bp
24: .LG
25: .B
26: .ce
27: 4. CLIENT/SERVER MODEL
28: .sp 2
29: .R
30: .NL
31: .PP
32: The most commonly used paradigm in constructing distributed applications
33: is the client/server model. In this scheme client applications request
34: services from a server process. This implies an asymmetry in establishing
35: communication between the client and server which has been examined
36: in section 2. In this section we will look more closely at the interactions
37: between client and server, and consider some of the problems in developing
38: client and server applications.
39: .PP
40: The client and server require a well known set of conventions before
41: service may be rendered (and accepted). This set of conventions
42: comprises a protocol which must be implemented at both ends of a
43: connection. Depending on the situation, the protocol may be symmetric
44: or asymmetric. In a symmetric protocol, either side may play the
45: master or slave roles. In an asymmetric protocol, one side is
46: immutably recognized as the master, with the other as the slave.
47: An example of a symmetric protocol is the TELNET protocol used in
48: the Internet for remote terminal emulation. An example
49: of an asymmetric protocol is the Internet file transfer protocol,
50: FTP. No matter whether the specific protocol used in obtaining
51: a service is symmetric or asymmetric, when accessing a service there
52: is a \*(lqclient process\*(rq and a \*(lqserver process\*(rq. We
53: will first consider the properties of server processes, then
54: client processes.
55: .PP
56: A server process normally listens at a well known address for
57: service requests. That is, the server process remains dormant
58: until a connection is requested by a client's connection
59: to the server's address. At such a time
60: the server process ``wakes up'' and services the client,
61: performing whatever appropriate actions the client requests of it.
62: .PP
63: Alternative schemes which use a service server
64: may be used to eliminate a flock of server processes clogging the
65: system while remaining dormant most of the time. For Internet
66: servers in 4.3BSD,
67: this scheme has been implemented via \fIinetd\fP, the so called
68: ``internet super-server.'' \fIInetd\fP listens at a variety
69: of ports, determined at start-up by reading a configuration file.
70: When a connection is requested to a port on which \fIinetd\fP is
71: listening, \fIinetd\fP executes the appropriate server program to handle the
72: client. With this method, clients are unaware that an
73: intermediary such as \fIinetd\fP has played any part in the
74: connection. \fIInetd\fP will be described in more detail in
75: section 5.
76: .PP
77: A similar alternative scheme is used by most Xerox services. In general,
78: the Courier dispatch process (if used) accepts connections from
79: processes requesting services of some sort or another. The client
80: processes request a particular <program number, version number, procedure
81: number> triple. If the dispatcher knows of such a program, it is
82: started to handle the request; if not, an error is reported to the
83: client. In this way, only one port is required to service a large
84: variety of different requests. Again, the Courier facilities are
85: not available without the use and installation of the Courier
86: compiler. The information presented in this section applies only
87: to NS clients and services that do not use Courier.
88: .NH 2
89: Servers
90: .PP
91: In 4.3BSD most servers are accessed at well known Internet addresses
92: or UNIX domain names. For
93: example, the remote login server's main loop is of the form shown
94: in Figure 2.
95: .KF
96: .if t .ta .5i 1.0i 1.5i 2.0i 2.5i 3.0i 3.5i
97: .if n .ta .7i 1.4i 2.1i 2.8i 3.5i 4.2i 4.9i
98: .sp 0.5i
99: .DS
100: main(argc, argv)
101: int argc;
102: char *argv[];
103: {
104: int f;
105: struct sockaddr_in from;
106: struct servent *sp;
107:
108: sp = getservbyname("login", "tcp");
109: if (sp == NULL) {
110: fprintf(stderr, "rlogind: tcp/login: unknown service\en");
111: exit(1);
112: }
113: ...
114: #ifndef DEBUG
115: /* Disassociate server from controlling terminal */
116: ...
117: #endif
118:
119: sin.sin_port = sp->s_port; /* Restricted port -- see section 5 */
120: ...
121: f = socket(AF_INET, SOCK_STREAM, 0);
122: ...
123: if (bind(f, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
124: ...
125: }
126: ...
127: listen(f, 5);
128: for (;;) {
129: int g, len = sizeof (from);
130:
131: g = accept(f, (struct sockaddr *) &from, &len);
132: if (g < 0) {
133: if (errno != EINTR)
134: syslog(LOG_ERR, "rlogind: accept: %m");
135: continue;
136: }
137: if (fork() == 0) {
138: close(f);
139: doit(g, &from);
140: }
141: close(g);
142: }
143: }
144: .DE
145: .ce
146: Figure 2. Remote login server.
147: .sp 0.5i
148: .KE
149: .PP
150: The first step taken by the server is look up its service
151: definition:
152: .sp 1
153: .nf
154: .in +5
155: .if t .ta .5i 1.0i 1.5i 2.0i
156: .if n .ta .7i 1.4i 2.1i 2.8i
157: sp = getservbyname("login", "tcp");
158: if (sp == NULL) {
159: fprintf(stderr, "rlogind: tcp/login: unknown service\en");
160: exit(1);
161: }
162: .sp 1
163: .in -5
164: .fi
165: The result of the \fIgetservbyname\fP call
166: is used in later portions of the code to
167: define the Internet port at which it listens for service
168: requests (indicated by a connection).
169: .KS
170: .PP
171: Step two is to disassociate the server from the controlling
172: terminal of its invoker:
173: .DS
174: for (i = 0; i < 3; ++i)
175: close(i);
176:
177: open("/", O_RDONLY);
178: dup2(0, 1);
179: dup2(0, 2);
180:
181: i = open("/dev/tty", O_RDWR);
182: if (i >= 0) {
183: ioctl(i, TIOCNOTTY, 0);
184: close(i);
185: }
186: .DE
187: .KE
188: This step is important as the server will
189: likely not want to receive signals delivered to the process
190: group of the controlling terminal. Note, however, that
191: once a server has disassociated itself it can no longer
192: send reports of errors to a terminal, and must log errors
193: via \fIsyslog\fP.
194: .PP
195: Once a server has established a pristine environment, it
196: creates a socket and begins accepting service requests.
197: The \fIbind\fP call is required to insure the server listens
198: at its expected location. It should be noted that the
199: remote login server listens at a restricted port number, and must
200: therefore be run
201: with a user-id of root.
202: This concept of a ``restricted port number'' is 4BSD
203: specific, and is covered in section 5.
204: .PP
205: The main body of the loop is fairly simple:
206: .DS
207: .if t .ta .5i 1.0i 1.5i 2.0i
208: .if n .ta .7i 1.4i 2.1i 2.8i
209: for (;;) {
210: int g, len = sizeof (from);
211:
212: g = accept(f, (struct sockaddr *)&from, &len);
213: if (g < 0) {
214: if (errno != EINTR)
215: syslog(LOG_ERR, "rlogind: accept: %m");
216: continue;
217: }
218: if (fork() == 0) { /* Child */
219: close(f);
220: doit(g, &from);
221: }
222: close(g); /* Parent */
223: }
224: .DE
225: An \fIaccept\fP call blocks the server until
226: a client requests service. This call could return a
227: failure status if the call is interrupted by a signal
228: such as SIGCHLD (to be discussed in section 5). Therefore,
229: the return value from \fIaccept\fP is checked to insure
230: a connection has actually been established, and
231: an error report is logged via \fIsyslog\fP if an error
232: has occurred.
233: .PP
234: With a connection
235: in hand, the server then forks a child process and invokes
236: the main body of the remote login protocol processing. Note
237: how the socket used by the parent for queuing connection
238: requests is closed in the child, while the socket created as
239: a result of the \fIaccept\fP is closed in the parent. The
240: address of the client is also handed the \fIdoit\fP routine
241: because it requires it in authenticating clients.
242: .NH 2
243: Clients
244: .PP
245: The client side of the remote login service was shown
246: earlier in Figure 1.
247: One can see the separate, asymmetric roles of the client
248: and server clearly in the code. The server is a passive entity,
249: listening for client connections, while the client process is
250: an active entity, initiating a connection when invoked.
251: .PP
252: Let us consider more closely the steps taken
253: by the client remote login process. As in the server process,
254: the first step is to locate the service definition for a remote
255: login:
256: .DS
257: sp = getservbyname("login", "tcp");
258: if (sp == NULL) {
259: fprintf(stderr, "rlogin: tcp/login: unknown service\en");
260: exit(1);
261: }
262: .DE
263: Next the destination host is looked up with a
264: \fIgethostbyname\fP call:
265: .DS
266: hp = gethostbyname(argv[1]);
267: if (hp == NULL) {
268: fprintf(stderr, "rlogin: %s: unknown host\en", argv[1]);
269: exit(2);
270: }
271: .DE
272: With this accomplished, all that is required is to establish a
273: connection to the server at the requested host and start up the
274: remote login protocol. The address buffer is cleared, then filled
275: in with the Internet address of the foreign host and the port
276: number at which the login process resides on the foreign host:
277: .DS
278: bzero((char *)&server, sizeof (server));
279: bcopy(hp->h_addr, (char *) &server.sin_addr, hp->h_length);
280: server.sin_family = hp->h_addrtype;
281: server.sin_port = sp->s_port;
282: .DE
283: A socket is created, and a connection initiated. Note
284: that \fIconnect\fP implicitly performs a \fIbind\fP
285: call, since \fIs\fP is unbound.
286: .DS
287: s = socket(hp->h_addrtype, SOCK_STREAM, 0);
288: if (s < 0) {
289: perror("rlogin: socket");
290: exit(3);
291: }
292: ...
293: if (connect(s, (struct sockaddr *) &server, sizeof (server)) < 0) {
294: perror("rlogin: connect");
295: exit(4);
296: }
297: .DE
298: The details of the remote login protocol will not be considered here.
299: .NH 2
300: Connectionless servers
301: .PP
302: While connection-based services are the norm, some services
303: are based on the use of datagram sockets. One, in particular,
304: is the \*(lqrwho\*(rq service which provides users with status
305: information for hosts connected to a local area
306: network. This service, while predicated on the ability to
307: \fIbroadcast\fP information to all hosts connected to a particular
308: network, is of interest as an example usage of datagram sockets.
309: .PP
310: A user on any machine running the rwho server may find out
311: the current status of a machine with the \fIruptime\fP(1) program.
312: The output generated is illustrated in Figure 3.
313: .KF
314: .DS B
315: .TS
316: l r l l l l l.
317: arpa up 9:45, 5 users, load 1.15, 1.39, 1.31
318: cad up 2+12:04, 8 users, load 4.67, 5.13, 4.59
319: calder up 10:10, 0 users, load 0.27, 0.15, 0.14
320: dali up 2+06:28, 9 users, load 1.04, 1.20, 1.65
321: degas up 25+09:48, 0 users, load 1.49, 1.43, 1.41
322: ear up 5+00:05, 0 users, load 1.51, 1.54, 1.56
323: ernie down 0:24
324: esvax down 17:04
325: ingres down 0:26
326: kim up 3+09:16, 8 users, load 2.03, 2.46, 3.11
327: matisse up 3+06:18, 0 users, load 0.03, 0.03, 0.05
328: medea up 3+09:39, 2 users, load 0.35, 0.37, 0.50
329: merlin down 19+15:37
330: miro up 1+07:20, 7 users, load 4.59, 3.28, 2.12
331: monet up 1+00:43, 2 users, load 0.22, 0.09, 0.07
332: oz down 16:09
333: statvax up 2+15:57, 3 users, load 1.52, 1.81, 1.86
334: ucbvax up 9:34, 2 users, load 6.08, 5.16, 3.28
335: .TE
336: .DE
337: .ce
338: Figure 3. ruptime output.
339: .sp
340: .KE
341: .PP
342: Status information for each host is periodically broadcast
343: by rwho server processes on each machine. The same server
344: process also receives the status information and uses it
345: to update a database. This database is then interpreted
346: to generate the status information for each host. Servers
347: operate autonomously, coupled only by the local network and
348: its broadcast capabilities.
349: .PP
350: Note that the use of broadcast for such a task is fairly inefficient,
351: as all hosts must process each message, whether or not using an rwho server.
352: Unless such a service is sufficiently universal and is frequently used,
353: the expense of periodic broadcasts outweighs the simplicity.
354: .PP
355: The rwho server, in a simplified form, is pictured in Figure
356: 4. There are two separate tasks performed by the server. The
357: first task is to act as a receiver of status information broadcast
358: by other hosts on the network. This job is carried out in the
359: main loop of the program. Packets received at the rwho port
360: are interrogated to insure they've been sent by another rwho
361: server process, then are time stamped with their arrival time
362: and used to update a file indicating the status of the host.
363: When a host has not been heard from for an extended period of
364: time, the database interpretation routines assume the host is
365: down and indicate such on the status reports. This algorithm
366: is prone to error as a server may be down while a host is actually
367: up, but serves our current needs.
368: .KF
369: .DS
370: .if t .ta .5i 1.0i 1.5i 2.0i
371: .if n .ta .7i 1.4i 2.1i 2.8i
372: main()
373: {
374: ...
375: sp = getservbyname("who", "udp");
376: net = getnetbyname("localnet");
377: sin.sin_addr = inet_makeaddr(INADDR_ANY, net);
378: sin.sin_port = sp->s_port;
379: ...
380: s = socket(AF_INET, SOCK_DGRAM, 0);
381: ...
382: on = 1;
383: if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) {
384: syslog(LOG_ERR, "setsockopt SO_BROADCAST: %m");
385: exit(1);
386: }
387: bind(s, (struct sockaddr *) &sin, sizeof (sin));
388: ...
389: signal(SIGALRM, onalrm);
390: onalrm();
391: for (;;) {
392: struct whod wd;
393: int cc, whod, len = sizeof (from);
394:
395: cc = recvfrom(s, (char *)&wd, sizeof (struct whod), 0,
396: (struct sockaddr *)&from, &len);
397: if (cc <= 0) {
398: if (cc < 0 && errno != EINTR)
399: syslog(LOG_ERR, "rwhod: recv: %m");
400: continue;
401: }
402: if (from.sin_port != sp->s_port) {
403: syslog(LOG_ERR, "rwhod: %d: bad from port",
404: ntohs(from.sin_port));
405: continue;
406: }
407: ...
408: if (!verify(wd.wd_hostname)) {
409: syslog(LOG_ERR, "rwhod: malformed host name from %x",
410: ntohl(from.sin_addr.s_addr));
411: continue;
412: }
413: (void) sprintf(path, "%s/whod.%s", RWHODIR, wd.wd_hostname);
414: whod = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
415: ...
416: (void) time(&wd.wd_recvtime);
417: (void) write(whod, (char *)&wd, cc);
418: (void) close(whod);
419: }
420: }
421: .DE
422: .ce
423: Figure 4. rwho server.
424: .sp
425: .KE
426: .PP
427: The second task performed by the server is to supply information
428: regarding the status of its host. This involves periodically
429: acquiring system status information, packaging it up in a message
430: and broadcasting it on the local network for other rwho servers
431: to hear. The supply function is triggered by a timer and
432: runs off a signal. Locating the system status
433: information is somewhat involved, but uninteresting. Deciding
434: where to transmit the resultant packet
435: is somewhat problematical, however.
436: .PP
437: Status information must be broadcast on the local network.
438: For networks which do not support the notion of broadcast another
439: scheme must be used to simulate or
440: replace broadcasting. One possibility is to enumerate the
441: known neighbors (based on the status messages received
442: from other rwho servers). This, unfortunately,
443: requires some bootstrapping information,
444: for a server will have no idea what machines are its
445: neighbors until it receives status messages from them.
446: Therefore, if all machines on a net are freshly booted,
447: no machine will have any
448: known neighbors and thus never receive, or send, any status information.
449: This is the identical problem faced by the routing table management
450: process in propagating routing status information. The standard
451: solution, unsatisfactory as it may be, is to inform one or more servers
452: of known neighbors and request that they always communicate with
453: these neighbors. If each server has at least one neighbor supplied
454: to it, status information may then propagate through
455: a neighbor to hosts which
456: are not (possibly) directly neighbors. If the server is able to
457: support networks which provide a broadcast capability, as well as
458: those which do not, then networks with an
459: arbitrary topology may share status information*.
460: .FS
461: * One must, however, be concerned about \*(lqloops\*(rq.
462: That is, if a host is connected to multiple networks, it
463: will receive status information from itself. This can lead
464: to an endless, wasteful, exchange of information.
465: .FE
466: .PP
467: It is important that software operating in a distributed
468: environment not have any site-dependent information compiled into it.
469: This would require a separate copy of the server at each host and
470: make maintenance a severe headache. 4.3BSD attempts to isolate
471: host-specific information from applications by providing system
472: calls which return the necessary information*.
473: .FS
474: * An example of such a system call is the \fIgethostname\fP(2)
475: call which returns the host's \*(lqofficial\*(rq name.
476: .FE
477: A mechanism exists, in the form of an \fIioctl\fP call,
478: for finding the collection
479: of networks to which a host is directly connected.
480: Further, a local network broadcasting mechanism
481: has been implemented at the socket level.
482: Combining these two features allows a process
483: to broadcast on any directly connected local
484: network which supports the notion of broadcasting
485: in a site independent manner. This allows 4.3BSD
486: to solve the problem of deciding how to propagate
487: status information in the case of \fIrwho\fP, or
488: more generally in broadcasting:
489: Such status information is broadcast to connected
490: networks at the socket level, where the connected networks
491: have been obtained via the appropriate \fIioctl\fP
492: calls.
493: The specifics of
494: such broadcastings are complex, however, and will
495: be covered in section 5.
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.