기존에 테스트 했던 웹서버는 하나의 장치에 여러 클라이언트가 연결되어 있을경우 서버의 상태 변화가 발생하면 동기가 맞지 않는 현상이 있다.
이러한 문제에 대한 해결책으로 웹소켓을 사용하면 된다.
일반 웹서버에서는 HTTP 요청에서 실행되며 업데이트된 데이터를 가져오려면 웹 페이지를 완전히 새로 고쳐야 하는 단점이 있다. 이러한 단점을 극복하기 위해 나온것이 웹소켓 이다.
웹소켓(WebSocket)의 가장큰 특징은 전이중(서버와 클리이언트가 동시에 데이터를 보내고 받을수 있음) 프로토콜이다.
예를 들면 서버에 연결된 릴레이의 상태를 변경 시켰을때 모든 클라이언트(PC 웹페이지, 핸드폰 페이지, 원격지의 페이지)가 동일하게 상태를 변경시켜야 하는 예를 테스트 해볼 수 있다.
일반 웹서버는 http://로 시작한다 웹 소켓은 ws://(웹 소켓) 또는 wss://(웹 소켓 보안)로 시작 한다
따라서 아래와 같이 AsyncWebSocket 로 소켓을 생성 할 수 있다.
웹소켓 이벤트 처리 함수
void notifyClients()
{
//웹소켓으로 LED 상태 값을 전송
ws.textAll(String(ledState));
}
void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
if (strcmp((char*)data, "toggle") == 0) {
ledState = !ledState;
notifyClients();
}
}
}
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
ESP32 웹소켓 서버 메인 함수
int flag = 0;
void loop(){
ws.cleanupClients();
digitalWrite(ledPin, !ledState);
if(digitalRead(SwPin) == 0)
{
if(!flag)
{
flag = 1;
ledState ^= 1;
notifyClients()
}
}
else
{
if(flag)flag = 0;
}
}
웹소켓 서버 웹페이지
웹페이이지는 간단히 상태를 표시하는 텍스트와 이미지를 출력 하도록 했다.
<!DOCTYPE HTML><html>
<head>
<title>ESP Web Socket Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="index.js"></script>
</head>
<body>
<center>
<div class="topnav">
<h1>ESP32 WebSocket Server</h1>
</div>
<div class="content">
<div class="card">
<p class="state">state: <span id="state">%STATE%</span></p>
<p><button id="button" class="button"><span id="state_img">%STATE%</span></button></p>
</div>
</div>
</script>
</body>
</html>
웹소켓 통신을 하기 위해 서버 페이지쪽에서 처리해야 할 사항은 javascript로 작성 했다.
서버로 수신되는 이벤트는 onMessage() 함수에서 처리 되고 상태 값에 따라 웹페이지의 텍스트와 이미지를 변경할 수 있다.
function onMessage(event) {
var state;
if (event.data == "1")
{
state = "ON";
document.getElementById('state_img').innerHTML = "<img src=\"led_on.jpg\">";
}
else
{
state = "OFF";
document.getElementById('state_img').innerHTML = "<img src=\"led_off.jpg\">";
}
document.getElementById('state').innerHTML = state;
}
웹페이지상의 버튼(이미지)클릭시 처리 함수
function initButton() {
document.getElementById('button').addEventListener('click', toggle);
}
function toggle(){
websocket.send('toggle');
}
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onLoad);
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- add this line
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
function onMessage(event) {
var state;
if (event.data == "1"){
state = "ON";
document.getElementById('state_img').innerHTML = "<img src=\"led_on.jpg\">";
}
else{
state = "OFF";
document.getElementById('state_img').innerHTML = "<img src=\"led_off.jpg\">";
}
document.getElementById('state').innerHTML = state;
}
function onLoad(event) {
initWebSocket();
initButton();
document.getElementById('state_img').innerHTML = "<img src=\"led_off.jpg\">";
}
function initButton() {
document.getElementById('button').addEventListener('click', toggle);
}
function toggle(){
websocket.send('toggle');
}
웹소켓 서버 접속시 이미지
ESP32 웹소켓 서버 테스트 동영상
https://youtube.com/shorts/xHfWI1-rMNc