zoukankan      html  css  js  c++  java
  • Python实现网络图形化界面多人聊天室

    网络图形化界面多人聊天室 - Linux

    Windows版本:https://www.cnblogs.com/noonjuan/p/12078524.html

    Python实现网络多人聊天室基础上,添加图形化界面,实现网络图形化界面多人聊天室。

    代码结构:

    chatroom
    ├── client.py
    ├── server.py
    └── settings.py

    思路:

    server.py

      首先,在主进程(__main__)中启动两个进程,一个处理与客户端的连接和消息接收以及和图形化界面的信息传输,在终端中打印运行日记;另一个进程处理图形化界面,在这个进程中,新开一个线程,监听Pipe管道,实现与终端进程的信息交流。

    client.py

      结构与server.py相似,有两个进程——终端进程和图形化界面进程,但是新增了客户登录输入用户名的窗口,从这个窗口中获取用户名,使用管道将用户名传输给终端进程,终端进程再将用户名传给服务器等待登录请求验证,获得服务器发来的登录请求验证成功信息后,通过管道发送给图形化进程中的管道监听线程,使得图形化界面进程获得登录成功信息,进入聊天室。  

    注意:

      本项目运行环境为Ubuntu 16.04,可以运行。我尝试了一下在Windows 10下运行,发现需要将__main__主进程下的全局变量作为参数发给进程,而且在windows下运行会报AttributeError: module 'signal' has no attribute 'SIGKILL'错误,具体原因:https://blog.csdn.net/polyhedronx/article/details/81988335。我将SIGKILL改为了SIGTERM,运行中在关闭窗口时却会报PermissionError: [WinError 5] 拒绝访问错误。除此之外,还有许多的地方会报错,具体原因:https://segmentfault.com/a/1190000013681586

    运行截图:

    settings.py:

    # settings.py
    
    HOST = 'localhost'
    PORT = 5555
    buffersize = 1024
    ADDR = HOST, PORT
    
    login_code = ''
    for i in [bin(ord(i)) for i in 'login']:
        login_code += i
    
    logout_code = ''
    for i in [bin(ord(i)) for i in 'logout']:
        logout_code += i
    
    exit_code = ''
    for i in [bin(ord(i)) for i in 'exit']:
        exit_code += i
    
    
    if __name__ == '__main__':
        for v in dir():
            if not v.startswith('__'):
                print(v, eval(v))

    server.py

    # server.py
    
    import os
    import signal
    from socket import *
    from tkinter import *
    from settings import *
    from select import select
    from threading import Thread
    from time import ctime, sleep
    from tkinter.scrolledtext import ScrolledText
    from multiprocessing import Process, Pipe, Value
    
    def terminal():
        '实现终端操作'
        shm_terminal_pid.value = os.getpid()
        # 开启服务器
        s =  socket()
        s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        try:
            s.bind(ADDR)
        except:
            # 如果端口已经被占用
            print('Port is already in use now!')
            os.kill(shm_gui_pid.value, signal.SIGKILL)
            os._exit(0)
        s.listen()
    
        # IO多路复用,监听客户端连接通信以及保持与gui的通信
        rlist = [s, pipe_terminal]
        wlist = []
        xlist = []
    
        while True:
            # 阻塞等待IO事件
            print('Waiting for connection...')
            try:
                rs, ws, xs = select(rlist, wlist, xlist)
            except KeyboardInterrupt:
                # 如果服务器退出了,通知所有客户端并退出
                for c in rlist[2:]:
                    c.send(exit_code.encode())
                for r in rlist:
                    r.close()
                break
    
            for r in rs:
                if r is s:
                    # 接收客户端连接
                    print('IO: server')
                    conn, addr = s.accept()
                    print('Connected from', addr)
                    rlist.append(conn)
                elif r is pipe_terminal:
                    # 接收与pipe_gui的信息
                    print('IO: pipe_terminal')
                    data = pipe_terminal.recv()
                    # 将接收到的信息发送给所有客户端
                    print(data)
                    for c in rlist[2:]:
                        c.send(data.encode())
                else:
                    # 接收客户端信息
                    print('IO: client')
                    data = r.recv(buffersize)
                    if not data:
                        # 如果客户端退出了,关闭与客户端的连接,并告知其他客户端
                        print('客户端退出了')
                        r.close()
                        rlist.remove(r)
                    else:
                        # 如果发来的是登录验证信息
                        if data.decode().startswith(login_code):
                            print(data.decode())
                            username = data.decode().split(' ')[1]
                            if username not in users:
                                # 验证成功,将成功信息发送给客户端,并告知其他客户端新用户加入
                                data = login_code + ' Success'
                                r.send(data.encode())
                                users.append(username)
                                data = ctime() + '
    ' + username + ': ' + '加入了聊天室
    '
                                pipe_terminal.send(data)
                                for c in rlist[2:]:
                                    if c is not r:
                                        c.send(data.encode())
                            else:
                                data = login_code + ' Failure'
                                r.send(data.encode())
                        elif data.decode().startswith(logout_code):
                            print(data.decode())
                            username = data.decode().split(' ')[1]
                            users.remove(username) 
                        else:
                            # 将客户端发送的信息群发给其他客户端
                            for c in rlist[2:]:
                                if c is not r:
                                    c.send(data)
                            print(data.decode())
                            # 并将信息发送给pipe_gui以显示在gui上
                            pipe_terminal.send(data.decode())
    
    def gui():
        '实现图形化界面操作'
        # 设置共享内存
        shm_gui_pid.value = os.getpid()
    
        main = Tk()
        main.title('Chatroom - Administrator')
    
        ent = Entry(main, width=100)
        cnt = ScrolledText(main)
    
        cnt.pack(expand=True, fill=BOTH)
        ent.pack(expand=True, fill=BOTH)
    
        ent.focus()
        main.bind('<Return>', lambda event: write(widgets))
    
        widgets = {}
        widgets['ent'] = ent
        widgets['cnt'] = cnt
    
        thread_bridge = Thread(target=bridge, args=(widgets, ))
        thread_bridge.start()
    
        main.protocol('WM_DELETE_WINDOW', exit)
    
        mainloop()
        pipe_gui.close()
        
    def exit():
        print('Exit!')
        pipe_gui.send(exit_code)
        sleep(0.1)
        os.kill(shm_terminal_pid.value, signal.SIGKILL)
        os._exit(0)
    
    def bridge(widgets):
        # 监听与pipe_terminal的通信,将获得的信息显示在gui上
        while True:
            print('IO: pipe_gui')
            data = pipe_gui.recv()
            print(data)
            widgets['cnt'].insert(END, data)
    
    def write(widgets):
        print('Gui <Return> Event')
        # 打印ent文本到cnt文本框中去
        data = ctime() + '
    ' + 'Administrator: ' + widgets['ent'].get() + '
    '
        widgets['cnt'].insert(END, data)
        widgets['ent'].delete(0, END)
        # 将信息发送给pipe_terminal
        pipe_gui.send(data)
    
    
    if __name__ == '__main__':
        # 创建用户信息
        users = []
    
        # 共享内存,保存pid
        shm_gui_pid = Value('i', 0)
        shm_terminal_pid = Value('i', 0)
    
        # 创建管道,实现终端与图形化界面的通信
        pipe_terminal, pipe_gui = Pipe()
    
        # 创建两个进程,分别实现终端和图形化界面操作
        process_terminal = Process(target=terminal)
        process_gui = Process(target=gui)
    
        # 开始进程
        process_terminal.start()
        process_gui.start()
    
        # 回收进程
        process_terminal.join()
        process_gui.join()

    client.py

    # client.py
    
    import os
    import signal
    from socket import *
    from tkinter import *
    from settings import *
    from select import select
    from threading import Thread
    from time import ctime, sleep
    from tkinter.scrolledtext import ScrolledText
    from multiprocessing import Process, Pipe, Value
    from tkinter.messagebox import showerror, showinfo
    
    def terminal():
        '实现终端操作'
        shm_terminal_pid.value = os.getpid()
        # 开启客户端连接
        c =  socket()
        try:
            c.connect(ADDR)
        except:
            # 如果无法连接到客户端
            os.kill(shm_gui_pid.value, signal.SIGKILL)
            print('Failed to connect to server')
            os._exit(0)
        print('Connected to', ADDR)
    
        # IO多路复用,监听服务端通信以及保持与gui的通信
        rlist = [c, pipe_terminal, pipe_validate_terminal]
        wlist = []
        xlist = []
    
        # 服务器关闭信号
        flag = False
    
        while True:
            if flag:
                break
    
            # 阻塞等待IO事件
            try:
                rs, ws, xs = select(rlist, wlist, xlist)
            except KeyboardInterrupt:
                # 如果客户端ctrl-c退出程序
                for r in rlist:
                    r.close()
                break
    
            for r in rs:
                if r is c:
                    # 接收服务端的信息
                    print('IO: client')
                    data = c.recv(buffersize)
                    if not data:
                        print('服务器关闭了')
                        for r in rlist:
                            r.close()
                        flag = True
                    else:
                        # 如果发来的是登录验证结果信息
                        if data.decode().startswith(login_code):
                            print(data.decode())
                            status_code = data.decode().split(' ')[1]
                            if status_code == 'Success':
                                pipe_validate_terminal.send(login_code)
                            else:
                                pipe_validate_terminal.send('bad')
                        # 如果发来的消息是服务器退出消息
                        elif data.decode() == exit_code:
                            pipe_gui.send('管理员关闭了服务器')
                            os.kill(shm_gui_pid.value, signal.SIGKILL)
                            os._exit(0)
                        else:
                            print(data.decode())
                            # 将信息发送给pipe_gui
                            pipe_terminal.send(data.decode())
                elif r is pipe_terminal:
                    # 接收pipe_gui的信息
                    print('IO: pipe_terminal')
                    data = pipe_terminal.recv()
                    # 并把消息发送给服务端
                    c.send(data.encode())
                elif r is pipe_validate_terminal:
                    # 验证管道
                    data = pipe_validate_terminal.recv()
                    c.send(data.encode())
    
    def gui():
        '实现图形化界面操作'
        shm_gui_pid.value = os.getpid()
        # 登录界面
        login()
    
        main = Tk()
        main.title('Chatroom - ' + curuser)
    
        ent = Entry(main, width=100)
        cnt = ScrolledText(main)
    
        cnt.pack(expand=True, fill=BOTH)
        ent.pack(expand=True, fill=BOTH)
    
        ent.focus()
        main.bind('<Return>', lambda event: write(widgets))
    
        widgets = {}
        widgets['ent'] = ent
        widgets['cnt'] = cnt
    
        # 开启一个线程,监听pipe_terminal传过来的信息
        thread_bridge = Thread(target=bridge, args=(widgets, ))
        thread_bridge.start()
    
        main.protocol('WM_DELETE_WINDOW', exit_main)
        mainloop()
        pipe_gui.close()
        thread_bridge.join()
    
    def exit_main():
        data = ctime() + '
    ' + curuser + ': ' + '退出了聊天室
    '
        pipe_gui.send(data)
        print(data)
        data = logout_code + ' ' + curuser
        pipe_validate_gui.send(data)
        print(data)
        sleep(0.1)
        os.kill(shm_terminal_pid.value, signal.SIGKILL)
        os._exit(0)
    
    def bridge(widgets):
        # 监听与pipe_terminal的通信,将获得的信息显示在gui上
        while True:
            print('IO: pipe_gui')
            data = pipe_gui.recv()
            print(data)
            widgets['cnt'].insert(END, data)
    
    def write(widgets):
        print('Gui <Return> Event')
        # 打印ent文本到cnt文本框中去
        data = ctime() + '
    ' + curuser + ': ' + widgets['ent'].get() + '
    '
        widgets['cnt'].insert(END, data)
        widgets['ent'].delete(0, END)
        # 将信息发送给pipe_terminal
        pipe_gui.send(data)
    
    def login():
        top = Tk()
        top.title('chatroom - login')
    
        Label(top, text='username:').grid(row=0, column=0)
        ent = Entry(top)
        ent.grid(row=0, column=1)
    
        ent.focus()
    
        btn = Button(top, text='confirm', command=lambda: validate(top, ent))
        btn.grid(row=1, columnspan=2)
    
        top.bind('<Return>', lambda event: validate(top, ent))
    
        top.protocol('WM_DELETE_WINDOW', exit_login)
    
        mainloop()
    
    def validate(top, ent):
        print('validate')
        if not ent.get():
            showerror('Login Error', 'Empty Username!')
        else:
            username = ent.get()
            # 将用户名发送给terminal,再发送给服务器以验证
            pipe_validate_gui.send(login_code + ' ' + username)
            data = pipe_validate_gui.recv()
            if data == login_code:
                global curuser
                curuser = username
                showinfo('Login Successful', 'Welcome to Internet Chatroom.')
                top.destroy()
            else:
                showerror('Login Failure', 'Username already exists!')
                ent.delete(0, END)
    
    def exit_login():
        os._exit(0)
    
    if __name__ == '__main__':
        # 当前用户名
        curuser = ''
    
        # 共享内存
        shm_gui_pid = Value('i', 0)
        shm_terminal_pid = Value('i', 0)
    
        # 创建管道,实现终端与图形化界面的通信
        pipe_terminal, pipe_gui = Pipe()
    
        # 创建管道,实现login->client->server的登录验证
        pipe_validate_gui, pipe_validate_terminal = Pipe()
    
        # 创建两个进程,分别实现终端和图形化界面操作
        process_terminal = Process(target=terminal)
        process_gui = Process(target=gui)
    
        # 开始进程
        process_terminal.start()
        process_gui.start()
    
        # 回收进程
        process_terminal.join()
        process_gui.join()
    Resistance is Futile!
  • 相关阅读:
    升级.net core 3.x 后mvc项目调试状态编辑view代码不能实时预览
    C# 方法执行超时处理
    css 带换行的垂直居中
    Golang Web应用 创建docker镜像笔记(win 平台)
    vue element-ui tree 根节点固定子节点懒加载 首次加载根节点并展开
    记一则 Lambda内递归调用方法将集合对象转换成树形结构
    一个简单的异常/条件重试类(C#)
    查找由于页面宽度溢出导致网页出现莫名空白的查看方法
    锯齿下边框效果
    cdh5.7 做完HA后hive 查询出现异常: expected: hdfs://nameservice
  • 原文地址:https://www.cnblogs.com/noonjuan/p/11305117.html
Copyright © 2011-2022 走看看