Figure: user | application (client and server) | application protocol | <the rest of the protocol stack>Client interacts with user, communicates with server
Server usually accesses non-volatile storage (database, file system) to do work
Client and server talk via application protocol
Programmers write client and server code. They code the application protocol via the services of a transport layer API.
The terms client and server most generically refer to software processes.
Machines with operating systems where server processes execute are often called "servers", but it's the process that is the real server.
Machines with operating systems where client processes execute are often called "clients", but it's the process that is the real client.
Alternative to a monolithic app
split the user interaction and the engine into two processes
Advantages
- each process can run on a machine suited to its needs
- server characteristics?
- client characteristics?
- machines can be on a network: geographical independence
- failures are isolated to process boundaries
- problem is made more tractable by breaking it into smaller pieces
Servers maintain, or can find, data stored permanently (database) used to answer client requests.Servers need to worry about authenticating clients.
Servers should be robust and reliable.
Servers tend to be complex for these reasons.
Cients interact with users.Clients need nice interfaces.
Clients assume servers are available and operating correctly, making their development simpler.
How do you ensure that the client and server processes start talking to each other at the same (or nearly the same) time?Answer: you don't
The client interacts with the user. The client makes requests from the server. The server is quiescent until the client makes a request. The server is assumed to be always available. After handling a client request it waits for the next request. This takes care of the rendezvous problem.
Alternative models
No work is done until the client needs data and makes a request of the server. The work is demand driven.An alternative model which also solves the rendevous problem is to pre-compute responses and store them, anticipating the needs of clients. Since this model isn't demand driven it has the potential to waste resources. But it has the advantage of possibly working even when the network goes down, since the answer sought may already be found locally.
Scaling is a problem with this approach, though some applications use it (ruptime example from Comer).
ASCII vs binary protocols
Numeric response codes
Service naming
Starting services at boot time
Super servers: inetd, example configuration file
Servers run and offer services on well-known ports. Here's an example of some services that are available on a typical UNIX workstation, along with connections that have already been made.
The issue: having a path between client and serverPrivate, possibly proprietary, networks - string your own wires
Public networks like the Internet - security is an issue
Virtual private neworks
Platform: hardware + OSLanguage and portability
Dedicated app versus universal clientCustom written client can be very specific to the application, but requires time/money to develop and is unlikely to be platform independent.
Java allows for custom clients that are more or less platform independent.
Using a web browser as a client allows for complete platform independence but limits what you can do and the sophistication of user interface elements.
Privacy or secrecy
Authentication
Non-repudiation
Integrity
Maintaining state vs stateless protools
The basic idea of an atomic transaction is that much can happen back and forth between multiple processes, but at some point everyone agrees to commit to something firm, and the state of the system is then made permanent at this point. Until this happens the changes made by a process can be backed out. The result is that an atomic transaction either happens, or it doesn't. It can't happen part way. Two simple examples:Money transfer operation
Suppose you want to do a transfer operation between two bank accounts. You implement your transfer in the following way:
withdraw(amount, acct1);
What happens if the first operation succeeds, but the second one fails? The money just disappears.
deposit(amount, acct2);If you could make an atomic transaction
transfer(amount, acct1, acct2);
then it would either succeed, or it would fail, but it couln't happen partially.File append
Suppose some processes are reading a file with size X. Another process begins an atomic transaction to append bytes to the file. It may be several write() calls, and take some time to append all the data in the transaction. When the transaction is complete, the file instantaneously becomes X+Y bytes, where Y is the result of the operations done in the transaction. No process ever sees the file as something other than either X or X+Y bytes. No intermediate operations are visible.
The system is some number of independent processes any of which might fail at any time. The transport level is assumed to be reliable (that is messages can be lost, but someone else below the transaction level deals with those).Transaction primitives identify the start and end of a transaction and allow for the abortion of a transaction. These could either be system or library calls, or statements in a language. They let you group together a series of operations into a transaction, like this
TRANS_BEGIN
Transactions have four essential properties (ACID)withdraw(amount, acct1);
TRANS_END
deposit(amount, acct2);
- Atomicity - the transaction is indivisible
- Consistent - the transaction doesn't violate system invariants
- Isolated - concurrent transactions can't interfere with each other
- Durable - once committed, the changes made by a transaction are permanent