[포스코x코딩온] 웹 풀스택 과정 9주차 회고 | Socket

이원노·2023년 8월 28일
0

지난 시간까지 복습을 마치고 이번주부터 소켓에 대해 배우게 됐다.

1. TCP/IP

1. TCP

TCP는 데이터를 신뢰성있게 전송하기 위한 프로토콜을 말한다.

  • 신뢰성 : 데이터의 손실, 손상 최소화하고 데이터 순서 보장
  • 연결지향 : 데이터를 주고받기 전 송수신자간에 연결
  • 흐름제어 : 데이터의 흐름을 제어하여 수신자가 처리할 수 있는 속도에 맞춰 데이터 전송
  • 혼잡제어 : 네트워크 혼잡 상태를 감지하고 조절하여 네트워크 성능 유지

2. IP

인터넷상에서 데이터를 주고받기 위한 통신 규약

  • 패킷기반 : 데이터를 작은 패킷 단위로 나누어 전송
  • 비연결성 : 패킷은 독립적으로 처리되며, 수신자와 직접적인 연결이 필요하지 않음
  • 라우팅 : 각 라우터가 패킷의 경로를 결정하여 목적지까지 전달
  • IP주소 : IP는 각 컴퓨터를 식별하기 위한 IP 주소를 사용

3. TCP/IP 4계층

4. UDP (User Datagram Protocol)

  • UDP는 비연결성 프로토콜로, 데이터의 신뢰성은 낮지만 속도가 빠르고 간편하다.
  • 데이터 단위로 데이터 전송
  • 순서 보장 및 데이터 신뢰성 보장 X
  • 흐름 제어나 혼잡 제어 메커니즘이 없어 오버헤드가 작음
  • VOIP, 스트리밍, 온라인 게임 등

2. 소켓

소켓은 프로세스가 네트워크로 데이터를 내보내거나 데이터를 받기 위한 실제적인 창구역할을 한다.

  • 서버와 클라이언트 연결
  • 서버 : 클라이언트 소켓의 연결 요청을 대기, 요청이 오면 클라이언트 소켓을 생성해 통신을 가능하게 함
  • 클라이언트 : 실제로 데이터 송수신이 일어나는 곳
  • 소켓은 프로토콜, IP주소, 포트넘버로 정의된다.
  • TCP와 UDP 프로토콜을 사용하여 데이터 전송

1. 소켓프로그래밍 흐름

1) 서버

  • socket() : 소켓 생성함수
  • bind() : ip와 port 번호 설정 함수
  • listen() : 클라이언트 요청에 수신 대기열을 만드는 함수
  • accept() : 클라이언트와 연결을 기다리는 함수

2) 클라이언트

  • socket() : 소켓을 여는 함수
  • connect() : 통신할 서버의 설정된 ip와 port 번호에 통신을 시도하는 함수
  • 통신 시도 시, 서버가 accept()함수를 이용해 클라이언트의 socket decriptor를 반환
  • 클라이언트와 서버가 서로 read(), write()반복하며 통신

3. Web Socket

1. HTTP vs WebSocket

2. Web Socket

양방향 소통을 위한 프로토콜

  • HTML5 웹 표준 기술
  • 빠르게 작동하며 통신할 때 아주 적은 데이터 이용
  • 이번트를 단순히 듣고, 보내는 것만 가능
  • HandShake : 클라이언트가 서버로 웹소켓 연결을 요청할 때, 서버와 클라이언트 간 초기 HandShake가 이루어진다. HandShake 과정을 통해 웹소켓 연결.

3. Web Socket 이벤트

  • open : 웹소켓 연결이 성공적으로 열렸을 때 발생
  • message : 웹소켓을 통해 데이터를 주고받을 때 발생
  • error : 웹소켓 연결 중 오류가 발생했을 때 발생. 연결 실패, 통신 오류 등
  • close : 웹소켓 연결이 종료되었을 때 발생

4. 서버

브라우저 환경에서 웹소켓 API를 사용하여 웹소켓 클라이언트를 만들 수 있지만, 서버를 만들려면 별도의 라이브러리나 모듈이 필요하다.

npm install ws
https://www.npmjs.com/package/ws

1. ws 모듈 이벤트

  • connection : 클라이언트가 웹소켓 서버에 연결되었을 때 발생
  • message : 클라이언트로부터 메시지를 받았을 때 발생
  • error : 웹소켓 연결 중 오류가 발생했을 때 발생
  • close : 클라이언트와의 연결이 종료되었을 때 발생

5. 실습

