Python實現網絡圖形化界面多人聊天室 - Linux


網絡圖形化界面多人聊天室 - 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() + '\n' + username + ': ' + '加入了聊天室\n'
                            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() + '\n' + 'Administrator: ' + widgets['ent'].get() + '\n'
    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() + '\n' + curuser + ': ' + '退出了聊天室\n'
    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() + '\n' + curuser + ': ' + widgets['ent'].get() + '\n'
    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()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM