当前位置:   article > 正文

Python多人聊天室_python+socket实现多人聊天室

python+socket实现多人聊天室

多人聊天室的实现

一. 源代码链接

链接:https://pan.baidu.com/s/1kzxiLTkvdxGAMgF3SQzcaw?pwd=vb9h
提取码:vb9h

二. 项目简介

  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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
②对于聊天服务器,主要模块可以分为

(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--------------------------
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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()  # 关闭登录窗口
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
②聊天窗口模块,即登录成功后为用户显示出来的窗口,用于用户聊天和进行其他操作,主要也是利用了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('')  # 发送后清空文本框
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
⑥私聊模块,主要思路是确定好聊天的两个对象,然后将用户和其聊天对象名拼接成字符串显示到聊天框中,方便用户确定当前聊天对象,核心代码如下
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
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
⑦邮件功能模块,主要思路如下

(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 = ''
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
⑧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')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

(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")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

(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")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

(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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
⑨接收服务端发来的信息模块,主要思路是

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/44612
推荐阅读
相关标签
  

闽ICP备14008679号