Python多人聊天室

多人聊天室的实现

一. 源代码链接

暂时先不提供

二. 项目简介

  1. 利用socket方式编写一个多人聊天室程序,可以实现多个用户之间的群聊功能,私聊功能,显示当前用户功能
  2. 在聊天室程序中增加利用ftp实现文件的上传,下载,删除,查看当前文件功能
  3. 在聊天室程序中增加利用ftp实现在聊天过程中可以发送图片功能
  4. 在聊天室程序中增加发送邮件功能,并提供附件发送服务

三. 功能演示

  1. 设置好服务端的配置代码
    在这里插入图片描述

  2. 登录界面
    在这里插入图片描述

  3. 使用过程
    在这里插入图片描述

四. 模块化分析

1. 服务端

服务端主要用于为客户端提供聊天服务和FTP服务,所以我分别创建了两个类为ChatServer和FtpServer

①对于服务端的代码分析,首先是要让用户对服务器的基本信息进行设置,具体设置代码如下
# 用户所需输入信息分界线--------------------------------------
IP_server = '127.0.0.1'
PORT = 6666
FTP_PORT = 21

# 关于ftp的设置
username_server = 'mary'  # ftp登录用的用户名
password_server = '123456'
ftp_catalogue = 'C:/my_soft/python/code/lesson_code/ftp_test'  # ftp的目录
user_power = 'elradfmwMT'  # 权限
# 用户所需输入信息分界线--------------------------------------
②对于聊天服务器,主要模块可以分为

(1)TCP连接模块,主要代码就是与客户端建立起TCP通信
(2)当前在线用户模块,主要就对当前连接的客户端数目进行判断,然后返回一个当前在线用户数
(3)接收信息模块,主要代码是接收客户端发来的信息,然后存入一个队列中
(4)发送信息模块,主要代码是对客户端发来的信息进行分析,分析完后转入对于的函数去执行相关操作,比如接收到的是聊天信息就发到聊天用户的客户端中,接收到的是图片信息就发送指定字符串给聊天用户的客户端,然后对指定图片执行FTP上传下载操作。
(5)服务器启动检查模块,主要是令服务端与客户端进行连接,然后在控制台内打印连接状况的信息。

③对于FTP服务器,主要代码是用于初始化FTP服务器,如设置FTP的用户,密码和FTP的文件夹,具体如下图所示
# ----------------------------------------文件传输服务器1--------------------------
class FtpServer():
    # 新建一个用户组
    authorizer = DummyAuthorizer()
    # 将用户名,密码,指定目录,权限 添加到里面
    authorizer.add_user(username_server, password_server, ftp_catalogue, perm=user_power)  # adfmw
    # 这个是添加匿名用户,任何人都可以访问,如果去掉的话,需要输入用户名和密码,可以自己尝试
    authorizer.add_anonymous(ftp_catalogue)

    handler = FTPHandler
    handler.authorizer = authorizer

    def run(self):
        # 开启服务器
        server = FTPServer((IP_server, FTP_PORT), self.handler)
        server.serve_forever()
# ----------------------------------------文件传输服务器2--------------------------

2. 客户端

①初始登录模块,即用tkinter库创建出一个界面,再在里面添加输入框,最后将用户输入的信息保存为对应的全局变量给后续代码使用。核心代码如下所示
# 登录按钮
def login(*args):
    global IP, PORT, user, ftpUser, ftpPassword
    IP, PORT = entryIP.get().split(':')  # 获取IP和端口号
    PORT = int(PORT)  # 端口号需要为int类型
    user = entryUser.get()
    ftpUser = entryFtpUser.get()
    ftpPassword = entryFtpPassword.get()
    if not user:
        tkinter.messagebox.showerror('错误', message='请输入用户名')
    else:
        root1.destroy()  # 关闭登录窗口
②聊天窗口模块,即登录成功后为用户显示出来的窗口,用于用户聊天和进行其他操作,主要也是利用了tkinter库来创建出对应的界面
③图片发送模块,具体思路如下

(1)先进行FTP登录,方便后续把图片发送到服务器中
(2)打开本地文件资源管理器,找到图片的位置,修改图片的大小,因为聊天窗口无法加载过大的图片
(3)利用FTP把图片上传到服务器中
(4)给服务器发送一个指定的消息,表示这是一个发送图片命令

