개발 노트

[Next.js] web worker을 이용한 mqtt와 웹소켓 적용하기 본문

React

[Next.js] web worker을 이용한 mqtt와 웹소켓 적용하기

한츄 2024. 2. 6. 17:37

mqtt

https://mqtt.org/

 

MQTT - The Standard for IoT Messaging

Why MQTT? Lightweight and Efficient MQTT clients are very small, require minimal resources so can be used on small microcontrollers. MQTT message headers are small to optimize network bandwidth. Bi-directional Communications MQTT allows for messaging betwe

mqtt.org

web socket

참고: https://ko.wikipedia.org/wiki/%EC%9B%B9%EC%86%8C%EC%BC%93

 

웹소켓 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 웹소켓(WebSocket)은 하나의 TCP 접속에 전이중 통신 채널을 제공하는 컴퓨터 통신 프로토콜이다. 웹소켓 프로토콜은 2011년 IETF에 의해 RFC 6455로 표준화되었으며 웹

ko.wikipedia.org

 

 

web worker

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API

 

Web Workers API - Web APIs | MDN

Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application. The advantage of this is that laborious processing can be performed in a separate thread, allowing the main (usuall

developer.mozilla.org

 

 

 

web worker 적용하기

 

1. 기존에 Mqtt와 웹소켓을 적용한 코드

'use client';

import mqtt, { MqttClient } from 'mqtt';
import React, { useEffect, useState } from 'react';

const mqttUri = 'ws://127.0.0.1:9001';

const options: mqtt.IClientOptions = {
  clientId: 'nextjs',
  reconnectPeriod: 2000,
};

const Mqtt: React.FC = () => {
  const [messages, setMessages] = useState<string[]>(['init']);

  const addMessage = () => {
    const newMessages = [...messages, Math.random().toString()];
    setMessages(newMessages);
  };

  
  useEffect(() => {
    const client: MqttClient | undefined = mqtt.connect(mqttUri, options);
    console.dir(client);
    client.subscribe('#');
    client.on('message', (topic, message) => {
      setMessages((prevMessages) => [...prevMessages,topic+ ' : ' +message.toString()]);
    });

      return () => {
        if (client) {
          client.unsubscribe('#');
          client.end();
        }
      };
  }, []);

  return (
    <div>
      <button
      className='border'
       onClick={addMessage}>addMessage</button>
      <h2>Received Messages: </h2>
      <ul>
        {messages.map((message, index) => (
          <li key={index}>{message}</li>
        ))}
      </ul>
    </div>
  );
};

export default Mqtt;

 

 

 

2. public에 mqttWorker.ts만들기

// mqttWorker.ts
import mqtt, { MqttClient } from 'mqtt';

const mqttUri = 'ws://127.0.0.1:9001';

const options: mqtt.IClientOptions = {
  clientId: 'nextjs',
  reconnectPeriod: 2000,
};

self.addEventListener('message', (e) => {
  const client: MqttClient | undefined = mqtt.connect(mqttUri, options);

  
  client.subscribe('#');
  client.on('connect',()=>{
    console.log('connected');
  })
  client.on('error', (error) => {
    postMessage('Error: ' + error.message);
  });
  client.on('message', (topic, message) => {
    postMessage(topic + ' : ' + message.toString());
  });
});

 

 

3. mqttWorker를 빌드하기 위해 webpack.worker.config.js만들기

const WorkerPlugin = require('worker-plugin');
const path = require('path');

module.exports = {
  mode: 'production',
  target: 'webworker',
  entry: './public/mqttWorker.ts',
  output: {
    filename: 'mqttWorker.built.js',
    path: path.resolve(__dirname, 'public'),
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js'],
  },
  module: {
    rules: [
      { test: /\.tsx?$/, loader: 'ts-loader' },
    ],
  },
  plugins: [
    new WorkerPlugin()
  ]
};

 

 

4. build용 package설치하기

pnpm add webpack webpack-cli worker-plugin ts-loader
//package.json
{
  "name": "controlweb",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "build:worker": "webpack --config webpack.worker.config.js"
  },
  "dependencies": {
    "mqtt": "^5.3.5",
    "next": "14.1.0",
    "react": "^18",
    "react-dom": "^18",
    "worker-plugin": "^5.0.1",
    "zustand": "^4.5.0"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.1.0",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "ts-loader": "^9.5.1",
    "typescript": "^5",
    "webpack": "^5.90.1",
    "webpack-cli": "^5.1.4"
  }
}

 

