본문 바로가기

컴퓨터 네트워크/소켓 프로그래밍

TCP 소켓프로그래밍으로 웹서버 구현하기

네트워크 강의를 들으면서 배운 TCP 소켓프로그래밍을 통해 간단한 웹서버를 구현하였다.

 

일단 이 내용을 따라오기 위해선 소켓프로그래밍에 대한 이해가 필요한데 내 블로그에 자세한 설명이 있으므로 읽고오자!

 

또한 전문지식이 부족한 내가 직접 구현한 코드이므로 살짝 조잡하긴 하다.

 

1. 소켓프로그래밍 (1) (링크)

 

Socket Programming (소켓 프로그래밍) (1)

API API란 시스템이 어플리케이션에 제공하는 인터페이스이다. API에는 여러 종류가 있는데 우리는 TCP/IP에서 쓰이는 다양한 API중 Sokcet에 대해 알아 볼 예정이다. TCP/IP에 쓰이는 API는 Socket말고도 TL

tjdahr25.tistory.com

2. 소켓프로그래밍 (2) (링크)

 

Socket Programming (소켓 프로그래밍) (2)

지난 글에서는 server와 client의 연결, 그리고 메시지를 보내고 받는 함수들에 대해서 알아보았다. 본 글에서는 앞서 배운 과정과 함수들을 이용하여 직접 통신해보는 프로그램을 구현한다. 지난

tjdahr25.tistory.com

 

웹서버 구현


기본적으로 소켓프로그래밍, 통신과 웹서버에 대한 이해관계를 파악해야 한다.


좋은 그림이 있어서 가져와봤다.

서버에서 자신의 어떤 포트넘버에 대해 소켓을 생성해 응답을 기다리고 있으면, 클라이언트가 인터넷 브라우저에서 서버의 (주소:포트넘버)로 요청을 보내는 것이다. 이때 인터넷 브라우저는 HTTP 프로토콜로 구현이 되어있으므로 HTTP request 메시지 형식에 맞춰 서버에게 메시지를 보낸다. 

 

그 메시지를 통해 서버는 GET/POST방식을 구분하고 그에 따라 처리해주는 것이다.

 

일단 구현하기에 앞서 서버는 두가지의 html페이지를 가지고 있다고 가정한다.

 

1. index.html (디폴트 페이지가 될 예정)

<!DOCTYPE html>
<html>
<head>
 <title>My Web Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="icon" href="data:,">
</head>
<body>
 <h1>Welcome to My Web Page!</h1>
</body>
</html>

2. query.html (정보를 입력할 수 있는 페이지)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <title>POST Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="icon" href="data:,">
</head>
<body>
<h2>Example of sending data using POST</h2>
<form action="/sample" method="post">
 Name:
 <input type="text" name="name"/><br/>
 Student number:
 <input type="text" name="snumber"/><p/>
 <input type="submit" value="submit"/>
</form>
</body>
</html>

 

1. 서버의 bind와 listen


    if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1) {
    	error_handling("bind() error");
    }
    if(listen(serv_sock, 5)==-1){
    	error_handling("listen() error");
    }

서버는 accept를 하기 전 bind와 listen함수를 통해 클라이언트로부터 메시지를 받을 준비를 마친다.

 

 

