基于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地址和端口和用户信息,如下图所示
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_STREAM
或SOCK_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