모든 내용은 [윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어] 를 기반으로 제 나름대로 이해하여 정리한 것입니다. 다소 부정확한 내용이 있을수도 있으니 이를 유념하고 봐주세요!
# 웹 서버의 이해
- "HTTP 프로토콜을 기반으로 웹 페이지에 해당하는 파일을 클라이언트에게 전송하는 역할의 서버"
- HTTP : Hypertext Transfer Protocol의 약자
- Hypertext : 클라이언트의 선택에 따라서 이동이 가능한 조직화된 정보
- HTTP 프로토콜 : Hypertext의 전송을 목적으로 설계된 어플리케이션 레벨의 프로토콜(TCP/IP 기반)
- 인터넷 브라우저도 소켓 기반의 클라이언트 프로그램
(웹 서버에 접속을 시도하기 위해서 브라우저 내부적으로 소켓을 생성함)
- 브라우저는 서버가 전송하는 HTML문으로 이뤄진 Hypertext를 HTML 문법을 근거로 보기 좋게 변환해서 보여줌
- 웹서버는 HTTP라는 이름의 프로토콜을 기반으로 Hypertext를 전송하는 서버
# HTTP(Hypertext Transfer Protocol)
1. 상태가 존재하지 않는 Stateless 프로토콜
1) 클라이언트가 데이터를 요청하면
2) 서버가 클라이언트의 요청에 응답하고
3) 바로 연결을 종료함("클라이언트의 상태정보를 유지하지 않음") => Stateless
* 이러한 HTTP의 특징을 보완하고자 "쿠키"와 "세션"이라는 이름의 기술이 개발되어 사용됨.(상태정보의 유지)
2. 요청 메시지와 응답 메시지의 구성
- 클라이언트와 웹 서버 사이의 데이터 요청, 데이터 응답 방식은 표준화 되어 있음.
- 요청 메시지(클라이언트가 웹 서버에 전달) : HTTP 요청 헤더
1) 요청 라인 : 요청방식(요청목적)에 대한 정보가 삽입됨.
(GET : 주로 데이터 요청시 사용 / POST : 주로 데이터 전송시 사용)
반드시 하나의 행으로 구성해서 전송하도록 약속되어 있으므로,
전체 HTTP 요청 헤더 중 첫번째 행을 추출해서 쉽게 요청 라인에 삽입된 정보 확인 가능.
2) 메시지 헤더 : 요청에 사용된(응답 받을) 브라우저 정보, 사용자 인증정보 등
HTTP 메세지에 대한 부가적인 정보가 담김.
* 공백라인 : 헤더와 몸체 사이에 한 줄 삽입 되어서 둘을 구분하도록 약속 되어 있음.
3) 메시지 몸체 : 클라이언트가 웹서버에게 전송할 데이터가 담기는데,
이를 담기 위해서는 POST 방식으로 요청을 해야 함.
- 응답 메시지(웹 서버가 클라이언트에 전달) : HTTP 응답 헤더
1) 상태 라인 : 클라이언트의 요청에 대한 결과정보가 담김
* 대표적인 상태코드
200 OK : 요청이 성공적으로 처리됨.
404 Not Found : 요청한 파일이 존재하지 않음.
400 Bad Request : 요청 방식이 잘못되었으니 확인요망.
2) 메시지 헤더 : 전송되는 데이터의 타입 및 길이정보 등이 담김.
* 공백라인 : 헤더와 몸체 사이에 한 줄 삽입 되어서 둘을 구분하도록 약속 되어 있음.
3) 메시지 몸체 : 클라이언트가 요청한 파일의 데이터가 담김.
# 웹 서버는 HTTP 프로토콜을 기반으로 하기 때문에 IOCP나 epoll 모델을 적용한다고 해서 많은 이점을 얻을 수 있느 것은 아니다.(이점이 있기는 하다.) 클라이언트와 서버가 한번씩 데이터를 주고 받은 후에 바로 연결을 끊기 때문이다. IOCP나 epoll 모델은 서버와 클라이언트가 일정시간 연결을 유지한 상태에서 크고 작은 메시지를 빈번히 주고받는 경우에(온라인 게임 서버가 아주 대표적인 예) 그 위력이 돋보이는 모델들이다.
# 리눅스 기반 웹 서버
webserv_linux.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#include <stdbool.h>
#define BUF_SIZE 1024
#define SMALL_BUF 100
void* request_handler(void* arg);
void send_data(FILE* fp, char* ct, char* file_name);
char* content_type(char* file);
void send_error(FILE* fp);
void error_handling(char* message);
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
int clnt_adr_size;
char buf[BUF_SIZE];
pthread_t t_id;
if(argc!=2) // 실행파일 경로/PORT번호 를 입력으로 받아야함
{
printf("Usage : %s <port>\n", argv[0]);
exit(EXIT_FAILURE);
}
serv_sock=socket(PF_INET, SOCK_STREAM, 0); // TCP 소켓 생성
/* 서버 주소정보 초기화 */
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
/* 서버 주소정보를 기반으로 주소할당 */
if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
/*
서버소켓(리스닝 소켓)이 됨
연결요청 대기큐가 생성되며,
이 함수호출 이후로부터 클라이언트의 연결요청이 가능함.
*/
if(listen(serv_sock, 20)==-1)
error_handling("listen() error");
/* 요청 및 응답 */
while(1)
{
clnt_adr_size=sizeof(clnt_adr);
/*
클라이언트의 연결요청을 수락
클라이언트와의 송수신을 위해 새로운 소켓 생성
*/
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_size);
printf("Connection Request : %s:%d\n",
inet_ntoa(clnt_adr.sin_addr), ntohs(clnt_adr.sin_port));
pthread_create(&t_id, NULL, request_handler, &clnt_sock); // 쓰레드 생성 및 실행
pthread_detach(t_id); // 종료된 쓰레드의 리소스 소멸
}
close(serv_sock); // 서버소켓 연결종료
return 0;
}
void* request_handler(void *arg)
{
int clnt_sock=*((int*)arg);
char req_line[SMALL_BUF];
FILE* clnt_read;
FILE* clnt_write;
char method[10];
char ct[15];
char file_name[30];
/* 입출력 분할 */
clnt_read=fdopen(clnt_sock, "r");
clnt_write=fdopen(dup(clnt_sock), "w");
fgets(req_line, SMALL_BUF, clnt_read); // 클라이언트로부터 데이터 수신
if(strstr(req_line, "HTTP/")==NULL) // HTTP에 의한 요청인지 확인
{
send_error(clnt_write);
fclose(clnt_read);
fclose(clnt_write);
return NULL;
}
strcpy(method, strtok(req_line, " /"));
if(strcmp(method, "GET")!=0) // GET 방식 요청인지 확인
{
send_error(clnt_write);
fclose(clnt_read);
fclose(clnt_write);
return NULL;
}
strcpy(file_name, strtok(NULL, " /"));
strcpy(ct, content_type(file_name));
fclose(clnt_read); // 클라이언트로부터 데이터 수신을 완료했으므로 입력을 위한 스트림 종료
send_data(clnt_write, ct, file_name); // 클라이언트에게 데이터 송신
}
void send_data(FILE* fp, char* ct, char* file_name)
{
char protocol[]="HTTP/1.0 200 OK\r\n";
char server[]="Server:Linux Web Server\r\n";
char cnt_len[]="Content-length:2048\r\n";
char cnt_type[SMALL_BUF];
char buf[BUF_SIZE];
FILE* send_file;
sprintf(cnt_type, "Content-type:%s\r\n\r\n", ct);
send_file=fopen(file_name, "r"); // 클라이언트가 요청한 파일 열기
if(send_file==NULL)
{
send_error(fp);
fclose(fp);
return;
}
/* 헤더 정보 전송 */
fputs(protocol, fp);
fputs(server, fp);
fputs(cnt_len, fp);
fputs(cnt_type, fp);
/* 요청 데이터 전송 */
while(fgets(buf, BUF_SIZE, send_file)!=NULL)
{
fputs(buf, fp);
fflush(fp);
}
fflush(fp);
fclose(fp); // HTTP 프로토콜에 의해서 응답 후 종료
}
char* content_type(char* file) // Content-Type 구분
{
char extension[SMALL_BUF];
char file_name[SMALL_BUF];
strcpy(file_name, file);
strtok(file_name, ".");
strcpy(extension, strtok(NULL, "."));
if(!strcmp(extension, "html")||!strcmp(extension, "htm"))
return "text/html";
else
return "text/plain";
}
void send_error(FILE* fp) // 오류 발생시 메시지 전달
{
char protocol[]="HTTP/1.0 400 Bad Request\r\n";
char server[]="Server:Linux Web Server\r\n";
char cnt_len[]="Content-length:2048\r\n";
char cnt_type[]="Content-type:text/html\r\n\r\n";
char content[]="<html><head><title>NETWORK</title></head>"
"<body><font size=+5><br>오류 발생! 요청 파일명 및 요청 방식 확인!"
"</font></body></html>";
/* 헤더 정보 전송 */
fputs(protocol, fp);
fputs(server, fp);
fputs(cnt_len, fp);
fputs(cnt_type, fp);
fflush(fp);
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(EXIT_FAILURE);
}
- 이 소스파일과 동일한 경로에 "index.html" 파일이 있어야 제대로 동작합니다.
index.html
<meta charset="UTF-8" />
<html>
<head><title>NETWORK</title></head>
<body><font size=+5>
TCP/IP Programming이 <br>
재미있으셨나요?
</font></body>
</html>
[출처] : 윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어
'Programming > 열혈 TCP, IP 소켓 프로그래밍(저자 윤성우)' 카테고리의 다른 글
Ch 19. Windows에서의 쓰레드 사용 (0) | 2020.08.14 |
---|---|
Ch 24. 내용 확인문제 (0) | 2020.08.14 |
Ch 18. 내용 확인문제 (0) | 2020.08.10 |
Ch 18. 멀티쓰레드 기반의 서버구현 (0) | 2020.08.10 |
Ch 17. 내용 확인문제 (0) | 2020.08.10 |