Node.js-3 (22/12/07)

nazzzo·2022년 12월 7일
0

TCP Server, Client 구축하기



오늘의 서버 구축 과정은 노드에서 제공하는 라이브러리를 바탕으로 진행합니다
코드를 주고받는 흐름을 이해하는데 집중해야 합니다


npm init
// pacakge.json 파일을 생성합니다



1. 서버 생성하기


1) [server.js] 파일을 생성합니다

노드의 내장모듈인 net을 사용하겠습니다

const net = require("net");
// 노드가 가진 내장모듈 net을 불러옵니다

const server = net.createServer((client) => {
    client.on("data", (data) => {
        console.log(data)
    })
});
// 포트를 엽니다 (= 소켓 생성)
// 인자값으로는 콜백함수가 들어갑니다 (매개변수 client)
// 첫번째 콜백은 서버와 클라이언트의 커넥션을 의미합니다
// 두번째 콜백은 커넥션이 이루어졌을 때 실행될 함수입니다



const PORT = process.env.SERVER_PORT || 3000
const HOST = process.env.SERVER_HOST || "127.0.0.1"
// 미리 설정된 환경변수가 있으면 전자, 아니면 후자가 할당됩니다
// 상수를 선언할 때는 주로 대문자를 사용합니다


server.listen(PORT, HOST, ()=>{
    console.log(`Server Listening Port : ${PORT}`)
})
// listen()의 첫번째 인자값은 포트 넘버 : 3000,
// 두번째 인자값은 호스트(ip) : 127.0.0.1
// 세번째 인자값은 listen이 실행될 때 발동될 콜백함수 (콘솔로그 메시지)

createServer() 메서드는 서버 객체를 반환합니다

그리고 listen() 메서드가 발동되면 서버는 대기 상태에 놓이는데,
이는 클라이언트가 연결 요청을 할 수 있는 상태에 도달했음을 의미합니다


이제 터미널(서버 터미널)에 node server를 입력해서
서버를 On 시키도록 합시다

> Server Listening Port : 3000

(서버 터미널에 서버가 개설되었음을 알리는 메시지가 출력됩니다)



2. 클라이언트 등록 & 연결하기


2) [client.js] 파일을 생성하고 그에 대응할 새 터미널을 엽니다

const net = require("net")
// 노드 내장모듈 net을 불러옵니다

const config = {port:3000, host: '127.0.0.1'}
const socket = net.connect(config)
// 호스트(컴퓨터)에 커넥트를 요청합니다
// 인자값으로는 포트와 호스트 정보를 담은 객체를 기입합니다


socket.on("connect", ()=> {
    console.log("connected to server!")
})
// client > server > client. 
// 이 시점에서 서버와 클라이언트가 연결됩니다
// 연결이 이루어진 시점에서 두번째 인자값으로 넣은 콜백함수가 실행됩니다

새로 연 터미널 탭(이하 클라이언트 터미널)에 node client를 입력합니다

> connected to server!

서버와 클라이언트 연결 성공!




3) [server.js]의 listen() 안에 다음 코드를 추가합니다

server.listen(PORT, HOST, ()=>{
    console.log(`Server Listening Port : ${PORT}`)

    server.on("connecion", ()=> {
        console.log("클라이언트가 접속했습니다")
      	// 클라이언트의 접속을 알리는 메시지
    })
})




클라이언트 > 서버순으로 닫은 뒤 역순으로 재실행합니다

클라이언트가 접속했습니다

이제 서버 터미널에서도 클라이언트의 접속을 알립니다




+) 터미널을 추가해서 node client를 입력하면
서버 터미널에 클라이언트 접속 메시지가 추가되는 것을 확인할 수 있습니다

클라이언트가 접속했습니다
클라이언트가 접속했습니다

여기까지의 과정을 3-way handshake라고 합니다
(client > server > client)




3. 서버에 데이터 전송 & 접속해제하기


4) 다시 [client.js]에 다음 코드를 추가합니다

socket.on("connect", ()=> {
    console.log("connected to server!")

    socket.write("클라이언트가 데이터를 보냅니다")
    // 클라이언트가 서버에 데이터를 전달합니다
})



그런데 서버가 전달받은 데이터는 아래와 같이 16진수로 변환되어 출력되고 있습니다

<Buffer ed 81 b4 eb 9d bc ec 9d b4 ec 96 b8 ed 8a b8 ea b0 80 20 eb 8d b0 ec 9d b4 ed 84 b0 eb a5 bc 20 eb b3 b4 eb 83 85 eb 8b 88 eb 8b a4>

(16진수 코드를 한글 메시지로 변환하는 방법은 하단에서 설명하겠습니다)



[server.js]에 다음 코드를 추가한 뒤 클라이언트와 서버를 재실행합니다

const server = net.createServer((client) => {
    client.on("data", (data) => {
        console.log(data)
    })

    client.on("close", ()=> {
        console.log("클라이언트의 접속이 해제되었습니다")
    })
});

클라이언트를 종료하면 서버 터미널에 접속종료 메시지가 출력됩니다
(client > server > client > server
이런 순서로 연결 해제 과정은 4번을 통신을 거치면서 이루어지는데
이를 4-way handshake라고 합니다)



  • 모든 코드는 비동기로 이루어집니다

  • "data", "connection", "close" 등은 이벤트리스너와 유사한 역할을 합니다

  • "close" 이벤트를 클라이언트 변수에 넣는 이유는
    서버에서 나간 클라이언트를 특정할 필요가 있기 때문입니다

  • 서버를 끌 때는 항상 클라이언트 접속을 먼저 해제하도록 합니다

  • 소켓은 ...



