[SeSACX코딩온] Socket

JUHEE·2024년 4월 12일
0

SeSACX코딩온

목록 보기
21/26

- 오늘 Socket을 이용한 실시간 통신 기능을 배우고 사용해보았다
- socket은 프로토콜의 일종으로 통신규약이라고 한다
- websocket과 socket.io 두 종류를 사용해보았는데 socket.io가 더편리했다

TCP/IP

  • tcp/ip는 데이터 통신을 위한 프로토콜으로 네트워크간 데이터 교환을 가능하게 하는 기술이다
    - 데이터를 분할하여 전송하며 정확한 전송 보장, 데이터 경로 지정 등의 역할을 한다
    - TCP: 데이터를 신뢰성있게 전송하기 위한 프로토콜, 신뢰성, 연결지향, 흐름제어, 혼잡제어 등의 특징을 가진다
    - IP: 데이터를 주고받기 위한 통신규약, 패킷기반, 비연결성(패킷의 독립적 처리), 라우팅, ip주소로 컴퓨터 식별
    - tcp/ip 4계층: 네트워크 계층 > 인터넷 계층 > 전송계층 > 응용계층
    - UDP: user datagram protocal, 비연결성 프로토콜, 신뢰성은 낮지만 속도가 빠르고 간편하다. 데이터 그램단위 데이터 전송, 작업이 단순, 흐름 및 혼잡 제어 메커니즘이 없어 오버헤드가 적다

Socket

- 프로세스가 네트워크로 데이터를 내보내거나 데이터를 받기 위한 실제적인 창구역할을 하는 것
- 서버와 클라이언트를 연결해주는 도구로써 인터페이스 역할을 한다.
- 프로토콜, ip주소, 포트넘버로 정의된다
- tcp와 udp프로토콜을 사용하여 데이터를 전송한다
- 서버소켓(클라이언트 요청받음), 클라이언트 소켓(서버에 연결 요청, 수락시 데이터 주고받음), 포트(통신 시 사용되는 식별자)
- 프로그래밍 흐름은 아래와 같다

WebSocket

- 양방향 소통을 위한 프로토콜로 html5이상에서 사용가능하다
- Handshake: 클라이언트가 서버로 웹소켓 연결을 요청할때 서버와 클라이언트간에 초기 핸드셰이크가 이루어지며 이 과정을 통해 웹소켓이 연결된다
- 클라이언트에서는 기본제공되는 websocket을 사용하고, 서버에서는 ws모듈을 따로 설치하여 사용해야한다

server

- npm install ws설치
- 1) ws모듈을 가져온다
- 2) app.listen 서버를 server라는 형태로 저장한다
- 3) new ws.Server({생성한 server를 넣고}) wsServer를 생성한다
- 4) 모든 client를 담을 배열 sockets[]를 생성한다
- 5) 고유한 id값을 주기위해 랜덤 문자열을 반환하는 함수를 작성한다
- 6) wsServer.on("connection이벤트",(socket)=>{}) 연결 이벤트를 걸어 server와 client를 연결한다. 콜백함수는 각 연결된 한개의 클라이언트를 인자로 받아온다
- 7) 콜백함수의 실행문 내부에서 연결된 이후 클라이언트와 진행할 코드를 socket.on("이벤트",()=>{})의 형태로 작성한다
- 이벤트의 종류
• connection: 클라이언트가 웹소켓 서버에 연결되었을 때 발생. 이 이벤트의 콜백 함수는 새로운 클라이언트 연결마다 실행
• message: 클라이언트로부터 메시지를 받았을 때 발생
• error: 웹소켓 연결 중 오류가 발생했을 때 발생
• close: 클라이언트와의 연결이 종료되었을 때 발생
- 실습 코드

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

// 현재 시간관련 타임스탬프와 랜덤 문자열을 결함 > 고유 식별자 생성
function generateUniqueId() {
  // 타임스탬프
  const timestamp = Date.now().toString(36); //36진수로 반환한 문자열
  // 랜덤문자열
  const randomString = Math.random().toString(36).substring(2); //subString(a) a번 인덱스부터 끝까지 반환
  return timestamp + randomString;
}

