欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于Python的聊天室

程序员文章站 2022-03-04 09:05:20
本项目是一个在线的聊天室项目,主要运用了python的tkinter图形库、threading多线程库,以及soket库实现网络聊天室,主要实现了群聊和私聊功能,还有发送表情包功能。项目分为服务器端和客户端,采用Tcp协议进行网络数据传输。服务端主要用于存放用户连接信息包括用户的ip地址和端口和用户信息,客户端发送消息时经过服务器转发給其他用户。...

基于Python的聊天室

摘要

本项目是一个在线的聊天室项目,主要运用了python的tkinter图形库、threading多线程库,以及soket库实现网络聊天室,主要实现了群聊和私聊功能,还有发送表情包功能。项目分为服务器端和客户端,采用Tcp协议进行网络数据传输。服务端主要用于存放用户连接信息包括用户的ip地址和端口和用户信息,客户端发送消息时经过服务器转发給其他用户。

一、引言

1.1 背景和意义

随着互联网时代的到来,人与人之间的联系更加紧密。在现实生活中需要一个契机才有机会进行深入的沟通,人们对彼此的了解仅仅是浅层的,而在线的聊天则可以让我们抛开一切外在的东西,用语言去了解一个人的内在。

1.2 系统要实现的功能

1.2.1 用户登录

用户使用默认的服务器端口地址,输入昵称,接着点击‘登录’按钮,实现登录聊天系统。

1.2.2 群发消息

用户点击‘用户列表’,然后点击‘群发’,输入信息后点击‘发送’按钮实现群发消息。

1.2.3 一对一聊天

用户点击‘用户列表’,然后点击发送的对象,此时聊天窗口的标题变为当前用户指向目标用户,此时输入信息后点击‘发送’按钮实现一对一聊天。

1.2.4 发送表情

用户点击‘表情’按钮,此时点击需要发送的表情即可发送

二、系统结构

2.1 系统结构图

本项目分为服务器端和客户端,采用Tcp协议进行网络数据传输。服务端主要用于存放用户连接信息包括用户的ip地址和端口和用户信息,如下图所示
基于Python的聊天室

2.2 系统实现原理

系统采用C/S模式进行实现

2.2.1服务器端实现原理

登录和接收数据实现原理 :采用tcp协议,对8888端口进行监听,每当客户端的连接请求到来时,为其创建一个新的线程,判断该连接的用户是否在服务器的在线用户数组中,若不存在,则将用户的信息和用户的连接封装为一个在线用户存入用户数组中;若存在用户,则此次连接必然为发送数据的连接,因而服务器对客户端发送过来的数据进行解析,解析出其中的消息和用户

发送数据实现原理:服务端启动时,创建一个新的线程,用于监听消息队列中的数据是否为空,如果不为空,则调用发送数据的函数,以此往复。

2.2.2 客户端实现原理

发送数据实现原理:客户端数据以(消息内容:;当前用户:;目标用户)
的格式发送给服务器端,由服务器将消息转发给目标用户。

接收数据实现原理:客户端接收到数据以后,对数据进行解析,判断其是否为表情消息,若是则在存有表情的字典中找到其对应的图片,将其显示到聊天信息中;若不是表情,则直接将消息显示到聊天信息中。

2.3 系统技术分析

2.3.1 tkinter 图形用户界面库

Tkinter: Tkinter 模块(Tk 接口)是 Python 的标准 Tk GUI 工具包的接口 .Tk 和Tkinter 可以在大多数的 Unix 平台下使用,同样可以应用在 Windows 和 Macintosh系统里。Tk8.0 的后续版本可以实现本地窗口风格,并良好地运行在绝大多数平台中。

2.3.2 threading 多线程

​ 每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。

​ 指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
多线程详细介绍

2.3.3 socket 网络编程

​ Python 提供了两个级别访问的网络服务。

​ 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets
API,可以访问底层操作系统Socket接口的全部方法。

​ 高级别的网络服务模块 SocketServer,它提供了服务器中心类,可以简化网络服务器的开发。

​ Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。

Python 中,我们用 socket()函数来创建套接字,语法格式如下:

socket.socket([family[, type[, proto]]])

参数:

family: 套接字家族可以使AF_UNIX或者AF_INET

type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAMSOCK_DGRAM

protocol: 一般不填默认为0.

三、实现代码

