websocket 연결 과정
1. websocket handshake
TCP 소켓 리스닝을하며, HTTP 프로토콜의 GET request 에 응답한다
클라이언트는 websocket handshake process를 거친다. HTTP request가 시작이며 아래와 같은 HTTP header 를 보낸다
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
이과정에서 어떤 header라도 부정확한 값을 갖거나 서버가 해석하지 못하는경우 서버는 400(bad request)를 응답하게 된다.
websocket 서버가 handshake 요청을 받으면, 현재 프로토콜이 HTTP 에서 WebSocket 프로토콜로 변경된다는 응답을 내려주게 된다. 아래와 같은 응답이다.
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
클라이언트측 Sec-WebSocket-Key 헤더와 서버응답측 Sec-WebSocket-Accept 가 눈에 띈다. 이 값은 서버가 웹소켓연결할 준비가 되어있다는 표시를 명백히 하기위해 존재한다. 해당 표시만을 위해 존재하는 것 치고는 과정이 조금 과하다는 생각은 들지만.. mdn web docs를 참고하면 해당 내용이 security issue 와 연결되어있다는 간단한 설명만 나올 뿐이다. 우선 아래와 같은 과정이다
- 클라이언트가 Sec-WebSocket-Key 헤더를 보낸다
- 서버는 Sec-WebSocket-Key 와 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 라는 웹소켓 마스터키를 이어붙인후, SHA-1 해싱한다
- 해싱값을 base64 인코딩 해서 Sec-WebSocket-Accept를 내린다
- 클라이언트는 이 값을 검증 후, 웹소켓 연결한다
2. handshake 과정이 끝나면 양방향 데이터 교환을 하며, 이때 데이터는 특정포맷을 준수
또한 서로간의 연결을 확인하기위한 절차가 있는데, Ping Pong 매커니즘이다. 클라이언트 혹은 서버는 어느 시점이든 Ping 을 보낼 수 있고 나머지는 최대한 빠르게 pong을 보내야 한다.
3. closing 과정
특정 프레임을 보내면 나머지는 close frame 을 응답하며 연결이 끊긴다.
signalr 서버 클라우드에 세팅
websocket 연결은 첫 HTTP 프로토콜로 시작하므로, AWS ALB와 같은 http protocol 을 지원하는 LB를 통해 연결할 수 있다
https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html 참고하면, 다음과 같이 Application LoadBalancer 가 native websocket 을 지원한다는 안내가 있다.
Application Load Balancers provide native support for WebSockets. You can upgrade an existing HTTP/1.1
connection into a WebSocket (ws or wss) connection by using an
HTTP connection upgrade. When you upgrade, the TCP connection used for requests (to the
load balancer as well as to the target) becomes a persistent WebSocket connection
between the client and the target through the load balancer. You can use WebSockets with
both HTTP and HTTPS listeners. The options that you choose for your listener apply to
WebSocket connections as well as to HTTP traffic. For more information, see How the WebSocket Protocol Works in the
Amazon CloudFront Developer Guide.
signalr 문서(https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-7.0) 에서는 특정상황을 제외하면 LB에 sticky session 설정이 필수라고 안내 하고 있다. 제외되는 특정상황중에는 클라이언트와의 연결이 무조건 "websocket" 방식 뿐일때가 포함되어있다. 아마도 signalr 은 long polling 등의 웹소켓 like 한 다른방식또한 지원하기때문에 해당 방식을 사용시엔 클라이언트가 하나의 서버에만 연결되어있는 상태를 강제해야 하기 때문인 것으로 생각된다.
SignalR requires that all HTTP requests for a specific connection be handled by the same server process.
When SignalR is running on a server farm (multiple servers), "sticky sessions" must be used. "Sticky sessions" are also called session affinity by some load balancers. Azure App Service uses Application Request Routing (ARR) to route requests.
Enabling the "ARR Affinity" setting in your Azure App Service will enable "sticky sessions". The only circumstances in which sticky sessions are not required are:
When hosting on a single server, in a single process.
When using the Azure SignalR Service.
When all clients are configured to only use WebSockets, and the SkipNegotiation setting is enabled in the client configuration.
개인적으로 가장 궁금했던, 하나의 클라이언트가 어떻게 AWS Loadbalancer 뒤의 여러 웹소켓 서버들중 처음 handshake한 서버와만 지속적으로 통신할 수 있는지에 관하여
https://stackoverflow.com/questions/15266702/proxying-websockets-with-tcp-load-balancer-without-sticky-sessions 어느정도 설명이 나와있다.
즉, 클라이언트가 처음 웹소켓서버와 Handshake 하기위해 HTTP 요청을 보낼때 TCP연결을 맺게 되는데, 로드밸런서는 TCP 커넥션이 맺어진 이후엔 client-LB, LB-server 커넥션을 매핑하여 유지시키고, 웹소켓은 HTTP연결때 사용했던 TCP connection을 끊지않고 그대로 계속 사용하기 때문에 이후의 패킷들이 처음 맺어진 쌍대로 통신이 가능하다 는 것.
평소 알고있던 로드밸런서의 라운드로빈은 HTTP 통신 단위로 라운드로빈이 일어나며, TCP 단위에서는 라운드로빈이 일어나지 않는것으로 보인다. 더 자세한 과정은 찾아보면 될 것 같다.
참고
ASP.NET Core SignalR hosting and scaling
https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-7.0
Writing WebSocket servers
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
Listeners for your Application Load Balancers
https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html
Proxying WebSockets with TCP load balancer without sticky sessions
https://stackoverflow.com/questions/15266702/proxying-websockets-with-tcp-load-balancer-without-sticky-sessions