④显示当前在线用户模块,主要原理是先接收服务端发来的当前在线用户数,然后插入到聊天窗口中即可。
⑤与服务器通信模块,主要思路是先根据窗口信息确定聊天对象,然后再根据用户输入信息确定聊天内容,最后将这些信息加上分隔符后拼接成字符串,然后发送给服务端。核心代码如下
def send(*args):
    # 没有添加的话发送信息时会提示没有聊天对象
    users.append('------群聊模式-------')
    print(chat)
    if chat not in users:
        tkinter.messagebox.showerror('Send error', message='There is nobody to talk to!')
        return
    if chat == user:
        tkinter.messagebox.showerror('Send error', message='Cannot talk with yourself in private!')
        return
    mes = entry.get() + ':;' + user + ':;' + chat  # 添加聊天对象标记
    s.send(mes.encode())
    a.set('')  # 发送后清空文本框
⑥私聊模块,主要思路是确定好聊天的两个对象,然后将用户和其聊天对象名拼接成字符串显示到聊天框中,方便用户确定当前聊天对象,核心代码如下
def private(*args):
    global chat
    # 获取点击的索引然后得到内容(用户名)
    indexs = listbox1.curselection()
    index = indexs[0]
    if index > 0:
        chat = listbox1.get(index)
        # 修改客户端名称
        if chat == '------群聊模式-------':
            root.title(user + ' 的群聊窗口')
            return
        # 下面这两行用于在标题头处显示目前是谁和谁在聊天
        ti = user + '  与  ' + chat + ' 的聊天窗口'
        root.title(ti)
⑦邮件功能模块,主要思路如下

(1)在聊天窗口上扩大面积,插入输入框让用户填写发送邮件所需的对应信息、
(2)收集用户输入的信息,用对应的变量来保存
(3)对邮件的附件类型进行判断,以合适方式保存好插入邮件中
(4)登录邮箱服务器,发送邮件,核心代码如下

def send_mail():
        global fileName_mail_img
        global fileName_mail_file
        global fileName_mail_text
        global fileName_mail_video
        # 然后给对应变量赋值即可
        sender = entry_sender.get()
        # sender = input('请输入一个电子邮箱地址(163/QQ):')
        username, domain = sender.split('@')
        if domain == '163.com':
            host = 'smtp.163.com'
        elif domain == 'qq.com':
            host = 'smtp.qq.com'
        else:
            # print('当前代码只识别163和QQ邮箱,请检查邮箱地址或修改代码。')
            exit()
        port = 25

        body = entry_mail_body.get("1.0", "end")  # 这个get里面的参数是获得所有信息的

        # 输入密码,无回显,需要在cmd或PowerShell运行程序
        userpwd = entry_sender_passwd.get()
        # 要群发的电子邮件地址
        recipients = entry_recipients.get()

        # 登录邮箱服务器
        with SMTP(host, port) as server:
            server.starttls()
            server.login(username, userpwd)

            # 创建邮件
            msg = MIMEMultipart()
            msg.set_charset('utf-8')
            # 回复地址与发信地址可以不同
            # 但是大部分邮件系统在回复时会提示
            msg['Reply-to'] = sender
            # 设置发信人、收信人和主题
            msg.add_header('From', sender)
            msg.add_header('To', recipients)
            msg.add_header('Subject', entry_mail_topic.get())
            msg['Date'] = formatdate()
            msg['Message-Id'] = make_msgid()
            # 设置邮件文字内容
            msg.attach(MIMEText(body, 'plain', _charset='utf-8'))

            # ---------------------------------这里为添加附件功能-------------------
            # 首先是要打开一个窗口,然后获取到对应的文件名
            # 添加图片
            fn = fileName_mail_img
            print(fn)
            if fn :
                with open(fn, 'rb') as fp:
                    attachment = MIMEImage(fp.read())
                    attachment.add_header('content-disposition',
                                          'attachment', filename=fn.split('/')[-1])
                    msg.attach(attachment)

            # 添加文本文件
            fn = fileName_mail_text
            print(fn)
            if fn:
                with open(fn, 'rb') as fp:
                    attachment = MIMEBase('text', 'txt')
                    attachment.set_payload(fp.read())
                    encode_base64(attachment)
                    attachment.add_header('content-disposition',
                                          'attachment',
                                          filename=fn.split('/')[-1])
                    msg.attach(attachment)

            # 添加可执行程序
            fn = fileName_mail_file
            print(fn)
            if fn:
                with open(fn, 'rb') as fp:
                    attachment = MIMEApplication(fp.read(),
                                                 _encoder=encode_base64)
                    attachment.add_header('content-disposition',
                                          'attachment', filename=fn.split('/')[-1])
                    msg.attach(attachment)

            # 添加音乐文件
            fn = fileName_mail_video
            print(fn)
            if fn:
                with open(fn, 'rb') as fp:
                    attachment = MIMEAudio(fp.read(), 'plain',
                                           _encoder=encode_base64)
                    attachment.add_header('content-disposition',
                                          'attachment', filename=fn.split('/')[-1])
                    msg.attach(attachment)
            # 发送邮件
            server.send_message(msg)

            # 每次发完清空附件
            fileName_mail_file = ''
            fileName_mail_text = ''
            fileName_mail_video = ''
            fileName_mail_img = ''