웹소켓을 이용하여 실시간으로 메시지를 주고 받는 서버와 채팅기능을 만드는 실습이다. 사용자의 이름과 채팅내용을 입력 받아서 서버로 전송하고, 서버에서는 메시지를 전송한 사용자 외에 다른 사용자에게도 메시지를 전송한다.

server.js

const express = require("express");
const app = express();
const ws = require("ws");
const PORT = 8000;

app.set("view engine", "ejs");
app.use(express.urlencoded({extended:true}));
app.use(express.json());

app.get('/', (req, res)=>{
    res.render("client");
})

const server = app.listen(PORT, ()=>{
    console.log(`http://127.0.0.1:${PORT}`);
})

// 웹소켓 서버 접속
const wss = new ws.Server({server});

// 브라우저들을(클라이언트) 담을 변수
const sockets = [];

// socket 변수는 접속한 브라우저
wss.on('connection', (socket)=>{
    console.log("클라이언트가 연결되었습니다.");
    sockets.push(socket);

    
    // 메시지 이벤트
    socket.on('message', (message)=>{
        console.log(`클라이언트로부터 받은 메시지: ${message}`);
        sockets.forEach(element => {
            element.send(`${message}`);
        });
        
    })

    // 오류 이벤트
    socket.on('error', (error)=>{
        console.log(`에러가 발생했습니다: ${error}`);
    })

    // 접속종료 이벤트
    socket.on('close', (close)=>{
        console.log(`클라이언트와 연결이 종료되었습니다: ${close}`);
    })
})

client.ejs

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>클라이언트</title>
</head>
<body>
    <form id="chat">
        <input type="text" id="name" placeholder="이름"><br />
        <input type="text" id="msg" placeholder="메시지"><br />
        <button type="submit">전송</button>
    </form>

    <ul></ul>

    <script>
        const form = document.getElementById("chat");
        const ul = document.querySelector("ul");

        // 접속할 주소 입력 (ws://127.0.0.1:8000)
        const socket = new WebSocket('ws://127.0.0.1:8000');

        // 서버에 연결 요청
        socket.addEventListener('open', (event)=>{
            // send()는 서버로 메시지를 전송
            console.log("서버에 연결되었습니다", event);
        })

        // 메시지 주고 받음
        socket.addEventListener("message", (event)=>{
            const evt = JSON.parse(event.data);
            const li = document.createElement("li");
            li.textContent = evt.name + " : " + evt.msg;
            ul.append(li);
        })

        // 오류처리
        socket.addEventListener("error", (event)=>{
            console.log(`오류가 발생하였습니다: ${event.error}`);
        })

        // 종료이벤트
        socket.addEventListener("close", (event)=>{
            console.log(`서버와 연결이 종료되었습니다: ${event}`);
        })

        ////////////////////////// 폼 이벤트 //////////////////////////
        form.addEventListener('submit', (event)=>{
            event.preventDefault();
            const name = form.querySelector("#name");
            const msg = form.querySelector("#msg");
            socket.send(JSON.stringify({name:name.value, msg:msg.value}));
            msg.value = '';
        })
    </script>
</body>
</html>

클라이언트에서 서버로 메시지를 전송할 때, 이름이나 채팅내용 단일 값이었으면 그냥 문자열로 보내줘도 됐지만, 이름, 채팅내용을 한번에 보내기 위해 JSON 객체로 만들었다.

하지만 그래도 서버로 전송하면 [object, object]로 출력되기만 할 뿐, 전송한 데이터가 나오지 않았다.

해결방법은 JSON.stringify()를 이용해 JSON객체를 문자열로 변환해 전송하는 방법이었다. JSON.stringify를 이용해 문자열로 변환하고, 서버에서는 그 문자열을 그대로 다시 클라이언트로 전송한다.

마지막으로 클라이언트 socket.addEventListener("message", (event)=>{} 부분에서 메시지를 받을 때, JSON.parse() 메서드를 이용해 문자열을 다시 JSON객체로 변환해 이용할 수 있도록 하였다.

6. 정리

소켓을 이용하면 실시간 통신이 가능하다. 실시간으로 데이터를 주고 받을 수 있으므로, 채팅기능을 구현하기에 적합하다. 소켓에 대한 일부 지식과 웹소켓을 이용하는 실습을 통해 채팅기능을 구현해봄으로써 추후에 이러한 서비스를 만들게 된다면, 오늘 배웠던 내용을 이용하면 좋을 것 같다. 특히 서버로 데이터를 보낼 때, JSON 객체 자체를 보내는 게 아니라 문자열로 변환해 보내야한다는 사실이 새로웠다.

0개의 댓글