2. 서버의 accept


	while(1){
        clnt_addr_size=sizeof(clnt_addr);
        clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
        if(clnt_sock==-1){
    	    error_handling("accept() error");
            break;
        }  
        printf("Connection Request : %s:%d\n", inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
        request_handler(&clnt_sock);
    }

서버는 클라이언트로부터 request를 기다리고 응답을 받으면 request_handler를 호출한다. (응답에 대한 request)

 

3. 서버의 request handler


void request_handler(void *arg){
    char msg[BUFSIZE];
	char *firstLine[3];

	int sd = *(int*)arg;

	int rcvd = recv(sd, msg, BUFSIZE-1, 0);
	if(rcvd<=0) error_handling("Error about recv()!!");
	printf("-----------Request message from Client-----------\n");
	printf("%s",msg);
	printf("\n-------------------------------------------------\n");
	char post_information[SEND_MESSAGE_BUFSIZE];
	char *curr_msg = NULL;
	char METHOD[4]="";
	char VERSION[10]="";
	char URL[SEND_MESSAGE_BUFSIZE]="";

	curr_msg = strtok(msg, "\n");
	
	int line_count=1;
	while(curr_msg){
		if(line_count>=15) strcpy(post_information, curr_msg);
		curr_msg = strtok(NULL, "\n");
		line_count++;
	}

	firstLine[0] = strtok(msg, " \t\n");
	firstLine[1] = strtok(NULL, " \t");
	firstLine[2] = strtok(NULL, " \t\n");
	
	strcpy(METHOD, firstLine[0]);
	strcpy(URL, firstLine[1]);
	strcpy(VERSION, firstLine[2]);
	
	if(!strncmp(METHOD, "GET",3)) GET_handler(VERSION, msg, URL, sd);
	else if(!strncmp(METHOD, "POST",4)) POST_handler(VERSION, msg, URL, sd, post_information);

	shutdown(sd, SHUT_RDWR);
	close(sd);
}

클라이언트로부터 request를 받는다. 이때 request 메시지는 다음과 같은 형식이다.

위의 결과는 클라이언트가 GET방식을 통해 서버에게 접근요청을 할 때 보낸 메시지이다. 

1,2번째 줄을 보면 클라이언트가 203.252.112.26:50000/index.html에 대한 요청을 보냈다는 것을 알 수 있다.

 

여기서 첫 번째 줄을 보면 GET(method), /index.html(요청한 자료) 그리고 HTTP의 버전이 나타나는 것을 볼 수 있다.

이 내용은 strok함수를 이용해 적절히 자를 수 있으며 클라이언트에게 알맞는 답을 보내주면 된다.

만약 요청한 자료가 아무것도 없다면 기본화면 index.html을 보여주도록 구현했다.

 

우선 GET과 POST일 때의 처리를 따로 해주어야 하므로 각 방식에 대한 handler함수를 따로 만들었다.

 

추가로 클라이언트가 POST형식의 메시지를 보내면 메시지 형식은 다음과 같다.

 

이때, 맨 밑줄을 보면 클라이언트가 서버로 넘겨준 정보가 보이는데 이곳의 LINE이 15번째 줄부터 시작해서 post방식에서 클라이언트가 서버로 넘겨줄 정보는 15번째 줄부터 있다고 생각하고 코드를 짰다.

 

 

3-1. GET handler


void GET_handler(char *V, char *message, char *U, int client){
	int fd, str_len;
	char SEND_DATA[SEND_MESSAGE_BUFSIZE];
	char FIANL_PATH[BUFSIZE];
	char VERSION[10]="";
	char URL[SEND_MESSAGE_BUFSIZE]="";
	
	strcpy(VERSION, V);
	strcpy(URL, U);

	if(strncmp(VERSION, "HTTP/1.0",8)!=0 && strncmp(VERSION, "HTTP/1.1",8)!=0){
		write(client, "HTTP/1.1 400 Bad Request\n",25);
	}
	
	if( strlen(URL) == 1 && !strncmp(URL, "/",1)) strcpy(URL, "/index.html");

	strcpy(FIANL_PATH, CURR_MY_PATH_ROOT);
	strcat(FIANL_PATH, URL);

	if((fd=open(FIANL_PATH, O_RDONLY)) != -1){
		send(client, "HTTP/1.0 200 OK\n\n", 17, 0);
		while(1){
			str_len = read(fd, SEND_DATA, SEND_MESSAGE_BUFSIZE);
			if(str_len<=0) break;
			write(client, SEND_DATA, str_len);
		}
	}
	else {
		write(client, "HTTP/1.1 404 Not Found\n", 23);
	}
}

GET 방식일때는 client 가 요청한 정보를 제공 해주면 된다.

즉, 3번 과정에서 클라이언트가 요청한 자료의 이름 (ex index.html)을 알고 있으므로 현재 나의 directory에 해당하는 html file이 있다면 client 에게 byte stream 방식을 통해 그 내용을 보내준다.
그러나 만약 클라이언트가 요청한 자료가 없다면 클라이언트의 브라우저가 404 Not Found 에 대한 오류를 처리할 수 있도록 정확히 “HTTP/1.1 404 Not Found” 라는 메시지를 보내준다. 이때 해당 형식에 맞지 않게 대충 "404 Not Found"라고 보내면 처리를 제대로 안해주는 것을 확인했다.

 

 

 

3-2. POST handler


oid POST_handler(char *V, char *message, char *U, int client, char *PI){
	int fd, str_len;
	char FIANL_PATH[BUFSIZE];
	char VERSION[10]="";
	char URL[SEND_MESSAGE_BUFSIZE]="";
	char HTML_DATA[BUFSIZE];
	
	strcpy(VERSION, V);
	strcpy(URL, U);
	
	sprintf(HTML_DATA, "<!DOCTYPE html><html><body><h2>%s</h2></body></html>",PI);
	
	if(strncmp(VERSION, "HTTP/1.0",8)!=0 && strncmp(VERSION, "HTTP/1.1",8)!=0){
		write(client, "HTTP/1.1 400 Bad Request\n",25);
	}
	else{
		send(client, "HTTP/1.1 200 OK\n\n",17,0);
		write(client, HTML_DATA, strlen(HTML_DATA));
	}
}

서버가 클라이언트로부터 POST방식의 메시지를 전달받으면 해당 내용을 클라이언트의 브라우저를 통해 볼 수 있도록 하는 함수이다. (에코)

이때, 보내는 메시지만으로 클라이언트의 브라우저에서 화면에 나타날 수 있게끔 해야 하므로 HTML문법을 맞춰주었다.

 

4. 실행 캡쳐


클라이언트의 브라우저에서 서버주소:포트넘버요청을 한 모습 / 아무것도 요청하지 않았으므로 기본 화면인 index.html을 보여준다.
클라이언트의 브라우저에서 서버 디렉토리에 존재하지 않는 html파일을 요청했을 때 404 에러가 뜨는것을 확인할 수 있다.
클라이언트의 브라우저에서 query.html을 요청했을 때

 

query페이지에서 정보를 입력하는 모습
쿼리페이지에서 클라이언트가 서버로 넘겨준 정보를 다시 서버가 클라이언트의 브라우저에서 출력되게끔 구현한 모습

 

 

전체 코드


#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<signal.h>
#include<fcntl.h>
#define BUFSIZE 65536
#define SEND_MESSAGE_BUFSIZE 1024
char *CURR_MY_PATH_ROOT;

void error_handling(char *);
void GET_handler(char *, char *, char *, int);
void POST_handler(char *, char *, char *, int, char*);
void request_handler(void *);

int main(int argc, char **argv){
	
	CURR_MY_PATH_ROOT = getenv("PWD");

    int serv_sock;
    int clnt_sock;
    char message[BUFSIZE];
    int str_len;
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    int clnt_addr_size;
    if(argc!=2){
    	printf("Usage : %s <port>\n", argv[0]);
    	exit(1);
    }
    
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    
    if(serv_sock == -1) {
    	error_handling("socket() error");
    }
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));
    
    if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1) {
    	error_handling("bind() error");
    }
    if(listen(serv_sock, 5)==-1){
    	error_handling("listen() error");
    }
    
	while(1){
        clnt_addr_size=sizeof(clnt_addr);
        clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
        if(clnt_sock==-1){
    	    error_handling("accept() error");
            break;
        }  
        printf("Connection Request : %s:%d\n", inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
        request_handler(&clnt_sock);
    }
    
    close(clnt_sock);
    return 0;
}

