websockets.exceptions.ConnectionClosedOK错误解决方案

Python WebSocket数据处理与多线程解决方案
本文档描述了在Python中使用websockets库处理WebSocket数据时遇到的ConnectionClosedOK错误,该错误通常是由于服务器发送数据速率超过本地处理速度导致的。解决方案是采用多线程和消息队列技术,将接收的数据存入队列,然后由独立的线程进行处理,避免了快慢不匹配的问题。代码示例中展示了如何改进数据处理流程,确保数据的稳定接收和存储。

websockets.exceptions.ConnectionClosedOK错误原因及解决方案

针对python中websockets出现 sent 1000 (OK); then received 1000 (OK)错误的解决方案;

Traceback (most recent call last):
  File "d:\test\websocket\Tclients.py", line 42, in startup
    recv_text = await websocket.recv()
  File "C:\Program Files\Python39\lib\site-packages\websockets\legacy\protocol.py", line 552, in recv
    await self.ensure_open()
  File "C:\Program Files\Python39\lib\site-packages\websockets\legacy\protocol.py", line 920, in ensure_open  
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: sent 1000 (OK); then received 1000 (OK)

错误产生的原因

在python挖掘websocket数据时,比较完善的包文件是websockets文件,只需要

pip install websockets
然后在正文中引用即可完成
import websockets

完整使用代码一般如下:

import asyncio
import websockets
import logging
from datetime import datetime
import json
import traceback
import pymysql     # For find ipadress for ping
#当前存在的主要问题是各个线程、协程存在冲突,导致最后死锁。
def functionToDB(mes):
    dbconnect = pymysql.connect(host="localhost", user="root", password="root", database="****",charset="utf8")
    updateSQL= ("INSERT INTO keyvalue (`keys`,`starts`,`types`,`lens`) "
        "VALUES(%(keys)s,%(starts)s,%(types)s,%(lens)s)")
    start=mes.split(':')[0]
    start=start[2:].replace('"','')
    dic_json = json.loads(mes)
    cursor = dbconnect.cursor()
    if isinstance(dic_json,dict): #判断是否是字典类型isinstance 返回True false
        for key in dic_json:
            add_mes = {
                'keys': key,
                'starts': start,
                'types': str(type(dic_json[key])),
                'lens': len(str(dic_json[key])),
            }   
            try:
                cursor.execute(updateSQL,add_mes)
                dbconnect.commit()
            except Exception as e:
                print("更新失败!")
                traceback.print_exc()
                print(e.with_traceback)
sendstr = '{"SESSION_ID":"-1101623267","SESSION_NAME":"*****","HOST_IP":"*.*.*.*","USER_ID":"********","USER_NAME":"****","FILTER_ID":[*********],"msg_type":1}'
#存在问题在第1000个数据包时会报错:received 1000 (OK); then sent 1000 (OK)
async def startup(uri):
    async with websockets.connect(uri) as websocket:
        await websocket.send(sendstr)
        logging.info("< Hello world!")
        while True:
            try:
                recv_text = await websocket.recv()
                print(">:",recv_text)
                #ping pong 流程  根据服务器不同,流程不同
                if recv_text[0:13] =='{"msg":"ping"':
                    print("<:",recv_text)
                    await websocket.send(recv_text)
                functionToDB(recv_text)
            except Exception as e:
                traceback.print_exc()
                
if __name__ == '__main__':
        #websocket的地址     
        remote = 'ws://*.*.*.*:80/webSocket'
        loop=asyncio.get_event_loop()
        loop.run_until_complete(startup(remote))
    except KeyboardInterrupt as exc:
        logging.info('Quit.')

以上程序中存在的问题就是websocket.recv()中数据流量及速度以服务器为主,如果服务器发送信息流量速度快于insertDB()处理数据的速度,就会出现以上错误。因错误内容显示过于模糊,很多人无法一次性定位问题原因,导致无法使用。

解决办法

运用多线程及消息队列技术,将websocket.recv()接收的数据存入消息队列,将处理数据的比较慢的function从消息队列中获得数据,避免了快等慢的问题,可以非常完美的解决该问题。

