本文实现的功能:为了只让⼀个设备登陆⽽设置的⼀种机制。
1. ⻓连接应⽤
-
⽹络协议: TCP ⻓连接协议
-
代表性案例: QQ、⽹络游戏、QQ⻜⻋、魔兽世界、传奇
-
特点: 实时性⾼
-
处理⽅式:
- 服务器和客户端可以主动与对端通信
- 服务器可以确定每⼀个客户端,当同⼀个账号进⾏第⼆次登陆的时候,服务器可以 主动断开与第⼀个客户端的连接
- 代码示例:
import socket all_connections = { # 'user_id': {'conn': 'xxx', 'addr': 'xxx'} } server_sock = socket.socket() server_sock.bind(('0.0.0.0', 12345)) server_sock.listen(2048) while True: cli_sock, cli_addr = server_sock.accept() # 接受客户端的连接 uid = cli_sock.recv(1024) # 接受客户端的数据 if uid in all_connections: # 关闭旧连接(将旧的客户端踢下线) cli_sock, cli_addr = all_connections.pop(uid) cli_sock.close() # 将新的连接加入到全局连接容器中 all_connections[uid] = { 'conn': cli_sock, 'addr': cli_addr }
2. 短连接应用
-
网络协议:
-
⽹络协议: HTTP / HTTPS / UDP
-
代表性案例: 今⽇头条、淘宝、等各种⽹站或各种APP
-
特点: 实时性不⾼,⽤户刷新时才能看到效果
-
处理流程:
-
登陆时:
1.根据 user_id 从缓存⾥找到旧的 SessionID
2.根据旧的 SessionID 找到之前的 Session 记录,并将其删除
3.将最新的 SessionID 记录到缓存
-
普通登录代码:
# 此时只要服务端将对应 user 的登录状态进行保存即可
if check_password(user, pwd):
session['user_id'] = user.id
然后我们来通过实验来验证一下,首先我们打开两个浏览器,分别对程序进行登录。
这里我分别用 safari 和 chrome 来进行登录,通过 network 面板截取到了下面两个 sessionid。
safari: 1efd2a8a-0f82-435d-ac8f-e038a7293205
chrome: 36506cf4-5b74-45e6-abc0-f9a2fc5f8549
然后我们来看下 redis 当中存的 session:
可以看到,两者的内容,完全一样。
这个时候我们如果将 redis 当中某一个的 sessionkey 给删除,此时对应的浏览器就会被踢下线。
3. 流程梳理
当一个 用户A 在一个浏览器登录进来之后,此时登录状态的 session_key 存储在了服务端的 redis 当中,而客户端的 session_key 存储在了 cookie 当中,两者的值相互一致。
当用户A 在另一个浏览器中打开网站,并进行登录,在正常的登录验证完成后,此时取出前端传递过来的 session_key。对此,我们首先将服务端对应 用户A 原来的 session_id 从 redis 里面删掉,然后按原有的方式进行登录状态的设置,最后将传递过来新的 session_key 放入缓存当中。
4.代码整合
# 简单的流程:
...
if check_password(user, pwd):
kick_out(user.id)
session['user_id'] = user.id
checkin(user.id, session.sid)
...
对应的函数代码:
# CHECKIN_K = 'CHECKIN-%s'
def checkin(uid, session_id):
"""缓存中记录某人的 session_id"""
key = CHECKIN_K % uid
rds.set(key, session_id, 3600)
def kick_out(uid):
key = CHECKIN_K % uid
session_id = rds.get(key, '')
full_session_key = SESSION_KEY_PREFIX + session_id
rds.delete(full_session_key)