python 簡易微信實現(註冊登入+資料庫儲存+聊天+GUI+檔案傳輸)

weixin_43398590發表於2020-01-26

python socket+tkinter詳解+簡易微信實現

歷經多天的努力,查閱了許多大佬的部落格後終於實現了一個簡易的微信O(∩_∩)O~~

簡易資料庫的實現

使用pands+CSV實現資料庫框架搭建

import socket
import threading
from pandas import *
import pymysql
import csv
# 建立DataFrame物件
# 儲存使用者資料的表(狀態沒用上。。。)
df = pandas.DataFrame([['whs','123','1']], columns=['account','password','state'], index=[0])
df.to_csv('user.csv',index=0)
#儲存好友關係
df = pandas.DataFrame([['whs','wyl']], columns=['friend1','friend2'], index=[0])
df.to_csv('link.csv',index=0)
#儲存待新增好友關係的表
df = pandas.DataFrame([['whs','@@@']], columns=['friend1','friend2'], index=[0])
df.to_csv('to_be_link.csv',index=0)
#儲存線上人
df = pandas.DataFrame([['whs','127.0.0.1']], columns=['name','addr'], index=[0])
df.to_csv('onlinelist.csv',index=0)
#在客戶端用於儲存最近收到的資訊
df = pandas.DataFrame([['@@@','@@@','@@@']], columns=['name','title','message'], index=[0])
df.to_csv('resent_message.csv',index=0)
print(df)

通過對csv表的操作實現對好友操作

新增線上人
def addonlinelist(name,addr,connectionSocket):
   onlinelist = read_csv('onlinelist.csv', dtype={'name': str,'addr': str})
   #開啟csv檔案並指定格式
   flag = onlinelist[onlinelist.name == name].shape[0]
   #如果資料不存在
   if(flag!=0):
      k = onlinelist[onlinelist.name == name].index.tolist()[0]
      onlinelist=onlinelist.drop(k)
      #如果有之前的殘餘資料則刪去
   connect_list.append(connectclient(name,addr,connectionSocket))
   new = DataFrame({'name': name, 'addr': str(addr)},index=[0])
   onlinelist = onlinelist.append(new,ignore_index=True)
   #儲存,並且忽略index
   onlinelist.to_csv('onlinelist.csv',index=0)

對使用者登入的檢驗

語法與之前幾乎一樣,不做贅述

def adduser(name,passwd,status):#新增使用者
   user = read_csv('user.csv', dtype={'name': str,'passwd': str})
   flag = user[user.name == name].shape[0]
   if(flag != 0):
      return False
   new = DataFrame({'name': name, 'passwd': passwd, 'status': status}, index=[0])
   user = user.append(new, ignore_index=True)
   user.to_csv('user.csv',index=0)
   return True
def checkexist(name):#檢視使用者是否存在
   user = read_csv('user.csv', dtype={'name': str,'passwd': str})
   flag = user[user.name==name].shape[0]
   if(flag==0):
      return False
   else:
      return True
def checkuser(name,passwd):#檢視使用者名稱密碼正確性
   user = read_csv('user.csv', dtype={'name': str,'passwd': str})
   if(passwd==str(user[user.name == name].passwd.tolist()[0])):
      return True
   else:
      return False

對使用者刪除新增好友接受好友請求

def deletefriend(name1,name2):#刪除好友
   link = read_csv('link.csv', dtype={'friend1': str,'friend2': str})
   flag = link[((link.friend1 == name1) & (link.friend2 == name2)) | ((link.friend1 == name2) & (link.friend2 == name1))].shape[0]
   if(flag == 0):
      return 'not_exist'
   k = link[((link.friend1 == name1) & (link.friend2 == name2)) | ((link.friend1 == name2) & (link.friend2 == name1))].index.tolist()[0]
   link = link.drop(k)
   link.to_csv('link.csv', index=0)
   return 'delete_ok'
def addfriend(name1,name2):#新增好友
   if(isfriend(name1,name2)==True):
      return 'already_friend'
   to_be_link = read_csv('to_be_link.csv', dtype={'friend1': str,'friend2': str})
   user = read_csv('user.csv')
   flag = user[(user.name == name2)].shape[0]
   if(flag == 0):
      return 'not_exist'
   new = DataFrame({'friend1':name1, 'friend2':name2}, index = [0])
   to_be_link = to_be_link.append(new,ignore_index=True)
   to_be_link.to_csv('to_be_link.csv',index=0)
   return 'add_to_be'
