前言
就是自己想用python做一个聊天室,然后看看socket库,websocket库,有点底层,然后也会用到协程的东西,不是很明白,一时间也不知道怎么写,然后就用了封装好的python-socketio来实现,做好了以后,跟同学用,被吐槽说滚轮不会自动下滑到底部,难用,于是我找天找地,在网上啥也没找到,经过自己一个多小时的摸索,终于找到了解决方法
想看看聊天室代码的话,可以到https://github.com/cgynb/a-flask-project/tree/guiChatroom看看。或者直接访问这个地址
http://81.70.180.118:12347/聊天呀.exe
是打包好的程序,服务端有在服务器上跑着了,可以直接使用的,但是低版本的windows运行可能会有点问题
需求
需要用python-socketio实现双向通信
由于对协程不是很熟悉,不大会使用,所以我就先放弃使用asyncio,具体使用官方文档写的还是相当详细的,我这里就只说说这个聊天室通信的实现吧
具体内容
导入模块
socketio就是通信使用的模块了;
eventlet.wsgi是一个网络库,可以看看这篇文章,这里就不赘述了,因为我目前也没有能力搞得很明白;
random是一个随机数模块,因为是匿名聊天室,所以使用random产生随机整数从昵称文件里选取一个;
socket模块在这里是用来产生主机ip的,因为在运行的时候eventlet.wsgi.server(eventlet.listen((‘0.0.0.0’, 5000)), app),我是没想到,我这样写,它真就。。。(20380) wsgi starting up on http://0.0.0.0:5000,这样的,至于为什么把ip写成’0.0.0.0’,可以看看这篇文章;
logging是处理日志的模块,我这里并没有什么高级应用,只是为了打印一下上面用socket模块给到的主机ip罢了。。。
import socketio
import eventlet.wsgi
import random
import socket
import logging
打印主机ip
hostname = socket.gethostname()
ip = socket.gethostbyname(hostname)
logging.basicConfig()
logging.warning(' 服务器ip: ' + ip)
这样他就会打印出主机ip了,socket和logging库的使用就到此为止了
得到随机昵称
通过这里可以看到name_list是储存当前房间里的人的昵称的,可以看到name_list中元素是以(name, sid)的形式存储起来的,sid是客户端连接服务端的时候服务端分配给的唯一id,那么这里为什么要把sid和name绑定在一起呢,在后面会有讲到,这里是为了统计房间在线用户。random库的使用也到此为止了
name_list = []
def create_username(sid):
with open('name.txt', 'r', encoding='utf-8') as f:
names = f.readlines()
names = list(map(lambda x: x.strip(), names))
name = names[random.randint(0, len(names)-1)]
while name in name_list:
name = names[random.randint(0, len(names) - 1)]
name_list.append((name, sid))
return name
服务器跑起来
可以看到,我甚至连官方文档的注释都没有删,因为真的是复制过来就能用。。。那翻译一下第一行代码就是创建一个socket.io服务器,第二行是把他包装秤WSGI应用,那么WSGI又是什么呢,可以看看这篇文章
socket.io是一个事件驱动的库,可以看到最底下有个一装饰器@sio.on(‘msg’),如果有一点js基础的话可以知道这是一个事件监听函数,就是触发了这个事件,就会触发执行函数,具体来看,就是触发了msg事件,函数内部的sio.emit()函数就会携带data向连接服务端的用户(没有使用房间,否则可以发送到指定房间)发送’msg’事件。
那么上面的两个connect还有disconnect函数呢,为什么没有用@sio.on(‘connect’)和@sio.on(‘disconnect’)呢?诶,这里就要看看官方文档了
The connect, connect_error and disconnect events have to be defined explicitly and are not invoked on a catch-all event handler.
chrome自带的翻译一下
和事件必须明确定义connect,connect_error并且disconnect不能在包罗万象的事件处理程序上调用。
所以,只能使用@sio.event这个装饰器,当发生了connect/disconnect事件时,会执行connect和disconnect函数,那我们具体看看这两个函数
在connect函数中,我们可以看到data这个字典中的username是使用上面讲的create_username生成的,action表示这个客户端操作,是登陆,而name_list在客户端那里就可以改变用户列表,增加新用户。发送data
在disconnect函数中,得到断开用户的sid,删除name_list中该用户信息,然后发送data到客户端,即可改变用户列表,删除退出用户
# create a Socket.IO server
sio = socketio.Server()
# wrap with a WSGI application
app = socketio.WSGIApp(sio)
@sio.event
def connect(sid, environ, auth):
data = {'username': create_username(sid), 'action': 'login', 'name_list': name_list}
sio.emit('msg', data)
print('connect ', sid)
@sio.event
def disconnect(sid):
for i, j in name_list:
if j == sid:
name_list.remove((i, j))
sio.emit('msg', {'action': 'logout', 'name_list': name_list})
print(name_list)
print('disconnect ', sid)
@sio.on('msg')
def msg(sid, data):
print(data)
sio.emit('msg', data)
if __name__ == '__main__':
eventlet.wsgi.server(eventlet.listen(('', 5000)), app)
不知道各位有没有发现,登录,退出,发送消息,用的都是’msg’事件,这样不会乱掉吗,其实是不会滴,因为可以看到,不同动作,我设置的action不同,这样客户端就可以辨识应该做什么(做几次判断action即可)。
总结
通信的内容就这么多了,内容不多,如有问题,请多多指教