4. Buffer 세팅하기


'DataType Buffer'

Buffer는 2진수로 넘겨받은 데이터를 16진수로 변환해서 출력합니다
(사용자가 읽기 편하도록 하기 위함입니다)

이제 건네받은 16진수 데이터를 글자로 변환해야 하는데(인코딩 과정)
변환할 문자집합은 두 종류로 나뉩니다

  • ASCII(1바이트 문자 ~ 영어 알파벳),
  • UTF-8(가장 많이 사용하는 유니코드 인코딩 방식으로
    2바이트 이상의 문자에 사용합니다 ~한글은 2바이트입니다)

그리고 16진수 코드 한자리는 1Nibble(4비트),
두자리는 1바이트(8비트)가 된다는 것도 알아둬야 합니다


이제 변환을 위해 노드의 내장 메서드 toString()을 사용합니다

[server.js]

const server = net.createServer((client) => {
    client.on("data", (data) => {
        console.log(data)
        console.log(data.toString('hex'))
        console.log(data.toString('utf8'))
        // 인자값으로 ascii, utf8, hex(바이너리 타입) 등
    })

    client.on("close", ()=> {
        console.log("접속이 해제되었습니다")
    })
});

서버와 클라이언트를 재실행하면
서버 터미널에 16진수 데이터가 변환되어 출력됩니다

<Buffer ed 81 b4 eb 9d bc ec 9d b4 ec 96 b8 ed 8a b8 ea b0 80 20 eb 8d b0 ec 9d b4 ed 84 b0 eb a5 bc 20 eb b3 b4 eb 83 85 eb 8b 88 eb 8b a4>
ed81b4eb9dbcec9db4ec96b8ed8ab8eab08020eb8db0ec9db4ed84b0eba5bc20ebb3b4eb8385eb8b88eb8ba4
클라이언트가 데이터를 보냅니다

하지만 보통은 이렇게 수동적인 변환 과정을 거치지 않고
client.setEncoding("utf8")을 미리 입력합니다

const server = net.createServer((client) => {
    client.setEncoding("utf8")

    client.on("data", (data) => {
        console.log(data)
        // console.log(data.toString('hex'))
        // console.log(data.toString('utf8'))
        // // 인자값으로 ascii, utf8, hex(바이너리 타입)
    })

    client.on("close", ()=> {
        console.log("접속이 해제되었습니다")
    })
});

결과

Server Listening Port : 3000
client가 접속했습니다
클라이언트가 데이터를 보냅니다



5. HTTP ~ 브라우저와 서버 연결


  • HTTP는 기본적으로 TCP 통신을 기반으로 합니다
  • TCP 통신은 쌍방향 통신이 가능합니다 (Server <---> Client)
  • HTTP 프로토콜의 규격은 브라우저의 요쳥만으로도 처리할 수 있습니다
    브라우저 url : http://127.0.0.1:3000

[server.js]

const server = net.createServer((client) => {
  client.setEncoding("utf8")

  client.on("data", (data) => {
      console.log(data)

      client.write("서버가 답장을 보냅니다")
      // 서버가 클라이언트에 답장을 보냅니다
  })

[client.js]

socket.on("data", (chunk) => {
    console.log(`Recieved : ${chunk}`)
    // 서버의 담장에 대응할 코드입니다

    socket.end()
    // 소켓을 닫습니다
})

이제 클라이언트 터미널에 다음 메시지가 추가됩니다

connected to server!
Recieved : 서버가 답장을 보냅니다


TCP 통신 방식을 통해 서버와 브라우저가 데이터를 주고받는 과정을 살펴봅시다

[server.js]

const body = Buffer.from(`<h1>Hello World!</h1>`)
const response = `HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5
Content-type: text/html
Content-length: ${body.length}

${body.toString()}
`

const server = net.createServer((client) => {
    client.setEncoding("utf8")

    client.on("data", (data) => {
        console.log(data)

        client.write(response)
    })

    client.on("close", ()=> {
        console.log("접속이 해제되었습니다")
    })
});

브라우저 url에 다음 주소를 입력해서

http://localhost:3000/
or http://127.0.0.1:3000/

Buffer.from(<h1>Hello World!</h1>) 코드의 실행을 확인합니다


  • 서버의 터미널에 뜨는 접속해제 메시지는 브라우저가 소켓을 끊었음을 의미합니다

  • 해당 내용은 브라우저의 개발자도구 > 네트워크탭 > 요청 헤더에서도
    확인할 수 있습니다




[server.js]

const net = require("net");


const body = Buffer.from(`<h1>Hello World!</h1>`)
const response = `HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5
Content-type: text/html
Content-length: ${body.length}

${body.toString()}
`

const server = net.createServer((client) => {
    client.setEncoding("utf8")

    client.on("data", (data) => {
        console.log(data)
        client.write(response)
    })

    client.on("close", ()=> {
        console.log("접속이 해제되었습니다")
    })
});

const PORT = process.env.SERVER_PORT || 3000
const HOST = process.env.SERVER_HOST || "127.0.0.1"


server.listen(PORT, HOST, ()=>{
    console.log(`Server Listening Port : ${PORT}`)

    server.on("connection", ()=> {
        console.log("client가 접속했습니다")
    })
})

[client.js]

const net = require("net")


const config = {port:3000, host: '127.0.0.1'}
const socket = net.connect(config)



socket.on("connect", ()=> {
    console.log("connected to server!")

    socket.write("클라이언트가 데이터를 보냅니다")
})


socket.on("data", (chunk) => {
    console.log(`Recieved : ${chunk}`)

    socket.end()
})

0개의 댓글