11. Socket

11.1. socket

../_images/socket-flow.jpg

Fig. 11.1. Socket Flow

11.1.1. Protocols

  • IPv4 - socket.AF_INET
  • IPv6 - socket.AF_INET6
  • UDP - socket.SOCK_DGRAM
  • TCP - socket.SOCK_STREAM
  • Broadcast - socket.SO_BROADCAST

11.1.2. API

  • The bufsize argument of 1024 used above is the maximum amount of data to be received at once
  • accept(), connect(), send(), and recv() are blocking
  • Blocking calls have to wait on system calls (I/O) to complete before they can return a value
Tab. 11.1. API
Method Description
socket()  
bind()  
listen()  
accept()  
connect()  
connect_ex()  
send()  
recv()  
close()  

11.1.3. TCP

11.1.3.1. Server

Code Listing 11.1. socket TCP Server
import socket


HOST = '127.0.0.1'
PORT = 1337
RESPONSE = 'Thanks'



with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:

    print(f'Listening on {HOST}:{PORT}/TCP...')
    sock.bind((HOST, PORT))
    sock.listen(1)

    while True:
        conn, addr = sock.accept()

        received = conn.recv(1024).decode()
        print(f'From: {addr}, received: "{received}"')

        # Reply to client
        conn.sendall(RESPONSE.encode())

        if not received:
            print(f'Client {addr} disconnected.')
            conn.close()

11.1.3.2. Client

Code Listing 11.2. socket TCP Client
import socket


HOST = '127.0.0.1'
PORT = 1337
DATA = 'Hello World'


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    print(f'Connecting to {HOST}:{PORT}/TCP')
    sock.connect((HOST, PORT))

    print(f'Sending: "{DATA}"')
    sock.sendall(DATA.encode())

    data = sock.recv(1024).decode()
    print(f'Received: "{data}"')

11.1.4. UDP

11.1.4.1. Server

Code Listing 11.3. socket UDP Server
import socket


HOST = '127.0.0.1'
PORT = 1337


with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:

    print(f'Listening on {HOST}:{PORT}/UDP...')
    sock.bind((HOST, PORT))

    while True:
        received, addr = sock.recvfrom(1024)
        print(f'From: {addr}, received: "{received.decode()}"')

11.1.4.2. Client

Code Listing 11.4. socket UDP Client
import socket


HOST = '127.0.0.1'
PORT = 1337
DATA = 'Hello World'


with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
    print(f'Connecting to {HOST}:{PORT}/UDP')
    sock.connect((HOST, PORT))

    print(f'Sending: "{DATA}"')
    sock.sendall(DATA.encode())

    received = sock.recv(1024).decode()
    print(f'Received: {received}')

11.1.5. Multicast

Code Listing 11.5. socket Multicast Client
import socket


def send(data, port=50000, addr='239.192.1.100'):
    """send(data[, port[, addr]]) - multicasts a UDP datagram."""
    # Create the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # Make the socket multicast-aware, and set TTL.
    s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)  # Change TTL (=20) to suit
    # Send the data
    s.sendto(data, (addr, port))


def recv(port=50000, addr="239.192.1.100", buf_size=1024):
    """recv([port[, addr[,buf_size]]]) - waits for a datagram and returns the data."""

    # Create the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # Set some options to make it multicast-friendly
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    try:
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    except AttributeError:
        pass  # Some systems don't support SO_REUSEPORT
    s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 20)
    s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)

    # Bind to the port
    s.bind(('', port))

    # Set some more multicast options
    intf = socket.gethostbyname(socket.gethostname())
    s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(intf))
    s.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(addr) + socket.inet_aton(intf))

    # Receive the data, then unregister multicast receive membership, then close the port
    data, sender_addr = s.recvfrom(buf_size)
    s.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(addr) + socket.inet_aton('0.0.0.0'))
    s.close()
    return data

11.2. socketserver

11.2.1. TCP

11.2.1.1. Server

Code Listing 11.6. socketserver TCP Server
from socketserver import BaseRequestHandler, TCPServer


HOST = '127.0.0.1'
PORT = 1337


class MyHandler(BaseRequestHandler):

    def handle(self):
        host, port = self.client_address
        data = self.request.recv(1024)
        print(f'From: {host}:{port}/TCP, received: "{data.decode()}"')

        response = 'Thanks'
        self.request.sendall(response.encode())


if __name__ == '__main__':

    with TCPServer((HOST, PORT), MyHandler) as server:

        print(f'Accepting connections on {HOST}:{PORT}/TCP...')
        print(f'To stop the server use ``Ctrl-C``\n')

        try:
            server.serve_forever()
        except KeyboardInterrupt:
            print('Server closing... ', end='')
            server.server_close()
            print('Done.')

11.2.1.2. Client

Code Listing 11.7. socket TCP Client
import socket


HOST = '127.0.0.1'
PORT = 1337
DATA = 'Hello World'


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    print(f'Connecting to {HOST}:{PORT}/TCP')
    sock.connect((HOST, PORT))

    print(f'Sending: "{DATA}"')
    sock.sendall(DATA.encode())

    data = sock.recv(1024).decode()
    print(f'Received: "{data}"')

11.2.2. Asynchronous

11.2.2.1. Threaded

  • socketserver.ThreadingTCPServer
  • socketserver.ThreadingUDPServer
Code Listing 11.8. socketserver TCP Threaded Client
import socket
import threading
from socketserver import ThreadingTCPServer, BaseRequestHandler


HOST = '127.0.0.1'
PORT = 1337


class MyHandler(BaseRequestHandler):

    def handle(self):
        data = self.request.recv(1024).decode()
        thread = threading.current_thread()
        response = f'{thread.name}: {data}'
        self.request.sendall(response.encode())


def client(ip, port, message):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((ip, port))
        sock.sendall(message.encode())
        response = sock.recv(1024).decode()
        print(f'Received: {response}')


if __name__ == '__main__':
    
    with ThreadingTCPServer((HOST, PORT), MyHandler) as server:
        ip, port = server.server_address

        # Start a thread with the server -- that thread will then start one
        # more thread for each request
        server_thread = threading.Thread(target=server.serve_forever)

        # Exit the server thread when the main thread terminates
        server_thread.daemon = True
        server_thread.start()
        print(f'Server loop running in thread: {server_thread.name}')

        client(ip, port, 'Hello World 1')
        client(ip, port, 'Hello World 2')
        client(ip, port, 'Hello World 3')

        server.shutdown()

11.2.2.2. Forking

  • socketserver.ForkingTCPServer
  • socketserver.ForkingUDPServer
Code Listing 11.9. socketserver TCP Forking Client