Commit | Line | Data |
---|---|---|
f0b8d567 C |
1 | .ds RH "Client/Server Model |
2 | .bp | |
3 | .nr H1 4 | |
4 | .nr H2 0 | |
5 | .bp | |
6 | .LG | |
7 | .B | |
8 | .ce | |
9 | 4. CLIENT/SERVER MODEL | |
10 | .sp 2 | |
11 | .R | |
12 | .NL | |
13 | .PP | |
14 | The most commonly used paradigm in constructing distributed applications | |
15 | is the client/server model. In this scheme client applications request | |
16 | services from a server process. This implies an asymmetry in establishing | |
17 | communication between the client and server which has been examined | |
18 | in section 2. In this section we will look more closely at the interactions | |
19 | between client and server, and consider some of the problems in developing | |
20 | client and server applications. | |
21 | .PP | |
22 | Client and server require a well known set of conventions before | |
23 | service may be rendered (and accepted). This set of conventions | |
24 | comprises a protocol which must be implemented at both ends of a | |
25 | connection. Depending on the situation, the protocol may be symmetric | |
26 | or asymmetric. In a symmetric protocol, either side may play the | |
27 | master or slave roles. In an asymmetric protocol, one side is | |
28 | immutably recognized as the master, with the other the slave. | |
29 | An example of a symmetric protocol is the TELNET protocol used in | |
30 | the Internet for remote terminal emulation. An example | |
31 | of an asymmetric protocol is the Internet file transfer protocol, | |
32 | FTP. No matter whether the specific protocol used in obtaining | |
33 | a service is symmetric or asymmetric, when accessing a service there | |
34 | is a \*(lqclient process\*(rq and a \*(lqserver process\*(rq. We | |
35 | will first consider the properties of server processes, then | |
36 | client processes. | |
37 | .PP | |
38 | A server process normally listens at a well know address for | |
39 | service requests. Alternative schemes which use a service server | |
40 | may be used to eliminate a flock of server processes clogging the | |
41 | system while remaining dormant most of the time. The Xerox | |
42 | Courier protocol uses the latter scheme. When using Courier, a | |
43 | Courier client process contacts a Courier server at the remote | |
44 | host and identifies the service it requires. The Courier server | |
45 | process then creates the appropriate server process based on a | |
46 | data base and \*(lqsplices\*(rq the client and server together, | |
47 | voiding its part in the transaction. This scheme is attractive | |
48 | in that the Courier server process may provide a single contact | |
49 | point for all services, as well as carrying out the initial steps | |
50 | in authentication. However, while this is an attractive possibility | |
51 | for standardizing access to services, it does introduce a certain | |
52 | amount of overhead due to the intermediate process involved. | |
53 | Implementations which provide this type of service within the | |
54 | system can minimize the cost of client server | |
55 | rendezvous. The \fIportal\fP notion described | |
56 | in the \*(lq4.2BSD System Manual\*(rq embodies many of the ideas | |
57 | found in Courier, with the rendezvous mechanism implemented internal | |
58 | to the system. | |
59 | .NH 2 | |
60 | Servers | |
61 | .PP | |
62 | In 4.2BSD most servers are accessed at well known Internet addresses | |
63 | or UNIX domain names. When a server is started at boot time it | |
64 | advertises it services by listening at a well know location. For | |
65 | example, the remote login server's main loop is of the form shown | |
66 | in Figure 2. | |
67 | .KF | |
68 | .if t .ta .5i 1.0i 1.5i 2.0i | |
69 | .if n .ta .7i 1.4i 2.1i 2.8i | |
70 | .DS | |
71 | main(argc, argv) | |
72 | int argc; | |
73 | char **argv; | |
74 | { | |
75 | int f; | |
76 | struct sockaddr_in from; | |
77 | struct servent *sp; | |
78 | ||
79 | sp = getservbyname("login", "tcp"); | |
80 | if (sp == NULL) { | |
81 | fprintf(stderr, "rlogind: tcp/login: unknown service\en"); | |
82 | exit(1); | |
83 | } | |
84 | ... | |
85 | #ifndef DEBUG | |
86 | <<disassociate server from controlling terminal>> | |
87 | #endif | |
88 | ... | |
89 | sin.sin_port = sp->s_port; | |
90 | ... | |
91 | f = socket(AF_INET, SOCK_STREAM, 0); | |
92 | ... | |
93 | if (bind(f, (caddr_t)&sin, sizeof (sin)) < 0) { | |
94 | ... | |
95 | } | |
96 | ... | |
97 | listen(f, 5); | |
98 | for (;;) { | |
99 | int g, len = sizeof (from); | |
100 | ||
101 | g = accept(f, &from, &len); | |
102 | if (g < 0) { | |
103 | if (errno != EINTR) | |
104 | perror("rlogind: accept"); | |
105 | continue; | |
106 | } | |
107 | if (fork() == 0) { | |
108 | close(f); | |
109 | doit(g, &from); | |
110 | } | |
111 | close(g); | |
112 | } | |
113 | } | |
114 | .DE | |
115 | .ce | |
116 | Figure 2. Remote login server. | |
117 | .sp | |
118 | .KE | |
119 | .PP | |
120 | The first step taken by the server is look up its service | |
121 | definition: | |
122 | .sp 1 | |
123 | .nf | |
124 | .in +5 | |
125 | .if t .ta .5i 1.0i 1.5i 2.0i | |
126 | .if n .ta .7i 1.4i 2.1i 2.8i | |
127 | sp = getservbyname("login", "tcp"); | |
128 | if (sp == NULL) { | |
129 | fprintf(stderr, "rlogind: tcp/login: unknown service\en"); | |
130 | exit(1); | |
131 | } | |
132 | .sp 1 | |
133 | .in -5 | |
134 | .fi | |
135 | This definition is used in later portions of the code to | |
136 | define the Internet port at which it listens for service | |
137 | requests (indicated by a connection). | |
138 | .PP | |
139 | Step two is to disassociate the server from the controlling | |
140 | terminal of its invoker. This is important as the server will | |
141 | likely not want to receive signals delivered to the process | |
142 | group of the controlling terminal. | |
143 | .PP | |
144 | Once a server has established a pristine environment, it | |
145 | creates a socket and begins accepting service requests. | |
146 | The \fIbind\fP call is required to insure the server listens | |
147 | at its expected location. The main body of the loop is | |
148 | fairly simple: | |
149 | .DS | |
150 | .if t .ta .5i 1.0i 1.5i 2.0i | |
151 | .if n .ta .7i 1.4i 2.1i 2.8i | |
152 | for (;;) { | |
153 | int g, len = sizeof (from); | |
154 | ||
155 | g = accept(f, &from, &len); | |
156 | if (g < 0) { | |
157 | if (errno != EINTR) | |
158 | perror("rlogind: accept"); | |
159 | continue; | |
160 | } | |
161 | if (fork() == 0) { | |
162 | close(f); | |
163 | doit(g, &from); | |
164 | } | |
165 | close(g); | |
166 | } | |
167 | .DE | |
168 | An \fIaccept\fP call blocks the server until | |
169 | a client requests service. This call could return a | |
170 | failure status if the call is interrupted by a signal | |
171 | such as SIGCHLD (to be discussed in section 5). Therefore, | |
172 | the return value from \fIaccept\fP is checked to insure | |
173 | a connection has actually been established. With a connection | |
174 | in hand, the server then forks a child process and invokes | |
175 | the main body of the remote login protocol processing. Note | |
176 | how the socket used by the parent for queueing connection | |
177 | requests is closed in the child, while the socket created as | |
178 | a result of the accept is closed in the parent. The | |
179 | address of the client is also handed the \fIdoit\fP routine | |
180 | because it requires it in authenticating clients. | |
181 | .NH 2 | |
182 | Clients | |
183 | .PP | |
184 | The client side of the remote login service was shown | |
185 | earlier in Figure 1. | |
186 | One can see the separate, asymmetric roles of the client | |
187 | and server clearly in the code. The server is a passive entity, | |
188 | listening for client connections, while the client process is | |
189 | an active entity, initiating a connection when invoked. | |
190 | .PP | |
191 | Let us consider more closely the steps taken | |
192 | by the client remote login process. As in the server process | |
193 | the first step is to locate the service definition for a remote | |
194 | login: | |
195 | .DS | |
196 | sp = getservbyname("login", "tcp"); | |
197 | if (sp == NULL) { | |
198 | fprintf(stderr, "rlogin: tcp/login: unknown service\en"); | |
199 | exit(1); | |
200 | } | |
201 | .DE | |
202 | Next the destination host is looked up with a | |
203 | \fIgethostbyname\fP call: | |
204 | .DS | |
205 | hp = gethostbyname(argv[1]); | |
206 | if (hp == NULL) { | |
207 | fprintf(stderr, "rlogin: %s: unknown host\en", argv[1]); | |
208 | exit(2); | |
209 | } | |
210 | .DE | |
211 | With this accomplished, all that is required is to establish a | |
212 | connection to the server at the requested host and start up the | |
213 | remote login protocol. The address buffer is cleared, then filled | |
214 | in with the Internet address of the foreign host and the port | |
215 | number at which the login process resides: | |
216 | .DS | |
217 | bzero((char *)&sin, sizeof (sin)); | |
218 | bcopy(hp->h_addr, (char *)sin.sin_addr, hp->h_length); | |
219 | sin.sin_family = hp->h_addrtype; | |
220 | sin.sin_port = sp->s_port; | |
221 | .DE | |
222 | A socket is created, and a connection initiated. | |
223 | .DS | |
224 | s = socket(hp->h_addrtype, SOCK_STREAM, 0); | |
225 | if (s < 0) { | |
226 | perror("rlogin: socket"); | |
227 | exit(3); | |
228 | } | |
229 | ... | |
230 | if (connect(s, (char *)&sin, sizeof (sin)) < 0) { | |
231 | perror("rlogin: connect"); | |
232 | exit(4); | |
233 | } | |
234 | .DE | |
235 | The details of the remote login protocol will not be considered here. | |
236 | .NH 2 | |
237 | Connectionless servers | |
238 | .PP | |
239 | While connection-based services are the norm, some services | |
240 | are based on the use of datagram sockets. One, in particular, | |
241 | is the \*(lqrwho\*(rq service which provides users with status | |
242 | information for hosts connected to a local area | |
243 | network. This service, while predicated on the ability to | |
244 | \fIbroadcast\fP information to all hosts connected to a particular | |
245 | network, is of interest as an example usage of datagram sockets. | |
246 | .PP | |
247 | A user on any machine running the rwho server may find out | |
248 | the current status of a machine with the \fIruptime\fP(1) program. | |
249 | The output generated is illustrated in Figure 3. | |
250 | .KF | |
251 | .DS B | |
252 | .TS | |
253 | l r l l l l l. | |
254 | arpa up 9:45, 5 users, load 1.15, 1.39, 1.31 | |
255 | cad up 2+12:04, 8 users, load 4.67, 5.13, 4.59 | |
256 | calder up 10:10, 0 users, load 0.27, 0.15, 0.14 | |
257 | dali up 2+06:28, 9 users, load 1.04, 1.20, 1.65 | |
258 | degas up 25+09:48, 0 users, load 1.49, 1.43, 1.41 | |
259 | ear up 5+00:05, 0 users, load 1.51, 1.54, 1.56 | |
260 | ernie down 0:24 | |
261 | esvax down 17:04 | |
262 | ingres down 0:26 | |
263 | kim up 3+09:16, 8 users, load 2.03, 2.46, 3.11 | |
264 | matisse up 3+06:18, 0 users, load 0.03, 0.03, 0.05 | |
265 | medea up 3+09:39, 2 users, load 0.35, 0.37, 0.50 | |
266 | merlin down 19+15:37 | |
267 | miro up 1+07:20, 7 users, load 4.59, 3.28, 2.12 | |
268 | monet up 1+00:43, 2 users, load 0.22, 0.09, 0.07 | |
269 | oz down 16:09 | |
270 | statvax up 2+15:57, 3 users, load 1.52, 1.81, 1.86 | |
271 | ucbvax up 9:34, 2 users, load 6.08, 5.16, 3.28 | |
272 | .TE | |
273 | .DE | |
274 | .ce | |
275 | Figure 3. ruptime output. | |
276 | .sp | |
277 | .KE | |
278 | .PP | |
279 | Status information for each host is periodically broadcast | |
280 | by rwho server processes on each machine. The same server | |
281 | process also receives the status information and uses it | |
282 | to update a database. This database is then interpreted | |
283 | to generate the status information for each host. Servers | |
284 | operate autonomously, coupled only by the local network and | |
285 | its broadcast capabilities. | |
286 | .PP | |
287 | The rwho server, in a simplified form, is pictured in Figure | |
288 | 4. There are two separate tasks performed by the server. The | |
289 | first task is to act as a receiver of status information broadcast | |
290 | by other hosts on the network. This job is carried out in the | |
291 | main loop of the program. Packets received at the rwho port | |
292 | are interrogated to insure they've been sent by another rwho | |
293 | server process, then are time stamped with their arrival time | |
294 | and used to update a file indicating the status of the host. | |
295 | When a host has not been heard from for an extended period of | |
296 | time, the database interpretation routines assume the host is | |
297 | down and indicate such on the status reports. This algorithm | |
298 | is prone to error as a server may be down while a host is actually | |
299 | up, but serves our current needs. | |
300 | .KF | |
301 | .DS | |
302 | .if t .ta .5i 1.0i 1.5i 2.0i | |
303 | .if n .ta .7i 1.4i 2.1i 2.8i | |
304 | main() | |
305 | { | |
306 | ... | |
307 | sp = getservbyname("who", "udp"); | |
308 | net = getnetbyname("localnet"); | |
309 | sin.sin_addr = inet_makeaddr(INADDR_ANY, net); | |
310 | sin.sin_port = sp->s_port; | |
311 | ... | |
312 | s = socket(AF_INET, SOCK_DGRAM, 0); | |
313 | ... | |
314 | bind(s, &sin, sizeof (sin)); | |
315 | ... | |
316 | sigset(SIGALRM, onalrm); | |
317 | onalrm(); | |
318 | for (;;) { | |
319 | struct whod wd; | |
320 | int cc, whod, len = sizeof (from); | |
321 | ||
322 | cc = recvfrom(s, (char *)&wd, sizeof (struct whod), 0, &from, &len); | |
323 | if (cc <= 0) { | |
324 | if (cc < 0 && errno != EINTR) | |
325 | perror("rwhod: recv"); | |
326 | continue; | |
327 | } | |
328 | if (from.sin_port != sp->s_port) { | |
329 | fprintf(stderr, "rwhod: %d: bad from port\en", | |
330 | ntohs(from.sin_port)); | |
331 | continue; | |
332 | } | |
333 | ... | |
334 | if (!verify(wd.wd_hostname)) { | |
335 | fprintf(stderr, "rwhod: malformed host name from %x\en", | |
336 | ntohl(from.sin_addr.s_addr)); | |
337 | continue; | |
338 | } | |
339 | (void) sprintf(path, "%s/whod.%s", RWHODIR, wd.wd_hostname); | |
340 | whod = open(path, FWRONLY|FCREATE|FTRUNCATE, 0666); | |
341 | ... | |
342 | (void) time(&wd.wd_recvtime); | |
343 | (void) write(whod, (char *)&wd, cc); | |
344 | (void) close(whod); | |
345 | } | |
346 | } | |
347 | .DE | |
348 | .ce | |
349 | Figure 4. rwho server. | |
350 | .KE | |
351 | .PP | |
352 | The second task performed by the server is to supply information | |
353 | regarding the status of its host. This involves periodically | |
354 | acquiring system status information, packaging it up in a message | |
355 | and broadcasting it on the local network for other rwho servers | |
356 | to hear. The supply function is triggered by a timer and | |
357 | runs off a signal. Locating the system status | |
358 | information is somewhat involved, but uninteresting. Deciding | |
359 | where to transmit the resultant packet does, however, indicates | |
360 | some problems with the current protocol. | |
361 | .PP | |
362 | Status information is broadcast on the local network. | |
363 | For networks which do not support the notion of broadcast another | |
364 | scheme must be used to simulate or | |
365 | replace broadcasting. One possibility is to enumerate the | |
366 | known neighbors (based on the status received). This, unfortunately, | |
367 | requires some bootstrapping information, as a | |
368 | server started up on a quiet network will have no | |
369 | known neighbors and thus never receive, or send, any status information. | |
370 | This is the identical problem faced by the routing table management | |
371 | process in propagating routing status information. The standard | |
372 | solution, unsatisfactory as it may be, is to inform one or more servers | |
373 | of known neighbors and request that they always communicate with | |
374 | these neighbors. If each server has at least one neighbor supplied | |
375 | it, status information may then propagate through | |
376 | a neighbor to hosts which | |
377 | are not (possibly) directly neighbors. If the server is able to | |
378 | support networks which provide a broadcast capability, as well as | |
379 | those which do not, then networks with an | |
380 | arbitrary topology may share status information*. | |
381 | .FS | |
382 | * One must, however, be concerned about \*(lqloops\*(rq. | |
383 | That is, if a host is connected to multiple networks, it | |
384 | will receive status information from itself. This can lead | |
385 | to an endless, wasteful, exchange of information. | |
386 | .FE | |
387 | .PP | |
388 | The second problem with the current scheme is that the rwho process | |
389 | services only a single local network, and this network is found by | |
390 | reading a file. It is important that software operating in a distributed | |
391 | environment not have any site-dependent information compiled into it. | |
392 | This would require a separate copy of the server at each host and | |
393 | make maintenance a severe headache. 4.2BSD attempts to isolate | |
394 | host-specific information from applications by providing system | |
395 | calls which return the necessary information\(dg. | |
396 | .FS | |
397 | \(dg An example of such a system call is the \fIgethostname\fP(2) | |
398 | call which returns the host's \*(lqofficial\*(rq name. | |
399 | .FE | |
400 | Unfortunately, no straightforward mechanism currently | |
401 | exists for finding the collection | |
402 | of networks to which a host is directly connected. Thus the | |
403 | rwho server performs a lookup in a file | |
404 | to find its local network. A better, though still | |
405 | unsatisfactory, scheme used by the routing process is to interrogate | |
406 | the system data structures to locate those directly connected | |
407 | networks. A mechanism to acquire this information from the system | |
408 | would be a useful addition. |