Socket Programming
Socket Programming
Level Google Classroom Demo: file:///C:/Users/Andrea/Downloads/Socket%20Demo.html
Sockets → used to send data from 1 program to the other and vice versa, when 2 Python programs are running at the same time
- Bidirectional: can carry data (rep. by bytes) in both directions

Internet socket
- Can delivers data between any 2 programs, even programs running on diff. computers, as long as the 2 computers can access each other over the network
- The 2 programs set up a socket connection
- Data transmitted through an Internet socket may pass through multiple devices before reaching its destination
- Any of these devices can steal or modify the data ⇒ encrypt data first
- Uses the same TCP / IP suite that is used to transmit data over the Internet

Networks can become congested ⇒ data sent over the Internet sockets may not be transmitted instantaneously (e.g. program may receive only first half of the message, the rest arrive later)
- To avoid working with incomplete data, define a protocol so the start and end of messages can be detected unambiguously
IP addresses and ports
- Each end of a socket is associated with a running program and is uniquely identified by a combined IP address and port number
- IP address: identifies which device that end of the socket is attached to
- IPv4 addresses: have 32 bits; usually presented as 4 denary numbers separated by dots
- Each denary can range from 0-255 (inclusive) and corresponds to 1 byte (8 bits) of the IP address
- 127.0.0.1: local computer
- 0.0.0.0: all IP addresses for local compute
- IPv6 addresses
- Have 128 bits (usually 8 groups of 4 hexadecimal digits separated by colons; leading zeros & up to 1 consecutive sequence of zero-only groups may be omitted)

- Port number: identifies which program on that device is using the socket
- Distinguishes between attached sockets
- Keeps track of which port numbers (0-65, 535) are still available for use by new sockets
- Only first 1024 port numbers are reserved for specific programs
- [cmd prompt] netstat -n ⇒ list sockets that are currently open on your computer (IP address & port number for each of its ends)

- [cmd prompt] netstat -no ⇒ reveal process ID (PID) associated with each socket
Then open Task Manager and match each PID to the name of a running program 
Creating a socket connection
- Requires 2 programs – one server and one client
- Server’s IP address and port number for accepting connections must be known ahead of time by the client
- Server creates a passive socket, binds it to the pre-chosen port number and listens for an incoming connection ⇒ is not connected, merely waits for an incoming connection
- Server’s IP address and port number is known to the client
- Client then initiates a connection request using server’s IP address and port number
- If no server is listening on the chosen port, the connection will be refused
- If connection request reaches an IP address and port number that a server is listening on, the server accepts and creates a new socket for the requesting client using a dynamically assigned port number (client’s port number)
- The passive socket goes back to listening for new connections;
Client and server can now exchange data using the newly-created socket 
- The newly-created socket is symmetrical: data sent on one end is received on the other end and vice versa ⇒ can transfer data in both directions (symmetric info sharing)
- Once a socket is established, it can send data both from client to server and from server to client
Unicode and encodings
- Sockets can only send and receive data in the form of raw bytes ⇒ must encode data into a sequence of 8-bit characters
- ‘string’.encode() / b’string**’.encode()** and bytes.decode()
- A Python str is actually treated as a sequence of numbers ⇒ Unicode code points
- UTF-8: represent code points using bytes in a space-efficient & consistent way
- To enter a sequence of bytes directly in code, use a bytes literal that starts with b, then a sequence of bytes (in the form of ASCII characters), enclosed using ‘ or ‘’
- Note: most escape codes that work for str also work for byte literals
| b’Raw bytes’.decode().strip() # convert bytes to str using UTF-8 encoding ‘Unicode str’.encode().strip() # convert str to bytes using UTF-8 encoding # .strip() to remove /n when sending bytes |
|---|
- 中 can be written as the str literal ‘\u4e2d’ in Python
⇒ Uses an escape code that produces a character by specifying its Unicode code point - len(‘\u4e2d’) ⇒ 1
- len(‘\u4e2d’.encode()) ⇒ 3
- The Unicode code point for 中 is represented using 3 bytes in UTF-8
Using the socket module
- Import socket module & create socket objects ⇒ create and manage sockets in Python
| Methods | Description |
|---|---|
| bind((host, port)) | Binds socket object to the given address tuple (host, port)Host = server’s IPv4 addressPort = server’s port number (pre-chosen) |
| listen() | Enables socket to listen for incoming connections from clients |
| accept() | Waits for an incoming connection, and returns a tuple containing a new socket object for the connection and an address tuple (host, port)Host = client’s IPv4 addressPort = client’s port number (dynamically assigned) |
| connect((host, port)) | Initiates a connection to the given address tuple (host, port)Host = server’s IPv4 addressPort = server’s port number (pre-chosen) |
| recv(max_bytes) | Receives and returns up to the given number of bytes from the socket |
| sendall(bytes) | Sends the given bytes to the socket |
E.g. basic_server.py: basic server program that listens for a client on port 12345, accepts a connection request, sends b’Hello from server\n’ to client through socket, then closes socket
import socketmy_socket = socket.socket()my_socket.bind((‘127.0.0.1’, 12345)) # or use any large port number my_socket.listen() new_socket, addr = my_socket.accept() # returns a new socket and nested address tupleprint(‘Connected to: ’ + str(addr))new_socket.sendall(b’Hello from server\n’) # new_socket is used to send and receive data new_socket.close() my_socket.close() |
|---|
Server should appear stuck shortly after it is started, because socket.accept() method is blocking (waiting for some event to occur) the program and prevents it from continuing until a connection request is received
To create a client that can connect to this server, start a 2nd copy of Python:
Move any windows from the 1st copy of Python to one side so the 2 copies of Python are clearly separated. Create a new Python program using the 2nd copy of Python.
E.g. basic_client.py: ask for server’s IP address and port number, request for a connection, receive and print at most 1024 bytes from server, then close socket
import socketmy_socket = socket.socket()address = input(“Enter IPv4 address of server: ”)port = int(input(“Enter port number of server: ”))my_socket.connect((address, port)) print(my_socket.recv(1024)) # 1024 or any small power of 2 (i.e. recv up to 1024 bytes)my_socket.close() |
|---|
- Run using 2nd copy of Python & ensure server you started previously is still running
- Else close client, restart server, and reopen client using 2nd shell window
- Each program should affect a different shell window when run (e.g. pressing F5)

