计算机网络课程pj,基于socket的网络聊天室。
database.py
文件中的init_database()
函数(main
中其他要注释)python server.py
python client.py
客户端可并行目前已经实现的功能有:
客户端
编号 | 助记符 | 消息体格式 | 应用场景 |
---|---|---|---|
1 | LOGIN | str(LOGIN) + username + "\r\n" + pwd |
用户登陆 |
2 | REGISTER | str(REGISTER) + user + "\r\n" + pwd |
用户注册 |
3 | SEND_MESSAGE | header :str(SEND_MESSAGE) + "\r\n" + str(len(text)) , content:send_text |
多人群聊中用户发送消息 |
4 | GET_ALL_USERS | str(GET_ALL_USERS) |
登陆多人聊天室后获得当前所有在线用户 |
5 | GET_ALL_CHAT_HISTORY | str(GET_ALL_CHAT_HISTORY) + username |
登陆多人聊天室后获得所有历史聊天记录 |
6 | SEND_EMOJI | header :str(SEND_EMOJI) + "\r\n" + str(len(emoji)) ,content: emoji |
多人群聊中用户发送表情符号 |
7 | SEND_FILE | header : file_info = str(SEND_FILE) + receiver + "\r\n" + filename + "\r\n" +str(file_size) , content : file content |
多人群聊中用户发送文件 |
8 | GET_ALL_FILE_HISTORY | str(GET_ALL_FILE_HISTORY) |
登陆多人聊天室后获得所有发送文件的历史记录 |
9 | ADD_FRIEND | header:str(ADD_FRIEND) ,content:source_user+"\r\n"+target_user |
添加好友 |
99 | DOWNFILE | header:str(DOWNFILE) ,content:username+"\r\n"+filename |
用户下载任意多人群聊中已经上传的文件 |
98 | PRIVATE_INIT | header:str(PRIVATE_INIT) + username + "\r\n" + friend_name | 获得用户与friend_name 间的所有聊天记录 |
|
0 | SHOW_ALL_FRIENDS | header:str(SHOW_ALL_FRIENDS) ,content:username |
获得用户所有好友名 |
服务端
编号 | 助记符 | 应用场景 |
---|---|---|
101 | LOGIN_SUCCESS | 用户登录成功 |
102 | LOGIN_WRONG_PWD | 用户登录时密码错误 |
103 | LOGIN_ACCOUNT_NOT_EXIST | 用户登录时账号不存在 |
104 | LOGIN_DUPLICATE | 登陆时账号重复 |
105 | LOGIN_USERNAME | 用户登录时以广播方式通知所有其他用户 |
201 | REGISTER_SUCCESS | 注册新账号成功 |
202 | REGISTER_ERROR | 注册新账号失败 |
301 | SEND_MESSAGE_ALL | 多人聊天室中发送消息成功 |
302 | SEND_MESSAGE_ERROR | 多人聊天室中发送消息失败 |
303 | SEND_MESSAGE_PER | 好友间私聊时对方在线,成功发送消息 |
304 | SEND_MESSAGE_PER_STORE | 好友间私聊时对方不在线,消息缓存到双方的聊天记录中 |
404 | GET_USERS_ERROR | 获取所有在线用户的用户名失败 |
401 | GET_SUCCESS | 成功获取所有在线用户的用户名 |
501 | RET_HISTORY_SUCCESS | 成功获取多人聊天室中所有聊天记录 |
502 | RET_HISTORY_ERROR | 获取多人聊天室中所有聊天记录失败 |
601 | SEND_EMOJI_SUCCESS | 成功发送表情符号 |
602 | SEND_EMOJI_ERROR | 发送表情符号失败 |
701 | SEND_FILE_SUCCESS | 成功发送文件 |
702 | SEND_FILE_ERROR | 发送文件失败 |
801 | RET_ALL_FILES_SUCCESS | 成功获取所有发送的文件名 |
802 | RET_ALL_FILES_ERROR | 获取所有发送的文件名失败 |
800 | LOGOUT_INFO | 用户登出时以广播方式通知所有其他用户 |
901 | ADD_FRIEND_SUCCESS | 成功添加好友 |
902 | ALREADY_ADD_ERROR | 添加好友错误:与对方已经是好友关系 |
903 | USERNAME_NOT_EXIST | 添加好友错误:输入的用户名不存在 |
904 | ADD_FRIEND_ERROR | 添加好友错误:其他异常错误 |
905 | ADD_FRIEND_REMIND | 新好友提示:在线时收到其他用户的好友请求 |
290 | DOWNFILE_SUCCESS | 成功下载多人群聊中的文件 |
291 | DOWNFILE_ERROR | 下载多人群聊中的文件失败 |
292 | PRIVATE_INIT_SUCCESS | 成功获得用户与某一好友所有聊天记录 |
293 | PRIVATE_INIT_NONE | 用户与某一好友间聊天记录为空 |
294 | PRIVATE_INIT_ERROR | 获取用户与某一好友所有聊天记录发送错误 |
295 | SHOW_FRIENDS_SUCCESS | 成功获取用户的所有好友名 |
296 | SHOW_FRIENDS_ERROR | 获取用户的所有好友发生错误 |
297 | NO_FRIENDS | 用户暂无好友 |
在设计的时候不足之处
复用性考虑不足
用户登陆成功后,需要首先获得聊天记录信息缓存、所有文件、该用户所有好友、与好友间聊天记录, 这些是分别用多个服务端请求实现。其实可以简化为一个请求
用户信息 - 用户名、密码
CREATE TABLE USERINFO
(USERNAME TEXT PRIMARY KEY NOT NULL,
PASSWORD TEXT NOT NULL);
聊天信息 - ID、发送方、时间、内容
CREATE TABLE GROUP_CHAT_HISTORY
( ID TEXT PRIMARY KEY NOT NULL,
SOURCE_USER TEXT NOT NULL,
TIME DATETIME NOT NULL,
CONTENT TEXT NOT NULL,
FOREIGN KEY ("source_user") REFERENCES userinfo("username")
);
文件信息 - ID、发送方、时间、文件名、文件内容
CREATE TABLE GROUP_FILE_HISTORY
(ID TEXT PRIMARY KEY NOT NULL,
SOURCE_USER TEXT NOT NULL,
TIME DATETIME NOT NULL,
FILENAME TEXT NOT NULL,
FILECONTENT TEXT NOT NULL,
FOREIGN KEY ("source_user") REFERENCES userinfo("username")
);
好友关系 - 用户1,用户2
CREATE TABLE FRIENDS
( USERNAME1 TEXT NOT NULL,
USERNAME2 TEXT NOT NULL,
PRIMARY KEY (USERNAME1,USERNAME2),
FOREIGN KEY ("username1") REFERENCES userinfo("username")
FOREIGN KEY ("username2") REFERENCES userinfo("username")
)
好友间聊天记录 - ID、发送方、接收方、时间、内容
CREATE TABLE HISTORY_PRIVATE_CHAT
( "id" INTEGER not NULL,
"target_user" TEXT not NULL,
"source_user" TEXT not NULL,
"time" DATETIME not NULL,
"text" TEXT not NULL,
PRIMARY KEY("id"),
FOREIGN KEY ("target_user") REFERENCES "userinfo"("username")
FOREIGN KEY ("source_user") REFERENCES "userinfo"("username")
);
method.py
中包含以下socket方法
def send(socket,header,msg = b""):
byte_msg = bytes(header+"\n\n",encoding = 'utf-8') + msg
socket.sendall(byte_msg)
receive - 接收消息,根据\n\n
分割头部和消息,utf-8解码后返回
def receive(sock):
data = sock.recv(1024)
for i in range(len(data)-1):
if data[i] == 10 and data[i+1] == 10 : #"\n\n"分割头部和消息
header = data[:i].decode("utf-8")
rest = data[i+2:].decode("utf-8")
return header, rest
return data.decode("utf-8"),""
文件处理
监听多个连接
reads, writes, errors = select.select(connections,[],[])
for cur in reads:
if cur == sock: #new connection
···
else : # old connection
···
try :
data, rest = receive(cur)
except Exception as e:
release(cur) # release this connection
continue
handle(cur,data,rest) # handle client's request
handle函数处理 对消息头部进行分割,判断消息类型并进行相应处理
def handle(conn, msg, rest):
state = ""
try:
type = int(msg[0])
except Exception as e:
print("!! wrong request",e)
send(conn,str(WRONG_MESSAGE))
return
if type == REGISTER:
···
elif type == LOGIN:
···
···
用户端开启时进入登陆界面,第一次进入欢迎界面时,与服务端连接并建立会话.登陆成功后,开启监听服务端发来的数据。
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
sock.connect((HOST,PORT))
print(sock)
except:
print("Fail to connect (%s,%s)"%(HOST,PORT))
开启服务端数据监听
_thread.start_new_thread(listener,(sock,window))
其中listener
函数与handle
类似,针对不同协议编号进行处理
def listener(sock,root):
print("?? then ??")
listen_this = [sock]
while True:
reads, writes, errors = select.select(listen_this,[],[])
···
msg_type = data[:3]
if msg_type == str(LOGIN_USERNAME):
···
elif msg_type == str(SEND_MESSAGE_ALL):
···
······
└ client.py % 客户端
└ server.py % 服务端
└ constant.py % 协议编号
└ method.py % socket方法,包括send\receive等
└ events.py % 客户端部分事件函数
└ database.py % 数据库操作函数
运行 client.py
初始界面
点击注册
多人聊天室主界面
Listbox
形式显示添加好友
好友间私聊
文件下载
待完善的功能