아두이노와 무선인터넷을 통한 간단한 서버 구성 세 번째
네트워크 정보를 제공하는 간단한 서버 만들기
이제 이전 글에서 만든 소스에 웹페이지를 전송하는 부분을 추가해 보겠습니다.
서버, 클라이언트 사이의 웹페이지 방식 통신 프로토콜은 HTTP를 사용합니다. 이제까지 봐 왔던 클라이언트의 요청 메시지가 HTTP 방식의 Request였고, 여기에 응답하는 서버 Response도 HTTP 방식을 사용합니다. 기본적인 HTTP Response Message Format은 아래 그림과 같습니다.
HTTP Response는 그림에서 보는 것처럼, 크게 네 부분으로 구성됩니다.
HTTP Response : Status Line, Header, Empty Line
HTTP/1.1 200 OK
응답 메시지의 헤더는 항상 Status Line으로 시작합니다. HTTP/1.1은 버전을 의미하고, 뒤 이어 오는 숫자는 Response Code 즉, 응답 코드입니다. 클라이언트 요청의 처리 결과에 따라 여러 가지 응답 코드를 전송하며, 200은 정상적인 상태를 의미하며 바로 옆의 메시지와 의미가 같습니다.
Content-type:text/html
header가 끝나고 이어지는 contents가 어떤 타입인지 클라이언트가 알게 합니다. 헤더의 종류에는 General, Response, Entity 헤더들이 있고 여러가지 내용의 헤더들이 올 수 있지만 필요한 내용만 언급하도록 하겠습니다.
WiFi 모듈을 통해서 클라이언트쪽으로 메시지를 전송하기 위해서 client.println() 함수를 사용하면 됩니다. Serial 모니터에 출력할 때와 동일한 모습이기 때문에 어려움이 없을 듯 합니다.
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
위와 같이 Status Line 및 Header를 구성합니다. 헤더 뒤에는 꼭 빈 줄을 삽입하여 구분하여 줍니다.
HTTP Response Message Body
이제 Message Body 부분을 작성해야 합니다. 메시지는 익히 알고 있듯이 태그로 구성되어 있습니다.
<!DOCTYPE html>
HTTP Response의 Contents는 위 태그로 시작합니다. HTML5로 만들어진 문서라는 선언입니다. 지금은 없어도 되지만, 앞으로를 위해 넣어 줍니다. 나머지는 일반적인 HTML 문서의 구성을 따르므로 따로 설명할 내용은 없을 듯 합니다.
client.println("<!DOCTYPE html>");
client.println("<html>");
client.println("<head>");
client.println("<title>Network Info</title>");
client.println("</head>");
client.println("<body>");
client.println("<h1>WiFi Connection Status</h1>");
client.println("</body>");
client.println("</html>");
break;
마지막 줄에 break 명령이 있습니다. 웹페이지를 모두 전송했으면 while문을 탈출하고 Client 접속을 종료하도록 합니다.
<html>, </html>태그내에 <head>와 <body>가 존재하는 일반적인 HTML 문서의 모습입니다.
여기까지 완료하고 구동했을 때의 모습입니다. 이제, 좀더 내용을 추가해 보겠습니다.
네트워크 정보 출력하기
웹페이지의 빈 공간을 채우기 위해서 우선 IP주소를 출력해 보겠습니다.
IPAddress ip = WiFi.localIP();
Serial.print("To see this page in action, open a browser to http://");
Serial.println(ip);
void setup()
함수의 마지막 부분입니다. 이전 글에서 얘기했듯이, IPAddress는 IP주소를 담기 위한 Class입니다. IPAddress type의 변수 "ip"를 생성하고 여기에 WiFi.localIP()
함수를 이용해서 모듈의 IP주소를 구하여 저장합니다. 이 IP주소를 웹페이지에 출력 하기위해 "ip" 변수를 바로 사용하면 에러가 납니다. ip변수는 setup()
함수내에서 선언한 지역 변수이기 때무에 loop()
함수 내에서 참조할 수 없기 때문입니다.
exit status 1
'ip' was not declared in this scope
loop()
함수내에선 선언한 바 없고 setup()
함수 안쪽은 볼 수 없기 때문에 발생하는 에러입니다. 이를 해결하기 위해서 'ip' 변수 선언 부분을 setup()
함수 위쪽으로 옮기겠습니다.
#include <SPI.h>
#include <WiFi101.h>
String ssid = "TURTLE";
String pass = "yesican1";
IPAddress ip;
int status = WL_IDLE_STATUS;
위와 같이 선언 부분을 옮겼습니다.
ip = WiFi.localIP();
Serial.print("To see this page in action, open a browser to http://");
Serial.println(ip);
또, 원래 있던 자리(setup()
함수 내...)도 위와 같이 선언 부분을 생략하도록 변경했습니다.
client.println("<!DOCTYPE html>");
client.println("<html>");
client.println("<head>");
client.println("<title>Network Info</title>");
client.println("</head>");
client.println("<body>");
client.println("<h1>WiFi Connection Status</h1>");
client.println("<p>IP Address : ");
client.println(ip);
client.println("</p>");
client.println("</body>");
client.println("</html>");
break;
IP주소를 출력하기 위해서 위와 같이 태그를 추가하였습니다. ip 변수는 이제 참조 가능하기에 바로 사용하면 됩니다. 헤더 부분과 달리 메시지 바디는 줄바꿈이 중요하지 않기 때문에, print(), println()
함수의 차이가 없습니다. 또, WiFi.localIP()
함수의 반환값을 바로 print()
함수내에 사용할 경우 다른 값이 출력되므로 꼭 IPAddress class type 변수에 담아서 사용해야 합니다.
위와 같이 결과를 확인했습니다. 이제 몇가지 정보를 더 추가하겠습니다.
client.print("<p>SSID : ");
client.print(WiFi.SSID());
client.print("</p>");
client.print("<p>RSSI : ");
client.print(WiFi.RSSI());
client.print(" dBm</p>");
결과 화면입니다. 서버로서 웹페이지를 제공할 수 있는 기본 틀은 완성했기 때문에, 태그 출력 부분만 수정하면 쉽게 원하는 페이지를 구성할 수 있습니다. 전체 소스는 아래 접혀진 부분을 참고하세요! 이상입니다.
#include <SPI.h>
#include <WiFi101.h>
String ssid = "TURTLE";
String pass = "yesican1";
IPAddress ip;
int status = WL_IDLE_STATUS;
WiFiServer server(80);
void setup() {
WiFi.setPins(8, 7, 4, 2);
Serial.begin(9600);
delay(1000); // 시리얼모니터 출력 준비를 위한 대기시간
// check for the presence of the shield:
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println("WiFi shield not present");
while (true); // don't continue
}
while ( status != WL_CONNECTED) { // 연결될 때까지 계속 실행
Serial.print("Attempting to connect to Network named: ");
Serial.println(ssid); // print the network name (SSID);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid, pass);
// wait 2 seconds for connection:
delay(2000);
}
Serial.println("Network connected.");
server.begin();
ip = WiFi.localIP();
Serial.print("To see this page in action, open a browser to http://");
Serial.println(ip);
}
void loop() {
WiFiClient client = server.available();
if (client) {
Serial.println("new client");
bool newLine = false;
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.write(c);
if (c == '\n') {
if (newLine) {
Serial.println("Client Request Ended!");
Serial.println("Web page transfer Start!");
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
client.println("<!DOCTYPE html>");
client.println("<html>");
client.println("<head>");
client.println("<title>Network Info</title>");
client.println("</head>");
client.println("<body>");
client.println("<h1>WiFi Connection Status</h1>");
client.println("<p>IP Address : ");
client.println(ip);
client.println("</p>");
client.print("<p>SSID : ");
client.print(WiFi.SSID());
client.print("</p>");
client.print("<p>RSSI : ");
client.print(WiFi.RSSI());
client.print(" dBm</p>");
client.println("</body>");
client.println("</html>");
break;
} else {
newLine = true;
}
} else if (c != '\r') {
newLine = false;
}
}
}
client.stop();
Serial.println("client disonnected");
}
}