3.1服务器端代码

3.1.1确定全局变量

IP = ''
PORT = 8888
que = queue.Queue()            # 用于存放客户端发送的信息的队列
users = []                     # 用于存放在线用户的信息  [conn, user, addr]
lock = threading.Lock()        # 创建锁, 防止多个线程写入数据的顺序打乱

3.1.2实现多线程接收数据


class ChatServer(threading.Thread):

    def __init__(self, port):
        threading.Thread.__init__(self) 
        self.ADDR = ('', port) 
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 用于接收所有客户端发送信息的函数
    def tcp_connect(self, conn, addr):
    pass;

    # 每当接收到一个socket 连接就为其创建并启动一个新的线程
    def run(self):
        self.s.bind(self.ADDR)
        self.s.listen(5)
        print('服务器正在运行中...')
        q = threading.Thread(target=self.sendData)
        q.start()
        while True:
            conn, addr = self.s.accept()
            t = threading.Thread(target=self.tcp_connect, args=(conn, addr))
            t.start()
        self.s.close()


3.1.3处理接收到的数据

# 将接收到的信息(ip,端口以及发送的信息)存入que队列
def recv(self, data, addr):
    lock.acquire()
    try:
        que.put((addr, data))
    finally:
        lock.release()

# 用于接收所有客户端发送信息,并将数据保存到消息队列中
def tcp_connect(self, conn, addr):
    # 连接后将用户信息添加到users列表
    user = conn.recv(1024)                 # 接收用户名
    user = user.decode()

    for i in range(len(users)):
        if user == users[i][1]:
            print('User already exist')
            user = '' + user + '_2'

    if user == 'no':
        user = addr[0] + ':' + str(addr[1])
    users.append((conn, user, addr))
    print(' 新的连接:', addr, ':', user, end='')         # 打印用户名
    d = onlines()                  # 有新连接则刷新客户端的在线用户显示
    self.recv(d, addr)
    try:
        while True:
            data = conn.recv(1024)
            data = data.decode()
            self.recv(data, addr)                         # 保存信息到队列
        conn.close()
    except:
        print(user + ' 断开连接')
        self.delUsers(conn, addr)                     # 将断开用户移出users
        conn.close()


3.1.4将在线用户存入online列表并返回

def onlines():
    online = []
    for i in range(len(users)):
        online.append(users[i][1])
    return online

3.1.5删除用户

 # 判断断开用户在users中是第几位并移出列表, 刷新客户端的在线用户显示
def delUsers(self, conn, addr):
    a = 0
    for i in users:
        if i[0] == conn:
            users.pop(a)
            print(' 在线用户: ', end='')       # 打印剩余在线用户(conn)
            d = onlines()
            self.recv(d, addr)
            print(d)
                break
            a += 1

3.1.6发送消息

# 将队列que中的消息发送给所有连接到的用户
def sendData(self):
    while True:
        if not que.empty():
            data = ''
            reply_text = ''
            message = que.get()                               # 取出队列第一个元素
            if isinstance(message[1], str):                   # 如果data是str则返回Ture
                for i in range(len(users)):
                    # user[i][1]是用户名, users[i][2]是addr, 将message[0]改为用户名
                    for j in range(len(users)):
                        if message[0] == users[j][2]:
                            print(' this: message is from user[{}]'.format(j))
                            data = ' ' + users[j][1] + ':' + message[1]
                            break
                    users[i][0].send(data.encode())
            # data = data.split(':;')[0]
            if isinstance(message[1], list):  # 同上
                # 如果是list则打包后直接发送  
                data = json.dumps(message[1])
                for i in range(len(users)):
                    try:
                        users[i][0].send(data.encode())
                    except:
                        pass

3.1.7 主函数

#监听服务器线程是否处于运行状态
if __name__ == '__main__':
    cserver = ChatServer(PORT)
    cserver.start()
    while True:
        time.sleep(1)
        if not cserver.isAlive():
            print("Chat connection lost...")
            sys.exit(0)

3.2 客户端代码

3.2.1确定全局变量

IP = ''
PORT = ''
user = ''
listbox1 = ''  # 用于显示在线用户的列表框
ii = 0  # 用于判断是开还是关闭列表框
users = []  # 在线用户列表
chat = '【群发】'  # 聊天对象, 默认为群聊

3.2.2登录窗口实现

