서버 - 클라이언트 간 통신 프로토콜 중 HTTP 는 비연결성으로 항상 연결되어 있는 상태가 아닙니다. 아주 간단히 설명하자면, 웹 서버(apache 또는 nginx 등)는 클라이언트로부터 요청이 들어오면 연결을 수립하고 요청한 자료를 만들어 클라이언트에 전송하고 연결을 종료시킵니다.
그렇다면 실시간으로 보여지는 👍 좋아요 라던지, 조회수 등 실시간 알람 처리는 어떤 방법으로 처리를 하게 되는 것일까요?
💡 Quotation
웹 소켓은 사용자의 브라우저와 서버 사이의 인터액티브 통신 세션을 설정할 수 있게 하는 고급 기술입니다. 개발자는 웹 소켓 API를 통해 서버로 메시지를 보내고 서버의 응답을 위해 서버를 폴링하지 않고도 이벤트 중심 응답을 받는 것이 가능합니다.
연결된 상태를 만들고 서버와 클라이언트 양방향 통신을 가능하게 해줍니다! 마치 소켓 통신과 비슷한데 사용하는 방법은 다소 차이가 있습니다.
💡 Information
일반 소켓과 웹 소켓은 서로 연결할 수 없습니다. 일반 소켓은 저수준의 TCP/UDP 프로토콜을 사용하며 웹 소켓은 HTTP 고수준의 프로토콜을 사용합니다.
통신 과정#
- 핸드 셰이킹 (프로토콜 전환)
- 연결 수립 및 통신
- 연결 종료
요청 패킷#
1
2
3
4
5
| ### HTTP Header ###
GET /index.html HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: websocket
|
4: Connection: upgrade
현재 연결된 프로토콜을 다른 프로토콜로 변경 요청
5: Upgrade: websocket
websocket 으로
응답 패킷#
1
2
3
4
| ### HTTP Header ###
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
|
💡 프로토콜이 전환되면 http:// 에서 ws:// 로 변환됩니다. 😎
웹 서버 설정 및 ASP.NET core 프로젝트 구현#
저는 Linux 환경이고 웹 서버로 nginx 를 사용할 예정입니다. 이 때 웹 소켓을 사용하기 위해 웹 서버에 몇 가지 설정해야할 것이 있습니다.
- proxy_pass 설정
- proxy_set_header 헤더 추가
- 웹 소캣 사용을 위한 Upgrade 필드 추가 ✅
Nginx#
1
2
3
4
5
6
7
8
9
10
11
12
13
| server {
listen 80;
server_name yourdomain.com;
location / {
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;
proxy_cache_bypass $http_upgrade;
}
}
|
SSL 을 사용한다면 80 포트를 HTTPS로 301 리턴해주면 됩니다.
ASP.NET core#
현재 닷넷 8 버전을 사용하고 있으며 웹 소켓 관련 기능이 모두 이미 내장되어 있어 몇 줄의 코드 입력 후 사용하면 됩니다. 👍
프로젝트를 만들면 기본적으로 Program.cs
파일에 웹 서버를 구동하기 위해 builder
를 생성하고 app
을 만들어 실행하게 됩니다. 이제 빌더로 만들어진 app
에 웹 소켓을 사용한다고 전달하고 웹 소켓을 만들어 보겠습니다. 😎
Program.cs#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| var app = builder.Build();
app.UseWebSockets();
app.Use(async (context, next) =>
{
if (context.Request.Path == "/ws")
{
if (context.WebSockets.IsWebSocketRequest)
{ // onAccept
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
await Echo(context, webSocket, clientId);
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await next();
}
});
static async Task Echo(HttpContext context, WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.WriteLine($"Received: {message}");
var serverMsg = Encoding.UTF8.GetBytes($"Server: {message}");
await webSocket.SendAsync(new ArraySegment<byte>(serverMsg, 0, serverMsg.Length), result.MessageType, result.EndOfMessage, CancellationToken.None);
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription,
}
|
6: context
에서 URL에 /ws
가 있는지 확인
8: /ws
가 있으면서 Upgrade 필드에 websocket 이 있는지 체크
10: 클라이언트 요청에 따라 프로토콜 전환 및 웹 소켓 생성
11: 웹 소켓을 Worker
로 전달하여 메세지 처리
27: result
결과에 따라 메세지 송신 완료 됐는지, 연결이 끊어졌는지 파악 가능
⚠️ Warning
26 번째 줄의 buffer
사이즈 4096 보다 큰 메세지가 온다면 잘릴 수 있으며 이를 처리하는 로직 별도 필요 (result
객체의 EndOfMessage
로 확인 가능)