1. 웹 요소 추가하기
브라우저 화면에 입력 필드와 목록을 만들고, 메시지를 주고받은 결과를 바로바로 표시할 수 있게 해보자.
※ 당연히 서버는 실행되어져 있어야함.
1-1) home.pug (뷰 엔진) 수정
코드의 ul부분은 메시지를 받을 때마다 자바스크립트를 이용해서 채울 예정 ( like 카카오톡 )
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Zoom
//MVP.css는 우리가 태그에 class나 id같은 특성을 추가하지 않아도 자동으로 스타일을 적용해주는 라이브러리다.
body
//h1 It works!
header
h1 Zoom
main
ul
form
input(type="text", placeholder="write a msg", required)
button Send
script(src="/public/js/app.js")
<결과화면>
1-2). form 이벤트 등록하기
form에 텍스트를 입력하고 그것을 전송하면 submit 이벤트가 발생하는데, 그때 어떤 일이 일어나길 바라는지 정의하는 것.
→ app.js에 다음의 코드 추가 + 5초 지연메시지는 지우기
const messageList = document.querySelector("ul");
const messageForm = document.querySelector("form");
function handleSubmit(event){
event.preventDefault();
const input = messageForm.querySelector("input");
socket.send(input.value);
input.value = "";
}
messageForm.addEventListener("submit", handleSubmit);
<전체코드>
const messageList = document.querySelector("ul");
const messageForm = document.querySelector("form");
const socket = new WebSocket(`ws://${window.location.host}`);
//연결되었을 때
socket.addEventListener("open", () => {
console.log("Connected to Server");
})
//메시지가 전달되었을 때
socket.addEventListener("message", (message) => {
console.log("Just got this:", message.data, "from the server");
})
//서버가 오프라인 되었을 때
socket.addEventListener("close", () => {
console.log("Disconnected from Server");
})
function handleSubmit(event){
event.preventDefault();
const input = messageForm.querySelector("input");
socket.send(input.value);
input.value = "";
}
messageForm.addEventListener("submit", handleSubmit);
설명 : querySelector 메서드를 이용하여 목록(messageList)과 입력 폼(messageForm)을 선택.
handleSubmit이라는 함수를 만들어 입력 폼에서 submit 이벤트가 발생했을 때 동작하도록 설정.
input박스의 값을 받아서 input변수에 담고 soket으로 보냄 / input박스의 값 초기화.
<결과화면>
send 클릭 시
값이 초기화 되고
터미널에 메시지 값이 찍힌다.
why?) 우리가 server.js에서 메시지가 보내졌을 때, 콘솔(터미널)에 그 내용을 출력하도록 코드를 추가해뒀기 때문.
server.js
1-3) 메시지 돌려받기
서버에서 메시지를 받으면 브라우저에서는 확인할 길이 없음 ( 서버의 콘솔(터미널)을 통해서만 확인 가능한 상황 ㅠ)
따라서 서버에서 메시지를 받으면, 같은 메시지를 도로 브라우저로 전송해주는 기능 추가해보자.
server.js를 다음과 같이 수정하자.
wss.on("connection", (socket) => {
// console.log(socket);
// 브라우저가 연결됐을 때.
console.log("Connected to Browser");
//브라우저가 종료되었을 때를 의미하는 close
socket.on("close", () => console.log("Disconnected from Browser"));
//브라우저에서 서버로 메시지가 보내졌을때, 콘솔(터미널)에 그 내용을 출력
socket.on("message", (message) => {
//console.log(`${message}`)
socket.send(`${message}`);
});
//브라우저로 메시지 전송 테스트
//socket.send("hello!!");
})
console.log를 socket.send로 정정했음
<결과 화면>
그 결과 브라우저의 콘솔을 확인해보면
서버가 받은 메시지도 브라우저 콘솔창에서 확인할 수 있게 되었다!
2. 사용자 간 채팅하기
2-1) 서로 다른 탭에서 메시지 보내기
사실 채팅앱은 2명의 다른 사용자가 메시지를 주고받아야 비로소 의미가 있는 것임을 우리는 알고있다.
따라서 브라우저 탭을 2개 열고 같은 주소에 접속하고 , 각각 test1/test2메시지를 전송해보자.
이처럼 각각 독립적으로 서버가 구동되어짐을 확인할 수 있다.
여기서 핵심은 '독립적'으로 각각의 서버가 돌아가면 안되고
'상호적'으로 메시지를 주고받아야 한다.
→ 서로 다른 사용자 간의 메시지를 교환할 수 있게 만들려면, 우선 누가 연결되었는지를 알아야함.
결론 : 임시방편으로 가짜 데이터베이스를 만들어 이를 활용해볼 예정
→ server.js을 다음과 같이 수정
wss.on("connection", (socket) => {
socket.push(socket);
// 브라우저가 연결됐을 때.
console.log("Connected to Browser");
//브라우저가 종료되었을 때를 의미하는 close
socket.on("close", () => console.log("Disconnected from Browser"));
//브라우저에서 서버로 메시지가 보내졌을때, 콘솔(터미널)에 그 내용을 출력
socket.on("message", (message) => {
//console.log(`${message}`)
socket.send(`${message}`);
});
//브라우저로 메시지 전송 테스트
//socket.send("hello!!");
})
설명 : socket이라는 배열을 만들고,
웹소켓 서버에 connection이라는 이벤트가 발생할 때 마다, 이 배열에 생성된 소켓이 push를 통해 추가될 예정.
→ 사용자별로 socket을 sokets라는 배열에 담아 관리한다는 뜻. ( 그럼 각각의 socket별로 데이터 주고받기 가능 )
다음의 코드 추가
//브라우저에서 서버로 메시지가 보내졌을때, 콘솔(터미널)에 그 내용을 출력
socket.on("message", (message) => {
sockets.forEach(aSocket => aSocket.send(`${message}`));
});
forEach는 반복문. 따라서 message이벤트가 발생할 때 마다 sockets에 들어있는 모든 socket에 접근하여 메시지 보냄
→ 결론 : 한 사용자가 보낸 메시지를 서버에 접속한 다른 모든 사용자가 받을 수 있다는 뜻.
<결과 화면>
웹브라우저 1로 test1을 전송해도 웹브라우저 2에서 test1을 확인할 수 있음
두 번째 웹브라우저에 test1이 잘 출력됨을 확인할 수 있다.
물론 첫 번째 웹브라우저에서도 test1이 잘 출력됨.
2-2) app.js 수정하기
언제까지나 콘솔창으로 메시지를 확인할 수는 없다! → 메시지를 화면에 보여주도록 프론트 수정 필요!
메시지가 전송될 때 마다, li요소를 만들어주고 그 안에 메시지를 적은 다음, li를 ul 안으로 넣어줄 예정.
다음과 같이 app.js수정
//서버로부터 메시지를 받았을 때 때
socket.addEventListener("message", (message) => {
const li = document.createElement("li");
li.innerText = message.data;
messageList.append(li);
})
설명 : 이미 사용한 message 이벤트 처리함수를 수정하여 li요소 생성 후, 거기에 message를 담음.
메시지를 담은 li를 messageList에 append로 붙히는데 이 messageList는 우리가 이전에 ul을 탐색하는 변수로 설정해뒀음
const messageList = document.querySelector("ul");
따라서 결국 ul에 li가 담기게 되는 것임.
<결과 화면>
ㅎㅇ를 텍스트박스에 적어 send 누르면 li형식으로 ㅎㅇ가 화면에 잘 출력됨을 확인할 수 있음.
※ 다만 브라우저 1의 채팅내용은 브라우저 2의 화면에도 출력됨....
우리는 브라우저 - 서버 - 모든 브라우저로 데이터를 뿌리는 flow를 따르고 있음.
3. 닉네임 추가하기
각각의 사용자별로 구별수단(key값)이 필요. → 닉네임 부여해보자
3-1) home.pug의 코드에 닉네임 폼을 추가하자.
<home.pug 전체 코드>
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Zoom
//MVP.css는 우리가 태그에 class나 id같은 특성을 추가하지 않아도 자동으로 스타일을 적용해주는 라이브러리다.
body
//h1 It works!
header
h1 Zoom
main
form#nick
input(type="text", placeholder="choose a nickname", required)
button Save
ul
form#message
input(type="text", placeholder="write a msg", required)
button Send
script(src="/public/js/app.js")
3-2) app.js에 새로운 폼에대한 코드 추가하기
<app.js 전체코드>
const messageList = document.querySelector("ul");
const nickForm = document.querySelector("#nick");
const messageForm = document.querySelector("#message");
const socket = new WebSocket(`ws://${window.location.host}`);
//연결되었을 때
socket.addEventListener("open", () => {
console.log("Connected to Server");
})
//서버로부터 메시지를 받았을 때 때
socket.addEventListener("message", (message) => {
const li = document.createElement("li");
li.innerText = message.data;
messageList.append(li);
})
//서버가 오프라인 되었을 때
socket.addEventListener("close", () => {
console.log("Disconnected from Server");
})
//메시지 이벤트 처리
function handleSubmit(event){
event.preventDefault();
const input = messageForm.querySelector("input");
socket.send(input.value);
input.value = "";
}
//닉네임 이벤트처리
function handleNickSubmit(event){
event.preventDefault();
const input = nickForm.querySelector("input");
socket.send(input.value);
input.value = "";
}
//서버로 메시지 보내기
messageForm.addEventListener("submit", handleSubmit);
설명 : 기존 메시지 폼에서 메시지가 입력되면 submit 이벤트가 발생해 handleSubmit을 호출하는 것처럼,
닉네임 폼에서 닉네임이 입력되면 submit 이벤트가 발생해 handleNickSubmit을 호출하도록 했음.
<결과화면>
4. 닉네임과 메시지 구별하기
닉네임과 메시지는 엄연히 다른 정보라 구별을 반드시 해줘야함!
→ 입력값을 뚜렷하게 구분하기 위해서 JSON데이터를 만들어서 거기에 입력값을 포함시키고 추가 정보를 더 넣어줄 예정
4-1) app.js 코드 수정 ( JSON처리 )
//닉네임 이벤트처리
function handleNickSubmit(event){
event.preventDefault();
const input = nickForm.querySelector("input");
socket.send({
type: "nickname",
payload: input.value
});
input.value = "";
}
설명 : JSON에 키를 2개 추가해서 전송했음
이 데이터가 어떤 유형인지 나타내는 'type'과 실제 값을 의미하는 'payload' 2가지로 구성됨.
4-2) JSON 문자열로 변환하기
JSON은 객체형태로 전송되니 이를 문자열로 변환해야 우리가 원하는 데이터작업이 가능함.
- JSON을 문자열로 변환할 때는 JSON.stringify메서드를 사용
- 문자열을 JSON으로 변환할 때는 JSON.parse 메서드를 사용
4-2-1) app.js에서 JSON을 문자열로 변환하기
<app.js 변경 코드>
//JSON을 문자열로 변환하기
function makeMessage(type, payload){
const msg = { type, payload};
return JSON.stringify(msg);
}
//메시지 이벤트 처리
function handleSubmit(event){
event.preventDefault();
const input = messageForm.querySelector("input");
socket.send(makeMessage("new_message", input.value));
input.value = "";
}
//닉네임 이벤트처리
function handleNickSubmit(event){
event.preventDefault();
const input = nickForm.querySelector("input");
socket.send(makeMessage("nickname", input.value));
input.value = "";
}
설명 : makeMessage 함수는 입력값 유형(type)과 입력값(payload)을 인자로 전달받아서 그것들로 이루어진 JSON을 만든 뒤 문자열 형태로 변환하는 역할을 함.
<전체 코드>
const messageList = document.querySelector("ul");
const nickForm = document.querySelector("#nick");
const messageForm = document.querySelector("#message");
const socket = new WebSocket(`ws://${window.location.host}`);
//JSON을 문자열로 변환하기
function makeMessage(type, payload){
const msg = { type, payload};
return JSON.stringify(msg);
}
//연결되었을 때
socket.addEventListener("open", () => {
console.log("Connected to Server");
})
//서버로부터 메시지를 받았을 때 때
socket.addEventListener("message", (message) => {
const li = document.createElement("li");
li.innerText = message.data;
messageList.append(li);
})
//서버가 오프라인 되었을 때
socket.addEventListener("close", () => {
console.log("Disconnected from Server");
})
//메시지 이벤트 처리
function handleSubmit(event){
event.preventDefault();
const input = messageForm.querySelector("input");
socket.send(makeMessage("new_message", input.value));
input.value = "";
}
//닉네임 이벤트처리
function handleNickSubmit(event){
event.preventDefault();
const input = nickForm.querySelector("input");
socket.send(makeMessage("nickname", input.value));
input.value = "";
}
//서버로 메시지 보내기
messageForm.addEventListener("submit", handleSubmit);
nickForm.addEventListener("submit", handleNickSubmit);
<테스트 및 결과 화면>
이렇게 닉네임과 메시지를 각각 save/send하면
다음과 같이 타입이 구분되어 JSON형식으로 전송된다.
4-2-2) app.js에서 문자열을 JSON으로 변환하기
server.js 코드를 다음과 같이 수정
//브라우저에서 서버로 메시지가 보내졌을때, 콘솔(터미널)에 그 내용을 출력
socket.on("message", (msg) => {
const message = JSON.parse(msg);
console.log(message.type, message.payload);
sockets.forEach(aSocket => aSocket.send(`${message}`));
});
설명 : 데이터를 JSON으로 잘 전달받아 JSON.parse를 이용해 콘솔(터미널)에 잘 출력하고,
다시 전체 socket에 뿌려주고 있음 ( 문자열 → JSON )
<결과화면>
이렇게 각각의 값을 입력 시
콘솔에 잘 입력됨.
다만 json으로 변환한 상태에서 다시 브라우저로 그 값을 쏴주는데, 객체상태로 넘어가므로
json형태로 넘어옴... 다시 문자열 변환 과정 필요..
서버 사이드에서 브라우저 사이드로 재전송시에는 문자열로 변환하는 과정이 반드시 필요할것 이해하기!