做项目的时候涉及到即时通信了,所以在 gladuo 的建议下看了一篇教程,【转】Tornado 搭建基于 WebSocket 的聊天服务,经过一番修改调试实现了功能,在此总结分享一下。
按思路来聊:
类似微信,点击用户可以进入一对一聊天页面;另有聊天框列表包含所有存在聊天记录的一对一聊天框,点击进入聊天页面。
【数据结构】
因为双方都有聊天记录,所以每一个聊天实际上得储存两份,设计的数据结构如下:
A :user_a = {“id”:1,”name”:”A”}
B :user_b = {“id”:2,”name”:”B”}
A的聊天记录:chat_a = { “id”:1, “user”:1, “who”:2, “name”:”B”, “new”:0, msg:[]}
B的聊天记录:chat_b = { “id”:2, “user”:2, “who”:1, “name”:”A”, “new”:0, msg:[]}
msg
实际上是个list
,结构如下:msg = { “user”:发送者id, “name”:发送者name, “date”:发送时间, “content”:消息内容 }
【业务逻辑】
当A
点击好友列表中B
的名字–>进入聊天框(根据双方id
通过字段user
、who
找到对应chat_a
,chat = coll.find_one({“user”:user_a[‘id’], “who”:user_b[‘id’]});
如果该chat
不存在,则利用双方id
创建chat_a
)
发送消息(更新chat_a
和chat_b
,如果chat_b
不存在则创建chat_b
;如果chat_b
不在线则更新chat_b[‘new’] = 1
)
A
删除聊天框(删除chat_a
)
【记录客户端连接】
由于是多个一对一聊天,所以不能直接用教程里的set
来记录连接。
最后的决定是用一个 dict
,用双方用户id
拼接的字符串作为key
,用list
存客户端连接。
...SocketHandler(...):
chats = dict()
...
def on_open(self):
...
#通过双方id来生成一个独一无二的字符串
min = user_a['id']
max = user_b['id']
if min >max:
max = user_a['id']
min = user_b['id']
key = str(user_a['id'])+"_"+str(user_b['id'])
#判断当前会话是否存在,存在则添加当前用户
if key in chats:
SocketHandler.chats[key].append(self)
#不存在则创建会话,并将当前用户添加进去
else
SocketHandler.chats[key] = [self]
【发送消息】
从客户端调用send
函数,在服务端on_message
函数中接受参数后更新双方聊天记录。之后调用send_to_all(key, message)
来更新聊天窗口。
【发通知/更新聊天窗口】
更新数据库里的聊天记录后还要在聊天窗口更新html
,所以需要通知该会话的连接者。
根据我们记录连接者的方式,对应的通知函数如下:
def send_to_all(key,message):
for user in SocketHandler.chats[key]:
user.write_message(json.dumps(message))
【关闭连接】
根据我们记录连接者的方式,对应的关闭函数如下:
def on_close(self):
...
#用on_open函数中的方法构造key
if key in SocketHandler.chats:
SocketHandler.chats[key].remove(self)#删除当前连接
if len(SocketHandler.chats[key]) == 0:
del SocketHandler.chats[key]#当会话无连接者则删除会话
经过上面的改造,就实现多个一对一聊天功能
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。