As stated above, one of the goals of the client-server paradigm is to be able to arbitrarily place clients and servers on a network of interconnected machines. This means, for example, that a client application Cl running on a certain machine MCl must be (ideally) able to work properly - i.e. to exchange information with a server Sr - independently on the machine MSr the server runs on: the machine might be substituted with another one, or the server might be replaced with another program offering the same kind of service, without the client taking notice of it.
Let's see how this goal can be achieved, and let's proceed step by step, starting with the server application. The application will in general run as a process, or a set of related processes on a machine. Let's assume, since this is the most common case, that there's only one process dedicated to the communication needs of the server application. The clients then need a way to find this process.
The simplest possible way is to hardwire the network address of the server's machine in the client program. The client would then pass this address to the transport/network services of the operating system of the its machine, so that the communication be established. This is clearly non-transparent from the client's perspective, since the location of the server is explicitly accounted. Moreover, in this scheme a machine could offer only one kind of service by means of one single server application: there's no way to distinguish between two servers running on the same machine.
Since the client is not interested in the machine per se, but in the server process, a possible way to solve the second problem might be to specify a process number along with the server machine address: something like email@example.com, where 6234 is the process id of a program running on the machine whose network address is 220.127.116.11 (which, btw, is a sample IP address). However, this would generally be too much of a constraint for the operating system of the server machine, since process id.s are generally assigned independently of the programs they run.
A better scheme is one in which the server registers itself with the OS at a fixed port, i.e. at a logical communication address which is guaranteed to be unique within the machine, so that the pair portnumber@machine be unique over the network. The client would then address the server at the machine through its port number in order to establish a connection. Furthermore, the server OS might assign logical names to the ports corresponding to service names (i.e. map service names to ports and vice versa), so that the client might request connections for a certain service at a given machine in the form servicename@machine, the server OS providing for the translation of the service name into the appropriate port number (thus freeing the server programmer or administrator from having to always use the same port number).
However this scheme is still far from the ideal one, since it still assumes that the client has explicit knowledge of the machine offering the service. Clearly it's needed a way to assign machine-independent identifiers to servers. A solution, useful for network that allow for broadcasting (i.e. sending messages to be received by all connected stations) is to let the servers choose their own identifier, say random number from a large space like the set of the 128-bit integers. Upon choosing an address, the server would broadcast a message to all the connected stations, asking whether that number is already taken by a previously set-up application, and if not that would be its number. The client's OS could then find the server for a certain service in two ways:
This scheme solves he transparency problem, scales well with the number of connected stations, and it's simple to implement (Apple's Appletalk is an example of it). However the broadcasts may put a heavy load on the communication bandwidth and on the station's performances, since they all must listen to them.
An alternative way is to use a name server to provide mapping between service names and machine/port pairs. Initially the clients need to know only the machine/port address of the name server (and they can either have it hardwired in a configuration file, or get it by the above broadcasting method. Whenever they need another service they would first ask the name server for the port/address pair(s) of the machine(s) where the server(s) for that service run, cache this information (possibly updating by a new query every once in a while, to take into account changes network/machine configuration), and use it for all subsequent queries. Evidently this method always use connections.
This third scheme is widely used (e.g. the Domain Name Server on the Internet). However, it's not free from defects: since it relies upon a centralized facility (the name server) it's prone to malfunctions if it fails; replicating the name server is obviously feasible, but introduces the problem f maintaining consistent information among servers in a dynamically changing environment (workstations are rebooted, printers are switched off and on, etc). Moreover, the size of the addressing tables in the name server may grow so huge that some kind of hyerachical organization becomes mandatory (e.g. the DNS again).