1"use strict";(self.webpackChunksample_website=self.webpackChunksample_website||[]).push([[728],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>u});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?i(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):i(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function o(e,t){if(null==e)return{};var n,a,r=function(e,t){if(null==e)return{};var n,a,r={},i=Object.keys(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a<i.length;a++)n=i[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},k="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),k=p(n),m=r,u=k["".concat(s,".").concat(m)]||k[m]||d[m]||i;return n?a.createElement(u,l(l({ref:t},c),{},{components:n})):a.createElement(u,l({ref:t},c))}));function u(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,l=new Array(i);l[0]=m;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[k]="string"==typeof e?e:r,l[1]=o;for(var p=2;p<i;p++)l[p]=n[p];return a.createElement.apply(null,l)}return a.createElement.apply(null,n)}m.displayName="MDXCreateElement"},3909:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var a=n(3117),r=(n(7294),n(3905));const i={title:"How it works",sidebar_position:2,slug:"/how-it-works/",toc_max_heading_level:4},l=void 0,o={unversionedId:"categories/Documentation/how-it-works",id:"categories/Documentation/how-it-works",title:"How it works",description:"The bidirectional connection between the Socket.IO server and the Socket.IO client is established with either:",source:"@site/docs/categories/01-Documentation/how-it-works.md",sourceDirName:"categories/01-Documentation",slug:"/how-it-works/",permalink:"/docs/v4/how-it-works/",draft:!1,editUrl:"https://github.com/socketio/socket.io-website/edit/main/docs/categories/01-Documentation/how-it-works.md",tags:[],version:"current",lastUpdatedAt:1784117104,formattedLastUpdatedAt:"Jul 15, 2026",sidebarPosition:2,frontMatter:{title:"How it works",sidebar_position:2,slug:"/how-it-works/",toc_max_heading_level:4},sidebar:"sidebar",previous:{title:"Introduction",permalink:"/docs/v4/"},next:{title:"Delivery guarantees",permalink:"/docs/v4/delivery-guarantees"}},s={},p=[{value:"Engine.IO",id:"engineio",level:2},{value:"Transports",id:"transports",level:3},{value:"HTTP long-polling",id:"http-long-polling",level:4},{value:"WebSocket",id:"websocket",level:4},{value:"WebTransport",id:"webtransport",level:4},{value:"Handshake",id:"handshake",level:3},{value:"Upgrade mechanism",id:"upgrade-mechanism",level:3},{value:"Disconnection detection",id:"disconnection-detection",level:3},{value:"Socket.IO",id:"socketio",level:2}],c={toc:p},k="wrapper";function d(e){let{components:t,...i}=e;return(0,r.kt)(k,(0,a.Z)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"The bidirectional connection between the Socket.IO server and the Socket.IO client is established with either:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"a ",(0,r.kt)("a",{parentName:"li",href:"https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API"},"WebTransport bidirectional stream")),(0,r.kt)("li",{parentName:"ul"},"a ",(0,r.kt)("a",{parentName:"li",href:"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"},"WebSocket connection")),(0,r.kt)("li",{parentName:"ul"},"or HTTP long-polling, in the worst case")),(0,r.kt)("p",null,"The Socket.IO codebase is split into two distinct layers:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"the low-level plumbing: what we call Engine.IO, the engine inside Socket.IO"),(0,r.kt)("li",{parentName:"ul"},"the high-level API: Socket.IO itself")),(0,r.kt)("h2",{id:"engineio"},"Engine.IO"),(0,r.kt)("p",null,"Engine.IO is responsible for establishing the low-level connection between the server and the client. It handles:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"the various ",(0,r.kt)("a",{parentName:"li",href:"#transports"},"transports")," and the ",(0,r.kt)("a",{parentName:"li",href:"#upgrade-mechanism"},"upgrade mechanism")),(0,r.kt)("li",{parentName:"ul"},"the ",(0,r.kt)("a",{parentName:"li",href:"#disconnection-detection"},"disconnection detection"))),(0,r.kt)("p",null,"A detailed version of the Engine.IO protocol can be found ",(0,r.kt)("a",{parentName:"p",href:"/docs/v4/engine-io-protocol/"},"here"),"."),(0,r.kt)("p",null,"The source code of the reference implementation (written in TypeScript) can be found in the Socket.IO monorepo:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Component"),(0,r.kt)("th",{parentName:"tr",align:null},"Package"),(0,r.kt)("th",{parentName:"tr",align:null},"Link"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Server"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"engine.io")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://github.com/socketio/socket.io/tree/main/packages/engine.io"},"https://github.com/socketio/socket.io/tree/main/packages/engine.io"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Client"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"engine.io-client")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://github.com/socketio/socket.io/tree/main/packages/engine.io-client"},"https://github.com/socketio/socket.io/tree/main/packages/engine.io-client"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Parser"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"engine.io-parser")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://github.com/socketio/socket.io/tree/main/packages/engine.io-parser"},"https://github.com/socketio/socket.io/tree/main/packages/engine.io-parser"))))),(0,r.kt)("h3",{id:"transports"},"Transports"),(0,r.kt)("p",null,"There are currently three built-in transports:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#http-long-polling"},"HTTP long-polling")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#websocket"},"WebSocket")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#webtransport"},"WebTransport"))),(0,r.kt)("h4",{id:"http-long-polling"},"HTTP long-polling"),(0,r.kt)("p",null,'The HTTP long-polling transport (also simply referred as "polling") consists of successive HTTP requests:'),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"long-running ",(0,r.kt)("inlineCode",{parentName:"li"},"GET")," requests, for receiving data from the server"),(0,r.kt)("li",{parentName:"ul"},"short-running ",(0,r.kt)("inlineCode",{parentName:"li"},"POST")," requests, for sending data to the server")),(0,r.kt)("p",null,"It is available on all platforms, but it is also the least performant transport since each packet needs a new HTTP request (with its headers)."),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Metric"),(0,r.kt)("th",{parentName:"tr",align:null},"Value"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Support"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"Best"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Performance"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"Acceptable"))))),(0,r.kt)("h4",{id:"websocket"},"WebSocket"),(0,r.kt)("p",null,"The WebSocket transport uses a WebSocket connection to send and receive data."),(0,r.kt)("p",null,"References:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"MDN: ",(0,r.kt)("a",{parentName:"li",href:"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"},"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API")),(0,r.kt)("li",{parentName:"ul"},"Specification: ",(0,r.kt)("a",{parentName:"li",href:"https://datatracker.ietf.org/doc/html/rfc6455"},"https://datatracker.ietf.org/doc/html/rfc6455")," (published in December 2011)"),(0,r.kt)("li",{parentName:"ul"},"Can I use: ",(0,r.kt)("a",{parentName:"li",href:"https://caniuse.com/mdn-api_websocket"},"https://caniuse.com/mdn-api_websocket")," (",(0,r.kt)("inlineCode",{parentName:"li"},"99.84%")," of all tracked, as of early 2026)")),(0,r.kt)("p",null,"It is available on all platforms and has great performance (unlike HTTP long-polling, the HTTP headers are sent once at the beginning of the session), but might still be blocked by some proxies."),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Metric"),(0,r.kt)("th",{parentName:"tr",align:null},"Value"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Support"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"Great"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Performance"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"Great"))))),(0,r.kt)("h4",{id:"webtransport"},"WebTransport"),(0,r.kt)("p",null,"The WebTransport transport uses a WebTransport bidirectional stream to send and receive data."),(0,r.kt)("p",null,"References:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"MDN: ",(0,r.kt)("a",{parentName:"li",href:"https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API"},"https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API")),(0,r.kt)("li",{parentName:"ul"},"Specification (draft): ",(0,r.kt)("a",{parentName:"li",href:"https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/"},"https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/")),(0,r.kt)("li",{parentName:"ul"},"Can I use: ",(0,r.kt)("a",{parentName:"li",href:"https://caniuse.com/mdn-api_webtransport"},"https://caniuse.com/mdn-api_webtransport")," (",(0,r.kt)("inlineCode",{parentName:"li"},"84.48%")," of all tracked, as of early 2026)")),(0,r.kt)("p",null,"Its availability is limited (currently in technical preview on Safari), but it's also the most efficient transport, especially in environments prone to packet loss."),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Metric"),(0,r.kt)("th",{parentName:"tr",align:null},"Value"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Support"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"In progress"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Performance"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"}
1,"Best"))))),(0,r.kt)("h3",{id:"handshake"},"Handshake"),(0,r.kt)("p",null,"At the beginning of the Engine.IO connection, the server sends some information:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n "sid": "FSDjX-WRwSA4zTZMALqx",\n "upgrades": ["websocket"],\n "pingInterval": 25000,\n "pingTimeout": 20000,\n "maxPayload": 1000000\n}\n')),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"the ",(0,r.kt)("inlineCode",{parentName:"li"},"sid")," is the ID of the session, it must be included in the ",(0,r.kt)("inlineCode",{parentName:"li"},"sid")," query parameter in all subsequent HTTP requests"),(0,r.kt)("li",{parentName:"ul"},"the ",(0,r.kt)("inlineCode",{parentName:"li"},"upgrades"),' array contains the list of all "better" transports that are supported by the server'),(0,r.kt)("li",{parentName:"ul"},"the ",(0,r.kt)("inlineCode",{parentName:"li"},"pingInterval")," and ",(0,r.kt)("inlineCode",{parentName:"li"},"pingTimeout")," values are used in the heartbeat mechanism"),(0,r.kt)("li",{parentName:"ul"},"the ",(0,r.kt)("inlineCode",{parentName:"li"},"maxPayload")," value indicates the max number of bytes per packet accepted by the server")),(0,r.kt)("h3",{id:"upgrade-mechanism"},"Upgrade mechanism"),(0,r.kt)("p",null,"By default, the client establishes the connection with the HTTP long-polling transport."),(0,r.kt)("p",null,(0,r.kt)("strong",{parentName:"p"},"But, why?")),(0,r.kt)("p",null,"While WebSocket is clearly the best way to establish a bidirectional communication, experience has shown that it is not always possible to establish a WebSocket connection, due to corporate proxies, personal firewall, antivirus software..."),(0,r.kt)("p",null,"From the user perspective, an unsuccessful WebSocket connection can translate in up to 10 seconds of waiting for the realtime application to begin exchanging data. This ",(0,r.kt)("strong",{parentName:"p"},"perceptively")," hurts user experience."),(0,r.kt)("p",null,"To summarize, Engine.IO focuses on reliability and user experience first, marginal potential UX improvements and increased server performance second."),(0,r.kt)("p",null,"To upgrade, the client will:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"ensure its outgoing buffer is empty"),(0,r.kt)("li",{parentName:"ul"},"put the current transport in read-only mode"),(0,r.kt)("li",{parentName:"ul"},"try to establish a connection with the other transport"),(0,r.kt)("li",{parentName:"ul"},"if successful, close the first transport")),(0,r.kt)("p",null,"You can check in the Network Monitor of your browser:"),(0,r.kt)("p",null,(0,r.kt)("img",{alt:"Successful upgrade",src:n(5290).Z,width:"585",height:"216"})),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"handshake (contains the session ID \u2014 here, ",(0,r.kt)("inlineCode",{parentName:"li"},"zBjrh...AAAK")," \u2014 that is used in subsequent requests)"),(0,r.kt)("li",{parentName:"ol"},"send data (HTTP long-polling)"),(0,r.kt)("li",{parentName:"ol"},"receive data (HTTP long-polling)"),(0,r.kt)("li",{parentName:"ol"},"upgrade (WebSocket)"),(0,r.kt)("li",{parentName:"ol"},"receive data (HTTP long-polling, closed once the WebSocket connection in 4. is successfully established)")),(0,r.kt)("h3",{id:"disconnection-detection"},"Disconnection detection"),(0,r.kt)("p",null,"The Engine.IO connection is considered as closed when:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"one HTTP request (either GET or POST) fails (for example, when the server is shutdown)"),(0,r.kt)("li",{parentName:"ul"},"the WebSocket connection is closed (for example, when the user closes the tab in its browser)"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"socket.disconnect()")," is called on the server-side or on the client-side")),(0,r.kt)("p",null,"There is also a heartbeat mechanism which checks that the connection between the server and the client is still up and running:"),(0,r.kt)("p",null,"At a given interval (the ",(0,r.kt)("inlineCode",{parentName:"p"},"pingInterval")," value sent in the handshake) the server sends a PING packet and the client has a few seconds (the ",(0,r.kt)("inlineCode",{parentName:"p"},"pingTimeout")," value) to send a PONG packet back. If the server does not receive a PONG packet back, it will consider that the connection is closed. Conversely, if the client does not receive a PING packet within ",(0,r.kt)("inlineCode",{parentName:"p"},"pingInterval + pingTimeout"),", it will consider that the connection is closed."),(0,r.kt)("p",null,"The disconnection reasons are listed ",(0,r.kt)("a",{parentName:"p",href:"/docs/v4/server-socket-instance/#disconnect"},"here")," (server-side) and ",(0,r.kt)("a",{parentName:"p",href:"/docs/v4/client-socket-instance/#disconnect"},"here")," (client-side)."),(0,r.kt)("h2",{id:"socketio"},"Socket.IO"),(0,r.kt)("p",null,"Socket.IO provides some additional features over the Engine.IO connection:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"automatic reconnection"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/docs/v4/client-offline-behavior/#buffered-events"},"packet buffering")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/docs/v4/emitting-events/#acknowledgements"},"acknowledgments")),(0,r.kt)("li",{parentName:"ul"},"broadcasting ",(0,r.kt)("a",{parentName:"li",href:"/docs/v4/broadcasting-events/"},"to all clients")," or ",(0,r.kt)("a",{parentName:"li",href:"/docs/v4/rooms/"}
1,"to a subset of clients"),' (what we call "Room")'),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/docs/v4/connection-state-recovery"},"connection state recovery"),", for temporary disconnections"),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"/docs/v4/namespaces/"},"multiplexing"),' (what we call "Namespace")')),(0,r.kt)("p",null,"A detailed version of the Socket.IO protocol can be found ",(0,r.kt)("a",{parentName:"p",href:"/docs/v4/socket-io-protocol/"},"here"),"."),(0,r.kt)("p",null,"The source code of the reference implementation (written in TypeScript) can be found in the Socket.IO monorepo:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Component"),(0,r.kt)("th",{parentName:"tr",align:null},"Package"),(0,r.kt)("th",{parentName:"tr",align:null},"Link"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Server"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"socket.io")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://github.com/socketio/socket.io/tree/main/packages/socket.io"},"https://github.com/socketio/socket.io/tree/main/packages/socket.io"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Client"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"socket.io-client")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://github.com/socketio/socket.io/tree/main/packages/socket.io-client"},"https://github.com/socketio/socket.io/tree/main/packages/socket.io-client"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Parser"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"socket.io-parser")),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://github.com/socketio/socket.io/tree/main/packages/socket.io-parser"},"https://github.com/socketio/socket.io/tree/main/packages/socket.io-parser"))))))}d.isMDXComponent=!0},5290:(e,t,n)=>{n.d(t,{Z:()=>a});const a=n.p+"assets/images/network-monitor-2e47dbe233100aa290595f8687a9fcba.png"}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.