This article is participating in Python Theme Month. See the link for details
The socket encapsulates the TCP/IP protocol family and communicates on the network through the socket. Python also realizes the support of socket programming, which can be convenient to develop network applications
Client-server communication application
Here is a simple example of client-server communication
The communication process
- The server starts and creates a socket
- The server binds the socket to a port
- The server starts listening on the port
- The port on which the client requests the server to listen
- The server receives the connection request from the client and establishes a connection with the client
- The client and server send data to each other
- The client sends a request to the server to close the connection, and then exits
- The server closes the connection
Server-side implementation
import socket
HOST = '127.0.0.1' Set the listening address to local 127.0.0.1
PORT = 65432 Set listening port to 65432
Create a socket using socket.socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
Bind the socket to the specified address and port
s.bind((HOST, PORT))
The server starts listening
s.listen()
The server waits for the connection request from the client
conn, addr = s.accept()
with conn:
print('Connected by', addr)
The server loop receives data from the client
while True:
data = conn.recv(1024)
if not data:
break
Return the data to the client after receiving it
conn.sendall(data)
Copy the code
Creating a Socket connection
At the beginning of the server implementation, a socket object is created using two parameters, socket.AF_INET and socket.SOCK_STREAM, to indicate the use of IPv4 address and TCP protocol respectively. If an IPv6 address is required, the first argument can be socket.af_inet6, for example:
import socket
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
Copy the code
If the socket is created based on a Unix file, the first argument can be socket.af_UNIX, and the bound address and port need to be replaced by the desired file path, for example:
import socket
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.bind("/tmp/socket_test.s")
s.listen()
Copy the code
If you want to use UDP instead of TCP, change the second parameter to socket.sock_dgram and drop listen(), for example:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.bind((HOST, PORT))
Copy the code
Listen () has an optional backlog parameter that sets the maximum number of clients waiting for connections. After the number of clients waiting for connections exceeds this value, new connections will be rejected.
Receive client connections
conn, addr = s.accept()
with conn:
print('Connected by', addr)
Copy the code
Accept () is used to accept client connections. It is a blocking method that returns a addr representing the client’s address, consisting of conn and (Host, Port), when a new client connection is available. When you get conn, manipulate the CONN object in scope with conn, so that conn.close() is called automatically when you leave scope with conn, eliminating the need to manually close the connection.
Receive data
The conn.recv() method is used to read data from the connection object, where 1024 means a maximum of 1024 bytes are read at a time. If any data is received, it is sent back to the client using the sendall() method.
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
Copy the code
The difference between send and SendAll
s.send() | Send TCP data, sending the data in string to the connected socket. The return value is the number of bytes to send, which may be smaller than the size of the string. |
---|---|
s.sendall() | Send TCP data in full. Sends the data in the string to the connected socket, but attempts to send all the data before returning. Returns None on success and raises an exception on failure. |
Client-side implementation
import socket
HOST = '127.0.0.1'
PORT = 65432
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(b'Hello, world')
data = s.recv(1024)
Copy the code
The client implementation is similar to the server implementation, except that after the socket object is created, the client uses Connect () to initiate a connection request to the server. After a successful connection, the client can send data to the server using sendall() or receive data from the server using recv()
I/O multiplexing
In the previous example, the server can only handle requests from one client at a time and wait until the client exits before continuing to process other clients. Python’s library of selectors is used in order for a server to support simultaneous connections to multiple clients. Selectors multiplexing system calls for the encapsulation, using selectors. DefaultSelector can according to the code execution platform automatically select the most efficient method of system calls.
Selectors sample
I’m going to create a Selector object
import selectors
sel = selectors.DefaultSelector()
Copy the code
Here the DefaultSelector automatically selects one of the I/O multiplexing methods kqueue, epoll, Devpoll, poll, Select, depending on the platform on which the code is running, Priorities for epoll | kqueue | devpoll > poll > select. Create a socket object as before, bind it to port 65432, and enable listening:
host = '127.0.0.1'
port = 65432
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((host, port))
sock.listen()
sock.setblocking(False)
print('listening on', (host, port))
Copy the code
Note that you need to use socket.setblocking(False) to set operations performed on the socket to non-blocking mode, otherwise the system call executing the socket will block. In non-blocking mode, send() or recv() is returned immediately, but an exception is thrown, even if the operation cannot be performed immediately. Now we register the socket with the selector object, where only readable events are listened for, so the second argument uses selectors.EVENT_READ
sel.register(sock, selectors.EVENT_READ, data=None)
Copy the code
We can then get the socket that generated the event from the selector object when there is a readable event, for example:
while True:
# read event
events = sel.select(timeout=None)
for key, mask in events:
Handle client connections
if key.data is None:
Accept client connections
handle_conn(key)
Handle client events
else:
Read and write client data
handle_rw(key, mask)
Copy the code
Sel.select () returns a list of tuples, each made up of (selectorKey, events). Selectorkey.fileobj is used to get the socket where the event occurred, and events represents the mask for ready events. If selectorKey.data is None, selectorKey is the socket listening on port 65432, so it is a new connection request. You need to register it with a selector in order to receive subsequent incoming data. Otherwise, it indicates that the client is connected and receives data directly.
Receive client connections
Key. Data is None indicates that the key is the socket that listens on port 65432, needs to receive new connection requests through this socket, and then registers with the selector.
def handle_conn(sock) :
# Receive connection
conn, addr = sock.accept()
Set the operation to non-blocking mode
conn.setblocking(False)
Create a data object to store the data associated with this connection
data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')
For new connections, you need to pay attention to readable events (client has data incoming) and writable events (allows server data to be written back to client).
events = selectors.EVENT_READ | selectors.EVENT_WRITE
Register the socket corresponding to the new connection with selector
sel.register(conn, events, data=data)
Copy the code
Handles client reads and writes
Events triggered by the client can be classified into readable events and writable events:
- Readable event: indicates that the client sends new data to the server, which needs to be invoked by the server
recv()
Read data from the corresponding socket of the client. - Writable event: indicates that the socket corresponding to the client can receive data. In this case, the server can send data to the client
send()
orsendall()
Method to send data to the client.
def handle_data(key, mask) :
Get the socket that triggers the event
sock = key.fileobj
Register = register; register = register;
# is a types.SimpleNamespace object
data = key.data
Check if there are any readable events
if mask & selectors.EVENT_READ:
Receive data and add it to the outb property of data
recv_data = sock.recv(1024)
if recv_data:
data.outb += recv_data
The client sends empty data, indicating that the data has been sent
else:
print('closing connection to', data.addr)
The selector doesn't need to listen on the socket anymore, so it does
Unregister the corresponding socket in selector
sel.unregister(sock)
Close the socket connection
sock.close()
Check if there are writable events
if mask & selectors.EVENT_WRITE:
Return the data sent by the client to the client as is
if data.outb:
print('echoing'.repr(data.outb), 'to', data.addr)
sent = sock.sendall(data.outb)
Copy the code
reference
- Socket Programming in Python (Guide) – Real Python
- Socket programming
- TCP programming
- Socket Programming HOWTO – Python 3.9.6 documentation
- User Datagram Client and Server – Python Module of the Week
- Selectors – high-level I/O multiplexing