import asyncio
import logging
from datetime import datetime
from aiowebsocket.converses import AioWebSocket
import pymysql     # For find ipadress for ping
import json
import traceback
#这个处理程序存在问题:将过长的frame没有进行拼接直接返回。
header="""
GET /auth/getcurrentuser HTTP/1.1
Host: *.*.*.*:8080
Connection: keep-alive
Accept: application/json
combine-token: 50b78947-9b7f-4d94-a242-e1e87dd89b66,de4751ef-ab3d-4127-8a15-1c228405d99d
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Content-Type: application/json;charset=UTF-8
Referer: http://*.*.*.*:8080/ams/view/ams_alarm_custom_view/54
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: ''; ''=''
"""
#这个函数是插入key的函数不太完善
def insertSQL(mes,dbconnect):
    addSQL= ("INSERT ignore INTO kayvalues (`keys`,`starts`,`cnts`) VALUES(%(keys)s,%(starts)s,0)")
    start=mes.decode('utf8').split(':')[0]
    start=start[2:].replace('"','')
    dic_json = json.loads(mes)
    cursor = dbconnect.cursor()
    if isinstance(dic_json,dict): #判断是否是字典类型isinstance 返回True false
        for key in dic_json:
            add_mes = {
                'keys': key,
                'starts': start,
            }   
            try:
                cursor.execute(addSQL,add_mes)
                dbconnect.commit()
            except Exception as e:
                print("插入失败!")
                traceback.print_exc()
                print(e.with_traceback)
def insertDB(mes,dbconnect):
    updateSQL= ("INSERT INTO keyvalue (`keys`,`starts`,`types`,`lens`) "
        "VALUES(%(keys)s,%(starts)s,%(types)s,%(lens)s)")
    start=mes.decode('utf8').split(':')[0]
    start=start[2:].replace('"','')
    dic_json = json.loads(mes)
    cursor = dbconnect.cursor()
    if isinstance(dic_json,dict): #判断是否是字典类型isinstance 返回True false
        for key in dic_json:
            add_mes = {
                'keys': key,
                'starts': start,
                'types': str(type(dic_json[key])),
                'lens': len(str(dic_json[key])),
            }   
            try:
                cursor.execute(updateSQL,add_mes)
                dbconnect.commit()
            except Exception as e:
                print("更新失败!")
                traceback.print_exc()
                print(e.with_traceback)

def updateDB(mes,dbconnect):
    updateSQL= ("UPDATE keyvalue SET counts=counts+1 "
        "WHERE `keys`=%(keys)s AND `starts`=%(starts)s")
    start=mes.decode('utf8').split(':')[0]
    start=start[2:].replace('"','')
    dic_json = json.loads(mes)
    cursor = dbconnect.cursor()
    if isinstance(dic_json,dict): #判断是否是字典类型isinstance 返回True false
        for key in dic_json:
            add_mes = {
                'keys': key,
                'starts': start,
            }   
            try:
                cursor.execute(updateSQL,add_mes)
                dbconnect.commit()
            except Exception as e:
                print("更新失败!")
                traceback.print_exc()
                print(e.with_traceback)
        
async def startup(uri,dbconnect):
    async with AioWebSocket(uri) as aws:
        converse = aws.manipulator
        # 客户端给服务端发送消息
        sendstr = '{'+'"SESSION_ID":"-593024204","SESSION_NAME":"全部告警2021-10-03 20:08:39.018 9.234453268703845","HOST_IP":"*.*.*.*","USER_ID":"263567140083","USER_NAME":"姜虎兵","FILTER_ID":[-1895577500],"MODEULE_TYPE":1,"msg_type":1,"START_TIME":"2021-09-03 20:08:33","END_TIME":"{time}"'.format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))+'}'

        await converse.send(sendstr)
        #通过bool类型避免重复发送心跳数据包
        sendMessgae=True
        longmes=bytes()
        bend ='}'.encode('utf8')
        bstar ='{'.encode('utf8')
        while True:
            mes =await converse.receive()
            try:
                strmes=mes.decode('utf8').strip()
            except Exception as e :
                print(e)
                continue
            lenmes=len(strmes)
            #先丢弃不正常的包
            if lenmes < 10:
                print("丢弃长度过短的包,长度为:"+str(lenmes))
                await converse.send(sendstr)
                continue
            time=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            #对数据包进行判断
            #如果json包完整,进行如下处理:
            if strmes.startswith("{") and strmes.endswith("}"):
                insertDB(mes,dbconnect)
            elif strmes.startswith("{"):
                longmes=mes
            elif strmes.endswith("}"):
                longmes=longmes+mes
                insertDB(longmes,dbconnect)
                longmes=bytes()
            else:
                try:
                    print("frame不正常\n"
                        +str(lenmes)+"\n"
                        +mes.decode('utf8'))
                except Exception as e:
                    print(e)
                continue
            #循环发送心跳数据包
            if str(time).endswith("59") or str(time).endswith("29"):
                if sendMessgae:
                    message='{'+f'"msg":"ping","msg_type":"2","update_time":"{time}'+'"}'
                    print(message)
                    await converse.send(sendstr)
                    sendMessgae=False
            else:
                sendMessgae=True            
            #jsonmes=json.dumps(mes.decode('utf-8'))
            '''     
            
            add_mes = {
                'gdate': time,
                'message': jsonmes,
            }    

            try:
                cursor.execute(addSQL, add_mes)
                dbconnect.commit()
            except Exception as e:
                print("插入失败!")
                print(e.with_traceback)

'''   