def acceptfriend(name1,name2):#接收好友請求
   link = read_csv('link.csv', dtype={'friend1': str,'friend2': str})
   to_be_link = read_csv('to_be_link.csv', dtype={'friend1': str,'friend2': str})
   print(type(name1))
   print(type(name1))
   if (istobefriend(name1, name2) == False):
      return 'not_exist'
   print('tttttttttt')
   new = DataFrame({'friend1': name1, 'friend2': name2}, index=[0])
   link = link.append(new, ignore_index=True)
   link.to_csv('link.csv', index=0)
   k = to_be_link[((to_be_link.friend1 == name1) & (to_be_link.friend2 == name2)) | ((to_be_link.friend1 == name2) & (to_be_link.friend2 == name1))].index.tolist()[0]
   to_be_link = to_be_link.drop(k)
   to_be_link.to_csv('to_be_link.csv',index=0)
   # connectionSocket.send(('新增好友成功').encode())
   return 'accept_ok'
def delete_to_be_friend(name1,name2):#從待接收列表中刪除
   to_be_link = read_csv('to_be_link.csv', dtype={'friend1': str,'friend2': str})
   k = to_be_link[((to_be_link.friend1 == name1) & (to_be_link.friend2 == name2)) | (
            (to_be_link.friend1 == name2) & (to_be_link.friend2 == name1))].index.tolist()[0]
   to_be_link = to_be_link.drop(k)
   to_be_link.to_csv('to_be_link.csv', index=0)
def isfriend(name1,name2):#判斷兩個人是否是好友
   link = read_csv('link.csv')
   flag = link[((link.friend1 == name1) & (link.friend2 == name2)) | ((link.friend1 == name2) & (link.friend2 == name1))].shape[0]
   if (flag == 0):
      return False
   return True
def istobefriend(name1,name2):#判斷是否發起好友請求
   to_be_link = read_csv('to_be_link.csv', dtype={'friend1': str,'friend2': str})
   flag = to_be_link[((to_be_link.friend1 == name1) & (to_be_link.friend2 == name2)) | (
            (to_be_link.friend1 == name2) & (to_be_link.friend2 == name1))].shape[0]
   if (flag == 0):
      return False
   return True


   

列印好友列表(待新增好友列表)

def printlist(name):#列印好友列表
   link = read_csv('link.csv', dtype={'friend1': str,'friend2': str})
   k = link[(link.friend1 == name)|(link.friend2 == name)].shape[0]
   if(k==0):
      return '.'
   # connectionSocket.send(('您的好友列表如下').encode())
   message = '/'.join(link[link.friend1 == name].friend2.tolist()+link[link.friend2 == name].friend1.tolist())
   # connectionSocket.send(message.encode())
   print(message)
   return '/' + message
def printtobelist(name):#列印待同意好友列表
   to_be_link = read_csv('to_be_link.csv', dtype={'friend1': str,'friend2': str})
   k = to_be_link[(to_be_link.friend2 == name)].shape[0]
   if (k == 0):
      return '.'
   # connectionSocket.send(('有如下的人要新增你為好友,想接受則輸入該好友名字,否則輸入/exit/退出').encode())
   print(to_be_link[to_be_link.friend2 == name].friend1.tolist())
   message = '/'.join(to_be_link[to_be_link.friend2 == name].friend1.tolist())
   return '/'+message

socket+thread實現多執行緒通訊

通訊協議

'''
2.0 報頭
   伺服器和客服端之間,使用自己定義的報頭進行通訊
協議整體大致呈樹形結構,收到訊息後進行一層層解析,得到最終的結果。
2.1 註冊
註冊傳送
root/reg/name/passwd
註冊接收
register_ok(註冊成功),register_error(使用者名稱以及存在)
2.2 登入
登入傳送
root/reg/name/passwd
登入接收
noPassword,user,noAccount
2.3 獲取好友列表
獲取已經有的好友的列表
root/friend_op(操作型別)/print_friend_list(操作方式)/myname
獲取向你提出好友申請的人的列表
root/friend_op(操作型別)/print_to_be_list(操作方式)/myname
2.4 新增好友
root/friend_op(操作型別)/add(操作方式)/myname/othername
伺服器端發出的資訊
root/friend_op/(delete)(add)/othername/state('not_exist''delete_ok''already_friend''not_exist''add_to_be')
使用者不存在    成功
2.5 好友請求回覆
root/friend_op(操作型別)/accept(操作方式)/myname/othername
2.6 刪除好友
root/friend_op(操作型別)/delete(操作方式)/myname/othername
2.7 [伺服器]好友請求
root/friend_op(操作型別)/add(操作方式)/othername
2.8 [伺服器]好友請求結果
root/friend_op/add/othername/state('not_exist''already_friend''add_to_be')
狀態分為分為該使用者不存在,你們已經是朋友,成功發出好友請求三種。
2.9 傳送訊息和檔案
從客服端傳輸資訊的格式
root/chat/message/from_name/to_name/message_content
客戶端傳送檔案的格式
root/chat/file/from_name/to_name
傳輸是否線上的系統訊息
2.10 [伺服器]訊息和檔案
root/chat/file/from_name/begin(end)
root/chat/message/to_name/message_content
2.11 離線
root/chat/info/system_message/from_name
(判斷是否線上後傳送)
'''

