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!
原文地址:https://www.cnblogs.com/noonjuan/p/11305117.html