[아두이노] [강좌] 51. 와이파이 통신 (5) - WebServer 예제 (2)
자, 이번 강좌에서는 WiFly_WebServer 예제의 loop() 함수 부분에 대해 알아보자. setup() 함수 부분은 지난 강좌를 참조.
소스는 다시 한 번 보고 시작할까.
WiFly_WebServer.ino |
#include <SPI.h> #include <WiFly.h> #include "Credentials.h" WiFlyServer server(80); void setup() { WiFly.begin(); if (!WiFly.join(ssid, passphrase)) { while (1) { // Hang on failure. } } Serial.begin(9600); Serial.print("IP: "); Serial.println(WiFly.ip());
server.begin(); } void loop() { WiFlyClient client = server.available(); if (client) { boolean current_line_is_blank = true; while (client.connected()) { if (client.available()) { char c = client.read(); if (c == '\n' && current_line_is_blank) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println();
for (int i = 0; i < 6; i++) { client.print("analog input "); client.print(i); client.print(" is "); client.print(analogRead(i)); client.println("<br />"); } break; } if (c == '\n') { current_line_is_blank = true; } else if (c != '\r') { current_line_is_blank = false; } } } delay(100); client.stop(); } } |
loop() 함수의 첫 줄부터 아는 함수가 나왔!!
WiFlyClient WiFlyServer.available()
매개 변수 없음 반환 값 WiFlyClient 타입의 클래스 변수. 서버로 연결을 요청한 클라이언트가 있을 경우 WiFlyClient 객체를 생성하여 반환하고, 없을 경우 NULL 값을 반환한다. |
WiFlyClient 클래스와 WiFlyServer 클래스가 연달아 나와서 헷갈릴 수 있지만, WiFlyClient 클래스는 반환 타입인 것에 유의하자.
WiFlyClient 클래스는 클라이언트 모드로 서버에 접속하기 위한 기능이 정의되어 있을 뿐만 아니라 서버가 클라이언트에 데이터를 전송하기 위한 기능 역시 포함되어 있다.
아무튼 WiFlyServer.available() 함수는 현재 서버에 접속을 요청한 클라이언트가 있는지 검사하여, 있다면 해당 클라이언트의 접속 요청을 승인하고 WiFlyClient 객체를 생성하여 반환한다. 없다면 NULL을 반환한다.
그래서 "if(client)" 조건문은 접속을 요청한 클라이언트가 있을 경우 수행된다.
if(client) { boolean current_line_is_blank = true; while(client.connected()) { if(client.available()) { char c = client.read(); // .. 중략 } } delay(100); client.stop(); } |
얼, 중요한 함수가 세 개나 연달아 나오다니. 'current_line_is_blink' 변수에 대한 건 이따가 설명하도록 하고, 우선 함수 설명부터.
boolean WiFlyClient.connected()
매개 변수 없음 반환 값 클라이언트와의 연결 여부. 연결 중일 경우 TRUE, 연결이 해제되었을 경우 FALSE를 반환. |
'WiFly_WebClient' 예제에 나왔던 WiFlyClient.connect() 함수와 혼동하지 말자. WiFlyClient.connect() 함수는 클라이언트가 서버로 연결 요청한 후 승인이 되어 연결이 정상적으로 이루어졌는가를 반환하는 함수이고, 지금 나오는 WiFlyClient.connected() 함수는 서버의 입장에서 클라이언트의 연결 요청을 승인한 후 연결이 유지되고 있는지를 반환하는 함수다. WiFlyClient.stop() 함수가 호출되거나 다른 오류에 의해 연결이 해제되기 전까지 'TRUE'가 반환되며, 연결이 해제되면 'FALSE'가 반환된다.
int WiFlyClient.available()
'WiFly_WebClient' 예제에서 설명했던 기능과 동일하다. 클라이언트로부터 수신된 데이터가 있다면 수신된 데이터의 길이를 반환한다. 없을 경우 '0'을 반환한다.
좀 전에 나온 WiFlyServer 클래스의 avaiable() 함수와 혼동하지 말 것!
int WiFlyClient.read()
역시 'WiFly_WebClient' 예제에서의 기능과 동일하다. 클라이언트로부터 수신된 데이터를 한 바이트 읽어온다.
설명하고보니 WiFlyClient.connected() 함수 빼고는 지지난 강좌에서 다 설명한 함수들이구먼. 단, 대상이 서버에서 클라이언트로 바뀌었을 뿐. 쨌든 다 같은 함수다.
연결을 요청한 클라이언트가 있다면 그 클라이언트가 접속해있는 동안 while() 반복문으로 수신된 데이터를 확인하고 읽어오는 동작을 반복한다. "// .. 중략" 부분에서 실제로 데이터를 처리하고 전송하는 동작.
while() 반복문 속의 내용을 살펴보자. 우선 뒷 부분.
if(c == '\n') { current_line_is_blank = true; } else if(c != '\r') { current_line_is_blank = false; } |
HTTP 구문에서 요청 메시지의 마지막 줄은 아무 문자 없이 줄바꿈 문자('\r'+'\n')만 있다. 'current_line_is_blank' 변수는 이 전 문자가 '\r'인지, 또 그 이전 문자가 '\n'이었는지를 확인하는 용도로 사용된다. 즉, 이전 줄의 '\n' 다음에 바로 '\r' 문자가 나오는 지를 확인하는 용도. 그럴 경우 'current_line_is_blank' 변수가 'true', 아닐 경우 'false' 값을 가지게 된다.
char c = client.read(); if(c == '\n' && current_line_is_blink) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); for(int i=0; i<6; i++) { client.print("analog input "); client.print(i); client.print(" is "); client.print(analogRead(i)); client.println("<br />"); } break; } |
그래서 현재 읽어온 값이 '\n'이고 'current_line_is_blink' 값이 'true'일 경우, 즉 이 전 줄의 '\n' 문자 다음에 다른 문자 없이 바로 '\r' 문자와 '\n' 문자가 연달아 나올 경우 "if(c == '\n' && current_line_is_blank)" 조건을 만족하게 된다. 구현된 방식이 조금 복잡해보이긴 하지만, 간단하게 말하면 클라이언트로부터의 데이터 수신이 끝날 경우를 검사하는 것.
클라이언트로부터의 데이터 수신이 끝나면 서버는 클라이언트로 요청 받은 데이터를 전송한다. 원래라면 수신된 데이터가 뭔지를 먼저 검사해야겠지만, 이건 간단한 예제니까 바로 화면 데이터 정보를 전달한다.
WiFlyClient.print(value)
WiFlyClient.println(value)
클라이언트로 데이터를 전송한다. 매개 변수로는 int, char, float, String, 문자열 등이 전부 들어갈 수 있다는 건 이제 말 안해도 알겠지!
윗 세 줄은 HTTP 구문 중 응답 데이터의 표준 헤더 구문이다. HTTP의 버전과 HTML 언어를 사용한 구문이라는 것 등.
그리고는 for() 반복문을 통해 A0번 핀부터 A5번 핀까지의 아날로그 입력 값을 문자열과 함께 출력한다. "<br />"은 HTML 언어에서 줄바꿈을 나타내는 구문이다. A5번 핀의 입력 값까지 출력이 끝나면 'break'를 호출하여 while() 반복문을 종료한다.
delay(100); client.stop(); |
반복문을 종료한 후 WiFlyClient.stop() 함수로 클라이언트와의 연결을 해제한다.
자, 소스 설명은 끝났으니 업로드한 후 실행해보자. 드디어!!
업로드 후 시리얼 모니터를 열면, AP에 정상적으로 접속됐을 경우 할당받은 IP 주소가 출력된다. setup() 함수에서 "Serial.println(WiFly.ip());" 했으니까.
그럼 표시된 IP로 접속해보자. 어떻게?? 웹 브라우저 열고 주소창에 IP만 치면 됨. 올.
포트 번호는 웹 브라우저에서 기본 '80'번으로 지정해주니까 생략. 만일 WiFlyServer 객체 생성 시 포트 번호를 다른 번호로 설정했다면 IP 주소 뒤에 ':'을 붙인 후 포트 번호를 함께 써줘야 한다.
아무튼 IP 주소를 넣으니까 아날로그 입력 값 6개가 똭!!
난 지금 A0~A5번 핀에 아무 것도 연결을 안 한 '플로팅' 상태기 때문에 임의의 값들이 출력되고 있다. 만일 아날로그 출력 센서들을 연결했다면 그 센서의 값이 출력되겠지.
A0번 핀에 습도 센서가, A1번 핀에 온도 센서가 연결되어 있다고 가정하자. 그리고 아두이노 보드와 센서들은 집 거실에 설치되어 있다. 물론 와이파이 모듈도 연결되어 있고.
그럼 웹 브라우저에서 위 IP를 치는 것만으로도 난 우리 집 거실의 온도와 습도 값을 알 수 있게 되는 것!! 오올!!!
당연히 모바일에서도 접속할 수 있다. 자기 핸드폰의 웹 브라우저를 실행한 후 주소 창에 IP를 적어 접속해보자.
뜬다. 얏호!
한 가지 치명적인 단점은 AP에서 할당받은 IP 주소가 내부 IP일 경우 같은 AP에 접속되어 있는 기기에서만 접근할 수 있다는 것이다. 외부에서는 접근할 수 없어.
외부에서도 접근하기 위해서는 AP의 설정을 변경하여 고유 IP를 할당하도록 해야 한다. 아무튼 방법은 있다니까. 히히.
'WiFly_WebServer' 예제와 'WiFly_WebClient' 예제를 이용하면 와이파이를 이용한 무궁무진한 예제들을 만들 수 있다.
이번 강좌는 여기까지. 다음 강좌에서 또 만나요. 제발~