用WebSocket解决即时通信应用中长连接问题

入门型解决方法

应用场景

很多个用户利用 WebSocketWebSocket Server 建立长连接, 另一边 Django Server 也会建立一个连接, 当某个用户向其他在线用户发送信息时, 这个 WebSocket Server 可以利用长连接跟用户通信, 告知用户有新消息, 让用户去 Django Server 拉取自己的新消息
上面这个就是大概的实现逻辑

重点是 服务器主动告知用户有新消息

WebSocket实现

服务端用 websockets 这个库, 有官方文档

大概基于以下思路:

  1. 一个用户(名)跟一个 websocket 连接对应, 保存在一个字典容器中
  2. 断开是删除这个 websocket 连接
  3. 服务器主动推消息时根据用户(名)来选择 websocket 连接

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import asyncio
import json
import websockets

USERS = {}

def rmsg(state, msg):
return json.dumps({'state':state, 'msg':msg})

async def connect(username, websocket):
print('connect')
USERS[username] = websocket
await USERS[username].send(rmsg('ok', 'connected'))
if '*_temp' in USERS:
del USERS['*_temp']

async def disconnect(username):
if username in USERS:
await USERS[username].send(rmsg('ok', 'disconnected'))
del USERS[username]
else:
print('no such user')

async def notify(username):
if username in USERS:
user = USERS[username]
await user.send(rmsg('ok', 'new'))
else:
print('no such user')

async def temp(websocket):
USERS['*_temp'] = websocket

async def handle(websocket, path):
await temp(websocket)
try:
await websocket.send(rmsg('ok', 'server online'))
async for message in websocket:
body = json.loads(message)

action = body['action']
data = body['data']
print(body, action, data)

if action == 'connect':
await connect(data, websocket)
elif action == 'disconnect':
await disconnect(data)
elif action == 'new':
await notify(data)
else:
print(rmsg('fail', 'no such action'))
except Exception as e:
print(e)
await websocket.send(rmsg('fail', 'handle error'))
finally:
print('disconnect')
print(USERS)
# await disconnect(data)


start_server = websockets.serve(handle, 'localhost', 6789)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

测试

初始让两个用户(test, server)连接:

server告知test收到新消息, 服务器应向test发送一条提示消息:

看到已经收到信息了, 成功!

目前存在的问题

  1. 未知的性能, 应该不会很高
  2. 保存连接的方式并不安全

小结

其实代码这么少, 但是查websocket各种资料花了很多时间, 都在看有没有更好的实现, 更好的工具这样, 之前也没有接触过所以入手挺慢的

消息表的实现

主要是查询用户收到的消息(根据seq)并返回对应的 ContentID, 并不复杂

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def messageTable(request, seq):
response = {'state':'fail', 'msg':'no msg'}

# 要在登录状态下
if 'login_id' not in request.session:
response['msg'] = 'no login'
return HttpResponse(json.dumps(response), content_type = 'application/json')

# 只接受 GET 请求
if request.method != 'GET':
response['msg'] = 'wrong method'
return HttpResponse(json.dumps(response), content_type = 'application/json')

# 已经登录, 所以拿取用户信息
t_username = request.session['login_id']

# 数据库操作
try:
t_msg = Msg.objects.filter(Username = t_username, Seq__gt = seq)
except Exception as e:
response['msg'] = 'db error'
return HttpResponse(json.dumps(response), content_type = 'application/json')
else:
if t_msg.count() <= 0:
response['msg'] = 'no data'
else:
temp = []
for index in range(t_msg.count()):
temp.append(model_to_dict(t_user[index]))

response = {'state':'ok', 'msg':'ok', "data":temp}

response['state'] = 'ok'
response['msg'] = 'get message successfully'

return HttpResponse(json.dumps(response), content_type = 'application/json')