⑧FTP功能模块,主要思路如下

(1)增大聊天窗口的界面,为用户提供进行FTP操作的按钮
(2)根据之前用户输入的信息进行FTP登录
(3)利用ftp.dir函数获取FTP服务器内的文件名称,将其存入数组中,然后提供翻页按钮,每次取出14条文件名称展示到聊天窗口中,核心代码为

def show_ftp():
        global now_page
        # 将打印出来的数据截取出对应的文件名(这个有点巧妙)
        dir_res = []
        ftp.dir('.', dir_res.append)  # 对当前目录进行dir(),将结果放入列表
        # print(dir_res)
        print(len(dir_res))     # 打印列表长度
        # 由文件名判断名称是文件还是目录
        for i in range(14):
            # 防止数组越界
            if (now_page*14)+i <= len(dir_res)-1:
                name = dir_res[(now_page*14)+i].split(' ')[-1]
                # print(name)
                # 将名称插入列表
                list2.insert(tkinter.END, name)
                # 通过.号判断是否为文件或文件夹,从而赋予不同的颜色(暂时未测试过文件夹)
                if '.' not in name:
                    list2.itemconfig(tkinter.END, fg='orange')
                else:
                    list2.itemconfig(tkinter.END, fg='blue')

(4)提供文件上传或文件夹上传服务,如果是文件就直接调用FTP的函数上传,如果是文件夹就先切换到该文件夹中,再遍历其中的文件并依次上传到FTP服务器中。上传文件夹的核心代码如下

def upload_ftpdir():
        # 思路:先获取到对应文件名,然后以二进制形式上传,ftp的目录信息是在服务端进行设置的
        # 获取文件夹名,并判断ftp目录里是否存在该文件夹
        fileName_ftp = tkinter.filedialog.askdirectory()
        fileName_ftp_end = fileName_ftp.split('/')[-1]
        dir = []
        ftp.dir('.', dir.append)  # 对当前目录进行dir(),将结果放入列表
        # 将打印信息拼接成字符串,方便后面的not in 判断
        rubbish = ''
        for i in dir:
            rubbish = rubbish + i
            # print(rubbish)
        if fileName_ftp_end not in rubbish:
            # 不存在则创建文件夹
            ftp.mkd(fileName_ftp_end)
            # 然后进行下载
            ftp.cwd(fileName_ftp_end)
            dir_res = []
            files = os.listdir(fileName_ftp)
            print(files)
            for i in files:
                dir_res.append(i)
            print(dir_res)
            for name in dir_res:
                print(fileName_ftp + '/' + name)
                fd = open(fileName_ftp + '/' + name, 'rb')
                # 以二进制的形式上传
                ftp.storbinary("STOR %s" % name, fd)
                fd.close()
        else:
            ftp.cwd(fileName_ftp_end)
            dir_res = []
            files = os.listdir(fileName_ftp)
            print(files)
            for i in files:
                dir_res.append(i)
            print(dir_res)
            # ftp.dir(fileName_ftp_end, dir_res.append)  # 对当前目录进行dir(),将结果放入列表
            # print(dir_res)
            for name in dir_res:
                print(fileName_ftp + '/' + name)
                fd = open(fileName_ftp + '/' + name, 'rb')
                # 以二进制的形式上传
                ftp.storbinary("STOR %s" % name, fd)
                fd.close()
        # 传完文件后再把目录切回原目录,再刷新界面
        ftp.cwd('..')
        # 上传完文件后刷新一遍目录窗口,首先要清空目录,再重载
        list2.delete(0, tkinter.END)  # 清空列表框
        show_ftp()
        print("upload finished")