5. src\component\MqttWorker.tsx만들기

'use client';

import React, { useEffect, useRef } from 'react';
import { useMqttStore } from '@/store/useMqttStore';

const MqttWorker: React.FC = () => {
  const { messages, addMessage } = useMqttStore();
  const workerRef = useRef<Worker | null>(null); // 웹 워커 참조를 저장하는 ref

  useEffect(() => {
    const URL = '/mqttWorker.built.js';
    const worker = new Worker(URL);
    workerRef.current = worker; // 웹 워커 객체를 ref에 저장
    worker.onmessage = function (event) {
      addMessage(event.data); // Zustand 스토어의 addMessage 액션 사용
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const startMqtt = () => {
    if (workerRef.current) {
      workerRef.current.postMessage({ CMD: 'MQTT_START' }); // 웹 워커에 메시지 보내기
    }
  };
  const stopMqtt = () => {
    if (workerRef.current) {
      workerRef.current.postMessage({ CMD: 'MQTT_STOP' }); // 웹 워커에 메시지 보내기
    }
  };
  const subscribeMqtt = () => {
    if (workerRef.current) {
      workerRef.current.postMessage({
        CMD: 'MQTT_SUBSCIBE',
        DATA: ['dawoon/ALIVE/#', 'dawoon/Robot/#', '/dawoon/RMAN/#', 'dawoon/Feeder/#'],
      }); // 웹 워커에 메시지 보내기
    }
  };

  return (
    <div className='relative flex flex-col w-full'>
      <ul className='fixed flex gap-2 justify-center w-full bg-[#d6dbdc] p-2 shadow-lg'>
        <li >
          <button
          className='border bg-white px-2.5 py-1.5 rounded-3xl shadow-xl border-gray-400 hover:bg-gray-600 hover:text-white active:bg-[#121212] active:text-white'
          onClick={startMqtt}>
            startMqtt
          </button>
        </li>
        <li>
          <button
          className='border bg-white px-2.5 py-1.5 rounded-3xl shadow-xl border-gray-400 hover:bg-gray-600 hover:text-white active:bg-[#121212] active:text-white'
          onClick={stopMqtt}>
            stopMqtt
          </button>
        </li>
        <li>
          <button
          className='border bg-white px-2.5 py-1.5 rounded-3xl shadow-xl border-gray-400 hover:bg-gray-600 hover:text-white active:bg-[#121212] active:text-white'
          onClick={subscribeMqtt}>
            subscribeMqtt
          </button>
        </li>
      </ul>
      <h2 className='ml-2 text-lg font-semibold mt-16'>Received Messages: </h2>
      <ul className='flex flex-col gap-y-2 px-2'>
        {messages.map((message: string, index: number) => (
          <li
          className='bg-white border px-2.5 py-1.5 rounded-xl'
           key={index}>
            {message}
            </li>
        ))}
      </ul>
    </div>
  );
};

export default MqttWorker;

 

6. Mqtt에서 받아온 값을 전역으로 관리하기 위해 useMqttStore.tsx만들기

// useMqttStore.ts
import { create } from 'zustand';

type Store = {
  messages: string[];
  addMessage: (message: string) => void;
};

export const useMqttStore = create<Store>((set) => ({
  messages: [],
  addMessage: (message) => set((state) => ({ messages: [...state.messages, message] })),
}));