伺服器的連線

while True:
   connectionSocket, addr = serverSocket.accept()
   print(addr)
   # connectionSocket.send(('連線主機成功').encode())
   # 每當客戶端連線後啟動一個執行緒為該客戶端服務
   threading.Thread(target=server_target, args=(connectionSocket, addr,)).start()
serverSocket.close()

客戶端的連線

if __name__ == '__main__':
    t = Client()
    main_login()

伺服器端對訊息的處理

def server_target(connectionSocket, addr):#伺服器主要函式
   while(1):
      message = connectionSocket.recv(1024).decode()
      deal_message = message.split('/')
      print(deal_message)
      lenth = len(deal_message)
      print(deal_message[0])
      if(deal_message[0]=='un_root'):#收到則退出
         connectionSocket.send(('un_root').encode())
         break
      else:
         if(deal_message[1] == 'login'):#進行登入操作
            account = deal_message[2]
            password = deal_message[3]
            if(checkexist(account)==False):
               connectionSocket.send(('noAccount').encode())
            elif(checkuser(account,password)==False):
               connectionSocket.send(('noPassword').encode())
            else:
               connectionSocket.send(('user').encode())
               addonlinelist(account, addr, connectionSocket)
         elif(deal_message[1] == 'register'):#進行註冊
            account = deal_message[2]
            password = deal_message[3]
            if(adduser(account, password, 1)):
               connectionSocket.send(('register_ok').encode())
               addonlinelist(account, addr, connectionSocket)
            else:
               connectionSocket.send(('register_error').encode())
         elif(deal_message[1] == 'chat'):#進行資訊互動
            type = deal_message[2]
            account = deal_message[3]
            friend_account = deal_message[4]
            if(type == 'file'):
               for clients in connect_list:
                  if (str(clients.name) == str(friend_account)):
                     print(('root/chat/' + type + '/' + account + '/begin'))
                     try:
                        clients.connectionSocket.send(
                           ('root/chat/' + type + '/' + account + '/begin').encode())
                     except:
                        pass
               # 申請相同大小的空間存放傳送過來的檔名與檔案大小資訊
               fileinfo_size = struct.calcsize('128sl')
               # 接收檔名與檔案大小資訊
               buf = connectionSocket.recv(fileinfo_size)
               for clients in connect_list:
                  if (str(clients.name) == str(friend_account)):
                     print(('root/chat/' + type + '/' + account + '/begin'))
                     try:
                        clients.connectionSocket.send(buf)
                     except:
                        pass
               # 判斷是否接收到檔案頭資訊
               if buf:
                  # 獲取檔名和檔案大小
                  filename, filesize = struct.unpack('128sl', buf)
                  fn = filename.strip(b'\00')
                  fn = fn.decode()
                  print ('file new name is {0}, filesize if {1}'.format(str(fn), filesize))

                  recvd_size = 0  # 定義已接收檔案的大小
                  # 儲存在該指令碼所在目錄下面
                  print ('start receiving...')
                  # 將分批次傳輸的二進位制流依次寫入到檔案
                  while not (recvd_size == filesize):
                     if filesize - recvd_size > 1024:
                        data = connectionSocket.recv(1024)
                        recvd_size += len(data)
                     else:
                        data = connectionSocket.recv(filesize - recvd_size)
                        recvd_size = filesize
                     for clients in connect_list:
                        if (str(clients.name) == str(friend_account)):
                           print(data)
                           try:
                              clients.connectionSocket.send(data)
                           except:
                              pass
                  print('end receive...')
            elif(type == 'message'):
               message = deal_message[5]+'/'+deal_message[6]
               isfind = False
               for clients in connect_list:#遍歷list傳送資訊
                  if(str(clients.name) == str(friend_account)):
                     print(('root/chat/' + type + '/' + account + '/' + message))
                     try:
                        clients.connectionSocket.send(('root/chat/'+type+'/'+account+'/'+ message).encode())
                        isfind = True
                     except:
                        pass
               if(isfind == False):
                  connectionSocket.send(('root/chat/info/'+friend_account+'/'+'對方不線上無法接收你的訊息').encode())
                  # print(('root/chat/'+friend_account+'/'+account+'/'+'對方不線上無法接收你的訊息'))
         elif(deal_message[1] == 'friend_op'):#對於好友命令進行操作
            print(deal_message[2])
            if(deal_message[2] == 'add'):
               account = deal_message[3]
               friend_account = deal_message[4]
               connectionSocket.send(('root/friend_op/add/' + friend_account + '/' + addfriend(account, friend_account)).encode())
            elif(deal_message[2] == 'accept'):
               account = deal_message[3]
               friend_account = deal_message[4]
               acceptfriend(account, friend_account)
               connectionSocket.send(('root/friend_op/accept/' + friend_account).encode())
            elif(deal_message[2] == 'delete'):
               account = deal_message[3]
               friend_account = deal_message[4]
               connectionSocket.send(('root/friend_op/delete/'+friend_account+'/'+deletefriend(account,friend_account)).encode())
            elif(deal_message[2] == 'reject'):
               account = deal_message[3]
               friend_account = deal_message[4]
               delete_to_be_friend(account,friend_account)
            elif(deal_message[2] == 'print_friend_list'):
               account = deal_message[3]
               print(printlist(account))
               connectionSocket.send((('root/friend_op/print_friend_list'+printlist(account)).strip('.')).encode())
               print(('root/friend_op/print_friend_list'+printlist(account)).strip('.'))
            elif(deal_message[2] == 'print_to_be_friend_list'):
               account = deal_message[3]
               connectionSocket.send((('root/friend_op/print_to_be_friend_list' + printtobelist(account)).strip('.')).encode())
               print(('root/friend_op/print_to_be_frined_list' + printtobelist(account)).strip('.'))

