ãã®è¨äºã¯Linux Advent Calendar 2024ã®23æ¥ç®ã®è¨äºã§ãã
WebSocketã使ã£ã¦ããã¢ããªã±ã¼ã·ã§ã³ãå®å ¨ã«ãããã¤ããæ¹æ³ã«ã¤ãã¦èãã¦ã¿ãæèå®é¨çãªè¨äºã§ã
ç®æ¬¡
- ç®æ¬¡
- WebSocketã¨ã¯
- ãããã¤ã®èª²é¡
- ã¯ã©ã¤ã¢ã³ãã«åæ¥ç¶ãã¸ãã¯ãå®è£ ã§ããªãã£ããï¼
- ã¾ã¨ã
WebSocketã¨ã¯
WebSocket ã¯ãã¯ã©ã¤ã¢ã³ãï¼é常ã¯Webãã©ã¦ã¶ï¼ã¨ãµã¼ãã¼éã§åæ¹åãã¤ãªã¢ã«ã¿ã¤ã éä¿¡ãå®ç¾ããããã®éä¿¡ãããã³ã«ã§ããããã«ãããWebã¢ããªã±ã¼ã·ã§ã³ãå¹ççãã¤ã¤ã³ã¿ã©ã¯ãã£ããªéä¿¡ãè¡ããã¨ãå¯è½ã«ãªãã¾ãã以ä¸ã¯ã¤ã¡ã¼ã¸ããããããããã®Pythonã§ã®WebSocketãµã¼ãã®ã³ã¼ãã§ãã
import asyncio import websockets async def echo(websocket, path): print("Client connected!") try: async for message in websocket: print(f"Received: {message}") await websocket.send(f"Echo: {message}") # ã¯ã©ã¤ã¢ã³ãã«éä¿¡ except websockets.ConnectionClosed: print("Client disconnected!") # ãµã¼ãã¼ãéå§ start_server = websockets.serve(echo, "localhost", 8765) print("WebSocket server started at ws://localhost:8765") asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()
WebSocketã®ä»çµã¿
ååæ¥ç¶
WebSocketã¯HTTPãå©ç¨ãã¦æåã®æ¥ç¶ï¼ãã³ãã·ã§ã¤ã¯ï¼ã確ç«ãã¾ããã¯ã©ã¤ã¢ã³ããUpgradeãããã¼ãéä¿¡ãããµã¼ãã¼ã対å¿ã確èªããã¨ãããã³ã«ãWebSocketã«åãæ¿ããã¾ãã以ä¸ã¯HTTPãªã¯ã¨ã¹ãä¾ã§ãã
GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
ãµã¼ãã¼ã¬ã¹ãã³ã¹ä¾
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
ãã¼ã¿äº¤æ
ãã³ãã·ã§ã¤ã¯å¾ã¯ã両è ãç¶ç¶çã«ãã¬ã¼ã ï¼ãã¼ã¿ã®å¡ï¼ã交æããã¼ã¿ã¯è»½éãªãã¤ããªãã©ã¼ãããã¾ãã¯ããã¹ããã©ã¼ãããã§éåä¿¡ãããã
æ¥ç¶ã®ç¶æ
WebSocketã¯é·æéæ¥ç¶ãç¶æãã¾ããææããã³ããã³ã¨å¼ã°ãããçå確èªã¡ãã»ã¼ã¸ããéä¿¡ãã¦æ¥ç¶ç¶æ ã確èªãããã¨ãããã¾ãã
WebSocketã®å©ç¹
- å¹çæ§: ãã¼ã¿ã®éä¿¡ãé »ç¹ãªãªã¢ã«ã¿ã¤ã ã¢ããªã±ã¼ã·ã§ã³ã§ãå¾æ¥ã®HTTPãã¼ãªã³ã°ãããå¹ççã
- ä½ã¬ã¤ãã³ã·: ãªã¯ã¨ã¹ããã¬ã¹ãã³ã¹ã®å¾ æ©æéãå°ãªãã
- ã·ã³ãã«ãªAPI: Webãã©ã¦ã¶ã§ä½¿ããæ¨æºAPIãæä¾ããã¦ãããããå®è£ ã容æã
ãããã¤ã®èª²é¡
ä¸è¬çãªWebãµã¼ãã§ããã°éä¿¡ã¯ã¯ã©ã¤ã¢ã³ããããªã¯ã¨ã¹ããæ¥ãªãéããµã¼ãå´ã¯å¦çãèµ°ãã¾ãããgraceful shutdownã®ãããªä»çµã¿ã§ã¯ã¯ã©ã¤ã¢ã³ãããã®ãªã¯ã¨ã¹ããæ¢ãã¦ããã¦ç¾å¨æå¹ãªå¦çä¸ã®ãªã¯ã¨ã¹ããåæ¢æ¬¡ç¬¬ã§ã¢ããªã±ã¼ã·ã§ã³ãå ¥ãæ¿ãããã¨ã§ãã¦ã³ã¿ã¤ã ãã¨ã©ã¼ãªãã§ãããã¤ãã§ãã¾ãã
ä¸æ¹ã§WebSocketã¯ã³ãã¯ã·ã§ã³ã¯ç¶æããã¾ã¾ã¯ã©ã¤ã¢ã³ããµã¼ãéã§åæ¹åéä¿¡ãè¡ããã¾ããä»®ã«ã¢ããªã±ã¼ã·ã§ã³ãã¹ãã¼ãã¬ã¹ã§ããã°ã³ãã¯ã·ã§ã³ãä¸æ¦ã¯ãã¼ãºãã¦ãã¾ã£ã¦WebSocketã®oncloseã¤ãã³ããç£è¦ããæ¥ç¶ãåãããã¨ãæ¤åºãã¯ã©ã¤ã¢ã³ãããåæ¥ç¶ããããã«å®è£ ããã¦ãããã¨ã§ã¢ããªã±ã¼ã·ã§ã³ã®ãããã¤ããããã¨ãã§ãã¾ãã
let socket; let reconnectInterval = 1000; // åæ¥ç¶ã®åæééï¼ããªç§ï¼ function connect() { socket = new WebSocket("wss://example.com/socket"); socket.onopen = () => { console.log("WebSocket connected"); reconnectInterval = 1000; // åæ¥ç¶ééããªã»ãã }; socket.onclose = () => { console.log("WebSocket disconnected. Reconnecting..."); setTimeout(connect, reconnectInterval); reconnectInterval = Math.min(reconnectInterval * 2, 30000); // æ大30ç§ã¾ã§ããã¯ãªã }; socket.onmessage = (event) => { console.log("Message received:", event.data); }; socket.onerror = (error) => { console.error("WebSocket error:", error); socket.close(); // åæãã¦åæ¥ç¶ }; } connect();
ãã®ãããªå®è£ ã§ã¯ã©ã¤ã¢ã³ããåæ¥ç¶ããã¦ãããã®ã§ã¨ã©ã¼ãªãã§ãã¤ããªã®ã¢ããã°ã¬ã¼ããå¯è½ã«ãªãã¾ãã
ã¯ã©ã¤ã¢ã³ãã«åæ¥ç¶ãã¸ãã¯ãå®è£ ã§ããªãã£ããï¼
ãã®é¨åãæ¬é¡ã§ããã¯ã©ã¤ã¢ã³ãå´ã«åæ¥ç¶ã®ãã¸ãã¯ãå®è£ ã§ããªãå ´åã¯ã©ããªãã§ããããï¼ãããã¤ã®ãã³ã«éä¿¡ä¸ã®ã³ãã¯ã·ã§ã³ãåãããã¨ã«ãªãã¾ãããããã©ãã«ããªããªãããèãã¦ã¿ã¾ãã
SO_REUSEPORT
Linuxãæä¾ããSO_REUSEPORTã¨ããã½ã±ãããªãã·ã§ã³ã¯ãå¹ççã§é«æ§è½ãªãããã¯ã¼ã¯éä¿¡ãå®ç¾ããããã®éµã¨ãªãæ©è½ã§ãããã®ãªãã·ã§ã³ãå©ç¨ãããã¨ã§ãè¤æ°ã®ããã»ã¹ãã¹ã¬ãããåä¸ã®ãã¼ãçªå·ãå ±æãã並åå¦çãå®ç¾ã§ãã¾ãã
é常ããããã¯ã¼ã¯ã¢ããªã±ã¼ã·ã§ã³ã§ã¯ã1ã¤ã®ãã¼ãçªå·ã«1ã¤ã®ããã»ã¹ãããã¤ã³ããããã¨ãã§ãã¾ããããã®å¶ç´ã¯ãå¤ãã®æ¥ç¶ãå¦çããå¿ è¦ãããã·ã¹ãã ã«ã¨ã£ã¦ããã©ã¼ãã³ã¹ã®ããã«ããã¯ã¨ãªããã¨ãããã¾ããããããSO_REUSEPORTãæå¹ã«ããã¨ãè¤æ°ã®ããã»ã¹ãã¹ã¬ãããåããã¼ãçªå·ã使ç¨ãã¦ãã¤ã³ãã§ããããã«ãªãã¾ããããã«ãããã«ã¼ãã«ãåä¿¡ãããªã¯ã¨ã¹ããè¤æ°ã®ã½ã±ããã«èªåçã«åé ãããããè² è·ãåçã«åæ£ããã¾ãããã®åé ã¯ã©ã¦ã³ãããã³æ¹å¼ã§è¡ãããããã»ã¹ãã¨ã«ãªã¯ã¨ã¹ããé 次å¦çããããããå¹ççãªè² è·åæ£ãå¯è½ã§ãã
ä¾ãã°ãWebãµã¼ãã¼ããããã·ãµã¼ãã¼ã®ãããªé«ã¹ã«ã¼ããããæ±ããããã¢ããªã±ã¼ã·ã§ã³ã§ã¯ããã®æ©è½ãæ´»ç¨ãããã¨ã§ã1ã¤ã®ãã¼ããè¤æ°ã®ããã»ã¹ã§å ±æããªããã並è¡ãã¦ã¯ã©ã¤ã¢ã³ãããã®æ¥ç¶ãå¦çã§ãã¾ããNGINXãHAProxyã¨ãã£ãé«æ§è½ãªãµã¼ãã¼ã½ããã¦ã§ã¢ãããã®SO_REUSEPORTãå©ç¨ãã¦è² è·åæ£ãå®ç¾ãã¦ãã¾ããéå»ã«ããã°ãæ¸ãã¦ããã®ã§è¦ã¦ã¿ã¦ãã ããã
ããã使ãã°è¤æ°ããã»ã¹ã§ä¸ã¤ã®ãã¼ããbind(2)ã§ãã¦è§£æ±ºã§ãããã§ããããã¯ããã¾ãããã³ãã¯ã·ã§ã³èªä½ã¯åä¸ã®ããã»ã¹ã§å¦çããã¾ããã«ã¼ãã«ãæ°ããæ¥ç¶ãåãå
¥ããéãã©ã®ããã»ã¹ããã®æ¥ç¶ãå¦çãããã決å®ãã¾ãã(SYNãæ¥ãæç¹ã§æ±ºå®ãã¦ãã)ãããã§ç»å ´ããã®ãSocket Migrationã§ããnet.ipv4.tcp_migrate_req ã¯ãLinux ã«ã¼ãã«ã TCP æ¥ç¶ã®ç§»è¡ï¼TCP Connection Migrationï¼ããµãã¼ãããéã«å©ç¨ããã sysctl ãã©ã¡ã¼ã¿ã®ä¸ã¤ã§ãããã®è¨å®ã¯ã確ç«æ¸ã¿ã® TCP æ¥ç¶ãå¥ã®ããã»ã¹ãã·ã¹ãã ã«ç§»è¡ã§ããããã«ããããã®ãã®ã§ããé常ãTCP æ¥ç¶ã¯ç¹å®ã®ããã»ã¹ãã½ã±ããã«åºå®ããã¦ãã¾ãããtcp_migrate_req
ãæå¹ã«ãããã¨ã§ããã®åºå®æ§ãæè»ã«å¤æ´ã§ãã¾ãã詳ããã¯ä»¥ä¸ã®ããã°ãã¨ã¦ãã¨ã¦ã詳ããã®ã§èªããã¨ããå§ããã¾ãã
ããã使ããã¨ã§ä»¥ä¸ã®ãããªãããã¤ããã¼ãå¯è½ã«ãªãã¾ãã
1. ã¯ã©ã¤ã¢ã³ãã¨ãµã¼ãã§WebSocketã®éä¿¡ãéå§ 2. ãµã¼ãå´ã¯ããã»ã¹Aã§3000çªãã¼ãã§éä¿¡ããã¦ãã 3. ãµã¼ãå´ã«æ°ãããã¤ããªããããã¤ãã¦ããã»ã¹Bãç«ã¡ä¸ã3000çªãã¼ããbind 4. 以éã®éä¿¡ã¯ããã»ã¹A or ããã»ã¹Bãè¡ã 5. ããã»ã¹Aã«ä½ããã®ã·ã°ãã«ãéã£ã¦read(2)ãªã©ã®ã·ã¹ãã ã³ã¼ã«ãå®è¡ããªãããã«ãã 6. 以éã¯ããã»ã¹Bã®ã¿ãéä¿¡ãè¡ã
ããã§å®å ¨ã«ããã»ã¹Aããããã»ã¹Bã«å®å ¨ã«ãããã¤ãã§ãã¾ããã
ã¾ã¨ã
net.ipv4.tcp_migrate_req ã¯ãTCPæ¥ç¶ãæè»ã«ç§»è¡ããæ©è½ãæä¾ããé«å¯ç¨æ§ãè² è·åæ£ã®æ¹åã«å¯ä¸ãã¾ãããã®è¨å®ãæ´»ç¨ãããã¨ã§ããªã¢ã«ã¿ã¤ã æ§ãæ±ããããã¢ããªã±ã¼ã·ã§ã³ããã¹ã±ã¼ã©ãã«ãªã·ã¹ãã ã®è¨è¨ãå¯è½ã¨ãªãã¾ãããã ããã»ãã¥ãªãã£ãã¯ã©ã¤ã¢ã³ãå´ã®å¯¾å¿ãååã«èæ ®ããªãããæ éã«å°å ¥ãããã¨ãéè¦ã§ãã