(5)提供下载文件服务,主要思路是先获取用户在聊天窗口中选中的文件名称,然后用这个文件名称去FTP服务器进行查找,最后将其下载到指定文件夹中,如果用户选择下载的是文件夹,那就先切换到文件夹中,再依次遍历其中的文件并下载到指定目录中。核心代码如下

def download_ftp():
        # 获取到要下载到本地的文件名(只需要名字就行)
        fileName_ftp = list2.get(list2.curselection())
        print(fileName_ftp)
        # 对文件名做判断
        if '.' not in fileName_ftp:
            # 如果是文件夹的话就切换ftp目录进该文件夹,再下载,最后返回源目录
            ftp.cwd(fileName_ftp)
            dir_res = []
            # 将目录内文件信息保存到dir_res中
            ftp.dir('.', dir_res.append)  # 对当前目录进行dir(),将结果放入列表
            # print(dir_res)
            # 从dir_res中取出文件名放入files数组中
            files = []
            for i in dir_res:
                files.append(i.split(' ')[-1])
            # print(files)
            # 设置文件存储路径(路径为文件夹类型)
            dir_path = tkinter.filedialog.askdirectory()
            os.mkdir(dir_path + '\\' + fileName_ftp)
            # print(dir_path_end)
            for j in files:
                # 是文件的话就直接下载
                # 这里要对目录路径进行一些小替换以符合格式
                dir_path_end = dir_path.replace("/", '\\') + '\\' + fileName_ftp + '\\' + j
                fd = open(dir_path_end, 'wb')
                # 以二进制形式下载,注意第二个参数是fd.write,上传时是fd
                ftp.retrbinary("RETR %s" % j, fd.write)
                fd.close()
            ftp.cwd('..')
        else:
            # 是文件的话就直接下载
            # 设置文件存储路径(路径为文件夹类型)
            dir_path = tkinter.filedialog.askdirectory()
            # 这里要对目录路径进行一些小替换以符合格式
            dir_path_end = dir_path.replace("/", '\\') + '\\' + fileName_ftp
            print(dir_path_end)
            fd = open(dir_path_end, 'wb')
            # 以二进制形式下载,注意第二个参数是fd.write,上传时是fd
            ftp.retrbinary("RETR %s" % fileName_ftp, fd.write)
            fd.close()
        print("download finished")

(6)提供删除FTP服务器内文件服务。主要思路是先获取用户在聊天窗口中选中的文件名称,然后判断文件是单个文件还是文件夹,最后调用相应的FTP函数进行删除。核心代码如下

def delete_ftp():
        # 获取到要下载到本地的文件名(只需要名字就行)
        fileName_ftp = list2.get(list2.curselection())
        print(fileName_ftp)
        # 通过.号判断是否为文件或文件夹,从而赋予不同的颜色
        if '.' not in fileName_ftp:
            # 因为rmd命令只能删空目录
            # 所以我们要先递归删除目录里的文件
            dir_res = []
            ftp.dir(fileName_ftp, dir_res.append)  # 对当前目录进行dir(),将结果放入列表
            print(dir_res)
            for i in dir_res:
                name = i.split(' ')[-1]
                print(name)
                ftp.delete(fileName_ftp + '/' + name)
            ftp.rmd(fileName_ftp)
        else:
            ftp.delete(fileName_ftp)
        print("delete finished")
        # 上传完文件后刷新一遍目录窗口,首先要清空目录,再重载
        list2.delete(0, tkinter.END)  # 清空列表框
        show_ftp()
⑨接收服务端发来的信息模块,主要思路是

(1)首先判断接收到的信息是否抛出异常,如果没有捕获到异常则接收到的是当前在线用户列表,则取出其中的当前在线用户名并依次插入到聊天窗口中。
(2)然后判断接收到的信息的指定位置是否含有特殊词“image”,如果有的话代表是发送图片的命令,则连接上FTP服务器并根据图片名称下载到本地,然后在本地打开该图片并加载到聊天窗口中。
(3)如果以上判断均不成立,则为普通的聊天信息。然后判断聊天对象,最后把聊天内容发送给对应的聊天对象的客户端即可。

  • 7
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值