- Client should prompt you for the address & port number of server
- Use the special IPv4 address 127.0.0.1 (local machine), enter 12345 as port number
- Client should successfully connect to server and print out the bytes received
- Server program should become unstuck and end normally

Flaw: when basic server program is used to send longer sequences of bytes, only part of the data may be successfully transmitted even if we inc. max no. of bytes for socket.recv()
- E.g. If sequence of bytes sent is long enough that it needs to be sent as multiple packets
- Simulate this by breaking sequence into 2 pieces & calling socket.sendall() twice
- To simulate a busy network that may delay transport of the 2nd packet, import time module and call time.sleep() before sending 2nd piece
# basic_server_split.pyimport socketimport timemy_socket = socket.socket()my_socket.bind((‘127.0.0.1’, 12345)) my_socket.listen() new_socket, addr = my_socket.accept()new_socket.sendall(b’Hello fr’) time.sleep(0.1) new_socket.sendall(b’om server\n’) new_socket.close() my_socket.close() |
|---|
- Run this server, then run client ⇒ both programs run simultaneously on same machine
- Client should receive only 1st piece of data
- If client closed socket, server may produce error when trying to send 2nd piece of data
- To be certain that any received data is complete (all bytes sent over at one go), agree beforehand on a protocol / set of rules for how communication should take place
- E.g. agree beforehand that any data transmitted will always end with \n (newline character) and that the data itself will never contain \n ⇒ detect end of transmission / message easily by just searching for \n
- New client calls socket.recv() continuously and appends the received bytes to a variable data until \n character encountered
# basic_client_protocol.py: successfully receives and prints all data sent by server up to and including the \n characterimport socketmy_socket = socket.socket()address = input(“Enter IPv4 address of server: ”)port = int(input(“Enter port number of server: ”))my_socket.connect((address, port)) data = b’’while b’\n’ not in data:data += my_socket.recv(1024) print(data)my_socket.close() |
|---|
- Server program exists immediately after it finished working with a client
Iterative and Concurrent Servers
- Server program runs continuously (deals with clients in an infinite loop) ⇒ always listening and available for multiple clients to send connection requests
# basic_server_iterative.pyimport socketmy_socket = socket.socket()my_socket.bind((‘128.0.0.1’, 12345)) my_socket.listen() while True:new_socket, addr = my_socket.accept()new_socket.sendall(b’Hello from server\n’) new_socket.close() |
|---|
- Server’s passive socket keeps a queue of connection requests that have been received
- A request is removed from this queue each time socket.accept() is called to create a connection
- If queue empty, socket.accept() will block the program until a connection request is received
- socket.accept() is called each time the infinite loop repeats ⇒ program can handle multiple clients by processing them 1 at a time ⇒ iterative server
- Iterative servers are easy to write but limited: can only handle one client at a time
- OR can write a server that starts a thread that runs simultaneously with the main program each time a client tries to connect ⇒ makes program more complicated but will let it handle multiple clients at the same time ⇒ concurrent server
Writing a Chat Program
- 2 users (server + client) take turns sending single lines of text to each other
- Each message is restricted to a single line ⇒ \n will never be part of a message
- Can use protocol of using \n to detect end of message
# chat_server.pyimport socketlisten_socket = socket.socket()listen_socket.bind((‘127.0.0.1’, 6789)) listen_socket.listen() chat_socket, addr = listen_socket.accept()while True:data = input(‘INPUT SERVER: ’).encode()chat_socket.sendall(data + b’\n’) if data == b’quit’:break print(“waiting for client…”)data = b’’while b’\n’ not in data:data += chat_socket.recv(1024) print(‘client wrote: ’ + data.decode())if data == b’quit\n’:break # if use quit:listen_socket.close() chat_socket.close() |
|---|
Alt: use while loop and Boolean indicator to exit the program with quit message# chat_server.pyimport socketlisten_socket = socket.socket()listen_socket.bind((‘127.0.0.1’, 6789)) listen_socket.listen() chat_socket, addr = listen_socket.accept()leave = Falsewhile not leave:data = input(‘INPUT SERVER: ’)chat_socket.sendall(data**.encode()** + b’\n’) if data == ’quit’:leave = Trueelse:print(“waiting for client…”)data = b’’while b’\n’ not in data:data += chat_socket.recv(1024) print(‘client wrote: ’ + data.decode())if data == b’quit\n’:leave = True# if use quit:listen_socket.close() chat_socket.close() |
# chat_client.pyimport socketchat_socket = socket.socket()address = input(“Enter IPv4 address of server: ”)port = int(input(“Enter port number of server: ”))chat_socket.connect((address, port)) while True:print(“waiting for server…”)data = b’’while b’\n’ not in data:data += chat_socket.recv(1024) print(“server wrote: ” + data.decode())if data == b’quit\n’:break data = input(“INPUT CLIENT: ”).encode()chat_socket.sendall(data + b’\n’) if data == b’quit’:break # if use quit:chat_socket.close() |
|---|
# Alt: use while loop and Boolean indicator to exit the program with quit messageimport socketchat_socket = socket.socket()addr = input("Enter IPv4 address of server: ")port = int(input("Enter port number of server: "))chat_socket.connect((addr, port)) leave = Falsewhile not leave:print("waiting for server...")data = b''while b'\n' not in data:data += chat_socket.recv(1024) print("server wrote: ", data.decode()if data == b'quit\n':leave = Trueelse:data = input("INPUT CLIENT: ")chat_socket.sendall(data.encode() + b’\n’) if data == 'quit':leave = Truechat_socket.close() |
- Run server & client using 2 diff copies of Python, on the same machine
- Can use 127.0.0.1 as server’s IPv4 address and 6789 as port number
- Exit both programs once message “quit’ is sent by any user
- Make sure all sockets are closed properly before exiting
Writing a Turn-Based Game
- Both server and protocol designs may be based on an existing standard or developed by someone else

