Python provides two levels of access to network services. :
View the help of the socket class as follows
import socket #Import the socket module
>>> help(socket.socket)
Focus on the initialization function:
__ init__(self, family=<AddressFamily.AF_INET:2>, type=<SocketKind.SOCK_STREAM:1>, proto=0, fileno=None)
SOCK_STREAM
or SOCK_DGRAM
according to whether it is connection-oriented or non-connectionThe following is a TCP chat room and a UDP chat room
Get the handling of multiple connections
Open the accept thread, the execution of the accept operation starts to block, and when there is a client connection, another thread recv is opened for data reception processing. Then the accept thread continues to block, waiting for subsequent client connections.
Blocking processing
When the server processes the client connection, there are two obstructions, namely:
Therefore, two threads need to be opened for separate processing, otherwise the main thread will be blocked.
Handling of active client disconnection
When the client actively disconnects, if the server is not notified, the client connection saved on the server will not be cleaned up, which is unreasonable. Therefore, when the client actively disconnects, we agree at the application layer that before the client launches, it needs to send the /quit
command to the server, and then the server closes the socket.
The server side of the chat room mainly monitors the port, handles the connection from the client side, and distributes data to all client sides
Code
import socket
import threading
classTcpChatServer:
def __init__(self, ip='192.168.110.13', port=9001):
self.ip = ip
self.port = port
self.clients ={}
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
self.event = threading.Event()
def recv(self, so, ip ,port):while not self.event.is_set():
data = so.recv(1024).decode() #Convert the received byte data bytes to utf-8 format string
if data.strip()=='/quit': #Handling when the client actively disconnects
so.close()
self.clients.pop((ip, port))returnfor s in self.clients.values(): #Broadcast
s.send('{}:{}\n{}'.format(ip, port, data).encode())
def accept(self):while not self.event.is_set():
so,(ip, port)= self.sock.accept()
self.clients[(ip, port)]= so
# Because so.recv will block, so a separate thread is opened to process the receiving part of the data. In this way accept can continue to accept links from other clients
threading.Thread(target=self.recv, args=(so, ip, port), name='client-{}:{}'.format(ip, port)).start()
def start(self):
self.sock.bind((self.ip, self.port))
self.sock.listen()
t = threading.Thread(target=self.accept, daemon=True) #In order not to block the main thread, open a separate thread to handle accept (accept will block the thread)
try:
t.start()
t.join() #Block until KeyboardInterrupt is obtained
except KeyboardInterrupt:
self.stop()
def stop(self):for s in self.clients.values():
s.close()
self.sock.close()
self.event.set() #Stop all loops
if __name__ =='__main__':
tcp_chat_server =TcpChatServer()
tcp_chat_server.start()
The client side of the chat room mainly initiates a connection, connects to the server side, and accepts messages broadcast and distributed from the server side.
Code
import socket
import threading
classTcpChatClient:
def __init__(self):
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
self.event = threading.Event()
def recv(self): #The client needs to always receive messages broadcast and distributed by the server
while not self.event.is_set():
data = self.sock.recv(1024).decode()
data = data.strip()print(data)
def send(self): #Send message
while not self.event.is_set():
data =input()
self.sock.send(data.encode())if data.strip()=='/quit': #send/Close itself when quit
self.stop()
def start(self, ip, port):
self.sock.connect((ip, port))
s = threading.Thread(target=self.send, daemon=False)
r = threading.Thread(target=self.recv, daemon=False)
s.start()
r.start()
def stop(self):
self.sock.close()
self.event.set()if __name__ =='__main__':
tcp_chat_client =TcpChatClient()
tcp_chat_client.start('192.168.110.13',9001)
Blocking processing
When the UDP server receives the client's message, the socket.recvfrom(1024)
method is used to save the client's address information. This method will block the current thread, so the thread needs to be opened for separate processing.
Handling of active client disconnection
After the UDP client is actively closed, the server cannot detect that the client has been closed. We can use the following two methods:
The UDP server program starts a thread waiting to receive the client's data, and then broadcasts it to other clients, and checks whether the heartbeats of all connections have timed out.
Code
import socket
import datetime
import threading
classUdpChatServer:
def __init__(self, ip='192.168.110.13', port=9001):
self.addr =(ip, port)
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.clients ={}
self.event = threading.Event()
def recv(self):while not self.event.is_set():
data, addr = self.sock.recvfrom(1024)
data = data.decode().strip()
now = datetime.datetime.now()if data =='#ping#': #Determine whether to receive a heartbeat
self.clients[addr]= now #When receiving the heartbeat, save the client address and update the timestamp
continue
disconnected =set() #Judge all broken links without receiving data once
for addr, timestamp in self.clients.items():if(now - timestamp).total_seconds()>10: #Failure condition: 2 times (that is, 10s), the client is judged to be closed without receiving a heartbeat
disconnected.add(addr)else:
self.sock.sendto('{}:{}\n{}'.format(addr[0], addr[1], data).encode(), addr)for addr in disconnected:
self.clients.pop(addr)
def start(self):
self.sock.bind(self.addr) #After binding the port, start the thread and always accept the client's data
t = threading.Thread(target=self.recv(), daemon=True)try:
t.start()
t.join()
except KeyboardInterrupt:
self.stop()
def stop(self):
self.event.set()
self.sock.close()if __name__ =='__main__':
udp_chat_server =UdpChatServer()
udp_chat_server.start()
The main thread of the UDP client has been waiting for the user to input data and then sending the data to the server. At the same time, a heartbeat process and a thread for receiving broadcast data from the server are started.
Code
import socket
import threading
import time
classUdpChatClient:
def __init__(self, ip, port):
self.addr =(ip, port)
self.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.event = threading.Event()
def heartbeat(self): #Heartbeat thread function: send a heartbeat every 5s
while not self.event.wait(5):
self.sock.sendto(b'#ping#', self.addr)
def recv(self): #Waiting to receive the data broadcast by the udp server
while not self.event.is_set():
data = self.sock.recv(1024)print(data.decode())
def start(self):
threading.Thread(target=self.heartbeat, name='heartbeat', daemon=True).start()
threading.Thread(target=self.recv, name='recv', daemon=True).start()print('Please speak in 5s')
time.sleep(5) #Because the server must receive a heartbeat before saving the secondary client, it needs to wait for 5s
print('Please start speaking')while not self.event.is_set():
data =input('')
data = data.strip()if data =='/quit':
self.event.set()
self.sock.close()return
self.sock.sendto(data.encode(), self.addr)if __name__ =='__main__':
udp_chat_client =UdpChatClient('192.168.110.13',9001)
udp_chat_client.start()
TODO (Flowsnow): Rewrite the TcpChatServer and UdpChatServer of the chat room program
Appendix 1: The essential difference between TCP and UDP
udp: All the datagrams sent by the client are stacked on the queue, and then the server processes them one by one
tcp: Each client and server have a connection channel, only processing the data stream corresponding to the client
Attachment 2: Reference Materials
Recommended Posts