客戶端對資訊的處理

class Client(object):
    def __init__(self):
        self.connecntsocket = socket(AF_INET, SOCK_STREAM)
        self.connecntsocket.connect(('127.0.0.1', 12000))
        #連線ip和埠
        self.myqueue=queue.Queue()
        self.name  = ''
        self.flag = True
        self.friend_list = []
        self.to_be_friend_list = []
    def read_from_server(self):
        while True:
            if(self.flag==False):
                break
            message = self.connecntsocket.recv(1024).decode()
            print(message)
            deal_message = message.split('/')
            print(deal_message)
            lenth = len(deal_message)
            print(deal_message[0])
            if(deal_message[0] == 'un_root'):
                break
            else:
                if(deal_message[1] == 'chat'):
                    if(deal_message[2] == 'message'):#處理資訊,放到該放的地方
                        print('message')
                        friend_account = deal_message[3]
                        friend_title = deal_message[4]
                        friend_message = deal_message[5]
                        resent_message = read_csv('resent_message.csv')
                        new = DataFrame({'name': friend_account, 'title': friend_title, 'message': friend_message}, index=[0])
                        resent_message = resent_message.append(new, ignore_index=True)
                        resent_message.to_csv('resent_message.csv', index=0)
                        isfind = False
                        for Chat_window in Chat_list:
                            if(Chat_window.name == friend_account):
                                try:
                                    Chat_window.insert_message(friend_title,friend_message)
                                except:
                                    pass
                                isfind = True
                        if(isfind == False):#to be continue
                            pass
                    elif(deal_message[2] == 'file'):
                        if(deal_message[4] == 'begin'):

                            # 申請相同大小的空間存放傳送過來的檔名與檔案大小資訊
                            fileinfo_size = struct.calcsize('128sl')
                            # 接收檔名與檔案大小資訊
                            buf = t.connecntsocket.recv(fileinfo_size)
                            # 判斷是否接收到檔案頭資訊
                            if buf:
                                # 獲取檔名和檔案大小
                                filename, filesize = struct.unpack('128sl', buf)
                                fn = filename.strip(b'\00')
                                fn = fn.decode()
                                print ('file new name is {0}, filesize if {1}'.format(str(fn), filesize))

                                recvd_size = 0  # 定義已接收檔案的大小
                                # 儲存在該指令碼所在目錄下面
                                fp = open('./' + str('new' + fn), 'wb')
                                print ('start receiving...')

                                # 將分批次傳輸的二進位制流依次寫入到檔案
                                while not recvd_size == filesize:
                                    if filesize - recvd_size > 1024:
                                        data = t.connecntsocket.recv(1024)
                                        recvd_size += len(data)
                                    else:
                                        data = t.connecntsocket.recv(filesize - recvd_size)
                                        recvd_size = filesize
                                    fp.write(data)
                                fp.close()
                                print ('end receive...')
                    elif(deal_message[2] == 'info'):
                        friend_account = deal_message[3]
                        system_info = deal_message[4]
                        isfind = False
                        for Chat_window in Chat_list:
                            if (Chat_window.name == friend_account):
                                Chat_window.insert_info(system_info)
                                isfind = True
                        if (isfind == False):
                            pass
                elif(deal_message[1] == 'friend_op'):
                    if(deal_message[2] == 'delete'):
                        if(deal_message[4] == 'not_exist'):
                            self.creatbox(deal_message[3] + '不在您的好友列表中')
                        elif(deal_message[4] == 'delete_ok'):
                            self.creatbox('已經把'+deal_message[3]+'從您的好友列表刪除')
                    elif(deal_message[2] == 'add'):
                        if(deal_message[4] == 'already_friend'):
                            self.creatbox('你和' + deal_message[3]+'已經是好友了')
                        elif(deal_message[4] == 'not_exist'):
                            self.creatbox('使用者' + deal_message[3] + '不存在')
                        elif(deal_message[4 == 'add_to_be']):
                            self.creatbox('已經傳送對'+deal_message[3]+'的好友申請')
                    elif(deal_message[2] == 'accept'):
                        self.creatbox('已經把' + deal_message[3] + '新增到您的好友列表')
                    elif(deal_message[2] == 'print_friend_list'):
                        self.friend_list = deal_message[3:lenth]
                    elif(deal_message[2] == 'print_to_be_friend_list'):
                        self.to_be_friend_list = deal_message[3:lenth]
                        print(1)
    def creatbox(self,message):
        tkinter.messagebox.showinfo(title='miniWechat', message=message)
    def terminate(self):
        self.flag = False
    def run(self):
        self.flag = True
        self.THD = threading.Thread(target=self.read_from_server, args=()).start()