# tictactoe.pyN = 3 # size of gridwidth = len(str(N**2)) # width for each cellplayers = (‘O’, ‘X’) # player symbolsclass TicTacToe:def __init__(self):self.board = [ ]for i in range(N):self.board.append([None]*N) def render_row(self, row_index):start = row_index * N + 1 # 1st no. of the row?row = self.board[row_index].copy()for column_index in range(N):if row[column_index] is None:cell = str(start + column_index)else:cell = players[row[column_index]]if len(cell) < width:cell += ‘ ’ * (width - len(cell)) row[column_index] = ‘ ’ + cell + ‘ ’ return ‘|’.join(row) + ‘\n’def render_board(self):rows = [ ]for row_index in range(N):rows.append(self.render_row(row_index)) divider = ‘-’ * ((width+3) * N - 1) + ‘\n’return divider.join(rows)def make_move(self, player_index, cell_index):cell_index -= 1 self.board[cell_index // N][cell_index % N] = player_index def is_valid_move(self, cell_index):if cell_index < 1 or cell_index > N ** 2:return Falsecell_index -= 1 return self.board[cell_index // N][cell_index % N] is Nonedef is_full(self):for row_index in range(N):for column_index in range(N):if self.board[row_index][column_index] is None:return Falsereturn Truedef get_winner(self):# check diagonalsif self.board[0][0] is not None: # left to right diagonalfound = Truefor i in range(N):if self.board[0][0] != self.board[i][i]:found = Falsebreak if found:return self.board[0][0]if self.board[0][N-1] is not None: # right to left diagonalfound = Truefor i in range(N):if self.board[0][N-1] != self.board[i][N - i - 1]:found = Falsebreak if found:return self.board[0][N-1]# check rows and columnsfor i in range(N):if self.board[i][0] is not None:found = Truefor j in range(N):if self.board[i][0] != self.board[i][j]:found = Falsebreak if found:return self.board[i][0]if self.board[0][i] is not None:found = Truefor j in range(N):if self.board[0][i] != self.board[j][i]:found = Falsebreak if found:return self.board[0][i]# no matching lines were found, so no winnerreturn None |
|---|
chatgpt’s alt to make board:class TicTacToe:def __init__(self, N):self.N = Nself.board = [[None] * N for _ in range(N)] # nestedself.players = ('O', 'X')self.width = len(str(N * N)) # dynamic widthdef render_board(self):rows = []for i in range(self.N):row = []for j in range(self.N):cell = self.board[i][j]if cell is None:value = str(i * self.N + j + 1)else:value = self.players[cell]row.append(value.rjust(self.width)) # right justify rows.append(” | “.join(row)) # + separator divider = "\n" + "-" * ((self.width + 3) * self.N - 3) + "\n"# = N*width ( ⇒ cells) + (N-1)*3 ( ⇒ separators)return divider.join(rows)def make_move(self, player, position):position -= 1 i, j = divmod(position, self.N)if self.board[i][j] is not None:return False # invalid moveself.board[i][j] = player return True… def get_winner(self):N = self.Nboard = self.board# Rowsfor i in range(N):first = board[i][0]if first is not None and all(board[i][j] == first for j in range(N)):return first# Columnsfor j in range(N):first = board[0][j]if first is not None and all(board[i][j] == first for i in range(N)):return first# Main diagonalfirst = board[0][0]if first is not None and all(board[i][i] == first for i in range(N)):return first# Anti-diagonalfirst = board[0][N-1]if first is not None and all(board[i][N-i-1] == first for i in range(N)):return firstreturn None |
| methods | description |
|---|---|
| render_row(row_index) | returns a string representation of the specified row e.g. 1 | 2 | 3 |
| render_board() | returns a string representation of the entire board e.g. 1 | 2 | 3 ----------- 4 | 5 | 6 ----------- 7 | 8 | 9 ----------- |
| make_move(player_index, cell_index) | modifies board such that the specified cell is marked with the symbol for the specified player |
| is_valid_move(cell_index) | returns whether the specified cell is currently blank |
| is_full() | returns whether the entire board has been filled up |
| get_winner() | returns winning player for the current board or None if there is no winner |
# game_server.py# for game_client.py, swap “client” and “server”, and move last step to the front⇒ symmetrical game: same rules for both players import socketimport tictactoelisten_socket = socket.socket() # use game_socket for game_client.pylisten_socket.bind((‘127.0.0.1’, 3456)) # use game_socket.connect listen_socket.listen() # not needed for game_client.py game_socket, addr = listen_socket.accept() # not needed for game_client.pygame = tictactoe.TicTacToe()# infinite loopwhile True:# display current tic-tac-toe boardprint(game.render_board())# check if current player won, end game with opponent winningif game.get_winner() is not None:print(“opponent wins!”)print()break # check if board is full, end game with a stalemate if soif game.is_full():print(“stalemate”)print()break # prompt for move from server player, update game board if valid move# server layer always starts first ⇒ start by receiving & processing servermove = -1while move != 0 and not game.is_valid_move(move):move = int(input(“server moves ” + “(0 to quit): ”))print()if move == 0:game_socket.sendall(b’END\n’) print(‘you quit, opponent wins!’)print()break game.make_move(0, move) game_socket.sendall(b’MOVE’ + str(move).encode() + b’\n’) # display current tic-tac-toe board againprint(game.render_board())# check if server player won, end game with player winning if soif game.get_winner() is not None:print(“you win!”)print()break # check if board is full, end game with a stalemate if soif game.is_full():print(“stalemate”)print()break # move to front for game_client.py# receive move from client player (opponent) via socket ⇒ last stepreceived = b’’while b’\n’ not in received:# end game with player winningreceived += game_socket.recv(1024) if received.startswith(b’MOVE’):# update board accordinglymove = int(received[4:])print(‘client moves: ’ + str(move))print()game.make_move(1, move) elif received.startswith(b’END’):print(‘opponent quits, you win!’)print()break game_socket.close() listen_socket.close() # no needed for game_client.py |
|---|
Asymmetrical game: 2 players behave differently from each other
E.g. guess-the-number (server generates random no. 1-100, client tries to guess within 5 tries)
# guess_client.pyimport sockets = socket.socket()s.connect((‘127.0.0.1’, 9999)) data = b’’while True:while b’\n’ not in data:data += s.recv(1024) received = data[:data.find(b’\n’)]data = data[len(received) + 1:] # +1 since “\n” = 1 chr; this removes the received + \nif received == b’LOW’:print(“your guess is too low”)elif received == b’HIGH’:print(“your guess is too high”)elif received == b’GUESS’:guess = int(input(“enter guess (1-100): ”))s.sendall(str(guess).encode() + b’\n’) elif received == b’WIN’:print(“you win!”)break elif received == b’GAMEOVER’:print(“you ran out of tries! game over.”)break s.close() |
|---|
# guess_server.pyimport socketfrom random import randintmy_socket = socket.socket()my_socket.bind((‘127.0.0.1’, 9999)) my_socket.listen() game_socket, addr = my_socket.accept()answer = randint(1,100)print(answer) # to check when testingcount = 1game_socket.sendall(b’GUESS\n’) # or use guessed = Falsewhile count < 5: # or use for i in range(5)guess = b''while b'\n' not in guess:guess += game_socket.recv(1024) # guess = int(guess.decode())guess = int(data[:data.find(b'\n')].decode())if guess == answer:game_socket.sendall(b’WIN\n’) # guessed = True break elif guess > answer:game_socket.sendall(b’HIGH\n’) game_socket.sendall(b’GUESS\n’) # or only send “if guessed: “ elif guess < answer:game_socket.sendall(b’LOW\n’) game_socket.sendall(b’GUESS\n’) # or only send “if guessed: “ count += 1 if count == 5:game_socket.sendall(b’GAMEOVER\n’) # “else” clause for “if guessed: ” game_socket.close() my_socket.close() |
|---|
socket module summary
| methods | description |
|---|---|
| bind((host, port)) | binds socket object to the given address tuple (host, port)host = IPv4 addressport = port number |
| listen() | enables socket to listen for incoming connections from clients |
| accept() | waits for an incoming connection and returns a tuple containing a new socket object for the connection and an address tuple (host, port) host = the IPv4 address of the connected client port = its port number |
| connect((host, port)) | initiates a connection to the given address tuple (host, port)host = IPv4 address of serverport = its port number |
| recv(max_bytes) | receives and returns up to the given no. of bytes from the socket |
| sendall(bytes) | sends the given bytes to the socket |
All notebooks, including extra practice, in jupyter notebook folder!