美文网首页学习提升模版Python
01.python网络电子白板雏形

01.python网络电子白板雏形

作者: __豆约翰__ | 来源:发表于2020-01-02 13:30 被阅读0次

    配套视频教程地址

    配套视频教程

    1.实现用户登录功能

    1.当有新客户端连接上来时,发送字符串'HLO'暗号接头;
    2.发送所有其他在线客户端的昵称给新客户端,以便新客户端确认用户输入的昵称是否合法;
    3.等待新客户端发送过来其昵称,收到昵称后,在Clients列表中添加新客户端;
    4.发送新客户端userid给其他客户端,发送其他客户端userid给新客户端
    5.有客户端断开,通知其他客户端

    server.py

    import socket
    import threading
    import time
    
    # Here we have the global variables
    # The Clients consists of the list of thread objects clients
    # The logs consists of all the messages send through the server, it is used to redraw when someone new connects
    Clients = []
    Logs = {}
    
    
    # -------------------------------SERVER ----------------------------------------
    # This is the Server Thread, it is responsible for listening to connexions
    # It opens new connections as it is a thread constantly listening at the port for new requests
    class Server:
        ID = 1
    
        def __init__(self, host, port):
            self.host = host
            self.port = port
    
            # Initialize network
            self.network = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.network.bind((self.host, self.port))
            self.network.listen(10)
            print("The Server Listens at {}".format(port))
    
            # Start the pinger
            threading.Thread(target=self.pinger).start()
    
        # Here we have the main listener
        # As somebody connects we send a small hello as confirmation
        # Also we give him an unique ID that will be able to differentiate them from other users
        # We send the server logs so the new user can redraw at the same state in the board
        # We send the list of connected users to construct the permission system
        def start(self):
            while True:
                connexion, infos_connexion = self.network.accept()
                print("Sucess at " + str(infos_connexion))
                connexion.send('HLO'.encode())
                time.sleep(0.1)
    
                # Send all ID's so user cannot repeat any id's
                msg = " "
                for client in Clients:
                    msg = msg + " " + client.clientID
                connexion.sendall(msg.encode())
                time.sleep(0.1)
    
                # Here we start a thread to wait for the users nickname input
                # We do this so a server can wait for a nickname input and listen to new connections
                threading.Thread(target=self.wait_for_user_nickname, args=[connexion]).start()
    
        # This function was created just to wait for the users input nickname
        # Once it's done if sends the logs so the user can be up to date with the board
        # And finally it creates the Client Thread which will be responsible for listening to the user messages
        def wait_for_user_nickname(self, connexion):
            # Receive the chosen ID from user
            try:
                new_user_id = connexion.recv(1024).decode()
    
                for log in Logs:
                    connexion.send(Logs[log])
    
                new_client = Client(connexion, new_user_id)
                new_client.load_users()
                Clients.append(new_client)
                Server.ID = Server.ID + 1
                new_client.start()
            except ConnectionResetError:
                pass
            except ConnectionAbortedError:
                pass
    
    
        # Function used by pinger
        # Sends a removal message to alert all users of the disconnection
        def announce_remove_user(self, disconnectedClient):
            msg = 'RE' + ' ' + str(disconnectedClient.clientID) + ' ' + 'Ø'
            msg = msg.encode('ISO-8859-1')
            print(threading.enumerate())
            for client in Clients:
                client.connexion.sendall(msg)
    
        # This is the pinger function, it is used to check how many users are currently connected
        # It pings all connections, if it receives a disconnection error, it does the following things:
        # 1.Sends a removal message to alert all users of the disconnection
        # 2.Removes client from list of clients to avoid sending messages to it again
        # 3.Sends the permission to delete the disconnected user stuff from the board!
        def pinger(self):
            while True:
                time.sleep(0.1)
                for client in Clients:
                    try:
                        msg = "ß".encode('ISO-8859-1')
                        print('ß')
                        client.connexion.send(msg)
                    except ConnectionResetError:
                        client.terminate()
                        Clients.remove(client)
                        self.announce_remove_user(client)
                    except ConnectionAbortedError:
                        client.terminate()
                        Clients.remove(client)
                        self.announce_remove_user(client)
    
    
    # -----------------------------------CLIENTS -------------------------------------
    # This is the client thread, it is responsible for dealing with the messages from all different clients
    # There is one thread for every connected client, this allows us to deal with them all at the same time
    class Client():
        MessageID = 0
    
        def __init__(self, connexion, clientID):
            self.connexion = connexion
            self.clientID = clientID
            self._run = True
    
        def load_users(self):
            for client in Clients:
                msg = 'A' + ' ' + str(client.clientID) + ' ' + 'Ø'
                self.connexion.send(msg.encode('ISO-8859-1'))
                msg = 'A' + ' ' + str(self.clientID) + ' ' + 'Ø'
                client.connexion.send(msg.encode('ISO-8859-1'))
    
        def terminate(self):
            self._run = False
    
        def start(self):
            while self._run:
                try:
                    # Here we start by reading the messages
                    # Split according to the protocol
                    msg = ""
                    while True:
                        data = self.connexion.recv(1).decode('ISO-8859-1')
                        if data == "Ø":
                            break
                        msg = msg + data
    
                    splitted_msg = msg.split()
    
                    # We do not want to keep the logs
                    if splitted_msg[0] in ['TA']:
                        self.echoesAct3(msg)
                        continue
                # We pass the Connection Reset Error since the pinger will deal with it more effectivelly
                except ConnectionResetError:
                    pass
                except ConnectionAbortedError:
                    pass
    
        # Main echoes function!
        # This is responsible for echoing the message between the clients
        def echoesAct3(self, msg):
            msg = msg + " Ø"
            msg = msg.encode('ISO-8859-1')
            for client in Clients:
                client.connexion.sendall(msg)
    
    if __name__ == "__main__":
        host = ''
        port = 5000
        server = Server(host, port)
        server.start()
    
    

    graphical_widgets.py
    ExternalWindows类实现获取用户输入ip端口的界面和输入昵称的界面

    from tkinter import *
    import tkinter.font as font
    
    class ExternalWindows:
    
        def __init__(self):
            pass
    
        # Default ip and port for debbuging
        _IP = "127.0.0.1"
        _Port = 5000
        # Text for the drawing text part!
        _Text = "WOW"
        _Nickname = "lol"
    
        # This temporary variable is used to get any other things we might need from the user
        # A little bit confusing but it works
        _Temp = ""
    
        # A flag to check whether you press the default exit button
        _Flag = False
    
        # This method is used to show error boxes
        # Everytime an error message we show a box with the given message
        @classmethod
        def show_error_box(cls, msg):
            master = Tk()
            Label(master, text= msg).grid(row=0)
            Button(master, text='OK', command= master.destroy ).grid(row=1,  pady=4)
            master.mainloop()
    
        # This is the method that is used to get the ip and the port from the user!
        # It sets the protected class variable _Ip and _port to the values given by our user
        # This value is set by inputing the value in the widgets
        @classmethod
        def getValuesFromUser(cls):
            def show_entry_fields():
                try:
                    cls._IP = e1.get()
                    cls._Port = int(e2.get())
                    cls._Flag = True
                except:
                    pass
                master.destroy()
    
            def exit_program():
                exit()
    
            cls._Flag = False
            master = Tk()
            Label(master, text="Please type the host information").grid(row=0)
            Label(master, text="IP:").grid(row=1)
            Label(master, text="Port:").grid(row=2)
    
            e1 = Entry(master)
            e2 = Entry(master)
    
            e1.grid(row=1, column=1)
            e2.grid(row=2, column=1)
    
            # Button(master,text='Start',command=master.quit).grid(row=3,column=1,sticky=W,pady=4)
            #Button(master, text='Set', command=show_entry_fields).grid(row=3, column=0, sticky=W, pady=4)
            #button_set = Button(master, text="Set", command=show_entry_fields).grid(row=3, column=0, sticky=W, pady=4)
            button = Button(master)
            button.config(text="Set", command=show_entry_fields)
            button.grid(row=3, column=0, sticky=W, pady=4)
            Button(master, text='Exit Program', command=exit_program).grid(row=4, column=0, sticky=W, pady=4)
            master.bind('<Return>', lambda event=None: button.invoke())
            master.mainloop()
    
            cls.check_ip_and_port()
    
    
        # This method checks using regular expressions to see if the IP and port number
        # are within valid parameters
        @classmethod
        def check_ip_and_port(cls):
            if cls._Flag == False:
                exit()
            expression = r"^(?=.*[^\.]$)((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.?){4}$"
            if re.search(expression, cls._IP) is None:
                cls.show_error_box("Please type a valid IP address")
                cls.getValuesFromUser()
    
            if (type(cls._Port) != int or cls._Port <= 1024 or cls._Port > 50000):
                cls.show_error_box("Please type a valid port number")
                cls.getValuesFromUser()
    
            print("Information received IP: {} Port: {}".format(cls._IP, cls._Port))
    
    
        # Method for getting text from user
        # This is used to print the text on the drawing board!
        @classmethod
        def get_text_from_user(cls):
    
            def get_text():
                temp = e1.get()
                master.destroy()
                if( "Ø" in temp):
                    cls.show_error_box("Invalid character in expression")
                else:
                    cls._Text = temp
    
            master = Tk()
            Label(master, text="What do you want to write?").grid(row=0)
            e1 = Entry(master)
            Label(master, text="Text: ").grid(row=1)
            e1.grid(row=1, column=1)
    
            button = Button(master)
            button.config(text='Set', command=get_text)
            button.grid(row=2, column=0, sticky=W, pady=4)
            master.bind('<Return>', lambda event=None: button.invoke())
            master.mainloop()
    
        # This class is used to retrieve the user's selected nickname
        @classmethod
        def get_nickname_from_user(cls):
            def get_text():
                try:
                    cls._Nickname = e1.get()
                    cls._Flag = True
                except:
                    pass
                master.destroy()
            cls._Flag = False
            master = Tk()
            Label(master, text="Choose a Nickname").grid(row=0)
            e1 = Entry(master)
            Label(master, text="Text: ").grid(row=1)
            e1.grid(row=1, column=1)
    
            button = Button(master)
            button.config(text='Set', command=get_text)
            button.grid(row=2, column=0, sticky=W, pady=4)
            master.bind('<Return>', lambda event=None: button.invoke())
            Button(master, text='Exit', command= exit).grid(row=2, column=0, pady=4)
            master.mainloop()
    
            cls.check_nickname()
    
        # This function is used to check to see if the nickname is within valid parameters
        # We only allow letters and 6 characters long
        # Why 6? Cause i wanted that way, it's a nickname for God sake
        @classmethod
        def check_nickname(cls):
            if cls._Flag == False:
                exit()
            if (len(cls._Nickname) > 6):
                ExternalWindows.show_error_box("Please choose a shorter nickname. 6 characters long")
                cls.get_nickname_from_user()
    
            expression = r"^[a-zA-Z]+$"
            if re.search(expression, cls._Nickname) is None:
                cls.show_error_box("Only letters")
                cls.get_nickname_from_user()
    
        # This method was created for getting general data from the user
        # It's not currently used, but it was implemented due to it's potential
        # It may be necessary to get things for the user within new updates
        # Therefore a flexible method that allows us to get anything we want serves it purpose
        @classmethod
        def get_anything_from_user(cls, msg):
    
            def get_text():
                cls._Temp = e1.get()
                master.destroy()
    
            master = Tk()
            Label(master, text = msg).grid(row=0)
            e1 = Entry(master)
            Label(master, text="Text: ").grid(row=1)
            e1.grid(row=1, column=1)
    
            Button(master, text='Set', command=get_text).grid(row=2, column=0, sticky=W, pady=4)
            master.mainloop()
    
        # Return methods for the protected variables!
        @classmethod
        def return_ip(cls):
            return cls._IP
    
        @classmethod
        def return_port(cls):
            return cls._Port
    
        @classmethod
        def return_text(cls):
            return cls._Text
    
        @classmethod
        def return_nickname(cls):
            return cls._Nickname
    
        @classmethod
        def return_temp(cls):
            return cls._Temp
    
    if __name__ == '__main__':
    
        ExternalWindows.getValuesFromUser()
        print(ExternalWindows.return_ip())
    
    
        ExternalWindows.get_nickname_from_user()
        print(ExternalWindows.return_nickname())
    
    

    whiteboard.py
    实现电子白板界面

    from tkinter import *
    
    class Whiteboard:
    
        # Here we initiate with the line drawing tool, this is the tool currently used to draw
        drawing_tool = "line"
        # Here we have the dictionary with the used colors to paint!
        Colors = {'b': 'blue', 'r': 'red', 'g': 'green', 'o': 'orange', 'y': 'yellow', 'c': 'cyan', 'p': 'purple1',
                  'd': 'black', 's': 'snow'}
    
        # Here we initiate the whiteboard by calling all the functions necessary to construct it
        # And also initiate the parent class!
        # We call the save and load and permission classes here, we need them to instantiate the buttons
        def __init__(self):
            self._init_whiteboard()
            self._init_item_button()
            # self._init_user_button()
            self._init_color_button()
            self._init_drawing_area()
            self.color = 'b'
    
        # Here we have the main loop of the Whiteboard when it is closed it executes the code just bellow
        # Which raises an exception that closes the software
        def show_canvas(self):
            mainloop()
            raise Exception("Board Closed Ending Execution")
    
        # Here we initiate the whiteboard with Tk() and set it's dimensions
        def _init_whiteboard(self):
            self.myWhiteBoard = Tk()
            self.myWhiteBoard.geometry('2000x1100')
    
        # ---------------------------------- Button functions ------------------------------------------
        # Here we have the buttons on the top of the whiteboard
        # Those buttons are responsible for changing the drawing tool as their name indicates
        # Every button pressed is a different drawing tool
        def _init_item_button(self):
            Button(self.myWhiteBoard, text='line', height=1, width=5, bg='dark goldenrod', font='Arial',
                   command=lambda: self.set_drawing_tool('line')).place(x=70, y=0)
            Button(self.myWhiteBoard, text='rect', height=1, width=5, bg='saddle brown', font='Arial',
                   command=lambda: self.set_drawing_tool('rectangle')).place(x=140, y=0)
            Button(self.myWhiteBoard, text='oval', height=1, width=5, bg='NavajoWhite4', font='Arial',
                   command=lambda: self.set_drawing_tool('oval')).place(x=210, y=0)
            Button(self.myWhiteBoard, text='text', height=1, width=5, bg='SteelBlue4', font='Arial',
                   command=self.get_text_from_user).place(x=280, y=0)
            Button(self.myWhiteBoard, text='pencil', height=1, width=5, bg='DeepSkyBlue2', font='Arial',
                   command=lambda: self.set_drawing_tool('pencil')).place(x=350, y=0)
            Button(self.myWhiteBoard, text='circle', height=1, width=5, bg='Turquoise2', font='Arial',
                   command=lambda: self.set_drawing_tool('circle')).place(x=420, y=0)
            Button(self.myWhiteBoard, text='square', height=1, width=5, bg='CadetBlue1', font='Arial',
                   command=lambda: self.set_drawing_tool('square')).place(x=490, y=0)
            Button(self.myWhiteBoard, text='eraser', height=1, width=5, bg='purple1', font='Arial',
                   command=lambda: self.set_drawing_tool('eraser')).place(x=560, y=0)
            Button(self.myWhiteBoard, text='drag', height=1, width=5, bg='green', font='Arial',
                   command=lambda: self.set_drawing_tool('drag')).place(x=630, y=0)
            # Button(self.myWhiteBoard, text='delALL', height=1, width=5, bg='snow', font='Arial',
            #        command=self.erase_all).place(x=700, y=0)
    
        # This is the own user button, it is used mostly as a display of the user name
        def _init_user_button(self):
            Button(self.myWhiteBoard, text=self.my_connexion.ID, height=1, width=5, bg='snow').place(x=1100, y=0)
    
        # This are the color buttons, they are responsible for changing the colors of the corresponding drawings
        def _init_color_button(self):
    
            Button(self.myWhiteBoard, height=1, width=5, bg='red',
                   command=lambda: self.set_color('red')).place(x=1010,y=50)
            Button(self.myWhiteBoard, height=1, width=5, bg='orange',
                   command=lambda: self.set_color('orange')).place(x=1010, y=100)
            Button(self.myWhiteBoard, height=1, width=5, bg='yellow',
                   command=lambda: self.set_color('yellow')).place(x=1010, y=150)
            Button(self.myWhiteBoard, height=1, width=5, bg='green',
                   command=lambda: self.set_color('green')).place(x=1010, y=200)
            Button(self.myWhiteBoard, height=1, width=5, bg='cyan',
                   command=lambda: self.set_color('cyan')).place(x=1010, y=250)
            Button(self.myWhiteBoard, height=1, width=5, bg='blue',
                   command=lambda: self.set_color('blue')).place(x=1010, y=300)
            Button(self.myWhiteBoard, height=1, width=5, bg='purple1',
                   command=lambda: self.set_color('purple1')).place(x=1010, y=350)
            Button(self.myWhiteBoard, height=1, width=5, bg='black',
                   command=lambda: self.set_color('black')).place(x=1010, y=400)
            Button(self.myWhiteBoard, height=1, width=5, bg='snow',
                   command=lambda: self.set_color('snow')).place(x=1010, y=450)
            # Button(self.myWhiteBoard, height=1, width=5, bg='snow', text="Save", command=self.save_and_load.save).place(x=1010,
            #                                                                                                    y=500)
            # Button(self.myWhiteBoard, height=1, width=5, bg='snow', text="Load", command=self.save_and_load.load).place(x=1010,
            #                                                                                                    y=550)
    
    
    
        # Here we initiate the drawing area, which is a canvas
        # and place it accordingly
        def _init_drawing_area(self):
            self.drawing_area = Canvas(self.myWhiteBoard, width=1000, height=1000, bg='white')
            self.drawing_area.place(y=50)
    
        # ---------CHANGE DRAWING TOOL---------------
        # Here we change the drawing tools according to the widget that was pressed on the top
        def set_drawing_tool(self, tool):
            self.drawing_tool = tool
    
        # This functions are called when the user presses color button!
        # They set the byte that will be send on the message to send the color to be used to draw
        def set_color(self, color):
            color_to_set = [k for k, v in self.Colors.items() if v == color]
            if len(color_to_set) == 1:
                self.color = color_to_set[0]
            else:
                print("Unknown color, check the code!")
    
        #################################GETIING THE TEXT##################################################################
        # This part gets text from the user before printing it!
        # It refers to the text functionality of the text button widget on the top
        def get_text_from_user(self):
            self.drawing_tool = 'text'
    
    
        # ----------------------------- Erase All Function -----------------------------------------------------------------
        # Since this is an extra functionality i will explain it more extensively
        # This function finds every object tagged with the user nickname (user ID)
        # And also every single object tagged with an user which is in his list of permissions
        # Since every user is in it's own list of permissions, we only need to check the list of permissions
        # Disconnected users loose their privileges!
        # Them it sends a delete message for every one of them!
    
    if __name__ == '__main__':
        wb = Whiteboard()
        wb.show_canvas()
    

    network.py
    代理客户端消息收发

    import socket
    from graphical_widgets import ExternalWindows
    
    # Here we have the connection class! It is responsible for starting the connection with the server
    # It also does the message sending and receiving part (handles all messages)
    class MConnection:
    
        # Here we have the first part, we start the program by using the getValuesFromUser function
        # This function is the main box that appears requesting the IP and the port from the user
        # From this class we recover the values that have been typed on the widget to start our connection!
        def __init__(self):
            ExternalWindows.getValuesFromUser()
            self._host = ExternalWindows.return_ip()
            self._port = ExternalWindows.return_port()
    
            # Here we attempt to establish a connection
            # We open a socket in the given port and IP
            # And start by checking to see if we received a greeting 'HLO'
            # Afterwards the server will send a list with all the names of the connected users
            # This is done to avoid repeating names when creating a new user
            # After the id has been chosen it responds to the server so it can add to the list of clients
            try:
                self.s = socket.socket()
                self.s.connect((self._host, self._port))
                data = self.s.recv(3).decode()
                if data == 'HLO':
                    print('[Network]Connection with %s:%s established.' % (self._host, self._port))
    
                data = self.s.recv(1024).decode()
                UserNames = data.split()
                print(UserNames)
    
                while True:
                    ExternalWindows.get_nickname_from_user()
                    self._ID = ExternalWindows.return_nickname()
                    if (self._ID in UserNames):
                        ExternalWindows.show_error_box("User name is taken")
                        continue
                    break
    
                self.s.sendall(self._ID.encode())
                print("Received ID is : " + self._ID)
            except SystemExit:
                exit()
            except:
                ExternalWindows.show_error_box("Could not connect to server")
                raise Exception("Connection Failed")
    
        # Here we have the send message function
        # The messages are received in the form of a tuple (A,B,C)
        # And in here they are transformed in a binary message of the form b"A B C Ø"
        # The Ø indicates the end of the message! and the type and beginning of the message is indicated
        # with a set of specific letters.
        def send_message(self, msg):
            msg = ' '.join(map(str, msg))
            msg = msg + " Ø"
            try:
                msg = msg.encode('ISO-8859-1')
                self.s.send(msg)
            except UnicodeEncodeError:
                ExternalWindows.show_error_box("Invalid character!")
    
        # Here we receive the messages, we take in a message of the form b"A B C Ø" and transform it
        # We transform it in a tuple of format (A,B,C)
        # From that tuple each class will recover the essential information for all it's functions
        # Now the ß that is ignored is a ping from the server
        # It is used to detect if users are still connected and act accordingly to their connection
        # So we ignore it when receiving messages
        def receive_message(self):
            msg = ""
            while True:
                data = self.s.recv(1).decode('ISO-8859-1')
                if data == "Ø":
                    break
                if data == "ß":
                    # print('receive ß')
                    continue
                msg = msg + data
            msg = msg.split()
            return msg
    
    
        # Encapsulation of the USER ID ##########################################################
        def get_user_id(self):
            return self._ID
    
        def set_user_id(self, ID):
            self._ID = ID
    
        ID = property(get_user_id, set_user_id)
    
    if __name__ == '__main__':
        m = MConnection()
        print('bye')
    
    

    client.py
    总控程序

    import time
    from threading import Thread
    from graphical_widgets import ExternalWindows
    from network import MConnection
    from whiteboard import Whiteboard
    
    
    class Client(Thread,Whiteboard):
    
    
        def __init__(self):
            self.my_connexion = MConnection()
            Whiteboard.__init__(self)
            Thread.__init__(self)
            self.setDaemon(True)
    
            # This part refers to the class that allows user to exchange messages between themselves
    
        # The run handles the messages
        # As it recognizes the type it assigns to the proper class that will handle it!
        def run(self):
            while True:
                try:
                    msg = self.my_connexion.receive_message()
                    if msg[0] == "ß":
                        print(msg[0])
                except ValueError:
                    pass
                except IndexError:
                    pass
                except ConnectionResetError:
                    ExternalWindows.show_error_box("Server down please save your work")
                    self.save_and_load.save()
                    self.myWhiteBoard.destroy()
    
    
    
    if __name__ == '__main__':
        c = Client()
        c.start()
        c.show_canvas()
    
    

    相关文章

      网友评论

        本文标题:01.python网络电子白板雏形

        本文链接:https://www.haomeiwen.com/subject/vhusoctx.html