# 登陆窗口
loginRoot = tkinter.Tk()
loginRoot.title('聊天室')
loginRoot['height'] = 110
loginRoot['width'] = 270
loginRoot.resizable(0, 0)  # 限制窗口大小

IP1 = tkinter.StringVar()
IP1.set('127.0.0.1:8888')  # 默认显示的ip和端口
User = tkinter.StringVar()
User.set('')

# 服务器标签
labelIP = tkinter.Label(loginRoot, text='地址:端口')
labelIP.place(x=20, y=10, width=100, height=20)

entryIP = tkinter.Entry(loginRoot, width=80, textvariable=IP1)
entryIP.place(x=120, y=10, width=130, height=20)

# 用户名标签
labelUser = tkinter.Label(loginRoot, text='昵称')
labelUser.place(x=30, y=40, width=80, height=20)

entryUser = tkinter.Entry(loginRoot, width=80, textvariable=User)
entryUser.place(x=120, y=40, width=130, height=20)


# 登录按钮
def login(*args):
    global IP, PORT, user
    IP, PORT = entryIP.get().split(':')  # 获取IP和端口号
    PORT = int(PORT)                     # 端口号需要为int类型
    user = entryUser.get()
    if not user:
        tkinter.messagebox.showerror('温馨提示', message='请输入任意的用户名!')
    else:
        loginRoot.destroy()                  # 关闭窗口


loginRoot.bind('<Return>', login)            # 回车绑定登录功能
but = tkinter.Button(loginRoot, text='登录', command=login)
but.place(x=100, y=70, width=70, height=30)

loginRoot.mainloop()


3.2.3 与服务器建立连接

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, PORT))
if user:
    s.send(user.encode())  # 发送用户名
else:
    s.send('no'.encode())  # 没有输入用户名则标记no

3.2.4 创建聊天窗口

# 聊天窗口
# 创建图形界面
root = tkinter.Tk()
root.title(user)  # 窗口命名为用户名
root['height'] = 400
root['width'] = 580
root.resizable(0, 0)  # 限制窗口大小

# 创建多行文本框
listbox = ScrolledText(root)
listbox.place(x=5, y=0, width=570, height=320)
# 文本框使用的字体颜色
listbox.tag_config('red', foreground='red')
listbox.tag_config('blue', foreground='blue')
listbox.tag_config('green', foreground='green')
listbox.tag_config('pink', foreground='pink')
listbox.insert(tkinter.END, '欢迎加入聊天室 !', 'blue')

# 创建多行文本框, 显示在线用户
listbox1 = tkinter.Listbox(root)
listbox1.place(x=445, y=0, width=130, height=320)


def showUsers():
    global listbox1, ii
    if ii == 1:
        listbox1.place(x=445, y=0, width=130, height=320)
        ii = 0
    else:
        listbox1.place_forget()  # 隐藏控件
        ii = 1
# 查看在线用户按钮
button1 = tkinter.Button(root, text='用户列表', command=showUsers)
button1.place(x=485, y=320, width=90, height=30)

# 创建输入文本框和关联变量
a = tkinter.StringVar()
a.set('')
entry = tkinter.Entry(root, width=120, textvariable=a)
entry.place(x=5, y=350, width=570, height=40)

3.2.5 发送数据

def send(*args):
    # 没有添加的话发送信息时会提示没有聊天对象
    users.append('【群发】')
    print(chat)
    if chat not in users:
        tkinter.messagebox.showerror('温馨提示', message='没有聊天对象!')
        return
    if chat == user:
        tkinter.messagebox.showerror('温馨提示', message='自己不能和自己进行对话!')
        return
    mes = entry.get() + ':;' + user + ':;' + chat  # 添加聊天对象标记
    s.send(mes.encode())
    a.set('')  # 发送后清空文本框


# 创建发送按钮
button = tkinter.Button(root, text='发送', command=send)
button.place(x=515, y=353, width=60, height=30)
root.bind('<Return>', send)  # 绑定回车发送信息

3.2.6 实现私聊

# 私聊功能
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)


# 在显示用户列表框上设置绑定事件
listbox1.bind('<ButtonRelease-1>', private)

3.2.7 接收数据并打印

