这学期我们网络编程这门课主要是Window Socket,并且还是用我们没学过C++实现的,不过值得庆幸的是老师上课讲的很仔细,并且他讲课主要是讲思想,实现思路,最后老师没有限制我们课程设计使用的语言。权衡了一下,我使用Python实现了多人在线聊天的聊天室。
这个聊天室的主要功能是:
1、多人聊天——群聊
2、私聊
3、发表情包
有种仿QQ和微信聊天的感觉,但是界面不太美观,功能没那么强大,但是用作网络编程的课程设计大作业还是OK的......
这个小项目首先是在b站看的教学视频,这个教程就只实现多人聊天的功能,后面我要在这个基础上实现了私聊、发表情的功能,源码我会放到文章的最后,有需要的可以下载......
这篇文章主要是讲和教程不一样的地方,详细可以看一下的教学视频,或是自己下载源码学习.....
教学视频连接:
【python项目实战】创作自己的多人聊天室_哔哩哔哩_bilibili
一、服务端
服务端主要是在处理客户端请求的死循环中,添加了一个判断,判断是否要进入私聊的聊天模式
def request_handle(self, client_sock):
"""处理客户端请求"""
while True:
"""1、接收客户端请求"""
recv_data = client_sock.recv_data()
if not recv_data: # 如果收到空字符串 就表示结束聊天
self.remove_offline_user(client_sock)
client_sock.close()
break
print("客户端发送的请求信息:%s" % recv_data)
"""2、解析客户端请求"""
parser_data = self.parser_request_text(recv_data)
print("解析过的数据为:%s" % parser_data)
"""3、判断请求类型,调用请求对应的处理函数"""
handle_function = self.request_handle_function.get(parser_data["request_id"])
if handle_function:
handle_function(client_sock, parser_data)
#处理聊天请求
def request_chat_handle(self, client_sock, request_data):
print("正在处理聊天请求~")
username = request_data['username']
messages = request_data['messages']
chat = request_data['chat']
nickname = self.clients[username]['nickname']
msg = ResponseProtocol.response_chat(nickname, messages, str(self.users), chat)
if chat == '------------------群聊------------------':
for u_name, info in self.clients.items():
if username == u_name:
continue
info['sock'].send_data(msg)
else:
for u_name, info in self.clients.items():
if username == u_name:
continue
elif chat == info['nickname']:
info['sock'].send_data(msg)
二、客户端
客户端的代码有较大的变化
首先是图形界面:
聊天界面的主要变化
def append_message(self, sender, messages, chat):
"""追加聊天内容到chat_text_area"""
send_time = strftime("%Y-%m-%d %H:%M:%S", localtime(time()))
send_info = "%s: %s\n" % (sender, send_time)
# 判断是不是表情 如果字典里有则贴图
if messages in self.dic:
if chat == '------------------群聊------------------':
self.children['chat_text_area'].insert(END, send_info, 'green') # END将信息加在最后一行
self.children['chat_text_area'].image_create(END, image=self.dic[messages])
self.children['chat_text_area'].insert(END, "\n")
self.children['chat_text_area'].see(END)
else:
self.children['chat_text_area'].insert(END, send_info, 'blue') # END将信息加在最后一行
self.children['chat_text_area'].image_create(END, image=self.dic[messages])
self.children['chat_text_area'].insert(END, "\n")
self.children['chat_text_area'].see(END)
else:
if chat == '------------------群聊------------------':
self.children['chat_text_area'].insert(END, send_info, 'green') # END将信息加在最后一行
self.children['chat_text_area'].insert(END, " " + messages + "\n")
self.children['chat_text_area'].see(END)
else: # 显示私聊
self.children['chat_text_area'].insert(END, send_info, 'blue') # END将信息加在最后一行
self.children['chat_text_area'].insert(END, " " + messages + "\n")
self.children['chat_text_area'].see(END) # 显示在最后
登录界面:
from tkinter import Tk
from tkinter import Label
from tkinter import Entry
from tkinter import Button
from tkinter import END
class WindowLogin(Tk):
def __init__(self):
super(WindowLogin, self).__init__()
# 初始化窗口
self.window_init()
# 添加控件
self.add_widgets()
def window_init(self):
self.title('登录')
self.resizable(False, False)
window_width = 450
window_height = 300
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
position_x = (screen_width - window_width) / 2
position_y = (screen_height - window_height) / 2
# 设置窗体大小和位置
self.geometry("%dx%d+%d+%d" % (window_width, window_height, position_x, position_y))
def add_widgets(self):
login_title = Label(self, fg="black", font=('宋体', 20))
login_title["text"] = "在 线 聊 天 室 登 录"
login_title.place(x=80, y=30)
username_label = Label(self)
username_label["text"] = "用户名:"
username_label.place(x=90, y=130)
username_entry = Entry(self, name='username_entry')
username_entry["width"] = 30
username_entry.place(x=150, y=130)
password_label = Label(self)
password_label["text"] = "密 码:"
password_label.place(x=90, y=170)
password_entry = Entry(self, name='password_entry')
password_entry["width"] = 30
password_entry["show"] = "*"
password_entry.place(x=150, y=170)
reset_button = Button(self, name='reset_button', bg="#00BFFF", fg="white", font=('宋体', 12))
reset_button["text"] = " 重 置 "
reset_button.place(x=130, y=210)
login_button = Button(self, name='login_button', bg="#00BFFF", fg="white", font=('宋体', 12))
login_button["text"] = " 登 录 "
login_button.place(x=240, y=210)
def on_login_click(self, command):
login_button = self.children["login_button"]
login_button["command"] = command
def on_reset_click(self, command):
reset_button = self.children["reset_button"]
reset_button["command"] = command
def get_username(self):
return self.children["username_entry"].get()
def get_password(self):
return self.children["password_entry"].get()
def clear_username(self):
return self.children["username_entry"].delete(0, END)
def clear_password(self):
return self.children["password_entry"].delete(0, END)
def on_window_closed(self, command):
self.protocol('WM_DELETE_WINDOW', command)
if __name__ == '__main__':
window = WindowLogin()
window.mainloop()
客户端主要功能的变化:
from tkinter import END, Button, FLAT
from window_login import WindowLogin
from request_protocol import RequestProtocol
from client_socket import ClientSocket
from threading import Thread
from config import *
from tkinter.messagebox import showinfo
from window_chat import WindowChat
import sys
class Client:
def __init__(self):
# 初始化用户名 用于保存在线用户
self.username = None
self.users = set() # 在线用户列表
self.pri_user = '' # 私聊对象
self.chat = '------------------群聊------------------' # 聊天对象, 默认为群聊
# 初始化登录界面
self.window = WindowLogin()
self.window.on_window_closed(self.exit)
"""重置按钮 和 登录按钮 的事件处理"""
self.window.on_reset_click(self.clear_inputs)
self.window.on_login_click(self.send_login_data)
# 初始化聊天界面
self.window_chat = WindowChat()
self.window_chat.withdraw() # 隐藏聊天窗口 等登陆成功后再显示
self.window_chat.on_window_closed(self.exit)
self.window_chat.on_eBut_click(self.express)
# 在显示用户列表框上设置绑定事件
self.window_chat.children['chat_count'].bind('<ButtonRelease-1>', self.privat_user)
"""发送信息按钮的 事件处理函数"""
self.window_chat.on_send_button_click(self.send_chat_data)
self.window_chat.on_reset_button_click(self.reset_chat_data)
# 创建套接字
self.conn = ClientSocket()
self.response_handle_function = {}
self.register(RESPONSE_LOGIN_RESULT, self.response_login_handle)
self.register(RESPONSE_CHAT, self.response_chat_handle)
def register(self, response_id, handle_function):
self.response_handle_function[response_id] = handle_function
def startup(self):
self.conn.connect() # 自动连接服务器
Thread(target=self.response_handle).start() # 开启多线程实现数据接收
self.window.mainloop()
def clear_inputs(self):
self.window.clear_username()
self.window.clear_password()
def send_login_data(self):
# 获取用户名 和 密码
username = self.window.get_username()
password = self.window.get_password()
# 封装请求信息
request_text = RequestProtocol.request_login_result(username, password)
# 蒋请求信息发送给服务器
print("客户端的请求登录的信息为:" + request_text)
self.conn.send_data(request_text)
# print(self.conn.recv_data())
def send_chat_data(self):
msg = self.window_chat.get_inputs()
self.window_chat.clear_inputs()
print("%s" % msg)
# 封装数据 发送到服务端
request_text = RequestProtocol.request_chat_result(self.username, msg, self.chat)
self.conn.send_data(request_text)
# 自己界面显示的信息
self.window_chat.append_message("我", msg, self.chat)
def mark(self, exp): # 参数是发的表情图标记, 发送后将按钮销毁
msg = exp
request_text = RequestProtocol.request_chat_result(self.username, msg, self.chat)
self.window_chat.append_message("我", msg, self.chat)
self.conn.send_data(request_text)
self.window_chat.b1.destroy()
self.window_chat.b2.destroy()
self.window_chat.b3.destroy()
self.window_chat.b4.destroy()
self.window_chat.ee = 0
# 四个对应的函数
def bb1(self):
self.mark('aa**')
def bb2(self):
self.mark('bb**')
def bb3(self):
self.mark('cc**')
def bb4(self):
self.mark('dd**')
def express(self):
if self.window_chat.ee == 0:
self.window_chat.ee = 1
self.window_chat.b1 = Button(self.window_chat, image=self.window_chat.p1, command=self.bb1, relief=FLAT,
bd=0)
self.window_chat.b2 = Button(self.window_chat, image=self.window_chat.p2, command=self.bb2, relief=FLAT,
bd=0)
self.window_chat.b3 = Button(self.window_chat, image=self.window_chat.p3, command=self.bb3, relief=FLAT,
bd=0)
self.window_chat.b4 = Button(self.window_chat, image=self.window_chat.p4, command=self.bb4, relief=FLAT,
bd=0)
self.window_chat.b1.place(x=10, y=333)
self.window_chat.b2.place(x=80, y=333)
self.window_chat.b3.place(x=150, y=333)
self.window_chat.b4.place(x=220, y=333)
else:
self.window_chat.ee = 0
self.window_chat.b1.destroy()
self.window_chat.b2.destroy()
self.window_chat.b3.destroy()
self.window_chat.b4.destroy()
def reset_chat_data(self):
self.window_chat.clear_inputs()
def response_handle(self):
while True:
msg = self.conn.recv_data()
if not msg:
self.conn.close()
break
print("服务端返回的消息:%s" % msg)
# 解析数据
response_data = self.parser_response_data(msg)
handle_function = self.response_handle_function[response_data['response_id']]
if handle_function:
handle_function(response_data)
# 接收到的是在线用户列表
self.window_chat.children['chat_count'].delete(0, END) # 清空列表框
number = (' 在线人数: ' + str(len(self.users)) + ' 人')
self.window_chat.children['chat_count'].insert(END, number)
self.window_chat.children['chat_count'].itemconfig(END, fg='green', bg="#f0f0ff")
self.window_chat.children['chat_count'].insert(END, '------------------群聊------------------')
self.window_chat.children['chat_count'].itemconfig(END, fg='green')
for i in range(len(self.users)):
self.window_chat.children['chat_count'].insert(END, (list(self.users)[i]))
self.window_chat.children['chat_count'].itemconfig(END, fg='green')
def parser_response_data(self, msg):
# 两种数据类型 登录1001|登陆结果|昵称|username 聊天 1002|昵称|messages
response_data_list = msg.split(DELIMITER)
response_data = {}
response_data['response_id'] = response_data_list[0]
if response_data['response_id'] == RESPONSE_LOGIN_RESULT:
response_data['result'] = response_data_list[1]
response_data['nickname'] = response_data_list[2]
response_data['username'] = response_data_list[3]
self.users = eval(response_data_list[4])
print(self.users)
elif response_data['response_id'] == RESPONSE_CHAT:
response_data['nickname'] = response_data_list[1]
response_data['messages'] = response_data_list[2]
self.users = eval(response_data_list[3])
self.chat = response_data_list[4]
return response_data
def response_login_handle(self, response_data):
print("客户端正在处理登录:%s" % response_data)
result = response_data['result']
if result == '0':
showinfo("登录失败", "登录失败,用户名或密码错误!")
print("登陆失败!")
return
# 登陆成功 获取昵称和用户名
showinfo("登陆成功", "登陆成功!")
nickname = response_data['nickname']
self.username = response_data['username']
self.window_chat.set_title(nickname)
self.window_chat.update()
self.window_chat.deiconify() # 登陆成功 显示聊天界面
self.window.withdraw() # 登录成功 隐藏登录界面
def response_chat_handle(self, response_data):
print("客户端正在处理聊天信息:%s" % response_data)
sender = response_data['nickname']
messages = response_data['messages']
self.window_chat.append_message(sender, messages, self.chat)
# 设置私聊时的窗体标题
def privat_user(self, response_data):
# 获取点击的索引然后得到内容(用户名)
indexs = self.window_chat.children['chat_count'].curselection()
index = indexs[0]
self.chat = self.window_chat.children['chat_count'].get(index)
# 修改客户端名称
if self.chat == '------------------群聊------------------':
self.window_chat.set_title(self.username)
return
ti = '我' + ' --> ' + self.chat
self.window_chat.set_title(ti)
return self.chat
def exit(self):
self.conn.close()
sys.exit(0) # 关闭程序