if __name__ == '__main__':
    remote = 'ws://*.*.*.*:8080/webSocketAlarm'
    dbconnect = pymysql.connect(host="localhost", user="root", password="****", database="***",charset="utf8")
    try:
        asyncio.get_event_loop().run_until_complete(startup(remote,dbconnect))
    except KeyboardInterrupt as exc:
        logging.info('Quit.')

ps: 主要问题的原因是websockets推送数据速度大于本地客户端及MySQL存储数据的速度。
以上代码通过线程锁的方式进行解决。
下面文章是线程问题,可以参考一下

我们将在飞牛NAS上部署一个Docker容器,该容器运行一个WebSocket服务器,要求:1.支持密钥验证2.接收数据并存储到SQLite数据库3.提供API接口用于查询实时数据、历史数据4.支持根据指定Tag生成历史曲线图##解决方案###1.项目结构创建项目目录ws-server,结构如下:ws-server/├──Dockerfile├──requirements.txt├──app/│├──main.py#主程序,包含WebSocket和API服务│├──auth.py#密钥验证│├──database.py#数据库操作│└──plotter.py#绘图功能###2.代码实现####2.1密钥验证(auth.py)pythonfromfastapiimportHTTPException,HeaderSECRET_KEY="your_secure_key_123"#实际部署时使用环境变量asyncdefverify_token(authorization:str=Header(None)):ifauthorizationisNoneorauthorization!=f"Bearer{SECRET_KEY}":raiseHTTPException(status_code=403,detail="Invalidauthenticationtoken")####2.2数据库操作(database.py)pythonimportsqlite3importosDB_PATH=os.getenv('DB_PATH','sensor_data.db')definit_db():conn=sqlite3.connect(DB_PATH)c=conn.cursor()c.execute('''CREATETABLEIFNOTEXISTSsensor_data(idINTEGERPRIMARYKEYAUTOINCREMENT,timestampDATETIMEDEFAULTCURRENT_TIMESTAMP,tag_idINTEGER,tag_nameTEXT,valueREAL,data_timeTEXT)''')conn.commit()conn.close()definsert_data(tag_id:int,tag_name:str,value:float,data_time:str):conn=sqlite3.connect(DB_PATH)c=conn.cursor()c.execute("INSERTINTOsensor_data(tag_id,tag_name,value,data_time)VALUES(?,?,?,?)",(tag_id,tag_name,value,data_time))conn.commit()conn.close()defget_latest_data(tag_id:int,limit:int=1):conn=sqlite3.connect(DB_PATH)c=conn.cursor()c.execute("SELECT*FROMsensor_dataWHEREtag_id=?ORDERBYtimestampDESCLIMIT?",(tag_id,limit))result=c.fetchall()conn.close()returnresultdefget_historical_data(tag_id:int,hours:int=24):conn=sqlite3.connect(DB_PATH)c=conn.cursor()#获取最近hours小时的数据c.execute("SELECT*FROMsensor_dataWHEREtag_id=?ANDtimestamp>=datetime('now',?)ORDERBYtimestamp",(tag_id,f'-{hours}hours'))result=c.fetchall()conn.close()returnresult####2.3绘图功能(plotter.py)pythonimportmatplotlib.pyplotaspltimportnumpyasnpfromdatetimeimportdatetimeimportosfrom.databaseimportget_historical_dataPLOT_DIR='plots'defgenerate_plot(tag_id:int,hours:int=24):#确保绘图目录存在ifnotos.path.exists(PLOT_DIR):os.makedirs(PLOT_DIR)#获取数据data=get_historical_data(tag_id,hours)ifnotdata:returnNonetimestamps=[row[1]forrowindata]#时间戳values=[row[4]forrowindata]#值#将时间字符串转换为datetime对象timestamps=[datetime.strptime(ts,'%Y-%m-%d%H:%M:%S')fortsintimestamps]plt.figure(figsize=(10,6))plt.plot(timestamps,values,'b-')plt.title(f'TagID:{tag_id}-Last{hours}Hours')plt.xlabel('Time')plt.ylabel('Value')plt.grid(True)plt.xticks(rotation=45)plt.tight_layout()#保存图像plot_path=os.path.join(PLOT_DIR,f'tag_{tag_id}_{hours}h.png')plt.savefig(plot_path)plt.close()returnplot_path####2.4主程序(main.py)pythonimportasyncioimportwebsocketsimportjsonimportosfromfastapiimportFastAPI,APIRouter,Depends,HTTPExceptionfromfastapi.responsesimportFileResponsefrom.importauth,database,plotterapp=FastAPI()api_router=APIRouter()#初始化数据库database.init_db()#WebSocket服务器asyncdefsensor_server(websocket,path):try:#第一步:接收密钥进行验证token=awaitwebsocket.recv()iftoken!=auth.SECRET_KEY:awaitwebsocket.close(code=1008,reason="Authenticationfailed")return#第二步:接收数据asyncformessageinwebsocket:try:data=json.loads(message)#数据格式:{"TagId":1,"TagName":"1#注浆A液压力","Value":0.76,"DataTime":"2025-6-182:28"}database.insert_data(tag_id=data['TagId'],tag_name=data['TagName'],value=data['Value'],data_time=data['DataTime'])print(f"Inserteddatafortag{data['TagId']}")exceptExceptionase:print(f"Errorprocessingmessage:{e}")exceptwebsockets.exceptions.ConnectionClosedase:print(f"Clientdisconnected:{e}")#API路由@api_router.get("/data/latest/{tag_id}")asyncdefget_latest_data(tag_id:int,_:str=Depends(auth.verify_token)):data=database.get_latest_data(tag_id)ifnotdata:raiseHTTPException(status_code=404,detail="Datanotfound")returndata[0]#返回最新一条@api_router.get("/data/history/{tag_id}")asyncdefget_history_data(tag_id:int,hours:int=24,_:str=Depends(auth.verify_token)):data=database.get_historical_data(tag_id,hours)ifnotdata:raiseHTTPException(status_code=404,detail="Datanotfound")returndata@api_router.get("/plot/{tag_id}")asyncdefget_plot(tag_id:int,hours:int=24,_:str=Depends(auth.verify_token)):plot_path=plotter.generate_plot(tag_id,hours)ifplot_pathisNone:raiseHTTPException(status_code=404,detail="Nodataavailableforplotting")returnFileResponse(plot_path)app.include_router(api_router,prefix="/api")#启动WebSocket服务器和FastAPI服务asyncdefstart_servers():#启动WebSocket服务器ws_server=awaitwebsockets.serve(sensor_server,"0.0.0.0",8765)print("WebSocketserverstartedonport8765")#注意:FastAPI服务需要单独启动,这里我们使用uvicorn,在Dockerfile中启动if__name__=="__main__":asyncio.run(start_servers())###3.Docker配置####3.1Dockerfile````dockerfileFROMpython:3.9-slimWORKDIR/app#安装依赖RUNapt-getupdate&&apt-getinstall-ylibsqlite3-devCOPYrequirements.txt.RUNpipinstall--no-cache-dir-rrequirements.txtCOPY..#设置环境变量(密钥通过环境变量传入)ENVSECRET_KEY=your_actual_key_here#启动命令:同时启动WebSocket服务器和FastAPI服务CMD["sh","-c","uvicornapp.main:app--host0.0.0.0--port8000&python-mapp.main"]```####3.2requirements.txt````fastapiuvicornwebsocketssqlalchemymatplotlibpython-multipart###4.在飞牛NAS上部署1.**构建镜像**:将整个`ws-server`目录上传到飞牛NAS,然后进入该目录执行:bashdockerbuild-tws-sensor-server.2.**运行容器**:bashdockerrun-d--namesensor-server-p8765:8765#WebSocket端口-p8000:8000#API端口-v/path/on/nas/data:/app/data#挂载数据库和绘图目录-eSECRET_KEY=“your_actual_key_here”\ws-sensor-server3.**飞牛NAS图形界面操作**:-打开飞牛NAS的Docker管理界面-选择“镜像”并导入构建好的`ws-sensor-server`镜像-创建容器,配置端口映射:8765和8000-设置环境变量:`SECRET_KEY=your_actual_key_here`-挂载数据卷:将NAS上的目录挂载到容器内的`/app/data`-启动容器###5.功能测试####5.1WebSocket数据发送pythonimportasyncioimportwebsocketsimportjsonasyncdefsend_data():asyncwithwebsockets.connect(‘ws://your_nas_ip:8765’)asws:#先发送密钥awaitws.send(“your_actual_key_here”)#发送数据data={“TagId”:1,“TagName”:“1#注浆A液压力”,“Value”:0.76,“DataTime”:“2025-06-1802:28:00”}awaitws.send(json.dumps(data))print(“Datasent”)asyncio.run(send_data())```####5.2API接口调用-获取最新数据:`GEThttp://your_nas_ip:8000/api/data/latest/1`需在Header中添加:`Authorization:Beareryour_actual_key_here`-获取历史数据:`GEThttp://your_nas_ip:8000/api/data/history/1?hours=24`-获取曲线图:`GEThttp://your_nas_ip:8000/api/plot/1?hours=72`###6.注意事项1.密钥管理:在实际部署中,应通过环境变量设置密钥,避免硬编码。2.数据持久化:通过挂载卷将数据库文件和绘图目录保存在NAS上,避免容器重启数据丢失。3.性能优化:对于高频数据,考虑使用批量插入和数据库索引优化。4.安全性:建议在生产环境中使用HTTPS加密API和WebSocket通信(可通过Nginx反向代理实现)。§§相关问题§§1.如何为SQLite数据库添加索引以提高查询性能?2.如何在Docker容器中安全地管理密钥?3.如何通过Nginx为WebSocket和API服务添加HTTPS加密?4.如何扩展此系统以支持多客户端并发连接?5.如何实现数据存储的分区或分表以优化大量历史数据的查询?
06-20
### 关于 `websockets.exceptions.ConnectionClosed` 异常的原因 `websockets.exceptions.ConnectionClosed` 是 Python 的 `websockets` 库中定义的一个异常类,用于表示 WebSocket 连接被意外关闭的情况。当发生此异常时,通常意味着客户端或服务端之一主动终止了连接,或者由于网络问题导致连接中断[^1]。 具体来说,该异常可能由以下几种情况引起: - **连接被强制关闭**:如果一方决定提前结束会话而未遵循正常的握手协议,则另一方可能会收到此类异常。 - **网络不稳定**:在网络条件较差的情况下(例如高延迟、丢包率高等),可能导致数据传输失败从而触发异常[^5]。 - **超时设置不当**:长时间无活动也可能引发服务器侧断开链接的行为。 针对上述提到的 code=1006 特殊状况,它表明连接是以一种不正常的方式关闭,并且没有提供具体的理由说明为何如此操作。 ### 解决方案 为了妥善应对这种类型的错误,可以从以下几个方面入手解决问题: #### 一、增强代码健壮性 通过引入 try-except 结构能够有效捕捉到这类运行期间抛出的异常事件,并采取相应的补救措施继续执行程序逻辑而不至于崩溃退出。下面给出了一段示范性的实现方式用来展示如何优雅地处理这种情况下的异常情形[^2]: ```python import asyncio import websockets async def communicate(): uri = "ws://example.com" async with websockets.connect(uri) as websocket: try: await websocket.send("Hello") response = await websocket.recv() print(f"Received: {response}") except websockets.exceptions.ConnectionClosed as e: print(f"Connection closed unexpectedly: {e.code}, {e.reason}") ``` 在此基础上还可以进一步扩展功能比如尝试重新建立新的连接直至成功为止等等策略应用其中去提升系统的可用性和用户体验满意度水平。 #### 二、选用合适的第三方库版本以及正确安装依赖项 确保所使用的 python-websocket 库是最新的稳定发行版可以帮助规避一些已知缺陷带来的麻烦;另外按照官方文档指示完成必要的准备工作也是必不可少的一环——即利用 pip 工具轻松获取所需软件组件并将其导入项目当中以便即时调用相关特性支持业务流程运转顺畅高效[^3]. #### 三、构建完整的 WebSockets 实现案例 这里还附上了一个更为全面的实际应用场景例子作为参考学习材料之用,展示了怎样创建一个简易的服务端来周期性推送 OBD 设备采集的数据给远端客户终端消费使用的同时也兼顾到了可能出现的各种边界条件考量因素影响下仍能保持良好表现状态的能力要求标准达成目标效果最佳实践指南建议如下所示[^4]: ```python import asyncio import websockets import json import obd async def send_data(websocket, path): connection = obd.OBD() while True: try: data = {"speed": connection.query(obd.commands.SPEED).value.magnitude} await websocket.send(json.dumps(data)) await asyncio.sleep(1) except websockets.exceptions.ConnectionClosed: break start_server = websockets.serve(send_data, "localhost", 8765) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever() ``` 以上就是对于 `websockets.exceptions.ConnectionClosed` 异常的一些见解分析及其对应的处置办法介绍完毕啦!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值