It is important to realize that clients and servers are processes and not ma- chines, or hosts as they are often called in this context.
Data received from the network is copied from the adapter across the I/O and memory buses into memory, typically by a DMA transfer.
Similarly, data can also be copied from memory to the network.
An IP address is an unsigned 32-bit integer. Network programs store IP addresses in the IP address structure shown in Figure 11.9.
Con- ceptually, the DNS database consists of millions of the host entry structures shown in Figure 11.11, each of which defines the mapping between a set of domain names (an official name and a list of aliases) and a set of IP addresses.
The gethostbyname function returns the host entry associated with the do- main name name.
The gethostbyaddr function returns the host entry associated with the IP address addr. The second argument gives the length in bytes of an IP address, which for the current Internet is always 4 bytes.
A connection is point-to-point in the sense that it connects a pair of processes. It is full-duplex in the sense that data can flow in both directions at the same time.
The port in the client’s socket address is assigned automatically by the kernel when the client makes a connection request, and is known as an ephemeral port
However, the port in the server’s socket address is typically some well-known port that is associated with the service.
On Unix machines, the file /etc/services contains a comprehensive list of the services provided on that machine, along with their well-known ports.
The Sockets Interface
Socket Address Structures
Internet socket addresses are stored in 16-byte structures of the type sockaddr_in, shown in Figure 11.15.
For Internet applications, the sin_family member is AF_INET, the sin_port member is a 16-bit port number, and the sin_ addr member is a 32-bit IP address. The IP address and port number are always stored in network (big-endian) byte order.
The socket Function
Clients and servers use the socket function to create a socket descriptor.
In our codes, we will always call the socket function with the arguments:
clientfd = Socket(AF_INET, SOCK_STREAM, 0);
where AF_INET indicates that we are using the Internet, and SOCK_STREAM indicates that the socket will be an end point for an Internet connection.
The clientfd descriptor returned by socket is only partially opened and cannot yet be used for reading and writing.
The connect Function
A client establishes a connection with a server by calling the connect function.
The connect function blocks until either the connection is successfully established or an error occurs.
If successful, the sockfd descriptor is now ready for reading and writing.
The bind Function
The remaining sockets functions—bind, listen, and accept—are used by servers to establish connections with clients.
The bind function tells the kernel to associate the server’s socket address in my_addr with the socket descriptor sockfd.
The addrlen argument is sizeof(sockaddr_in).
The listen Function
By default, the kernel assumes that a descriptor created by the socket function corresponds to an active socket that will live on the client end of a connection.
A server calls the listen function to tell the kernel that the descriptor will be used by a server instead of a client.
The listen function converts sockfd from an active socket to a listening socket that can accept connection requests from clients.
The backlog argument is a hint about the number of outstanding connection requests that the kernel should queue up before it starts to refuse requests.
The exact meaning of the backlog argument requires an understanding of TCP/IP that is beyond our scope. We will typically set it to a large value, such as 1024.
The open_listenfd(customized) Function
After we create the listenfd socket descrip- tor, we use the setsockopt function (not described here) to configure the server so that it can be terminated and restarted immediately.
By default, a restarted server will deny connection requests from clients for approximately 30 seconds, which seriously hinders debugging.
diffs when format sockaddr_in between client and server:
The accept Function
The accept function waits for a connection request from a client to arrive on the listening descriptor listenfd, then fills in the client’s socket address in addr,
and returns a connected descriptor that can be used to communicate with the client using Unix I/O functions.
The listening descriptor serves as an end point for client connection requests. It is typically created once and exists for the lifetime of the server.
The connected descriptor is the end point of the connection that is established between the client and the server.
It is created each time the server accepts a connection request and exists only as long as it takes the server to service a client.
Example Echo Client and Server
What does EOF on a connection mean?
First, we need to understand that there is no such thing as an EOF character. Rather, EOF is a condition that is detected by the kernel.
An application finds out about the EOF condition when it receives a zero return code from the read function.
For disk files, EOF occurs when the current file position exceeds the file length.
For Internet connections, EOF occurs when a process closes its end of the connection. The process at the other end of the connection detects the EOF when it attempts to read past the last byte in the stream.
The loop(fget loop) terminates when fgets encounters EOF on standard input, either because the user typed ctrl-d at the keyboard or because it has exhausted the text lines in a redirected input file.
After the loop terminates, the client closes the descriptor. This results in an EOF notification being sent to the server, which it detects when it receives a return code of zero from its rio_readlineb function.
After closing its descrip- tor, the client terminates.
Since the client’s kernel automatically closes all open descriptors when a process terminates, the close in line 24 is not necessary. How- ever, it is good programming practice to explicitly close any descriptors we have opened.
Notice that our simple echo server can only handle one client at a time. A server of this type that iterates through clients, one at a time, is called an iterative server.