The Django WebSocket server can receive data, but the data it sends cannot be received by the client.

My Django server is unable to deliver data to the client after communicating for a period of time, but the data sent by the client can still be received normally by the server. This issue only occurs on Linux, while it runs fine on Windows, and no errors are thrown.

Browser

wsl2 docker console

INFO 2025-02-26 16:31:49,663 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:49,685 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:49,720 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:49,724 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:49,727 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:49,750 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:49,750 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:49,766 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:49,786 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:49,807 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:49,819 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:49,851 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:49,851 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:49,878 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:49,879 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:49,884 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:49,920 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:49,954 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:50,009 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
INFO 2025-02-26 16:31:50,941 websockets 483 140037782731648 receive: {"type":"send","data":"\r"}
INFO 2025-02-26 16:31:50,974 custome_websocket 483 140037632177856 Sending: {"type": "receive", "status": true, "data": "\r\nroot@firefly:~# "}
DEBUG 2025-02-26 16:31:53,465 scheduler 483 140037683582656 frp devices online checker success
DEBUG 2025-02-26 16:32:03,442 scheduler 483 140037140248256 frp devices online checker success
INFO 2025-02-26 16:32:04,952 websockets 483 140037782731648 receive: {"type":"ping"}
INFO 2025-02-26 16:32:04,952 custome_websocket 483 140037782731648 Sending: {"type": "pong", "status": true, "data": null}

The websocket in the browser is still sending messages but is unable to receive data from the server

run command

daphne -b 0.0.0.0 -p 8105 server_backend.asgi:application

asgi.py

import os
import threading

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from django.core.asgi import get_asgi_application
from server_backend.logging import WebSocketLoggingMiddleware

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server_backend.settings')

django_asgi_app = get_asgi_application()

from server_backend import websocket_routing
from server_backend.scheduler import start

threading.Thread(target=start, daemon=True).start()

application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": WebSocketLoggingMiddleware(AuthMiddlewareStack(
        URLRouter(
            websocket_routing.websocket_urlpatterns
        )
    ), )
})

nginx.conf

worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  edm_pe;

		client_max_body_size 1024m;
		
		location /api/ {
			proxy_pass http://172.23.208.1:8105; 
			proxy_http_version 1.1;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection "upgrade";
			proxy_set_header Host $host:$server_port;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Forwarded-Proto $scheme;
			proxy_set_header Cookie $http_cookie;
		}

		location / {
			index index.html index.htm;
			root /usr/share/nginx/html/edm_pe_front/; 
			try_files $uri $uri/ /index.html;
		}
    }
	
	server {
	    listen       85;
        server_name  box_front;
		
		client_max_body_size 1024m;
	
		location /TU_ {
			proxy_pass http://172.23.208.1:6190;
			proxy_http_version 1.1;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection "upgrade";
			proxy_set_header Host $host:$server_port;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Forwarded-Proto $scheme;
			proxy_set_header Cookie $http_cookie;
		}

		location / {
			index index.html index.htm;
			root /usr/share/nginx/html/edm_pe_box_front/;
			try_files $uri $uri/ /index.html;
		}
	}
}

package

APScheduler==3.10.4
asgiref==3.8.1
attrs==24.2.0
autobahn==24.4.2
Automat==24.8.1
bcrypt==4.2.0
certifi==2024.8.30
cffi==1.17.1
channels==4.2.0
charset-normalizer==3.4.0
constantly==23.10.4
cryptography==43.0.3
daphne==4.1.2
Django==5.1.2
django-apscheduler==0.7.0
djangorestframework==3.15.2
djangorestframework-simplejwt==5.3.1
hyperlink==21.0.0
idna==3.10
incremental==24.7.2
psycopg2-binary
paramiko==3.5.0
psutil==6.1.0
pyasn1==0.6.1
pyasn1_modules==0.4.1
pycparser==2.22
PyJWT==2.9.0
PyNaCl==1.5.0
pyOpenSSL==24.2.1
pytz==2024.2
requests==2.32.3
scp==0.15.0
service-identity==24.1.0
six==1.16.0
sqlparse==0.5.1
Twisted==24.7.0
txaio==23.1.1
typing_extensions==4.12.2
tzdata==2024.2
tzlocal==5.2
urllib3==2.2.3
uuid==1.30
zope.interface==7.1.1

How long are you talking about here? 5 seconds? 5 minutes? 5 hours?

If I’m understanding your logs correctly here, it looks like Daphne is sending the packets, but the browser’s not receiving them?

Check for other errors in the browser’s console.
This could be an issue with the JavaScript code handling an unexpected condition in the connection.

Look at it from a network perspective - monitor the traffic at various locations along the way. (e.g. You can verify whether Daphne is still physically sending packets by monitoring traffic to/from port 8105 on that interface.)

Try a different browser, see if that creates a different set of symptoms.

Side note:

This is potentially dangerous in Twisted Python. See Using Threads in Twisted — Twisted 24.10.0.post0 documentation for full details.

My recommendation is to get rid of this. If that start thread is truly independent, run it as a separate process.

Since you’re describing different behavior based upon the OS being used to run Daphne, this actually seems like a likely possibility of being the problem, since Twisted does use different reactors.

Thank you for your reply.

Sorry for the delay in replying to you.
I finally solved the problem.

This paragraph of your reply reminds me,Was the key to my being able to fix this.

Looking through the Twisted documentation, I realized that using multiple runtimes to handle sending data would result in a data race.

asyncio_loop.py

def get_custom_event_loop():
    return asyncio.get_event_loop()


CUR_LOOP = get_custom_event_loop()


def run_loop():
    CUR_LOOP.run_forever()


threading.Thread(target=run_loop).start()


def async_run(coro):
    asyncio.run_coroutine_threadsafe(coro, CUR_LOOP)

I define a global runtime to send the results of some asynchronous threads in websocket, which may destabilize the data.

Now I’m using asyncio.get_running_loop() to get the runtime and pass it to the thread pool as an argument.

asyncio.get_running_loop()
...
def async_run(loop, func):
    asyncio.run_coroutine_threadsafe(func, loop)

Thank you very much.