8주차_유동 IP와 자가 서버 구축_웹 서버(3) - sookite22/SmartDivice_24 GitHub Wiki
- 인터넷과 자가 서버
- 유동 IP
- DDNS
- 집에서 웹 서버 운영하기
- 실습
- DHT11 온습 표시 웹 서버
- 외부 접속 웹 서버
- 후기
유동 IP는 IP가 변화하는 것이라고 할 수 있다. 인터넷 서비스 제공자(ISP)로부터 자동으로 할당 되는 IP 주소를 가리키며, 일정하지 않고, 일정한 주기로 변경될 수 있다. 이는 ISP가 사용자에게 할당하는 IP 주소를 주기적으로 회전시키는 것을 의미한다.
일반적으로 일반 가정용 인터넷 사용자 및 소규모 기업에게 많이 사용된다. 대부분의 경우 이러한 사용자들에게는 유동 IP가 충분하며, 고정 IP를 필요로 하는 경우에는 추가 비용이 발생할 수 있다.
- 동적 할당: ISP에서 동적 으로 할당되므로 사용자가 IP 주소를 직접 설정하거나 유지할 필요가 없다.
- 비용 절감: ISP는 고정 IP보다 유동 IP를 제공하는 것이 일반적으로 더 경제적 이다. IP 주소가 회전하기 때문에 IP 주소 자원을 효율적으로 관리할 수 있기 때문이다.
- 개인 정보 보호: 사용자의 개인 정보를 보호하는 데 도움이 된다. 고정 IP는 고유하고 식별 가능한 주소를 제공하기 때문에 개인 정보 유출의 위험이 있는 반면, 유동 IP는 주기적으로 변경되므로 개인 정보 유출 위험이 감소한다.
- 제한된 서비스: 유동 IP는 동적으로 변경되므로 특정 서비스를 호스팅하기 어려울 수 있다. 예를 들어, 웹 서버를 운영하는 경우 매번 IP 주소가 변경되면 사용자가 항상 최신 IP 주소를 알아야 하므로 편의성이 저하된다.
유동 IP 주소를 가진 네트워크 장치의 IP 주소를 동적으로 갱신 하여 고정된 도메인 이름과 연결하는 서비스이다. 사용자는 도메인 네임으로 접속하고, DDNS는 도메인과 호스트의 IP를 지속적으로 일치화 시키는 작업을 말하는 것이다. 이를 통해 사용자는 도메인을 통해 올바른 IP로 접속할 수 있게 된다. 매번 IP 주소가 변경될 수 있기 때문에 외부에서 해당 네트워크에 접근하기 어려울 수 있다는 점을 해결할 수 있다.
가정용 네트워크나 소규모 비즈니스에서 많이 사용되며, 유동 IP 주소를 가진 네트워크 장치에 대한 원격 액세스를 관리하는 데 유용하다.
- 유동 IP 주소 관리: 사용자의 네트워크 장치에 할당된 유동 IP 주소를 관리하고, 해당 IP 주소가 변경될 때마다 DNS 서버에 업데이트한다.
- 고정된 도메인 이름: 사용자는 고정된 도메인 이름을 설정하여 해당 이름을 통해 네트워크에 액세스할 수 있다. 이는 IP 주소가 변경되더라도 도메인 이름이 동일하게 유지되므로 사용자가 외부에서 네트워크에 접근 가능하게 한다.
- 간편한 설정: 대부분의 DDNS 서비스는 사용자가 간편하게 설정할 수 있도록 웹 인터페이스를 제공한다. 사용자는 자신의 계정을 생성, 도메인 이름을 선택, 네트워크 장치의 정보를 입력하여 DDNS를 설정할 수 있다.
-
웹 클라이언트: 웹 클라이언트는 웹 서버에 요청을 보내고, 응답을 받아 사용자에게 보여주는 소프트웨어이다. 대표적으로 웹 브라우저가 웹 클라이언트 역할을 한다. 또한, 다른 소프트웨어도 웹 클라이언트로 동작 가능하다. 예를 들어, API 호출을 위한 웹 앱, 모바일 앱 등이 있다. 웹 클라이언트는 URL을 통해 웹 서버에 요청을 보내고 받은 HTML, CSS, JavaScript 파일 등을 해석하여 사용자에게 웹 페이지를 표시한다.
-
웹 서버: 웹 서버는 클라이언트로부터 요청을 받아 처리하고, 요청에 따른 응답을 반환하는 소프트웨어 또는 하드웨어이다. 웹 서버는 주로 정적인 콘텐츠(HTML 파일, 이미지, CSS 파일 등)와 동적인 콘텐츠(서버 측 스크립트로 생성되는 페이지)를 제공한다. 대표적으로 아파치(Apache), Nginx, Microsoft IIS 등이 웹 서버로 사용된다.
-
웹 브라우저: 웹 브라우저는 사용자가 웹 서버에 요청한 웹 페이지를 표시하는 소프트웨어이다. 사용자가 웹 브라우저를 통해 URL을 입력하면 해당 URL에 대한 요청을 보내고, 받은 파일을 해석하여 웹 페이지를 화면에 표시한다. 대표적으로 구글 크롬, 모질라 파이어폭스, 마이크로소프트 엣지 등이 웹 브라우저로 사용된다.
사용자가 웹 브라우저를 통해 웹 페이지의 URL 입력. 웹 브라우저는 입력된 URL을 해석하고 해당 웹 서버에 요청 전송. 웹 서버는 요청을 받아 해당하는 웹 페이지를 찾고, HTML 파일 등을 클라이언트에게 응답으로 반환. 웹 브라우저는 받은 HTML 파일을 해석하여 사용자에게 웹 페이지를 화면에 표시. 사용자는 웹 페이지를 보며 상호작용, 필요에 따라 다른 페이지로 이동하거나 액션을 취함.
집에서 개인 pc로 웹 서버를 운영할 시 몇 가지 단점이 존재한다.
- 웹 서버를 지속적으로 운영하려면 PC가 항상 켜져있어야 하므로 상당한 전기세 가 많이 든다. 특히 고사양의 PC를 사용하거나 트래픽이 많은 경우에는 전력 소비가 더욱 증가할 수 있다.
- 가정용 인터넷은 비즈니스용 네트워크 연결과 비교하면 불안정 하다고 볼 수 있다. 가끔씩 끊기거나 느릴 수 있으며, 이는 웹 서버의 가용성과 성능에 영향을 줄 수 있다.
- 가정용 인터넷 서비스에서는 일반적으로 유동 IP 주소 를 사용한다. 이는 일정하지 않고 주기적으로 변경될 수 있기 때문에 도메인 이름과 IP 주소가 동기화되지 않을 수 있다. 또, 외부에서 웹 서버에 접근하기 어렵다. 이 문제를 해결하기 위해 DDNS 서비스를 사용할 수 있지만, 이 역시 완전한 해결책이 되지 못할 수 있다.
- 일부 ISP는 가정용 인터넷 서비스에서 웹 서버 운영을 제한하거나 제한적 으로 지원할 수 있다. ISP는 일반적으로 가정용 인터넷 서비스에 대해 고정 IP 주소를 제공하지 않는다. 이러한 제약으로 인해 외부에서의 접속이 제한되거나 어려울 수 있다.
1. 고정 IP 주소 설정: 웹 서버에 고정된 IP 주소를 할당해야 한다. 이는 로컬 네트워크 내에서 웹 서버를 식별 가능하게 해준다.
2. 포트 포워딩 설정: 공유기의 포트 포워딩 설정을 통해 외부에서 내부 네트워크의 웹 서버에 접속할 수 있도록 한다. 일반적으로 웹 서버는 80번 포트를 사용하므로, 공유기에서 80번 포트를 웹 서버가 있는 로컬 IP 주소로 포워딩한다.
3. 동적 DNS 설정: 집에 유동 IP 주소를 가지고 있다면 DDNS 서비스를 통해 유동 IP 주소를 고정된 도메인 이름으로 연결할 수 있다. 이를 통해 외부에서 도메인 이름을 통해 웹 서버에 접속 가능하다.
4. 웹 서버 설정: 로컬 네트워크에 웹 서버를 설정한다. 이를 위해 웹 서버 소프트웨어 (예: Apache, Nginx)를 설치하고 구성한다. 웹 서버는 로컬 네트워크에서 웹 페이지를 호스팅하는 데 사용된다.
5. 보안 설정: 웹 서버를 운영하기 전 보안을 고려해야 한다. 방화벽 및 보안 패치를 설정하고, SSL 인증서를 설치하여 안전한 연결을 제공할 수 있다.
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebSrv.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
// 실습 환경의 WIFI아이디와 비밀번호로 수정하세요.
const char* ssid = "네트워크_SSID";
const char* password = "네트워크_비밀번호";
#define DHTPIN 18 // DHT 센서에 연결된 디지털 핀
// 사용 중인 센서 유형으로 변경하세요. DHT11, DHT22, DHT21
#define DHTTYPE DHT11 //
// 온습도 센서 객체 생성
DHT dht(DHTPIN, DHTTYPE);
// 80번 포트에서 AsyncWebServer 객체 생성
AsyncWebServer server(80);
// DHT 온도 읽기 함수
String readDHTTemperature() {
// 센서 읽기는 최대 2초가 소요될 수 있습니다. (센서가 매우 느립니다.)
// 기본값으로 섭씨로 온도 읽기
float t = dht.readTemperature();
// 화씨로 온도 읽으려면 매개 변수를 true로 설정
//float t = dht.readTemperature(true);
// 읽기 실패 시 조기에 종료하고 다시 시도합니다.
if (isnan(t)) {
Serial.println("DHT 센서에서 읽기 실패!");
return "--";
}
else {
Serial.println(t);
return String(t);
}
}
// DHT 습도 읽기 함수
String readDHTHumidity() {
// 센서 읽기는 최대 2초가 소요될 수 있습니다. (센서가 매우 느립니다.)
float h = dht.readHumidity();
if (isnan(h)) {
Serial.println("DHT 센서에서 읽기 실패!");
return "--";
}
else {
Serial.println(h);
return String(h);
}
}
// 인덱스 HTML 페이지
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.7.2/css/all.css"
integrity=
"sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr"
crossorigin="anonymous">
<style>
html {
font-family: Arial;
display: inline-block;
margin: 0px auto;
text-align: center;
}
h2 { font-size: 3.0rem; }
p { font-size: 3.0rem; }
.units { font-size: 1.2rem; }
.dht-labels {
font-size: 1.5rem;
vertical-align: middle;
padding-bottom: 15px;
}
</style>
</head>
<body>
<h2>ESP32 DHT Server</h2>
<p>
<i class="fas fa-thermometer-half" style="color:#059e8a;"></i>
<span class="dht-labels">온도</span>
<span id="temperature">%TEMPERATURE%</span>
<sup class="units">°C</sup>
</p>
<p>
<i class="fas fa-tint" style="color:#00add6;"></i>
<span class="dht-labels">습도</span>
<span id="humidity">%HUMIDITY%</span>
<sup class="units">%</sup>
</p>
</body>
<script>
setInterval(function() {
// XMLHttpRequest 객체를 사용하여 "/temperature" 엔드포인트로 GET 요청을 전송
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// 요청이 완료되고 성공적인 응답을 받았을 때, 온도 값을 업데이트합니다.
document.getElementById("temperature").innerHTML = this.responseText;
}
};
xhttp.open("GET", "/temperature", true);
xhttp.send();
}, 2000); // 2초 간격으로 업데이트
setInterval(function() {
// XMLHttpRequest 객체를 사용하여 "/humidity" 엔드포인트로 GET 요청을 전송
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// 요청이 완료되고 성공적인 응답을 받았을 때, 습도 값을 업데이트
document.getElementById("humidity").innerHTML = this.responseText;
}
};
xhttp.open("GET", "/humidity", true);
xhttp.send();
}, 2000); //2초 간격으로 업데이트
</script>
</html>
)rawliteral";
// 플레이스홀더를 DHT 값으로 대체하는 함수
String processor(const String& var){
if(var == "TEMPERATURE"){
return readDHTTemperature();
}
else if(var == "HUMIDITY"){
return readDHTHumidity();
}
return String();
}
void setup(){
// 디버깅용 시리얼 포트 설정
Serial.begin(115200);
dht.begin();
// Wi-Fi 연결
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Wi-Fi에 연결 중...");
}
// ESP32 로컬 IP 주소 출력
Serial.println(WiFi.localIP());
// 루트(/) 웹 페이지 라우팅
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
// "/temperature" 엔드포인트에 대한 GET 요청 처리
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", readDHTTemperature().c_str());
});
// "/humidity" 엔드포인트에 대한 GET 요청 처리
server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", readDHTHumidity().c_str());
});
// 서버 시작
server.begin();
}
void loop(){
}
- 기능에만 집중하기 위하여 아래와 같이 조금 더 간결한 코드를 작성해보았다.
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebSrv.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
const char* ssid = "네트워크_SSID";
const char* password = "네트워크_비밀번호";
#define DHTPIN 18 // DHT 센서에 연결된 디지털 핀
#define DHTTYPE DHT11
// 온습도 센서 객체 생성
DHT dht(DHTPIN, DHTTYPE);
// 80번 포트에서 AsyncWebServer 객체 생성
AsyncWebServer server(80);
// DHT 온도 읽기 함수
String readDHTTemperature() {
// 센서 읽기는 최대 2초가 소요될 수 있습니다. (센서가 매우 느립니다.)
// 기본값으로 섭씨로 온도 읽기
float t = dht.readTemperature();
// 화씨로 온도 읽으려면 매개 변수를 true로 설정
//float t = dht.readTemperature(true);
// 읽기 실패 시 조기에 종료하고 다시 시도합니다.
if (isnan(t)) {
Serial.println("DHT 센서에서 읽기 실패!");
return "--";
}
else {
Serial.println(t);
return String(t);
}
}
// DHT 습도 읽기 함수
String readDHTHumidity() {
// 센서 읽기는 최대 2초가 소요될 수 있습니다. (센서가 매우 느립니다.)
float h = dht.readHumidity();
if (isnan(h)) {
Serial.println("DHT 센서에서 읽기 실패!");
return "--";
}
else {
Serial.println(h);
return String(h);
}
}
void setup(){
// 디버깅용 시리얼 포트 설정
Serial.begin(115200);
dht.begin();
// Wi-Fi 연결
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Wi-Fi에 연결 중...");
}
// ESP32 로컬 IP 주소 출력
Serial.println(WiFi.localIP());
// 루트(/) 웹 페이지 라우팅
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
String temperature = readDHTTemperature();
String humidity = readDHTHumidity();
String response = "온도 " + temperature + "\n습도 " + humidity;
request->send(200, "text/plain; charset=utf-8", response);
});
// 서버 시작
server.begin();
}
void loop(){
}
- 위와 같이 코드를 수정하여 온습도의 수치만을 확인할 수도 있다.
로컬 서버를 외부 네트워크 에 노출시켜주는 도구로, 로컬 컴퓨터에서 실행 중인 웹 서버나 API 서버 등을 외부에서 접근 가능 하게 해준다. 개발자가 로컬 환경에서 웹 애플리케이션을 개발하고 테스트할 때 유용하며, 외부 사용자에게 서비스를 제공하기 전에 테스트하는 데에도 사용할 수 있다.
- 아래는 Ngrok를 통해 웹 서버를 외부로 호스팅하여 외부에서도 접속이 가능하게 해주는 절차이다.
1) https://dashboard.ngrok.com/signup 에 접속하여 가입하고 이메일 계정을 인증한다.
3) https://ngrok.com/download 에서 프로그램을 다운 받고 압축을 풀어 둔다.
온습도 센서 모듈을 작동시키는 데에 그치는 것이 아니라, 직접 웹 서버를 구축하고 외부에서의 접속을 가능하게 하는 과정을 직접 실행해보았다. 이를 통해 유동 IP와 DDNS 서비스 등 네트워크에 대한 이해력이 크게 향상되었다. 특히 Ngrok와 같은 어떠한 서비스를 이용하여 외부로 호스팅하는 기술 등으로 외부 접속을 허용하게 할 수 있다는 이 흥미로웠다.