경매는 시간이 생명입니다. 특히 온라인 경매이므로 모든 사람이 같은 시간에 경매가 종료되어야 합니다.
따라서 모든 사람에게 같은 시간이 표시되어야 합니다. 하지만 클라이언트의 시간은 믿을 수 없습니다.
너무나도 손쉽게 시간을 변경할 수 있기 때문입니다. 따라서 서버 시간을 받아오는 것이 좋습니다.
폴링이나 웹 소켓을 통해서 서버 시간을 받아올 수도 있지만,
이번 예제에서는 서버 센트 이벤트를 사용해서 서버의 시간을 받아올 것입니다.
주기적으로 서버 시간을 조회하는 데는 양방향 통신이 필요하지 않기 때문입니다. 웹 소켓도 사용합니다.
웹 소켓은 경매를 진행하는 동안에 다른 사람이 참여하거나 입찰했을 때 모두에게 금액을 알리는 역할을 할 것입니다.
서버 센트 이벤트와 웹 소켓은 같이 사용할 수 있습니다. SSE 패키지와 Socket.IO 패키지를 동시에 설치하겠습니다
npm i sse socket.io@2
SSE 모델을 불러와 newSSE(expressServer)로 서버 객체를 생성하면 됩니다.
생성한 객체에는 connection 이벤트 리스너를 연결하여 클라이언트와 연결할 때 어떤 동작을 할지 정의할 수 있습니다.
매개변수로 client 객체를 사용할 수 있습니다. 클라이언트에 메시지를 보낼 때 이 객체를 활용합니다.
라우터에서 SSE를 사용하고 싶다면 app.set 메서드로 client 객체를 등록하고, req.app.get 메서드로 가져올 수 있습니다.
이 예제에서는 1초마다 접속한 클라이언트에서 서버 시간의 타임스탬프를 보내도록 했습니다.
client.send 메서드로 보낼 수 있습니다.
다만, 문자열만 보낼 수 있으므로 숫자인 타임스탬프를 toString 메서드를 사용하여 문자열로 변경했습니다.
Socket.IO와도 연결합니다.
이번에는 사용자 정의 네임스페이스를 쓰지 않고 기본 네임스페이스(socket.io())로 연결했습니다.
경매 화면에 실시간으로 입찰 정보를 올리기 위해 웹 소켓을 사용합니다.
클라이언트 연결 시 주소로부터 경매 방 아이디를 받아와 socket.join으로 해당 방에 입장합니다.
연결이 끊겼다면 socket.leave로 해당 방에서 나갑니다.
서버 센트 이벤트는 한 가지 단점이 있습니다.
IE나 엣지 브라우저에서 사용할 수 없다는 것입니다.
EventSource라는 객체를 지원하지 않기 때문인데, 다행히 EventSource을 사용자가 직접 구현할 수 있습니다.
IE나 엣지 브라우저를 위해 클라이언트 코드에 EventSource 폴리필(polyfill)을 넣었습니다
첫 번째 스크립트가 EventSource 폴리필입니다.
이것을 넣으면 IE와 엣지 브라우저에서도 서버 센트 이벤트를 사용할 수 있습니다.
두 번째 스크립트는 EventSource를 사용해서 서버 센트 이벤트를 받는 코드입니다.
new EventSource('/sse')로 서버와 연결하고, es.onmessage 또는 es.addEventListener('message')
이벤트 리스너를 사용하여 서버로부터 데이터를 받을 수 있습니다.
서버로부터 받은 데이터는 e.data에 들어있습니다.
아랫부분은 서버 시간과 경매 종료 시간을 계산하여 카운트다운하는 코드입니다. 24시간 동안 카운트다운되도록 했습니다
eventsource. min.js는 조금 전에 추가한 EventSource 폴리필 파일입니다.
GET /sse가 바로 서버 센트 이벤트에 접속한 것입니다. Typeol eventsource로 나와 있습니다.
일반 HTTP 연결을 통해 서버센트 이벤트를 사용할 수 있습니다.
GET /sse를 클릭해보면 Eventstream 탭이 있는데,
여기서 매초 서버로부터 타임스탬프 데이터가 오는 것을 확인할 수 있습니다.
경매를 진행하는 프론트단 코드 복사 해서 붙여넣기(서버 센트 이벤트와 웹소켓 모두 연결)
스크립트 코드가 상당히 길지만 별 내용은 없습니다.
먼저 axios, EventSource 폴리필과 Socket.IO 클라이언트 스크립트를 넣습니다.
네 번째 스크립트 태그는 입찰 시 POST /good/:id/bid로 요청을 보내는 것,
서버 센트 이벤트 데이터로 서버 시간을 받아 카운트다운하는 것,
다른 사람이 입찰했을 때 Socket.IO로 입찰 정보를 렌더링하는 것으로 이루어져 있습니다.
이 라우터에 GET /good/:id와 POST /good/:id/bid를 추가합니다
컨트롤러와 route 코드 생략
GET /good/:id 라우터는 해당 상품과 기존 입찰 정보들을 불러온 뒤 렌더링합니다.
상품(Good) 모델에 사용자(User) 모델을 include할 때 as 속성을 사용한 것에 주의하세요.
Good 모델과 User 모델은 현재 일대다 관계가 두 번 연결(Owner, Sold)되어 있으므로
이러한 경우에는 어떤 관계를 include할지 as 속성으로 밝혀야 합니다.
POST /good/:id/bid는 클라이언트로부터 받은 입찰 정보를 저장합니다.
만약 시작 가격보다 낮게 입찰했거나, 경매 종료 시간이 지났거나, 이전 입찰가보다 낮은 입찰가가 들어왔다면 반려합니다.
정상적인 입찰가가 들어왔다면 저장한 후 해당 경매 방의 모든 사람에게 입찰자, 입찰 가격, 입찰 메시지 등을 웹 소켓으로 전달합니다.
Good.findOne 메서드의 order 속성을 눈여겨보길 바랍니다.
include될 모델의 컬럼을 정렬하는 방법입니다. Auction 모델의 bid를 내림차순으로 정렬하고 있습니다.
이제 서버에 연결해서 경매를 시작해보겠습니다. 브라우저를 두 개 띄워서 각자 다른 아이디로 로그인한 후 진행해보세요.
'clone toy projects > node_expresss_chat' 카테고리의 다른 글
스케쥴링 구현 및 프로젝트 마무리 [흐름만 보기] (0) | 2023.08.30 |
---|---|
프로젝트 구조 갖추기 [흐름만 보기] (0) | 2023.08.30 |
이미지를 포함한 채팅 (0) | 2023.08.30 |
채팅 기능 구현하기 (0) | 2023.08.30 |
미들웨어와 소켓 연결하기 (0) | 2023.08.30 |