Python: network programming, TCP, mackfile, chat writing

catalogue

Socket introduction

TCP programming

TCP server programming [server programming steps]

Practice - write a group chat program

Common Socket methods

MakeFile

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()

namemeaning
AF_INETIPV4
AF_INET6IPV6
AF_UNIXUnix Domain Socket, no window

Socket type

namemeaning
SOCK_STREAMConnection oriented stream socket. Default, TCP protocol
SOCK_DGRAMConnectionless 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

namemeaning
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()

Keywords: Python Makefile socket TCPIP

Added by bbxrider on Mon, 24 Jan 2022 05:01:26 +0200