python编写的全双工聊天室
支持的功能为,用户能够进行注册与登录,用户的数据通过mysql数据库进行存取。程序通过模块json来进行客户端与服务器端的数据的封装,tcp协议传输。
成功登录后的用户,将会进入 “在线用户列表”界面,会显示对应聊天对像未读的信息数量,这里有一个缺陷便是如果有新的用户上线,则必须通过输入"update"进行更新界面,才能看到新的上线用户,以及如果用户下线后,也未作处理,即“在线用户列表”依然会显示该用户。
(补充,对密码的md5加密应该放在客户端上面,我这里是直接放在数据库上面)
服务端流程
客户端流程
服务器端 server.py
import socket
import threading
import queue
import json
import mysql_sqlalchemy
class TcpServer():
def __init__(self):
self.host=''
self.port=8002
self.sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.sock.bind((self.host,self.port))
self.maxNumber=10 #最大连接人数
self.buffsize=1024
self.login_verification_code=205#登录验证码
self.registered_code=206 #注册码
self.registered_ok_code = 207 #注册结果码
self.user_messages_code = 301 #表示 消息数据格式码
#self.user_status_code = 302 #表示 在线成员格式码
self.user_update_status_code = 303 #表示 更新在线成员格式码
self.loginOk_client_code = 351 #登录成功返回给客户端的码
self.login_verification_fails_code = 400 #验证失败返回给客户端的码
#self.users_in_online=[] #保存每个用户的 (username,s_client)
self.users_in_online_dic={} #保存在线用户{username,(s_client,name)} 便于取
self.q_messages=queue.Queue() #存放服务器接收的消息,[d_username,content]
self.usesql=mysql_sqlalchemy.Main() #返回一个可操作sql的对象
self.test_id=10
listen_connect_thread=threading.Thread(target=self.listen_user) #创建一个监听连接的线程
listen_connect_thread.start()
#listen_connect_thread.join() #设置为守护线程
listen_send_thread=threading.Thread(target=self.send_data) #创建一个随时监听数据,一有数据就发送的线程
listen_send_thread.start()
#listen_send_thread.join() #设置为守护线程
"监听函数"
def listen_user(self):
self.sock.listen(self.maxNumber)
while True:
print("wating to be connected...")
s_client,addr=self.sock.accept()
thr=threading.Thread(target=self.listen_login_or_registered,args=(s_client,))
thr.start()
#thr.join()
def listen_login_or_registered(self,s_client):
while True:
print("等待命令...")
messages = s_client.recv(self.buffsize).decode("utf8")
dic_messages = json.loads(messages)
commande_code=dic_messages["command_code"]
content = dic_messages["content"]
if commande_code == self.login_verification_code: #登录验证函数码
checkbool,name=self.login_aut(s_client,content)
if checkbool: #如果验证成功
break
elif commande_code==self.registered_code: #注册码
self.registered_user(s_client,content)
self.user_main(s_client, content["username"], name) #如果验证成功,则break跳出while循环,执行该函数
def registered_user(self,s_client,content):
username=content["username"]
password=content["password"]
name=content["name"]
add_bool=self.usesql.add_user(username,password,name) #True 成功,False失败
data={
"command_code":self.registered_ok_code, #注册结果反馈码
"content":add_bool
}
s_client.send(json.dumps(data).encode("utf8"))
"登录验证函数"
def login_aut(self,s_client,dic_content):
print("登录验证")
username = dic_content["username"]
password = dic_content["password"]
print(username,password)
check_bool, name = self.usesql.check_user(username=username, password=password) # 验证成功则返回TRUE
if check_bool: # 登录成功则返回用户信息
print("test_id:",self.test_id)
self.test_id+=1
# self.users_in_online.append((username, s_client)) # 将自己加入在线列表中
self.users_in_online_dic[username] = (s_client, name) # 将自己加入在线字典中
my_name=self.users_in_online_dic[username][1]
print("out my :"+my_name)
for i,j in self.users_in_online_dic.items():
print(i,j)
return (True,name)
else: # 验证失败,则返回400 命令码
data = {
"command_code": self.login_verification_fails_code,
# (表示登录验证)self.login_verification_fails_code=400
"content": ''
}
s_client.send(json.dumps(data).encode("utf8"))
return (False,"")
"进入用户界面响应"
def user_main(self,s_client,username,name):
users_in_online=[] #为每个用户新生成一个在线列表,列表中的成员除了自己,这样比较低效率
for the_username,tup_next in self.users_in_online_dic.items():
if the_username==username:
continue
the_name=tup_next[1] #(s_client,name) 获得name
users_in_online.append((the_username,the_name))
print("users_in_online :")
print(users_in_online)
data={
"command_code": self.loginOk_client_code, #(表示登录验证)self.loginOk_client_code=351
"content":{
"user_information":(username,name),
"users":users_in_online
}
}
s_client.send(json.dumps(data).encode("utf8")) #将初始信息发送给客户端
self.update_users(s_client,username,name) #一有新的用户连接入,就更新其他在线用户的在线列表
self.recv_data(s_client,username,name) #进入数据接收
#self.users_in_online.append((username,s_client)) #将自己加入在线列表中
def recv_data(self,s_client,username,name):
print("begin recv the messages from username:",username )
while True:
data=s_client.recv(self.buffsize).decode("utf8")
data=json.loads(data)
command_code=data["command_code"]
if command_code==301:
content=data["content"]
d_username = content["d_username"]
dic_content=[d_username,content]
self.q_messages.put(dic_content)
else:
continue
def update_users(self,my_client,username,name):
print("update_users")
data={
"command_code":self.user_update_status_code,
"content":(username,name)
}
data=json.dumps(data).encode("utf8")
for i,j in self.users_in_online_dic.items():
print(i,j)
#users_in_online_dic=self.users_in_online_dic
#users_in_online_dic.pop(username) #傻逼啊,这是浅拷贝,这TM对象是同一个啊,会删了自身的啊。。。
for tup_next in self.users_in_online_dic.values(): #给每个在线用户发送更新列表
s_client=tup_next[0]
if s_client==my_client:
continue
s_client.send(data)
def send_data(self):
print("send messages...")
while True:
if self.q_messages.qsize() > 0: # 有数据时
d_username, content = self.q_messages.get()
data={"command_code":self.user_messages_code,
"content":content
}
s_client=self.users_in_online_dic[d_username][0]
s_client.send(json.dumps(data).encode("utf8"))
print("send messages to username:" ,d_username)
if __name__=="__main__":
tcpserver=TcpServer()
数据库 mysql_sqlalchemy.py
from sqlalchemy import Column,Integer,String,create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from hashlib import md5
Base=declarative_base()
class User(Base):
__tablename__="users"
id=Column(Integer,primary_key=True,autoincrement=True)
username=Column(String(20),unique=True)
password=Column(String(64))
name=Column(String(64))
class Usesql():
def __init__(self):
engine = create_engine("mysql+pymysql://root:123456@localhost/chat_sql?charset=utf8", \
encoding='utf8')
Base.metadata.create_all(engine)
Session_class=sessionmaker(engine)
self.session=Session_class()
def add_user(self,username,password,name):
m=md5()
m.update(password.encode("utf8"))
user=self.session.query(User).filter(User.username==username).first()
if not user: #如果用户不存在
user = User(username=username, password=m.hexdigest(), name=name)
self.session.add(user)
self.session.commit()
print("提交成功")
return True
else:
print("用户已存在")
return False
def delete_user(self,username):
user=self.session.query().filter(User.username==username).first()
self.session.delete(user)
print("删除成功")
def check_user(self,username,password):
m = md5()
m.update(password.encode("utf8"))
user = self.session.query(User).filter(User.username == username).first()
if not user: #如果用户不存在,则返回失败
return (False,'')
if user.password==m.hexdigest(): #如果密码相同,则验证成功
return (True,user.name)
else:
return (False,'')
#导入文件时,调用Main开始使用,并返回管理对象
def Main():
usesql=Usesql()
return usesql
if __name__=="__main__":
usesql=Main()
客户端 client.py
import socket
import threading
import json
import queue
class TcpClient():
def __init__(self):
self.config={
"host":"127.0.0.1",
"port":8002
}
self.buffsize=1024
self.client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.login_verification_code = 205 #登录验证数据格式
self.registered_code = 206 #注册码
self.registered_ok_code = 207 # 注册结果状态码
self.user_messages_code = 301 #发送聊天消息码
self.user_update_status_code = 303 #更新 增加在线成员列表码
self.loginOk_client_code = 351 # 登录成功,返回的初始消息码
self.login_verification_fails_code=400 #验证失败,则接收400数据包
self.username=""
self.name=""
self.send_queue=queue.Queue() #将要发送的消息放入发送消息队列中
# content={
# "s_username": self.username,
# "d_username": d_username,
# "messages": messages
# }
self.dic_chat_queue={} #username:queue.Queue() 存放各聊天对象的未读消息
self.dic_chat_users={} #username:name
self.new_user=queue.Queue() #存放新上线的用户 (username,name)
def start_connect(self):
self.client.connect((self.config["host"],self.config["port"]))
# print(self.client)
#self.login(username,password) # 从GUI中获取登录数据
def registered_user(self,username,password,name):
data={
"command_code":self.registered_code, #注册码206
"content":{
"username":username,
"password":password,
"name":name
}
}
self.client.send(json.dumps(data).encode("utf8"))
data=self.client.recv(self.buffsize).decode("utf-8")
data=json.loads(data)
if data["command_code"]==self.registered_ok_code:
registered_bool=data["content"]
return registered_bool
else:
print("registered_user code error...")
return False
#验证登录函数
def login(self,username,password):
data={
"command_code":self.login_verification_code, #(表示登录验证)self.login_verification_code=205
"content":{
"username":username,
"password":password
}
}
self.client.send(json.dumps(data).encode("utf8"))
data=self.client.recv(self.buffsize).decode("utf8")
data=json.loads(data)
command_code=data["command_code"]
if command_code==self.loginOk_client_code:
self.user_main(data["content"])
return True
elif command_code==self.login_verification_fails_code:
string_prompt="帐号或密码错误"
print(string_prompt,username,password)
return False
else:
print("login error...")
return False
"用户主界面,登录成功后进入,并初始化个人信息与在线用户列表"
def user_main(self,content):
self.username,self.name=content["user_information"] #"user_information":(username,name)
users_in_online_list=content["users"] #users_in_online[(username,name),(username,name)]
# print(content["user_information"])
# print(users_in_online_list)
for username,name in users_in_online_list:
self.dic_chat_users[username]=name
self.dic_chat_queue[username]=queue.Queue()
send_thread=threading.Thread(target=self.send_data) #发送消息线程
send_thread.start()
recv_thread=threading.Thread(target=self.recv_data) #接收消息线程
recv_thread.start()
def recv_data(self):
while True:
#print("recv messages...")
data = self.client.recv(self.buffsize).decode("utf-8")
data = json.loads(data)
command_code = data["command_code"]
content = data["content"]
if command_code == self.user_messages_code: # 如果是聊天消息
s_username = content["s_username"] # 消息来源,即是哪个用户发来的消息
messages = content["messages"]
q = self.dic_chat_queue[s_username]
q.put(messages) # 将消息放入对应用户的消息队列中
elif command_code == self.user_update_status_code: # 如果是 增加在线成员列表
username = content[0] # content=(username,name)
name = content[1]
# print("hava new user :", username, name)
self.dic_chat_queue[username] = queue.Queue()
self.dic_chat_users[username] = name
self.new_user.put((username, name))
else:
print("recv commande_code error...")
#将待发数据加入 发送数据队列中
def add_data(self,d_username,messages):
content={
"s_username": self.username,
"d_username": d_username,
"messages": messages
}
self.send_queue.put(content)
def send_data(self):
#print("send messages...")
while True:
if self.send_queue.qsize()>0:
content=self.send_queue.get()
data={
"command_code":self.user_messages_code,
"content":content
}
self.client.send(json.dumps(data).encode("utf8"))
def show_data(self):
pass
def Main():
tcpclient=TcpClient()
tcpclient.start_connect()
return tcpclient
if __name__ == '__main__':
Main()
用户的菜单界面 login_cmd.py 客户端以此为主运行文件
import client
import threading
import queue
class Login():
def __init__(self,tcpclient):
self.username=""
self.name=""
self.tcpclient=tcpclient
self.in_users_dic={} # id:(username,name)
self.in_users_list=[] # (1,(username,name))
self.message_number={} #(username,number)
def start_login(self):
username=input("帐号:")
password=input("密码:")
login_bool=self.tcpclient.login(username,password)
if login_bool: #如果登录成功,进入主界面
self.in_users_dic_init()
self.main_in_user()
#初始化在线人员
def in_users_dic_init(self):
self.name=self.tcpclient.name
self.username=self.tcpclient.username
self.id=1
for username,name in self.tcpclient.dic_chat_users.items():
id=str(self.id)
self.in_users_dic[id]=(username,name)
self.in_users_list.append((id,(username,name)))
self.id+=1
"检查各对象未读消息数"
def check_message_number(self):
for username,q_message in self.tcpclient.dic_chat_queue.items():
self.message_number[username]=q_message.qsize()
"监听是否有新用户上线"
def read_new_user(self):
while self.tcpclient.new_user.qsize()>0:
username,name=self.tcpclient.new_user.get()
#self.in_users_dic[username]=name
self.in_users_dic[str(self.id)]=(username,name)
self.in_users_list.append((str(self.id),(username,name)))
self.id+=1
def main_in_user(self): #进入在线列表
while True:
print("序号 帐号 名称 未读消息数")
self.check_message_number()
for id, information in self.in_users_list:
username=information[0]
name=information[1]
number=str(self.message_number[username])
print(str(id) + " " + username + " " + name+" "+number)
print("输入 exit 则退出")
print("输入 update 刷新界面")
string_cmd = input("输入序号:")
if string_cmd == "exit":
break
elif string_cmd=="update":
self.read_new_user()
else:
information=self.in_users_dic[string_cmd]
if information: #如果存在
self.chat_to_user(information[0],information[1])
else: #不然为None
print("并没有这个。。。")
def chat_to_user(self,d_username,d_name):
d_queue=self.tcpclient.dic_chat_queue[d_username]
self.read_thread=threading.Thread(target=self.chat_read_messages,args=(d_queue,d_name))
self.read_thread.start()
self.chat_send_messages(d_username)
def chat_read_messages(self,d_queue,d_name): #读消息
self.stop_thread=False
while not self.stop_thread:
if d_queue.qsize()>0:
message=d_queue.get()
print(d_name+":"+message)
def chat_send_messages(self,d_username): #发消息
print("没有消息直接回国键,退出聊天")
while True:
message=input("")
if not message: #如果消息为空,则退出
break
self.tcpclient.add_data(d_username,message)
self.stop_thread=True #退出读消息线程
def check_login(self,username,password):
check_bool = self.tcpclient.login(username, password)
if check_bool: # True则登录验证成功,进行登录
print("登录成功")
return True
else:
print("帐号或密码错误")
return False
class SignIn():
def __init__(self,tcpclient):
self.tcpclient=tcpclient
def siginin(self):
print("SignIn")
name=input("名称:")
username=input("帐号:")
password=input("密码:")
password2=input("密码:")
self.check_signin_func(name,username,password,password2)
def check_signin_func(self,name,username,password,password2):
if password != password2:
print("你可能不信,两次密码不一样")
else:
registered_bool = self.tcpclient.registered_user(username, password, name)
if registered_bool:
print("baby 注册成功")
else:
print("帐号已存在")
class Menu():
def __init__(self):
self.tcpclient=client.Main() # return tcpclient
self.login = Login(self.tcpclient)
self.signin = SignIn(self.tcpclient)
def main_menu(self):
while True:
print("login")
print("signin")
print("exit")
str_choice = input("input:")
if str_choice == "login":
self.login.start_login()
elif str_choice=="signin":
self.signin.siginin()
elif str_choice=="exit":
exit(0)
else:
print("输错了啊!!!")
if __name__ == '__main__':
menu=Menu()
menu.main_menu()
下面附运行效果截图
注册界面:
登录界面:
消息发送:
在线列表更新:
进入聊天:
第一次写博客,也是第一个写这样的程序,有很多不足的地方,也没有再去思考解决,还是人比较懒。。。
请大佬们多多指教。
附文件下载链接