void request_handler(void *arg){
    char msg[BUFSIZE];
	char *firstLine[3];

	int sd = *(int*)arg;

	int rcvd = recv(sd, msg, BUFSIZE-1, 0);
	if(rcvd<=0) error_handling("Error about recv()!!");
	printf("-----------Request message from Client-----------\n");
	printf("%s",msg);
	printf("\n-------------------------------------------------\n");
	char post_information[SEND_MESSAGE_BUFSIZE];
	char *curr_msg = NULL;
	char METHOD[4]="";
	char VERSION[10]="";
	char URL[SEND_MESSAGE_BUFSIZE]="";

	curr_msg = strtok(msg, "\n");
	
	int line_count=1;
	while(curr_msg){
		if(line_count>=15) strcpy(post_information, curr_msg);
		curr_msg = strtok(NULL, "\n");
		line_count++;
	}

	firstLine[0] = strtok(msg, " \t\n");
	firstLine[1] = strtok(NULL, " \t");
	firstLine[2] = strtok(NULL, " \t\n");
	
	strcpy(METHOD, firstLine[0]);
	strcpy(URL, firstLine[1]);
	strcpy(VERSION, firstLine[2]);
	
	if(!strncmp(METHOD, "GET",3)) GET_handler(VERSION, msg, URL, sd);
	else if(!strncmp(METHOD, "POST",4)) POST_handler(VERSION, msg, URL, sd, post_information);

	shutdown(sd, SHUT_RDWR);
	close(sd);
}