tkinter實現GUI

登入介面

class Login(object):
    def __init__(self):
        # 建立主視窗,用於容納其它元件
        self.root = Tk()
        # 給主視窗設定標題內容
        self.root.title("miniWechat")
        #自定義關閉按鈕,不然關閉GUI後socket還在執行中
        self.root.protocol('WM_DELETE_WINDOW', self.my_close)
        self.root.geometry('450x300')
        #執行程式碼時記得新增一個gif圖片檔案,不然是會出錯的
        self.canvas = tkinter.Canvas(self.root, height=300, width=500)#建立畫布
        self.image_file = tkinter.PhotoImage(file='1572079024477.gif')
        self.image = self.canvas.create_image(0,0, anchor='nw', image=self.image_file)#將圖片置於畫布上
        self.canvas.grid(row=0, column=0)#放置畫布(為上端)
        #建立一個`label`名為`Account: `
        self.label_account = tkinter.Label(self.root, text='Account: ')
        #建立一個`label`名為`Password: `
        self.label_password = tkinter.Label(self.root, text='Password: ')
        # 建立一個賬號輸入框,並設定尺寸
        self.input_account = tkinter.Entry(self.root, width=30)
        # 建立一個密碼輸入框,並設定尺寸
        self.input_password = tkinter.Entry(self.root, show='*', width=30)
        # 建立一個登入系統的按鈕
        self.login_button = tkinter.Button(self.root, command = self.backstage_interface, text = "Login", width=10)
        # 建立一個註冊系統的按鈕
        self.siginUp_button = tkinter.Button(self.root, command = self.siginUp_interface, text = "Sign up", width=10)
        # 完成佈局
    def gui_arrang(self):
        self.label_account.place(x=60, y= 170)
        self.label_password.place(x=60, y= 195)
        self.input_account.place(x=135, y=170)
        self.input_password.place(x=135, y=195)
        self.login_button.place(x=140, y=235)
        self.siginUp_button.place(x=240, y=235)
     # 進入註冊介面
    def siginUp_interface(self):
        # self.root.destroy()
        account = self.input_account.get()
        password = self.input_password.get()
        verifyResult =self.reg_verifyAccountData(account,password)
        if(verifyResult == 'register_ok'):
            t.name = account
            self.start()
        else:
            tkinter.messagebox.showinfo(title='註冊資訊提示', message='使用者名稱已經被使用')
     # 進行登入資訊驗證
    def backstage_interface(self):
        account = self.input_account.get()#.ljust(10," ")
        password = self.input_password.get()#.ljust(10," ")
        #對賬戶資訊進行驗證,普通使用者返回user,管理員返回master,賬戶錯誤返回noAccount,密碼錯誤返回noPassword
        verifyResult =self.log_verifyAccountData(account,password)
        if verifyResult=='master':
            self.root.destroy()
            tkinter.messagebox.showinfo(title='miniWechat', message='進入管理介面')
        elif verifyResult=='user':
            t.name = account
            self.start()
        elif verifyResult=='noAccount':
            tkinter.messagebox.showinfo(title='miniWechat', message='該賬號不存在請重新輸入!')
        elif verifyResult=='noPassword':
            tkinter.messagebox.showinfo(title='miniWechat', message='賬號/密碼錯誤請重新輸入!')
    def run(self):
        self.root.run()
    def start(self):
        self.root.destroy()
        t.run()
        main_user()
    def log_verifyAccountData(self,account,password):
        #向伺服器傳送資訊
        t.connecntsocket.send(('root/'+'login'+'/'+account+'/'+password).encode())
        sentense = t.connecntsocket.recv(2048).decode()
        print(sentense)
        return sentense
    def reg_verifyAccountData(self,account,password):
        t.connecntsocket.send(('root/'+'register'+'/'+account+'/'+password).encode())
        sentense = t.connecntsocket.recv(2048).decode()
        print(sentense)
        return sentense
    def my_close(self):
        t.connecntsocket.send(('un_root').encode())
        self.root.destroy()
        sleep(1)
        t.connecntsocket.close()

