JUN0.DEV
JUN0.DEV

IoT 자율형 전등에서 TCP 소켓 서버 만들기

Published on
  • avatarJunyoung Yang

전국 SW 동아리 경진대회에 참가하면서 IoT 자율형 전등을 만들었다. 조도 센서로 주변 밝기를 읽고, 전등 밝기를 자동으로 조절하며, Android 앱에서도 상태를 확인하고 수동 제어할 수 있는 구조가 필요했다.

당시에는 백엔드나 네트워크를 깊게 이해하고 시작한 프로젝트는 아니었다. 다만 센서 값이 바뀌었을 때 앱에 바로 반영되어야 했고, 앱에서 제어한 값도 기기에 빠르게 전달되어야 했다. 이 글은 그 요구 때문에 HTTP polling이 아니라 TCP 소켓 서버를 만들었던 초기 프로젝트 기록이다.

처음 만든 구조

구성은 단순했다.

  • NodeMCU는 조도 센서 값을 읽고 전등을 제어한다.
  • Node.js 서버는 기기와 앱 사이 메시지를 중계한다.
  • Android 앱은 현재 상태를 보여주고 수동 제어 명령을 보낸다.

처음에는 HTTP 요청으로 상태를 주고받는 방식도 생각했다. 하지만 기기 상태가 바뀔 때마다 앱이 주기적으로 서버에 요청을 보내야 했고, 사용자가 앱에서 조작한 값도 즉시 전달된다는 보장이 약했다.

처음 보인 문제

이 프로젝트에서 필요한 것은 "가끔 조회"가 아니라 "상태 변화 전달"에 가까웠다.

HTTP polling을 쓰면 아래 문제가 있었다.

  • 앱이 계속 서버에 상태를 요청해야 한다.
  • polling 간격이 길면 반영이 느리다.
  • polling 간격을 짧게 잡으면 불필요한 요청이 많아진다.
  • 기기와 앱이 동시에 연결된 상태를 서버가 다루기 어렵다.

그래서 연결을 유지한 상태에서 메시지를 바로 전달할 수 있는 TCP 소켓 방식을 선택했다.

해결 방안

Node.js의 net 모듈을 사용해 간단한 TCP 서버를 만들었다. 서버는 연결된 클라이언트를 목록으로 들고 있다가, 한 클라이언트에서 메시지가 오면 다른 클라이언트로 전달하는 역할을 했다.

const net = require('net')

const clients = []

const server = net.createServer((socket) => {
  clients.push(socket)

  socket.on('data', (data) => {
    clients.forEach((client) => {
      if (client !== socket) {
        client.write(data)
      }
    })
  })

  socket.on('close', () => {
    const index = clients.indexOf(socket)
    if (index !== -1) {
      clients.splice(index, 1)
    }
  })
})

server.listen(3000)

지금 기준으로 보면 인증, 메시지 포맷, 연결 종료 처리, 장애 복구가 많이 부족한 코드이다. 그래도 당시 프로젝트에서는 HTTP polling보다 요구사항에 더 가까운 방식이었다.

적용 후 달라진 점

이 작업을 통해 처음으로 요청-응답 방식과 연결 유지 방식의 차이를 체감했다.

HTTP는 단순 조회나 명령 요청에는 편했다. 하지만 센서 값과 기기 제어처럼 양쪽 상태를 계속 맞춰야 하는 상황에서는 연결을 유지하는 구조가 더 자연스러웠다.

또 하나 배운 점은 서버가 단순히 메시지를 전달하는 역할만 해도, 연결 관리 책임을 갖게 된다는 점이었다.

  • 연결된 클라이언트를 추적해야 한다.
  • 끊어진 소켓을 정리해야 한다.
  • 메시지 수신자와 발신자를 구분해야 한다.
  • 잘못된 메시지가 들어왔을 때 서버가 죽지 않게 해야 한다.

이런 부분은 당시에는 단순히 "소켓 통신이 된다" 정도로만 이해했지만, 이후 실시간 기능을 이해하는 기준이 됐다.

마무리

이 프로젝트는 현재 주력 기술을 보여주는 글이라기보다, 초기에 네트워크와 실시간 통신을 직접 만져본 기록에 가깝다.

그래도 정리할 수 있는 기준은 분명했다.

  • 주기적으로 확인하는 문제인지, 변화가 생기면 바로 전달해야 하는 문제인지 먼저 구분한다.
  • HTTP가 익숙하다고 모든 통신에 맞는 것은 아니다.
  • 연결을 유지하는 순간 서버는 연결 상태를 관리할 책임을 갖다.
  • 간단한 실시간 기능도 장애 상황을 생각하면 처리할 것이 많아진다.

지금 다시 만든다면 WebSocket, MQTT, 메시지 포맷, 인증, 재연결 전략까지 함께 보겠지만, 당시에는 TCP 소켓을 직접 구현하면서 통신 방식 선택의 차이를 처음 배웠다.