void POST_handler(char *V, char *message, char *U, int client, char *PI){
	int fd, str_len;
	char FIANL_PATH[BUFSIZE];
	char VERSION[10]="";
	char URL[SEND_MESSAGE_BUFSIZE]="";
	char HTML_DATA[BUFSIZE];
	
	strcpy(VERSION, V);
	strcpy(URL, U);
	
	sprintf(HTML_DATA, "<!DOCTYPE html><html><body><h2>%s</h2></body></html>",PI);
	
	if(strncmp(VERSION, "HTTP/1.0",8)!=0 && strncmp(VERSION, "HTTP/1.1",8)!=0){
		write(client, "HTTP/1.1 400 Bad Request\n",25);
	}
	else{
		send(client, "HTTP/1.1 200 OK\n\n",17,0);
		write(client, HTML_DATA, strlen(HTML_DATA));
	}
}

void GET_handler(char *V, char *message, char *U, int client){
	int fd, str_len;
	char SEND_DATA[SEND_MESSAGE_BUFSIZE];
	char FIANL_PATH[BUFSIZE];
	char VERSION[10]="";
	char URL[SEND_MESSAGE_BUFSIZE]="";
	
	strcpy(VERSION, V);
	strcpy(URL, U);

	if(strncmp(VERSION, "HTTP/1.0",8)!=0 && strncmp(VERSION, "HTTP/1.1",8)!=0){
		write(client, "HTTP/1.1 400 Bad Request\n",25);
	}
	
	if( strlen(URL) == 1 && !strncmp(URL, "/",1)) strcpy(URL, "/index.html");

	strcpy(FIANL_PATH, CURR_MY_PATH_ROOT);
	strcat(FIANL_PATH, URL);

	if((fd=open(FIANL_PATH, O_RDONLY)) != -1){
		send(client, "HTTP/1.0 200 OK\n\n", 17, 0);
		while(1){
			str_len = read(fd, SEND_DATA, SEND_MESSAGE_BUFSIZE);
			if(str_len<=0) break;
			write(client, SEND_DATA, str_len);
		}
	}
	else {
		write(client, "HTTP/1.1 404 Not Found\n", 23);
	}
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}