使用者介面

class User(object):
    def __init__(self):
        self.tk = Tk()
        self.tk.title('使用者介面')
        self.tk.protocol('WM_DELETE_WINDOW', self.my_close)
        self.button_pos = 280
        self.friend_list = [None]*80
        self.friend_list_len = 0
        self.to_be_friend_list = [None] * 80
        self.to_be_friend_list_len = 0
        #好友列表
        self.friend_area = Canvas(self.tk, bg='pink')
        self.friend_area.place(x=5, y=260, heigh=340, width=340)
        self.friend_text = scrolledtext.ScrolledText(self.friend_area)
        self.friend_text.place(x=5, y=5, heigh=340, width=330)
        #待新增好友列表
        self.to_be_friend_area = Canvas(self.tk,bg='pink')
        self.to_be_friend_area.place(x=5, y=650, heigh=340, width=340)
        self.to_be_friend_text = scrolledtext.ScrolledText(self.to_be_friend_area)
        self.to_be_friend_text.place(x=5, y=5, heigh=340, width=340)
        self.tk.geometry('350x1000')
        # 執行程式碼時記得新增一個gif圖片檔案,不然是會出錯的
        self.canvas = tkinter.Canvas(self.tk, height=200, width=350)  # 建立畫布
        self.image_file = tkinter.PhotoImage(file='1572079024477.gif')
        self.image = self.canvas.create_image(0, 0, anchor='nw', image=self.image_file)  # 將圖片置於畫布上
        self.canvas.place(x=0,y=0)#pack(side='top')  # 放置畫布(為上端)
        # self.friend_list = tkinter.Button(self.tk, text='新增好友', bg='skyblue', command=self.to_chat, font=('Arial', 16))
        # self.friend_list.place(x=20, y=280)
        #新增TEXT文字
        self.add_friend_label = tkinter.Label(self.tk, text='名字 ')
        self.friend_list_label = tkinter.Label(self.tk, text='好友列表 ',bg='blue',fg='black',font=("華文行楷", 16),width=32,height=2)
        self.to_be_friend_list_label = tkinter.Label(self.tk, text='待通過的好友請求 ',bg='blue',fg='black',font=("華文行楷", 16),width=32,height=2)
        #新增輸入框
        self.input_friend_name = tkinter.Entry(self.tk, width=20)
        #新增按鈕
        self.add_friend_button = tkinter.Button(self.tk, command=self.add_friend, text="確認新增", width=6)
        self.delete_friend_button = tkinter.Button(self.tk, command=self.delete_friend, text="確認刪除", width=6)
        self.refresh_all_button = tkinter.Button(self.tk, command=self.refresh_all, text="重新整理列表", width=10)
        #放到指定位置
        self.add_friend_label.place(x=0,y=181)
        self.input_friend_name.place(x=35,y=182)
        self.add_friend_button.place(x=180,y=180)
        self.delete_friend_button.place(x=230, y=180)
        self.refresh_all_button.place(x=280,y=180)
        self.friend_list_label.place(x=10,y=210)
        self.to_be_friend_list_label.place(x=10,y=600)
        self.refresh_friend_list()
        self.refresh_to_be_friend_list()
    def refresh_to_be_friend_list(self):
        self.to_be_friend_text.delete(0.0,END)
        t.connecntsocket.send(('root/friend_op/print_to_be_friend_list/' + t.name).encode())
        time.sleep(0.1)
        self.to_be_deal_message = t.to_be_friend_list  # deal_message中存放所有名字
        print(self.to_be_deal_message)
        self.to_be_friend_list_len = len(self.to_be_deal_message)
        for i in range(0, self.to_be_friend_list_len):
            self.create_to_be_friend_bottun(i, self.to_be_deal_message[i])

    def refresh_friend_list(self):#重新整理好友列表
        self.friend_text.delete(0.0,END)
        t.connecntsocket.send(('root/friend_op/print_friend_list/'+t.name).encode())
        time.sleep(0.1)
        self.deal_message = t.friend_list#deal_message中存放所有名字
        self.friend_list_len = len(self.deal_message)
        for i in range(0, self.friend_list_len):
            self.create_friend_bottun(i, self.deal_message[i])

    def create_to_be_friend_bottun(self, i, name):
        self.to_be_friend_list[i] = tkinter.Button(self.to_be_friend_text, text=name, bg='skyblue', command=lambda:self.whether_accpet(name), font=('Arial', 16), width=33, height=1)
        self.to_be_friend_text.window_create(END, window=self.to_be_friend_list[i])
    def create_friend_bottun(self,i,name):
        self.friend_list[i] = tkinter.Button(self.friend_text, text=name, bg='skyblue', command=lambda:self.to_chat(name), font=('Arial', 16), width=23, height=1)
        self.friend_text.window_create(END, window=self.friend_list[i])
    def whether_accpet(self,name):
        flag = messagebox.askokcancel(title='miniWechat', message=('是否接受' + name + '的好友請求?'))
        if (flag == True):
            t.connecntsocket.send(('root/friend_op/accept/'+t.name+'/'+name).encode())
        else:
            t.connecntsocket.send(('root/friend_op/reject/'+t.name+'/'+name).encode())
    def to_chat(self,name):
        print(name)
        main_chat(name)
    def refresh_all(self):
        self.refresh_to_be_friend_list()
        self.refresh_friend_list()
    def delete_friend(self):
        name = self.input_friend_name.get()
        t.connecntsocket.send(('root/friend_op/delete/' + t.name + '/' + name).encode())
    def add_friend(self):
        name = self.input_friend_name.get()
        print(type(name))
        t.connecntsocket.send(('root/friend_op/add/'+t.name+'/'+name).encode())
        print(name)
    def handler(self,event, top, lb):
        """事件處理函式"""
        content = lb.get(lb.curselection())
        return messagebox.showinfo(title="Hey, you got me!", message="I am {0}".format(content), parent=top)
    def handler_adaptor(self,fun, **kwds):
        """事件處理函式的介面卡,相當於中介"""
        return lambda event, fun=fun, kwds=kwds: fun(event, **kwds)
    def run(self):
        self.tk.mainloop()
    def my_close(self):
        t.connecntsocket.send(('un_root').encode())
        self.tk.destroy()
        sleep(1)
        t.connecntsocket.close()