# 用于时刻接收服务端发送的信息并打印
def recv():
    global users
    while True:
        data = s.recv(1024)
        data = data.decode()
        # 没有捕获到异常则表示接收到的是在线用户列表
        try:
            data = json.loads(data)
            users = data
            listbox1.delete(0, tkinter.END)  # 清空列表框
            number = ('   在线用户数: ' + str(len(data)))
            listbox1.insert(tkinter.END, number)
            listbox1.itemconfig(tkinter.END, fg='green', bg="#f0f0ff")
            listbox1.insert(tkinter.END, '【群发】')
            listbox1.itemconfig(tkinter.END, fg='green')
            for i in range(len(data)):
                listbox1.insert(tkinter.END, (data[i]))
                listbox1.itemconfig(tkinter.END, fg='green')
        except:
            data = data.split(':;')
            data1 = data[0].strip()  # 消息
            data2 = data[1]  # 发送信息的用户名
            data3 = data[2]  # 聊天对象
            markk = data1.split(':')[1]
            # 判断是不是图片
            pic = markk.split('#')
            # 判断是不是表情
            # 如果字典里有则贴图
            if (markk in dic) or pic[0] == '``':
                data4 = '\n' + data2 + ':'  # 例:名字-> \n名字:
                if data3 == '【群发】':
                    if data2 == user:  # 如果是自己则将则字体变为蓝色
                        listbox.insert(tkinter.END, data4, 'blue')
                    else:
                        listbox.insert(tkinter.END, data4, 'green')  # END将信息加在最后一行
                elif data2 == user or data3 == user:  # 显示私聊
                    listbox.insert(tkinter.END, data4, 'red')  # END将信息加在最后一行
                listbox.image_create(tkinter.END, image=dic[markk])
            else:
                data1 = '\n' + data1
                if data3 == '【群发】':
                    if data2 == user:  # 如果是自己则将则字体变为蓝色
                        listbox.insert(tkinter.END, data1, 'blue')
                    else:
                        listbox.insert(tkinter.END, data1, 'green')  # END将信息加在最后一行
                    if len(data) == 4:
                        listbox.insert(tkinter.END, '\n' + data[3], 'pink')
                elif data2 == user or data3 == user:  # 显示私聊
                    listbox.insert(tkinter.END, data1, 'red')  # END将信息加在最后一行
            listbox.see(tkinter.END)  # 显示在最后

3.2.8 主函数

r = threading.Thread(target=recv)
r.start()  # 开始线程接收信息

root.mainloop()
s.close()  # 关闭图形界面后关闭TCP连接

四、实验结果

4.1 用户登录

用户使用默认的服务器端口地址,输入昵称,接着点击‘登录’按钮,即可登录聊天系统,以张三为例,如图4.1.1所示:

基于Python的聊天室

图4.1.1

4.2 群发消息

用户点击‘用户列表’,然后点击‘群发’,输入信息后点击‘发送’按钮即可群发消息,以张三群发消息为例,如图4.2.1所示
基于Python的聊天室

图4.2.1

4.3 一对一聊天

用户点击‘用户列表’,然后点击发送的对象,此时聊天窗口的标题变为,当前用户指向目标用户,如图4.3.1所示,此时输入信息后点击‘发送’按钮即可一对一聊天,以张三群发消息给李四为例,如图4.3.2所示

基于Python的聊天室

图4.3.1

基于Python的聊天室

图4.3.2

4.4 发送表情

用户点击‘表情’按钮,如图4.4.1所示,此时点击表情即可发送,以张三群发送表情给李四为例,如图4.4.2所示

基于Python的聊天室

图4.4.1

基于Python的聊天室

图4.4.2

五、总结和展望

5.1项目总结

本项目是一个在线的聊天室项目,主要运用了python的tkinter图形库、threading多线程库,以及soket库实现网络聊天室,主要实现了群聊和私聊功能,还有发送表情包功能。项目分为服务器端和客户端,采用Tcp协议进行网络数据传输。服务端主要用于存放用户连接信息包括用户的ip地址和端口和用户信息,客户端发送消息时经过服务器转发给其他用户。

5.2 项目展望

本项目实现了基本的聊天功能,后期将会加入的功能有,语音聊天、视屏聊天、文件共享等

参考文献:

[1] 菜鸟教程 python3 教程 https://www.runoob.com/python3/python3-tutorial.html

本文地址:https://blog.csdn.net/qq_38951154/article/details/107270408