Python WebSocket服务器在AWS Elastic Beanstalk上无法运行求助
我是编程和AWS新手,表述不当请见谅。正在开发Kotlin应用,后端Python服务部署在Elastic Beanstalk,除WebSocket数据传输外其他功能正常。
测试场景:点击应用按钮向domain/send_test发送POST请求,后端应通过WebSocket向前端推送"TEST RECEIVED!!!!!"。目前能看到后端send_test函数被调用,但前端收不到消息,后端日志显示已发送数据包。
实际需求:后端某函数需立即返回值,同时开启线程调用外部API,待API返回结果后通过WebSocket推送给前端。
环境为Single Instance (Free Tier),无负载均衡器,尝试配置WebSocket相关设置但旧教程不匹配新版界面,已卡一周。
后端简化代码
# Import... app = application = Flask(__name__) ping_timeout = 120 ping_interval = 60 socketio = SocketIO(application, cors_allowed_origins="*", engineio_logger=True, allowEIO3=True, ping_timeout=ping_timeout, ping_interval=ping_interval) logging.basicConfig(level=logging.DEBUG) @application.route('/send_test', methods=['POST']) def send_test(): socketio.emit('test', {'response': 'TEST RECEIVED!!!!!'}) return "Success", 200
前端简化代码
class MainActivity : AppCompatActivity(), TextToSpeech.OnInitListener, NavigationView.OnNavigationItemSelectedListener { private lateinit var socket: Socket override fun onCreate(savedInstanceState: Bundle?) { connectWebSocket() } private fun connectWebSocket() { Log.d("MainActivity", "connectwebsocket called") val socketUrl = "ws://travel32.us-east-1.elasticbeanstalk.com" val job = Job() val coroutineScope = CoroutineScope(Dispatchers.IO + job) IO.setDefaultOkHttpWebSocketFactory(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)).build()) IO.setDefaultOkHttpCallFactory(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)).build()) coroutineScope.launch { socket = IO.socket(socketUrl, IO.Options().apply { reconnection = true reconnectionAttempts = Integer.MAX_VALUE reconnectionDelay = 1000 // 1 second reconnectionDelayMax = 5000 // 5 seconds }) socket.connect() Log.d("MainActivity", "WebSocket connected") socket.on("test", testsocket) socket.on(Socket.EVENT_CONNECT, onConnect) socket.on(Socket.EVENT_DISCONNECT, onDisconnect) socket.on(Socket.EVENT_CONNECT_ERROR, onError) } } private val testsocket = Emitter.Listener { args -> runOnUiThread { val testresponse = args[0] as JSONObject Log.d("MainActivity", "Received TEST response: $testresponse") } } }
后端web.stdout.log日志
May 14 14:19:27 ip-172-31-13-189 web[2211]: jxIFnNaeB7crO36TAAAC: Sending packet MESSAGE data 2["test",{"response":"TEST RECEIVED!!!!!"}] May 14 14:19:27 ip-172-31-13-189 web[2211]: INFO:engineio.server:jxIFnNaeB7crO36TAAAC: Sending packet MESSAGE data 2["test",{"response":"TEST RECEIVED!!!!!"}] May 14 14:19:39 ip-172-31-13-189 web[2211]: jxIFnNaeB7crO36TAAAC: Sending packet MESSAGE data 2["test",{"response":"TEST RECEIVED!!!!!"}] May 14 14:19:39 ip-172-31-13-189 web[2211]: INFO:engineio.server:jxIFnNaeB7crO36TAAAC: Sending packet MESSAGE data 2["test",{"response":"TEST RECEIVED!!!!!"}] May 14 14:20:01 ip-172-31-13-189 web[2211]: jxIFnNaeB7crO36TAAAC: Sending packet PING data None May 14 14:20:01 ip-172-31-13-189 web[2211]: INFO:engineio.server:jxIFnNaeB7crO36TAAAC: Sending packet PING data None May 14 14:20:02 ip-172-31-13-189 web[2211]: luoag_u_sK4lq15kAAAE: Sending packet OPEN data {'sid': 'luoag_u_sK4lq15kAAAE', 'upgrades': ['websocket'], 'pingTimeout': 120000, 'pingInterval': 60000} May 14 14:20:02 ip-172-31-13-189 web[2211]: INFO:engineio.server:luoag_u_sK4lq15kAAAE: Sending packet OPEN data {'sid': 'luoag_u_sK4lq15kAAAE', 'upgrades': ['websocket'], 'pingTimeout': 120000, 'pingInterval': 60000} May 14 14:20:02 ip-172-31-13-189 web[2211]: luoag_u_sK4lq15kAAAE: Received packet MESSAGE data 0 May 14 14:20:02 ip-172-31-13-189 web[2211]: INFO:engineio.server:luoag_u_sK4lq15kAAAE: Received packet MESSAGE data 0 May 14 14:20:02 ip-172-31-13-189 web[2211]: luoag_u_sK4lq15kAAAE: Sending packet MESSAGE data 0{"sid":"1FGFeiKmah61v5UEAAAF"} May 14 14:20:02 ip-172-31-13-189 web[2211]: INFO:engineio.server:luoag_u_sK4lq15kAAAE: Sending packet MESSAGE data 0{"sid":"1FGFeiKmah61v5UEAAAF"} May 14 14:20:02 ip-172-31-13-189 web[2211]: luoag_u_sK4lq15kAAAE: Received request to upgrade to websocket May 14 14:20:02 ip-172-31-13-189 web[2211]: INFO:engineio.server:luoag_u_sK4lq15kAAAE: Received request to upgrade to websocket May 14 14:20:03 ip-172-31-13-189 web[2211]: luoag_u_sK4lq15kAAAE: Upgrade to websocket successful May 14 14:20:03 ip-172-31-13-189 web[2211]: INFO:engineio.server:luoag_u_sK4lq15kAAAE: Upgrade to websocket successful
问题分析与解决方案
1. 核心问题:连接SID不匹配
从日志可见,后端发送消息使用的连接SID是jxIFnNaeB7crO36TAAAC,而前端新建立的连接SID是luoag_u_sK4lq15kAAAE,两者不属于同一个连接会话,导致前端收不到消息。
2. 针对性修复步骤
(1)调整前端连接地址与监听顺序
- 将前端
socketUrl改为http://travel32.us-east-1.elasticbeanstalk.com,让Socket.IO客户端自动处理握手与WebSocket升级,而非直接使用ws://协议。 - 先添加事件监听再调用
connect(),避免异步连接导致的监听丢失:coroutineScope.launch { socket = IO.socket(socketUrl, IO.Options().apply { reconnection = true reconnectionAttempts = Integer.MAX_VALUE reconnectionDelay = 1000 reconnectionDelayMax = 5000 }) // 先绑定所有监听事件 socket.on("test", testsocket) socket.on(Socket.EVENT_CONNECT) { runOnUiThread { Log.d("MainActivity", "WebSocket连接成功") } } socket.on(Socket.EVENT_DISCONNECT) { runOnUiThread { Log.d("MainActivity", "WebSocket断开连接") } } socket.on(Socket.EVENT_CONNECT_ERROR) { args -> runOnUiThread { Log.e("MainActivity", "连接错误: ${args[0]}") } } // 再发起连接 socket.connect() }
(2)配置Elastic Beanstalk代理支持WebSocket
单实例环境下,默认反向代理(Apache/Nginx)未开启WebSocket升级支持,需添加配置文件:
在项目根目录创建.ebextensions文件夹,新增01-websocket.config:
- Apache环境:
files: "/etc/httpd/conf.d/websocket.conf": mode: "000644" owner: root group: root content: | LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so ProxyPass "/socket.io/" "ws://localhost:5000/socket.io/" ProxyPassReverse "/socket.io/" "ws://localhost:5000/socket.io/" - Nginx环境:
打包项目时将files: "/etc/nginx/conf.d/websocket.conf": mode: "000644" owner: root group: root content: | location /socket.io/ { proxy_pass http://localhost:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; }.ebextensions文件夹一并上传。
(3)修复后端消息推送逻辑
测试场景下,使用广播推送需确保前端连接处于活跃状态;实际需求中,需定向推送API结果,可通过存储客户端SID实现:
from flask_socketio import emit, async_to_sync import threading import requests # 存储客户端SID,生产环境建议用数据库或缓存 active_clients = {} @socketio.on('connect') def handle_connect(): active_clients[request.sid] = True print(f"客户端连接: {request.sid}") @socketio.on('disconnect') def handle_disconnect(): active_clients.pop(request.sid, None) print(f"客户端断开: {request.sid}") def call_external_api(sid): # 调用外部API try: result = requests.get("https://your-external-api-url.com") async_to_sync(socketio.emit)('api_result', {'data': result.json()}, room=sid) except Exception as e: async_to_sync(socketio.emit)('api_error', {'error': str(e)}, room=sid) @application.route('/trigger_api', methods=['POST']) def trigger_api(): # 从请求中获取客户端SID(前端需传递socket.id) sid = request.json.get('sid') if sid in active_clients: threading.Thread(target=call_external_api, args=(sid,)).start() return "API调用已启动", 200 return "无效的客户端连接", 400
(4)版本兼容性检查
后端设置了allowEIO3=True,需确保前端Socket.IO客户端版本为v2.x或v3.x(v4.x默认使用EIO v4协议),避免协议不兼容导致连接异常。
内容的提问来源于stack exchange,提问作者Tim Doll

