liveBook · Manning

Server

As explained in Grokking Concurrency

A server application is a program that listens for client connections and provides services over those connections.

Think of a server as a business with an office: clients come to it for a service. A server is a program that waits for client connections, receives requests, does the requested work, and may send a reply. A web browser and a web server are one familiar pair. section 10.2 section 10.3

To find the right office, a client needs the server’s IP address and port. The server binds a socket to that address and port and listens. When a client connects, the server gets a separate socket for exchanging messages with that client. The two programs can run on the same computer or on different computers across a network. section 10.3 section 10.4

A pizza-ordering server

A Python server listens at 127.0.0.1:12345. Its accept() call waits for a client; Netcat can connect from another terminal and send an order. For an order of 10, the server replies, “Thank you for ordering 10 pizzas!” The waiting terminal is not necessarily stuck: it is blocked until a connection arrives. section 10.4

# Chapter 10/pizza_server.py
from socket import socket, create_server

BUFFER_SIZE = 1024                                                 #1
ADDRESS = ("127.0.0.1", 12345)                                     #2

class Server:
    def __init__(self) -> None:
        try:
            print(f"Starting up at: {ADDRESS}")
            self.server_socket: socket = create_server(ADDRESS)    #3
        except OSError:
            self.server_socket.close()
            print("\nServer stopped.")

    def accept(self) -> socket:
        conn, client_address = self.server_socket.accept()         #4
        print(f"Connected to {client_address}")
        return conn

    def serve(self, conn: socket) -> None:
        try:
            while True:
                data = conn.recv(BUFFER_SIZE)                      #5
                if not data:                                       #5
                    break                                          #5
                try:
                    order = int(data.decode())
                    response = f"Thank you for ordering {order} pizzas!\n"
                except ValueError:
                    response = "Wrong number of pizzas, please try again\n"
                print(f"Sending message to {conn.getpeername()}")
                conn.send(response.encode())                       #6
        finally:
            print(f"Connection with {conn.getpeername()} has been closed")
            conn.close()                                           #7

    def start(self) -> None:
        print("Server listening for incoming connections")
        try:                                                       #8
            while True:                                            #8
                conn = self.accept()                               #8
                self.serve(conn)                                   #8
        finally:                                                   #8
            self.server_socket.close()                             #8
            print("\nServer stopped.")                             #8

if __name__ == "__main__":
    server = Server()
    server.start()

Follow the pizza server’s create_server(), accept(), and recv() calls to see how listening becomes a client conversation. section 10.4

That simple server has a limit: while one client occupies it, another waits. Try leaving the first client connected and opening a second. Serving many clients promptly calls for a concurrent approach; merely listening on a port does not provide one. section 10.5

A server can also coordinate workers rather than answer customer orders. In a word-count application, the server communicates with worker processes and asks a central scheduler for their next map or reduce tasks. The service is still organized around connections and messages, but the work is distributed among workers. section 13.14

How Grokking Concurrency covers it

How deep it goes
Works through binding, listening, accepting, and replying, then identifies the single-client bottleneck without detailing network internals.
Examples
Uses a Python pizza-ordering server tested with Netcat, a waiting second client, and a word-count server coordinating workers.
What it assumes
Introduces IP addresses, ports, and sockets before the server example, while using Python code to demonstrate it.
What you can do after
Build it from scratch as a basic Python socket server and diagnose why a second client waits.
How it connects
Connects network sockets, IP addresses, ports, client-server communication, concurrency, and map-reduce scheduling.

Also explained in

Compare how 11 books explain server →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Grokking Concurrency. The section links point to the parts of the book it draws on.