wsServer.on("connection", (socket) => {
  console.log("connection!?!!?!??!");
  const clientId = generateUniqueId();
  sockets.push(socket);
  socket.on("message", (message) => {
    console.log(`${message}`); //버퍼 객체

    //sockets 배열 순회
    sockets.forEach((el) => {
      el.send(`${message}`);
    });
  });

  socket.on("error", () => {
    console.log("에러가 났어요", err);
  });
  socket.on("close", () => {
    console.log(`클라이언트(${clientId})와 연결이 종료되었어요`);
  });
});

client

- WebSocket의 이벤트
• open: 웹소켓 연결이 성공적으로 열렸을 때 발생
• message: 웹소켓을 통해 데이터를 주고받을 때 발생
• error: 웹소켓 연결 중 오류가 발생했을 때 발생. 연결 실패, 통신 오류 등이 해당
• close: 웹소켓 연결이 종료되었을 때 발생
- 1) 기본제공되는 WebSocket을 사용하여 socket객체를 생성한다
- 2) socket.addEventListener("이벤트", ()=>{})를 통해 작동가능
- 3) 브라우저의 이벤트(click, submit 등)실행 시 내부에서 socket.send()메소드를 통해 데이터 전송가능
- 4) server는 socket.on()의 두번째 인자로 콜백함수를 받아 클라이언트에서 보낸 메시지를 확인할 수 있다. 다만 버퍼 객체로 오기 때문에 toString("utf8")을 사용하거나 템플릿 리터럴로 작성해야 문자열 형태로 출력할 수 있다
- 5) 콜백함수내부에서 send()를 사용하여 클라이언트로 다시 메시지를 보낼 수 있다(응답)
- 6) 클라이언트는 socket.addEventListen()로 해당 응답을 받아 이벤트함수를 인자로 가지는 콜백함수에서 e.data로 응답메시지에 접근할 수 있다. server와 client의 이벤트이름은 동일해야한다
- 7) 데이터로 배열 데이터는 전송이 불가능하기 때문에 전송할 때 json형태로 변환, 응답을 받을 때 다시 배열형태로 변환하는 과정이 필요하다
- 실습코드: 실행은 chatForm.addEventListener -> server -> socket.addEventListener 순서이다

const socket = new WebSocket("ws://localhost:8080");
 socket.addEventListener("message", (e) => {
   // console.log("서버에서 받은 메시지: ", e.data); //서버에서 준 메시지
   //string에서 obj로 parsing
   const data = JSON.parse(e.data);
   // console.log(data); //obj
   const li = document.createElement("li");
   // 이름: 내용
   li.textContent = `${data.name}: ${data.msg}`;
   ul.append(li);
      });
const chatForm = document.getElementById("chat");
const ul = document.querySelector("ul");

chatForm.addEventListener("submit", (e) => {
  e.preventDefault(); //새로고침 막기
  const msg = chatForm.querySelector("#message");
  const name = chatForm.querySelector("#name");
  const chatData = {
    msg: msg.value,
    name: name.value,
  };
  // socket.send(chatData); //obj -> string작업필요 JSON형태로 변경
  const chatDataStr = JSON.stringify(chatData);
  // console.log(chatDataStr);
  socket.send(chatDataStr);

  //전송 후 빈값 만들기
  msg.value = "";
  name.value = "";
});

Socket.io

- socket.io의 서버는 http기반 서버여야하기 때문에, http모듈을 이용해서 io서버를 생성한다
- npm install socket.io를 통해 모듈을 다운받아 사용할 수 있다
- 이벤트 기반, 자동 재연결의 특징을 가진다
- 기본 이벤트
• connection:
클라이언트가 서버에 연결되었을 때 발생.
클라이언트와의 상호작용을 초기화하거나 초기 데이터를 전달할 수 있음
• disconnect: 클라이언트가 연결을 해제했을 때 발생
• disconnecting: 클라이언트가 연결을 해제하려는 경우에 발생
• error: 연결 중에 오류가 발생했을 때 발생

server

- 1) 모듈을 불러오고 http로 생성한 server를 담아 io서버 객체 생성

const express = require("express");
const http = require("http");
const app = express();
const socketIO = require("socket.io");
const server = http.createServer(app);
//io생성
const io = socketIO(server);

- 2) io.on으로 connection이벤트와 socket(단일 클라이언트)를 인자로 받는 콜백함수를 받아준다

io.on("connection", (socket) => {
  //여기서 모든 코드 작성 예정
})

