基于Python的聊天室


文章目录

  • 基于Python的聊天室
  • 一、引言
  • 1.1 背景和意义
  • 1.2 系统要实现的功能
  • 1.2.1 用户登录
  • 1.2.2 群发消息
  • 1.2.3 一对一聊天
  • 1.2.4 发送表情
  • 二、系统结构
  • 2.1 系统结构图
  • 2.2 系统实现原理
  • 2.2.1服务器端实现原理
  • 2.2.2 客户端实现原理
  • 2.3 系统技术分析
  • 2.3.1 tkinter 图形用户界面库
  • 2.3.2 threading 多线程
  • 2.3.3 socket 网络编程
  • 三、实现代码
  • 3.1服务器端代码
  • 3.1.1确定全局变量
  • 3.1.2实现多线程接收数据
  • 3.1.3处理接收到的数据
  • 3.1.4将在线用户存入online列表并返回
  • 3.1.5删除用户
  • 3.1.6发送消息
  • 3.1.7 主函数
  • 3.2 客户端代码
  • 3.2.1确定全局变量
  • 3.2.2登录窗口实现
  • 3.2.3 与服务器建立连接
  • 3.2.4 创建聊天窗口
  • 3.2.5 发送数据
  • 3.2.6 实现私聊
  • 3.2.7 接收数据并打印
  • 3.2.8 主函数
  • 四、实验结果
  • 4.1 用户登录
  • 4.2 群发消息
  • 4.3 一对一聊天
  • 4.4 发送表情
  • 五、总结和展望
  • 5.1项目总结
  • 5.2 项目展望
  • 5.3 项目源码



摘要

本项目是一个在线的聊天室项目,主要运用了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在线聊天系统 基于python的聊天系统_socket

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将信息加在最后一行
                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在线聊天系统 基于python的聊天系统_网络_02


图4.1.1

4.2 群发消息

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

python在线聊天系统 基于python的聊天系统_python在线聊天系统_03


图4.2.1

4.3 一对一聊天

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

python在线聊天系统 基于python的聊天系统_python_04


图4.3.1

python在线聊天系统 基于python的聊天系统_python在线聊天系统_05


图4.3.2

4.4 发送表情

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

python在线聊天系统 基于python的聊天系统_tkinter_06


图4.4.1

python在线聊天系统 基于python的聊天系统_python_07


图4.4.2

五、总结和展望

5.1项目总结

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

5.2 项目展望

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