catalogue
TCP server programming [server programming steps]
Practice - write a group chat program
Complete code for ChatServer experiment
Socket introduction
Socket: socket is provided in Python Py standard library, a very low-level interface library
Socket is a general network programming interface, which has no one-to-one correspondence with the network level
The Socket file itself is used for transmission. If it is transmitted, there must be a BUffer. The BUffer is actually a queue
Protocol family: AF represents Address Family, which is used for the first parameter of socket()
name | meaning |
AF_INET | IPV4 |
AF_INET6 | IPV6 |
AF_UNIX | Unix Domain Socket, no window |
Socket type
name | meaning |
SOCK_STREAM | Connection oriented stream socket. Default, TCP protocol |
SOCK_DGRAM | Connectionless datagram socket. UDP protocol |
TCP programming
Socket programming requires both ends. Generally speaking, it requires a Server and a Client. The Server is called Server and the Client is called Client
TCP server programming [server programming steps]
- Create Socket object
- Bind IP Address and Port. bind() method; The IPv4 Address is a binary ("IP Address string", Port)
- Start listening. Listen on the specified IP port. listen() method
- Gets the Socket object that the user transfers data
- socket.accept() ----> (socket objcet,address info); The accept method blocks and waits for the client to establish a connection. It returns a new Socket object and a binary of the client address. The address is the address of the remote client. In IPv4, it is a binary (clientaddr,port)
- Accept data: recv(bufsize, [flags]) receives data using a buffer
- Send data: send(bytes) sends data
Question: what happens if you bind the same listening port twice?
- accept and recv are blocked. The main thread is often blocked and cannot work
import logging import socket import time #-*- coding:utf-8 -*- FORMAT='%(asctime)-15s\t [%(processName)s:%(threadName)s,%(process)d:%(thread)8d] %(message)s' logging.basicConfig(format=FORMAT,level=logging.INFO) socket_ob = socket.socket() #Create socket object socket_ob.bind(('127.0.0.1',9999)) #Create a binary socket_ob.listen() #Start listening #Open a connection socket_cob,info=socket_ob.accept() #Block until the connection with the client is successfully established, and a socket object and client address are returned #Apply a buffer to get data data_buff = socket_cob.recv(1024) #Multiple of 1024 logging.info(data_buff.decode("gbk")) socket_cob.send(b"day day up up") #Open another connection socket_cob2 ,info =socket_ob.accept() data_buff2 =socket_cob2.recv(1024) logging.info(data_buff2.decode("gbk")) socket_cob2.send(b"hello python") socket_ob.close() result: 2021-06-28 01:11:57,044 [MainProcess:MainThread,28864: 28184] I'm here 2021-06-28 01:11:57,044 [MainProcess:MainThread,28864: 28184] I'm coming.
result:
Practice - write a group chat program
Demand analysis:
- The chat tool is the CS program, C is each client and S is the server
- Functions of the server
- Start the service, including binding ports and listening
- Establish a connection and be able to establish a connection with multiple clients
- Accept information from different users
- Distribute, forward the accepted information of a user to all connected clients
- Stop distribution
- Record connected clients
code implementation
1. Basic framework implementation
class ChatServer: def __init__(self): def start(self): pass def accept(self): pass def recv(self): pass def stop(self): pass
2. Extend the code to realize the basic functions
import socket import threading import logging import datetime FORMAT ='%(asctime)s %(thread)d %(message)s' logging.basicConfig(level=logging.INFO) class ChatServer: def __init__(self,ip='127.0.0.1',port=9999): #Start service self.socket_server =socket.socket() self.addr_server = (ip,port) self.client = {} #client def start(self): #lsnrctl start self.socket_server.bind(self.addr_server) #Start service self.socket_server.listen() #Listener Service #accept blocks the main thread, so open a new thread threading.Thread(target=self.accept,name="accept").start() def accept(self): #Multiplayer connection while True: client_socket ,client=self.socket_server.accept() #block self.client[client] = client_socket #Add to client dictionary #Ready to accept data, recv is blocked, and start a new thread threading.Thread(target=self.recv,args=(client_socket,client),name="recv").start() def recv(self,clent_socket:socket.socket,client): while True: recv_data = clent_socket.recv(1024) #Blocking acceptance recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n '.format(datetime.datetime.now(),*client,recv_data.decode("gbk")) logging.info(recv_msg) logging.info(threading.enumerate()) recv_msg_en = recv_msg.encode() for s in self.client.values(): s.send(recv_msg_en) def stop(self): for s in self.client.values(): s.close() self.socket_server.close() cs = ChatServer() cs.start() result: D:\Python3.7.5\python.exe D:/rsa_code/tvp_chart_server.py INFO:root:20210703 17:45:39 127.0.0.1:52012 no1 INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>] INFO:root:20210703 17:45:58 127.0.0.1:52013 no2 INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>, <Thread(recv, started 25544)>] INFO:root:20210703 17:48:18 127.0.0.1:52034 no3 INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>, <Thread(recv, started 25544)>, <Thread(recv, started 6136)>] INFO:root:20210703 17:49:31 127.0.0.1:52034 If a client exits halfway, all will report an error INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>, <Thread(recv, started 25544)>, <Thread(recv, started 6136)>] Exception in thread recv: Traceback (most recent call last): File "D:\Python3.7.5\lib\threading.py", line 926, in _bootstrap_inner self.run() File "D:\Python3.7.5\lib\threading.py", line 870, in run self._target(*self._args, **self._kwargs) File "D:/rsa_code/tvp_chart_server.py", line 35, in recv recv_data = clent_socket.recv(1024) #Blocking acceptance ConnectionAbortedError: [WinError 10053] The software in your host has aborted an established connection. INFO:root:20210703 17:49:43 127.0.0.1:52012 no1 INFO:root:[<_MainThread(MainThread, stopped 7288)>, <Thread(accept, started 5584)>, <Thread(recv, started 10264)>, <Thread(recv, started 25544)>] Exception in thread recv: Traceback (most recent call last): File "D:\Python3.7.5\lib\threading.py", line 926, in _bootstrap_inner self.run() File "D:\Python3.7.5\lib\threading.py", line 870, in run self._target(*self._args, **self._kwargs) File "D:/rsa_code/tvp_chart_server.py", line 41, in recv s.send(recv_msg_en) ConnectionAbortedError: [WinError 10053] The software in your host has aborted an established connection.
3. The basic functions are completed, but there are problems. Event improvement is applicable [use evern to ensure that all programs exit when the program exits]
import socket import threading import logging import datetime FORMAT ='%(asctime)s %(thread)d %(message)s' logging.basicConfig(level=logging.INFO) class ChatServer: def __init__(self,ip='127.0.0.1',port=9999): #Start service self.socket_server =socket.socket() self.addr_server = (ip,port) self.client = {} #client self.event=threading.Event() def start(self): #lsnrctl start self.socket_server.bind(self.addr_server) #Start service self.socket_server.listen() #Listener Service #accept blocks the main thread, so open a new thread threading.Thread(target=self.accept,name="accept").start() def accept(self): #Multiplayer connection while not self.event.is_set(): client_socket ,client=self.socket_server.accept() #block self.client[client] = client_socket #Add to client dictionary #Ready to accept data, recv is blocked, and start a new thread threading.Thread(target=self.recv,args=(client_socket,client),name="recv").start() def recv(self,clent_socket:socket.socket,client): while not self.event.is_set(): recv_data = clent_socket.recv(1024) #Blocking acceptance recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n '.format(datetime.datetime.now(),*client,recv_data.decode("gbk")) logging.info(recv_msg) logging.info(threading.enumerate()) recv_msg_en = recv_msg.encode() for s in self.client.values(): s.send(recv_msg_en) def stop(self): for s in self.client.values(): s.close() self.socket_server.close() self.event.set() cs = ChatServer() cs.start() while True: cmd = input(">>>").strip() if cmd == "quit": cs.stop() threading.Event().wait(3) break
4. This version is basically functional and passed the test, but it still needs to be improved: add the client exit command
- For example: handling of various exceptions, removing client data from the dictionary after the client is disconnected, etc
- Problems caused by active disconnection of the client
- The server knows the appropriate disconnection. If the client is disconnected, the server does not know
- Therefore, the good practice is that the client develops a special message to inform the server to disconnect. However, if the client actively disconnects, the server actively sends an empty message, sends an exception when timeout, captures the exception and handles the exception
- Even if the disconnect command is provided for the client, there is no guarantee that the client will use it to disconnect, but this exit function should be added
- There are still flaws in the program, but the business capability is basically completed
import socket import threading import logging import datetime FORMAT ='%(asctime)s %(thread)d %(message)s' logging.basicConfig(level=logging.INFO) class ChatServer: def __init__(self,ip='127.0.0.1',port=9999): #Start service self.socket_server =socket.socket() self.addr_server = (ip,port) self.client = {} #client self.event=threading.Event() def start(self): #lsnrctl start self.socket_server.bind(self.addr_server) #Start service self.socket_server.listen() #Listener Service #accept blocks the main thread, so open a new thread threading.Thread(target=self.accept,name="accept").start() def accept(self): #Multiplayer connection while not self.event.is_set(): client_socket ,client=self.socket_server.accept() #block self.client[client] = client_socket #Add to client dictionary #Ready to accept data, recv is blocked, and start a new thread threading.Thread(target=self.recv,args=(client_socket,client),name="recv").start() def recv(self,clent_socket:socket.socket,client): while not self.event.is_set(): recv_data = clent_socket.recv(1024).decode("gbk").strip() #Blocking acceptance #Client exit if recv_data == "quit": self.client.pop(client) clent_socket.close() logging.info("{} quits".format(client)) break recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n'.format(datetime.datetime.now(),*client,recv_data) logging.info(recv_msg) logging.info(threading.enumerate()) for s in self.client.values(): s.send(recv_msg.encode()) def stop(self): #Out of Service for s in self.client.values(): s.close() self.socket_server.close() self.event.set() cs = ChatServer() cs.start() while True: cmd = input(">>>").strip() if cmd == "quit": cs.stop() threading.Event().wait(3) break logging.info(threading.enumerate()) #Used to observe the changes of threads after disconnection result: D:\Python3.7.5\python.exe D:/rsa_code/tvp_chart_server.py >>>INFO:root:20210703 22:31:36 127.0.0.1:62110 no1 INFO:root:[<_MainThread(MainThread, started 24548)>, <Thread(accept, started 5840)>, <Thread(recv, started 26420)>] INFO:root:20210703 22:31:46 127.0.0.1:62112 no2 INFO:root:[<_MainThread(MainThread, started 24548)>, <Thread(accept, started 5840)>, <Thread(recv, started 26420)>, <Thread(recv, started 24008)>] INFO:root:('127.0.0.1', 62112) quits INFO:root:20210703 22:32:13 127.0.0.1:62113 no3 INFO:root:[<_MainThread(MainThread, started 24548)>, <Thread(accept, started 5840)>, <Thread(recv, started 26420)>, <Thread(recv, started 26196)>] INFO:root:('127.0.0.1', 62113) quits
Common Socket methods
name | meaning |
socket.recv(bufsize [ ,flags]) | Get data. The default is blocking mode |
socket.recvfrom(bufsize[, flags]) | Get data and return a binary (bytes, address) |
sockt.recv_into(buffer[,nbytes[,flags]]) | After obtaining the data of nbytes, it is stored in the buffer. If nbytes is not specified or 0, the data of buffer size is stored in the buffer and the number of bytes received is returned |
socket.recvfrom_into(buffer[,nbytes[,flags]]) | Get the data and return a byte (addres) to the buffer |
socket.send(bytes,[, flags]) | TCP send data |
socket.sendall(bytes[,flags]) | TCP sends all data and returns None successfully |
socket.sendto(string[,flag],address) | UDP send data |
socket.sendfile(file,offset=0,count=None) | Send a file until EOF, suitable for high-performance OS Sendfile mechanism returns the number of bytes sent. If sendfile is not supported under win or it is not an ordinary file, send the file using send() and offset the high-speed starting position,. Start of version 3.5 |
socket.getpeername() | Returns the remote address of the connection socket. The return value is usually a tuple (ipaddr, port) |
socket.getsockname() | Returns the address of the socket itself, usually a tuple (ipaddr,port) |
socket.setblocking(flag) | If the flag is 0, set the socket to non blocking mode, otherwise set the socket to blocking mode (default) In non blocking mode, if the call recv() does not find any data, or the call send() cannot send data immediately, it will cause socket Error exception |
socket.settimeout(value) | Set the time period of the socket. Timeout is a floating-point number in seconds. A value of None indicates that there is no timeout. In general, forward periods should be set when sockets are first created, as they may be used for connection operations (such as connect()) |
socket.setsockopt(level,optname,value) | Set the socket value, such as the buffer size. There are too many. Go to the document. Different systems and versions are different |
MakeFile
def makefile(self, mode="r", buffering=None, *,encoding=None, errors=None, newline=Non
Create a file object related to socket change, regard recv method as read method and send method as write method
Use makefile to rewrite group chat
import socket import threading import logging import datetime FORMAT ='%(asctime)s %(thread)d %(message)s' logging.basicConfig(level=logging.INFO) class ChatServer: def __init__(self,ip='127.0.0.1',port=9999): #Start service self.socket_server =socket.socket() self.addr_server = (ip,port) self.client = {} #client self.event=threading.Event() def start(self): #lsnrctl start self.socket_server.bind(self.addr_server) #Start service self.socket_server.listen() #Listener Service #accept blocks the main thread, so open a new thread threading.Thread(target=self.accept,name="accept").start() def accept(self): #Multiplayer connection while not self.event.is_set(): client_socket ,client=self.socket_server.accept() #block #The rotating cup receives data. If it is blocked during recv, start a new thread f = client_socket.makefile("rw") self.client[client] = f #Add to client dictionary #Ready to accept data, recv is blocked, and start a new thread threading.Thread(target=self.recv,args=(f,client),name="recv").start() def recv(self,f,client): while not self.event.is_set(): data = f.readline().strip() #Blocking newline #Client exit if data == "quit": self.client.pop(client) f.close() logging.info("{} quits".format(client)) break recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n'.format(datetime.datetime.now(),*client,data) logging.info(recv_msg) logging.info(threading.enumerate()) for s in self.client.values(): s.write(recv_msg) s.flush() def stop(self): #Out of Service for s in self.client.values(): s.close() self.socket_server.close() self.event.set() cs = ChatServer() cs.start() while True: cmd = input(">>>").strip() if cmd == "quit": cs.stop() threading.Event().wait(3) break logging.info(threading.enumerate()) #Used to observe the changes of threads after disconnection
The above example completes the basic functions, but if the client actively disconnects or the readline is abnormal, the obsolete socket will not be removed from the clients. You can use exception handling to solve this problem
Complete code for ChatServer experiment
import socket import threading import logging import datetime FORMAT ='%(asctime)s %(thread)d %(message)s' logging.basicConfig(level=logging.INFO) class ChatServer: def __init__(self,ip='127.0.0.1',port=9999): #Start service self.socket_server =socket.socket() self.addr_server = (ip,port) self.client = {} #client self.event=threading.Event() def start(self): #lsnrctl start self.socket_server.bind(self.addr_server) #Start service self.socket_server.listen() #Listener Service #accept blocks the main thread, so open a new thread threading.Thread(target=self.accept,name="accept").start() def accept(self): #Multiplayer connection while not self.event.is_set(): client_socket ,client=self.socket_server.accept() #block #The rotating cup receives data. If it is blocked during recv, start a new thread f = client_socket.makefile("rw") self.client[client] = f #Add to client dictionary #Ready to accept data, recv is blocked, and start a new thread threading.Thread(target=self.recv,args=(f,client),name="recv").start() def recv(self,f,client): while not self.event.is_set(): try: data = f.readline() #Blocking newline except Exception as e: logging.error(e) #Exit with any exceptions data = "quit" data = data.strip() #Client exit if data == "quit": self.client.pop(client) f.close() logging.info("{} quits".format(client)) break recv_msg = '{:%Y%m%d %H:%M:%S} {}:{}\n{}\n'.format(datetime.datetime.now(),*client,data) logging.info(recv_msg) logging.info(threading.enumerate()) for s in self.client.values(): s.write(recv_msg) s.flush() def stop(self): #Out of Service for s in self.client.values(): s.close() self.socket_server.close() self.event.set() def main(): cs = ChatServer() cs.start() while True: cmd = input(">>>").strip() if cmd == "quit": cs.stop() threading.Event().wait(3) break logging.info(threading.enumerate()) #Used to observe the changes of threads after disconnection if __name__ == '__main__': main()