聊天介面

class Chat(object):
    def __init__(self,name):
        self.root = Toplevel()
        self.name = name
        self.root.title(name)
        self.root.protocol('WM_DELETE_WINDOW', self.my_close)
        '''建立分割槽'''
        self.f_msglist = Frame(self.root,height=300, width=300)  # 建立<訊息列表分割槽 >
        self.f_msgsend = Frame(self.root,height=300, width=300)  # 建立<傳送訊息分割槽 >
        self.f_floor = Frame(self.root,height=100, width=300)  # 建立<按鈕分割槽>
        self.f_right = Frame(self.root,height=700, width=100)  # 建立<圖片分割槽>
        '''建立控制元件'''
        self.txt_msglist = Text(self.f_msglist)  # 訊息列表分割槽中建立文字控制元件
        self.txt_msglist.tag_config('green', foreground='blue')  # 訊息列表分割槽中建立標籤
        self.txt_msgsend = Text(self.f_msgsend)  # 傳送訊息分割槽中建立文字控制元件
        self.txt_msgsend.bind('<KeyPress-Up>', self.msgsendEvent)  # 傳送訊息分割槽中,繫結‘UP’鍵與訊息傳送。
        '''txt_right = Text(f_right) #圖片顯示分割槽建立文字控制元件'''
        self.button_send = Button(self.f_floor, text='傳送', command=self.msgsend)  # 按鈕分割槽中建立按鈕並繫結傳送訊息函式
        self.button_cancel = Button(self.f_floor, text='取消', command=self.cancel)  # 分割槽中建立取消按鈕並繫結取消函式
        self.button_send_file = Button(self.f_floor, text='上傳', command=self.filesend)
        self.button_resent_message = Button(self.f_floor, text='最近訊息', command=self.get_resent_message)
        # self.photo = PhotoImage(file='1572079024477.gif')
        # self.label = Label(self.f_right, image=self.photo)  # 右側分割槽中新增標籤(繫結圖片)
        self.label = Label(self.f_right)
        # self.label.image = self.photo
        self.canvas = Canvas(self.root, height=300, width=500)  # 建立畫布
        self.image_file = PhotoImage(file='15720790244771.gif')
        self.image = self.canvas.create_image(0, 0, anchor='nw', image=self.image_file)  # 將圖片置於畫布上
        self.canvas.grid(row=1, column=1)  # 放置畫布(為上端)
        '''分割槽佈局'''
        self.f_msglist.grid(row=0, column=0)  # 訊息列表分割槽
        self.f_msgsend.grid(row=1, column=0)  # 傳送訊息分割槽
        self.f_floor.grid(row=2, column=0)  # 按鈕分割槽
        self.f_right.grid(row=0, column=1, rowspan=3)  # 圖片顯示分割槽
        self.txt_msglist.grid()  # 訊息列表文字控制元件載入
        self.txt_msgsend.grid()  # 訊息傳送文字控制元件載入
        self.button_send.grid(row=0, column=0, sticky=W)  # 傳送按鈕控制元件載入
        self.button_cancel.grid(row=0, column=1, sticky=W)  # 取消按鈕控制元件載入
        self.button_send_file.grid(row=0, column=2, sticky=W)
        self.button_resent_message.grid(row=0, column=3, sticky=W)
        self.label.grid()  # 右側分割槽載入標籤控制元件
    def insert_message(self,massage_title,massage_content):
        self.txt_msglist.insert(END, massage_title+'\n', 'green')
        self.txt_msglist.insert(END, massage_content)
    def insert_info(self,system_info):
        self.txt_msglist.insert(END, system_info + '\n', 'red')
    def get_resent_message(self):
        resent_message = read_csv('resent_message.csv')
        k = resent_message[resent_message.name == self.name].shape[0]
        if (k == 0):
            return
        title1 = '/'.join(resent_message[resent_message.name == self.name].title.tolist())
        message1 = '/'.join(resent_message[resent_message.name == self.name].message.tolist())
        title = title1.split('/')
        message = message1.split('/')
        self.txt_msgsend.delete(0.0,END)
        lenth = len(title)
        for i in range(0,lenth):
            self.txt_msglist.insert(END, title[i] + '\n', 'green')
            self.txt_msglist.insert(END, message[i])
    def msgsend(self):
        self.msg = t.name + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
        self.txt_msglist.insert(END, self.msg + '\n', 'green')  # 新增時間
        self.txt_msglist.insert(END, self.txt_msgsend.get('0.0', END))  # 獲取傳送訊息,新增文字到訊息列表
        message =self.msg+'/'+self.txt_msgsend.get('0.0', END)
        mm=('root/chat/'+t.name+'/'+self.name+'/'+message)
        print(mm)
        t.connecntsocket.send(('root/chat/message/'+t.name+'/'+self.name+'/'+message).encode())
        resent_message = read_csv('resent_message.csv')
        new = DataFrame({'name': self.name, 'title': self.msg + '\n', 'message': self.txt_msgsend.get('0.0', END)}, index=[0])
        resent_message = resent_message.append(new, ignore_index=True)
        resent_message.to_csv('resent_message.csv', index=0)
        self.txt_msgsend.delete('0.0', END)  # 清空傳送訊息
    '''定義取消傳送 訊息 函式'''
    def filesend(self):
        filepath = self.txt_msgsend.get('0.0', END)
        self.txt_msgsend.delete('0.0', END)  # 清空傳送訊息
        t.connecntsocket.send(('root/chat/file/'+t.name+'/'+self.name+'/begin').encode())
        filepath = filepath.strip('\n')
        if os.path.isfile(filepath):
            # 定義定義檔案資訊。128s表示檔名為128bytes長,l表示一個int或log檔案型別,在此為檔案大小
            fileinfo_size = struct.calcsize('128sl')
            # 定義檔案頭資訊,包含檔名和檔案大小
            fhead = struct.pack('128sl', os.path.basename(filepath).encode('utf-8'), os.stat(filepath).st_size)
            # 傳送檔名稱與檔案大小
            t.connecntsocket.send(fhead)
            # 將傳輸檔案以二進位制的形式分多次上傳至伺服器
            fp = open(filepath, 'rb')
            while 1:
                data = fp.read(1024)
                if not data:
                    print ('{0} file send over...'.format(os.path.basename(filepath)))
                    break
                t.connecntsocket.send(data)
    def cancel(self):
        self.txt_msgsend.delete('0.0', END)  # 取消傳送訊息,即清空傳送訊息

    '''繫結up鍵'''
    def msgsendEvent(self,event):
        if event.keysym == 'Up':
            self.msgsend()
    def run(self):
        self.root.mainloop()
    def my_close(self):
        self.root.destroy()
        my_removw(self.name)
def my_removw(name):
    for c in Chat_list:
        if c.name == name:
            Chat_list.remove(c)
            return

執行效果圖

登入介面

在這裡插入圖片描述

使用者介面

在這裡插入圖片描述

聊天介面

在這裡插入圖片描述

相關文章