ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2019-09-04 개발일지
    개발일지 2019. 9. 4. 18:59

    아직 기획이 안나와서 공부 중이다.

     

    그래서 개인 프로젝트를 어떻게 해야할 지 좋은 아이디어도 안떠오르고 해서 언젠가 한번 공부해야지 했던 소켓을 학습하는 시간을 가졌다.

     

    Socket.IO란?

    소켓은 브라우저와 서버간의 실시간 양방향 및 이벤트 기반 통신을 가능하게 해주는 라이브러리다.

     

    Socket.IO 특징

    • 신뢰성
      • 프록시 및 로드 밸런스 환경에서도 연결이 가능하다.
      • 개인 방화벽 및 바이러스 백신 소프트웨어로부터 신뢰를 얻을 수 있다.
    • 자동 재 연결 지원
      • 별도의 설정이 없는 한 연결이 끊긴 클라이언트는 서버에 다시 연결할 수 있다.
    • 연결 해제 감지
      • HeartBeat 메커니즘으로 서버와 클라이언트 모두 어느 한 쪽이 응답이 없을 때를 알 수 있다.
    • 바이너리 지원
      • 브라우저의 ArrayBuffer 및 Blob에 대하여 직렬화 가능한 데이터 구조를 생성할 수 있다.
      • Node.js의 ArrayBuffer 및 buffer에 대하여 직렬화 가능한 데이터 구조를 생성할 수 있다.
    • 멀티 플렉싱 지원
      • 네임 스페이스를 지원하여 각각의 통신 채널을 지원한다.
    • Room(채널) 지원
      • 각 네임 스페이스내에서 join 및 leave가 가능한 임의의 채널을 정의할 수 있다.

     

    주의사항

    필요한 경우 WebSocket으로 전송하는 것이 가능하지만 Socket.IO는 WebSocket을 구현한 것은 아니다. 

     

    참고:

    https://socket.io/docs/

     

    Socket.IO — Docs

    What Socket.IO isSocket.IO is a library that enables real-time, bidirectional and event-based communication between the browser and the server. It consists of: a Node.js server: Source | API a Javasc

    socket.io

     

    위의 내용으로 채팅 시스템을 만들어보자.

     

    가장 먼저 접속 시 화면이다.

    사용자를 구분할 수 있게 익명일 경우 난수를 붙여준다

    화면에 접속하면 닉네임을 설정한다.

    만약 닉네임을 정하고 싶지 않을 경우 주어진 익명 닉네임을 사용할 수 있다.

     

    시스템은 회색 바탕, 나는 노란색 바탕, 상대방은 파란색 바탕으로 표현된다.

    닉네임을 설정하면 채팅방에 입장이 되고, 메시지를 주고받을 수 있다.

     

    위의 컨셉을 기반으로 화면을 만들어보자!

    html
        head
            title
            style.
                * { width: 500px; margin: 0 auto; padding: 0; box-sizing: border-box; }
                body { font: 13px Helvetica, Arial; }
                #room { height: 70%; margin-top: 100px; border: 1px solid black; border-radius: 10px 10px 0px 0px; }
                #title { height: 5%; padding-top: 5px; padding-left: 10px; font-size: 2em; font-weight: bold; border-bottom: 1px solid black; }
                #messages { list-style-type: none; height: 95%; margin: 0; padding: 0; overflow: hidden; }
                #messages:hover { overflow-y: scroll; }
                #messages li { width: 90%; padding: 5px 10px; }
                .system { background: #979797; color: white; }
                .me { background: #fdc635; }
                .other { background: #80d8e8; }
                #send-box { background: #000; padding: 3px; bottom: 0px; }
                #send-box input { width: 90%; border: 0; padding: 10px; margin-right: .5%; }
                #send-box button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
        body
            div(id = 'room')
                p(id = 'title') 채팅방
                ul(id = 'messages')
                div(id = 'send-box') 
                    input(type = "text" id='m', autocomplete='off')
                    button(id = 'send-btn') send

     

    템플릿은 pug 템플릿 엔진을 사용한다.

    위의 코드로 기본적인 디자인을 잡았다.

     

    그럼 다음은 서버 코드를 작성해보자.

    mySocket.ts 이름으로 파일을 하나 만들고 아래의 내용을 작성하자.

    import socketIO from 'socket.io';
    import http from 'http';
    
    class MySocket {
        socket: socketIO.Server;
    
        constructor(server: http.Server) {
            this.socket = socketIO(server);
        }
    
        connect() {
            // 소켓 연결 이벤트 캐치
            this.socket.sockets.on('connection', socket => {
                let user: string = '';
                
                // 신규 사용자 접속 이벤트 캐치
                socket.on('newUser', name => {
                    console.log(`system    : ${name}사용자가 접속했습니다.`);
    
                    user = name;
    
                    this.socket.sockets.emit('update', {
                        type: 'connect',
                        name: 'system',
                        message: `${name}님이 접속하였습니다.`
                    });
                });
    
                // 클라이언트 메시지 발송 이벤트 캐치
                socket.on('message', data => {
                    user = data.name;
                    console.log('data :', data);
    
                    this.socket.sockets.emit('update', data);
                });
    
                // 접속 종료 이벤트 캐치
                socket.on('disconnect', () => {
                    console.log(`system    : ${user}님이 접속을 종료했습니다.`);
                    socket.boardcast.emit('update', {
                        type: 'disconnect',
                        name: 'system',
                        message: `${user}님이 접속을 종료했습니다.`
                    });
                });
            });
        }
    }
    
    export default MySocket;
    

     

    1. MySocket이라는 클래스를 만들었다.

    2. 멤버 변수로 socket.io의 Server 타입으로 socket을 선언했다.

    3. socket 멤버변수를 초기화할 수 있도록 http의 Server를 인자로 받아 생성자를 만들었다.

    4. socket.io의 커넥션 정의 로직을 수행할 수 있도록 connect()함수를 정의했다.

    5. socket.io.sockets.on('connection', socket => {})은 소켓 연결 이벤트다. 콜백으로 socket을 리턴한다

    인자로 받는 socket을 이용하여 통신을 만들 수 있다.

    6. socket.on('newUser', name => {})은 사용자가 접속할 때 마다 발생하는 이벤트다.

    첫번째 인자로 들어간 'newUser'는 custom string이다. 즉 string타입의 어떠한 값이라도 넣을 수 있는데, 이는 클라이언트와 서버가 어떠한 이벤트를 발생시킨 것인지 서로 알기 위한 통신 규약이다. (요런걸 뭐라고 하더라...)

    두번째 인자로 들어간 name은 클라이언트 측에서 서버로 전달하는 값이다.

    클라이언트에서 newUser 이벤트를 발생시킬 때 인자를 함께 보낼 수 있다.

    클라이언트로부터 받은 name은 사용자 닉네임이고, 시스템 메시지에 활용한다.

    update이벤트는 socket의 sockets에 on으로 바인딩했는데, sockets는 본인을 포함하여 채널의 모든 커넥션들에게 이벤트를 발생시킬 수 있는 속성이다.

    7. socket.on('message' data => {})는 클라이언트에서 사용자가 메시지를 발송하면 이에 대한 처리를 하는 이벤트다.

    message라는 통신 규약을 정해서 클라이언트는 서버에게 data를 전달한다.

    서버는 받은 데이터를 채널의 모든 사용자들에게 전송해준다.

    8. socket.on('disconnect', ()=>{})는 사용자의 접속이 끊어졌을 때 발생하는 이벤트다.

    disconnect는 따로 커스텀 통신 규약이 아닌 socket.io에 정해져있는 통신 규약이다.

    나를 제외한 모든 커넥션들에게 내가 채팅에서 나갔다는 이벤트를 발생시키기 위해 broadcast를 사용하여 update 이벤트를 발생시켰다.

     

     

    그리고 서버를 리스닝하는 코드에 mySocket 모듈을 임포트하고 생성해주자.

    import express from 'express';
    import http from 'http';
    import MySocket from './mySocket';
    
    const app = express();
    const server = http.createServer(app);
    
    const mySocket = new MySocket(server);
    mySocket.connect();
    
    server.listen(3000, () => {
        console.log('listening on port 3000.');
    });

     

    이렇게 서버측 코드를 완성 했으면 이제 클라이언트 측인 템플릿에 코드를 추가해야한다.

     

    위에서 작성한 템플릿을 열고 아래의 코드를 작성해주자.

    html
        head
            title
            style.
                * { width: 500px; margin: 0 auto; padding: 0; box-sizing: border-box; }
                body { font: 13px Helvetica, Arial; }
                #room { height: 70%; margin-top: 100px; border: 1px solid black; border-radius: 10px 10px 0px 0px; }
                #title { height: 5%; padding-top: 5px; padding-left: 10px; font-size: 2em; font-weight: bold; border-bottom: 1px solid black; }
                #messages { list-style-type: none; height: 95%; margin: 0; padding: 0; overflow: hidden; }
                #messages:hover { overflow-y: scroll; }
                #messages li { width: 90%; padding: 5px 10px; }
                .system { background: #979797; color: white; }
                .me { background: #fdc635; }
                .other { background: #80d8e8; }
                #send-box { background: #000; padding: 3px; bottom: 0px; }
                #send-box input { width: 90%; border: 0; padding: 10px; margin-right: .5%; }
                #send-box button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
            script(src='/js/socket.io.js')
        body
            div(id = 'room')
                p(id = 'title') 채팅방
                ul(id = 'messages')
                div(id = 'send-box') 
                    input(type = "text" id='m', autocomplete='off')
                    button(id = 'send-btn') send
        script. 
            const socket = io();
    
            const messages = document.querySelector('#messages');
            const textInput = document.querySelector('#m');
            const sendBtn = document.querySelector('#send-btn');
    
            let userName = `익명${Math.floor(Math.random() * (100000 - 100 + 1)) + 100}`;
            
            // 소켓 연결 이벤트
            socket.on('connect', () => {
                let user = prompt('환영합니다.', userName);
                if (!user) 
                {
                    user = userName;
                }
                else
                {
                    userName = user;
                }
                
                // 서버에 newUser 이벤트를 발생시킴
                socket.emit('newUser', user);
            });
    
            // 메시지 수신 이벤트
            socket.on('update', (data) => {
                console.log(`${data.name}: ${data.message}`);
                console.log(userName);
                
                const newMessage = `${data.name}: ${data.message}`;
                
                const newMessageLine = document.createElement('li');
                if (data.name === 'system')
                {
                    newMessageLine.setAttribute('class', 'system');
                }
                else if (data.name === userName) 
                {
                    newMessageLine.setAttribute('class', 'me');
                }
                else 
                {
                    newMessageLine.setAttribute('class', 'other');
                }
                newMessageLine.append(newMessage);
    
                messages.append(newMessageLine);
            });
    
            // 메시지 발송 이벤트
            sendBtn.addEventListener('click', () => {
                const message = textInput.value;
                textInput.value = '';
                
                // 서버에 message 이벤트를 발생시키며, 객체를 값으로 전달한다.
                socket.emit('message', { type: 'message', name: userName, message });
            });
            
            // 입력란에 엔터키 입력 탐지
            textInput.addEventListener('keyup', (event) => {
                if (event.keyCode === 13)
                {
                    sendBtn.click();
                }
            });

    header에 socket.io.js를 임포트하는 구문을 추가했다.

    여기서 socket.io.js는 node_modules/socket.io-client/dist 경로에서 취득할 수 있다.

     

    1. io()로 소켓 인스턴스를 생성한다.

    2. 사용자가 최초 접속했을 때 닉네임을 작성할 수 있는데, 디폴트 값으로 익명 + 난수의 값을 만들어준다.

    3. socket.on('connect', () => {})는 서버의 소켓과 커넥션이 이루어졌을 때 작동한다.

    사용자에게 프롬프트를 보여주고, 닉네임을 입력받는다.

    닉네임을 입력받으면 접속했다는 시스템 메시지를 모든 사용자들에게 알리기 위해 newUser 이벤트를 발생시키고 접속한 사용자의 닉네임을 전달한다.

    4. socket.on('update', data => {})는 특정 사용자나 시스템에서 이벤트가 발생할 경우 해당 이벤트로 데이터를 받을 수 있다.

    메시지에 css의 백그라운드 컬러를 적용하기 위해, 데이터를 받았을 때 name을 이용하여 시스템 메시지인지, 내가 작성한 메시지인지, 상대방이 작성한 메시지인지를 구분하여 클래스를 추가한다. 

    5. socket.emit('message', {})는 메시지 발송 이벤트이다.

    객체를 만들어 데이터를 전송할 수 있다.

     

    작업 흐름은 아래와 같다.

    1. 클라이언트에서 message 이벤트를 발생

    2. 서버에서 message 이벤트를 캐치 후 모든 사용자들에게 update 이벤트 발생

    3. 클라이언트에서 update 이벤트 캐치

     

     

     

    꽤 재밌었다!

    '개발일지' 카테고리의 다른 글

    2019-09-06 개발일지  (0) 2019.09.06
    2019-09-05 개발일지  (0) 2019.09.05
    2019-09-03 개발일지  (0) 2019.09.03
    2019-09-02 개발일지  (0) 2019.09.03
    2019-08-30 개발일지  (0) 2019.08.30
Designed by Tistory.