- 3) socket.io는 기본으로 클라이언트의 id를 제공하기 때문에 socket.id로 접근할 수 있다
- 4) socket.on("이벤트",(인자, cb)=>{
//이벤트이름은 사용자 뜻대로 작성하지만 클라이언트와 서버에서 동일해야 전달된다
//인자는 클라이언트에서 전달하는 데이터를 의미(개수 달라질 수 있다)
//cb는 callback함수로 이벤트를 전달한 클라이언트에 응답메시지를 전송할 수 있다
})
- 아래의 코드는 클라이언트에서 new_message 라는 이름의 이벤트를 받아온다

//server
socket.on("new_message", (message, cb) => {
  console.log(message);
  cb(message); //한 개의 클라이언트에게만 전송
});

- 모든 클라이언트에게 전송하고 싶으면 socket이 아니라 io.emit()으로 전송하면 된다

socket.on("new_message", (message) => {
  io.emit("message_render", message); //모든 클라이언트에게 전송
});

client

- head에 script를 추가해준다

<script src="/socket.io/socket.io.js"></script>

- script에서 const socket = io();로 곧바로 사용가능하다
- 데이터 전송은 socket.emit("이벤트이름",데이터,(응답데이터)=>{})형태로 전송한다(데이터, 콜백은 생략가능, 이벤트명은 필수!!!!)
- 아래코드는 form이 제출되면 배열 데이터를 담아 new_message라는 이름의 이벤트로 전송한다
- res는 server에서 cb함수에 담아 보낸 응답 데이터를 가져온다

chatForm.addEventListener("submit", (e) => {
  e.preventDefault();
  const user = chatForm.querySelector("#user");
  const message = chatForm.querySelector("#message");

  const data = {
    name: user.value,
    message: message.value,
  };

  socket.emit("new_message", data, (res) => {
    console.log(res); //{name: 'juhee', message: 'hi'}
  });
});
보낼때는 emit 받을 때는 on 이벤트명은 동일!

Room

- socket.io는 room이라는 개념을 이용해서 채팅방을 구현하고 클라이언트들이 해당 방에 선택적으로 참여하도록 설정할 수 있다
- room관련 함수

- 1) join을 통해 room생성, 키에 룸이름 저장(사용자 지정)

//client
const chattingRoom = prompt("채팅방 이름을 입력해주세요");

const h2 = document.createElement("h2");
h2.innerText = `채팅방 ${chattingRoom}`;
document.body.prepend(h2);

// 1. 클라이언트에서 서버에게, 방이름 전달
socket.emit("join", chattingRoom);

//server
// 2. 클라이언트에게 방이름을 전달받아서 방 생성
socket.on("join", (chatRoom) => {
  //여기서 room관련 코드 작성
  socket.join(chatRoom); 
  socket.room = chatRoom; //이름저장
  //참고: 나를 제외한 사람에게 전송시 socket.broadcast.to(chatRoom).~
  io.to(chatRoom).emit("userjoin", `${socket.id}님이 입장하셨습니다`);
}

- 2) server에서 전송한 입장메시지 띄우기

socket.on("userjoin", (notice) => {
  const p = document.createElement("p");
  p.textContent = notice;
  users.append(p);
  // 2초후 입장 문구 없애기
  setTimeout(() => {
    p.remove();
  }, 2000);
});

- 3) emit으로 서버에 메시지 전달

chatForm.addEventListener("submit", (e) => {
	//여기에 emit 전달 코드 작성함
});

\ -4) server는 받은 메시지를 해당 방(room)의 모든 사용자에게 전달

socket.on("message", (message) => {
  console.log(message);
  //socket.room은 위에서 저장한 room이름
  io.to(socket.room).emit("message_toAll", message, socket.id);
});

- 5) 메시지를 받아서 화면에 보여줌 아래코드는 socket.id를 사용해 내 채팅과 상대방 채팅에 각각 다른 class를 적용해준 것

socket.on("message_toAll", (message, id) => {
  //id를 socket.id와 비교
  const p = document.createElement("p");
  p.textContent = `${id}: ${message}`;
  if (id === socket.id) {
    //나는 노란색
    p.classList.add("me");
  } else {
    p.classList.add("you");
  }
  space.append(p);
});

- socket.io를 활용한 채팅 기능 실습영상

profile
초보개발자

0개의 댓글