HTTP 서버 구현.c (1) : https://slumpdev.tistory.com/entry/C%EC%96%B8%EC%96%B4-HTTP-%EC%84%9C%EB%B2%84-%EA%B5%AC%ED%98%84c-1
HTTP 서버 구현.c (2) : https://slumpdev.tistory.com/entry/C%EC%96%B8%EC%96%B4-HTTP-%EC%84%9C%EB%B2%84-%EA%B5%AC%ED%98%84c-2
앞선 글들에 이어서 마지막으로 서버를 열 코드를 구성하고,
만들 코드들을 컴파일 하는 방법과 컴파일 명령어들을 한방에 정리해서
Makefile을 만들어 make 하는 방법까지 공부해보려고 한다.
✔️ main.c 클라이언트가 통신할 c 파일 만들기
#include "httpd.h"
int main(int c, char** v)
{
// httpd 헤더파일에 선언된 함수에 12000 포트를 적어준다.
server_forever("12000");
return 0;
}
void route()
{
// httpd 헤더파일에 사용자가 정의한 함수에 definde한 값들이다.
// ROUTE 시작!
ROUTE_START()
ROUTE_GET("/")
{
// GET 메소드에 정상적으로 성공하면 응답 코드 200을 출력한다.
// 앞서 공부한 것처럼 HTTP 통신에서 캐리지 리턴하고 라인 피드를 확인할 수 있다.
printf("HTTP/1.1 200 OK\r\n\r\n");
// request_header에 들어가는 User-Agent 사용에 대한 값을 출력한다.
printf("Hello! You are using %s", request_header("User-Agent"));
}
ROUTE_POST("/")
{
// POST 메소드에 정상적으로 성공하면 응답 코드 200을 출력한다.
printf("HTTP/1.1 200 OK\r\n\r\n");
// payload 크기도 출력한다.
printf("WoW, Seems that you POSTed %d bytes. \r\n", payload_size);
printf("Fetch the data using 'payload' variable.");
}
// ROUTE 종료!
ROUTE_END()
}
앞서 우리가 만들어주었던 httpd 헤더파일을 가져와서
정의하고 선언해준 변수와 함수들을 사용한다.
그 다음, C 언어를 시작했거나 알고있다면 제일 많이 보게되는
main 함수를 작성해준다. main 함수 컴퓨터에게 이 함수가 중요한 함수이니
프로그램 실행 시 맨 처음 호출하고 마지막으로 return 해라라는 의미를 가진다고 한다.
그리고나서, httpd.h 헤더 파일에서 작성해준 server_forever 함수를 통해
커스텀하여 열고 싶은 포트를 적어서 호출한다. 해당 함수를 호출하게 되면,
바로 앞의 글인 httpd.c 파일에 정의해준 server_forever 함수를 실행하게 된다.
그리고 매개변수에 담아준 12000포트는 server_forever 함수에서 나오는
startServer 함수에 담기게 되고, 서버를 시작하는 함수인 startServer 함수를 호출한다.
그 다음은 바로 사용자가 정의해줄 수 있는 route 함수를 작성해준다.
httpd.h 헤더파일에 선언한 이름들을 여기에서 볼 수 있는데,
ROUTE_START, ROUTE_GET, ROUTE_POST, ROUTE_END로
route 함수가 동작하는 것을 볼 수 있다.
ROUTE_GET가 정상적으로 실행되면, HTTP status 상태인 200 Ok를 반환한다.
앞에서 배운것처럼 캐리지 리턴하고 라인 피드를 여기에서도 작성하는 것을 볼 수 있다.
그리고나서 User-Agent 를 출력하게 printf 함수에 적어준다.
여기서 나오는 User-Agent의 내용에는 다음과 같은 것들이 들어있다.
(참고: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent)
User-Agent: Mozilla/5.0 (<system-information>) <platform> (<platform-details>) <extensions>
해당 값들을 출력하게 되고, 실행 값은 글 밑에서 확인할 수 있다.
GET 함수가 끝나면 POST 함수가 실행되는데, 해당 함수도 정상적인 연결이 되었다면
200 상태 코드를 출력하고, 앞서 작성해주었던 payload_size를 출력하게 된다.
✔️ Makefile을 만들어 한방에 컴파일하기
해당 내용을 공부할 때 계속해서 컴파일 오류가 나서 1시간동안 삽질했었는데,
라인오류에 틀린게 없었다고 생각했는데 사실 serve_forever 함수를
server_forever 함수라고 작성해버려서 계속 오류가 났던 것이었던 것이다..
그래서 모든 코드들이 깃헙과는 다르게 server_forever 이라는 이름을 사용하고 있다는..
아무튼, C 언어를 실행파일로 만들기 위해서는 컴파일이라는 과정을 거치게 된다.
Makefile을 만들기 전에 C 언어의 컴파일 과정부터 공부해보려고 한다.
(참고: https://www.javatpoint.com/compilation-process-in-c)
조금 허접(..?)하지만 다음은 컴파일 과정을 그림화해보았다.
우선, 컴파일이란 소스 코들을 객체 코드로 변환하는 과정이라고 이야기한다.
그래서 서버 구현 1번 게시물을 가서 파일 구성도를 보면
c 파일들이 .o 라는 확장자를 가진 파일을 가리키고 있는데,
컴파일 과정을 통해서 .c 확장자를 가진 코드 파일이 .o 확장자를 가진
object 코드 파일, 즉 객체 코드로 변환되어 파일이 생성되는 것이다.
우리가 만든 소스 코드는 처음 전처리 과정을 거친다.
① 전처리기(Preprocessor)
- 소스 코드는 텍스트 편집기로 작성된 코드이고, 소스 코드의 확장자는 .c이다.
- 소스 코드는 우선 전처리기에 전달되고 다음의 전처리기가 이 코드를 확장한다.
- 코드를 확장한 후 확장된 코드는 컴파일러로 전달된다.
확장된(expanded) 코드는 다음 컴파일러에게 전달되어 수행된다.
② 컴파일러(Compiler)
- 전처리기에 의해 확장된 코드가 전달되었다.
- 컴파일러는 해당 코드를 어셈블리 코드로 변환한다.
- 또는 컴파일러가 사전에 처리된 코드를 어셈블리 코드로 변환한다고 말한다.
컴파일러 과정을 거쳐 나온 어셈블리어(assembly) 코드는 어셈블러에 전달된다.
③ 어셈블러(Assembler)
- 어셈블리 코드는 어셈블러를 통해 object code로 변환된다.
- 어셈블러를 통해 생성된 오브젝트 파일 이름은 소스 파일과 동일하다.
- DOS에서는 오브젝트 파일 확장자가 .obj 이고 UNIX에서는 확장자가 .o 이다.
- ex) hello.c -> hello.obj / hello.c -> hello.o
변환된 오브젝트 코드와 더불어 다른 오브젝트 파일들과 라이브러리 들은
링커라는 과정을 거쳐 최종 실행 파일로 만들어지게 된다.
④ 링커(Linker)
- 라이브러리 함수들은 미리 컴파일 되어있어서 이러한 파일의 object code를
프로그램의 object code와 결합하는 역할을 링커가 수행한다.
- 또한, 프로그램이 다른 파일에 정의된 함수를 참조할 때 해당 파일의 object code를
우리 프로그램에 연결하는 역할도 링커가 수행한다.
- 그러므로(Therefore), 링커의 역할은 프로그램의 object code를 라이브러리 파일과
기타 파일의 object 코드와 연결하는 것을 포함한다.
- 링커의 출력은 실행 파일이다. (executable file, exe)
- 실행 파일 이름은 소스 파일과 동일하지만, 확장자가 다르다.
- DOS에서는 .exe 이며, UNIX 에서는 실행 파일 이름을 a.out과 같이 지정할 수 있다.
이렇게 다음 4가지의 과정을 거친 컴파일 과정은 Loader에 전달되고
로더가 실행을 위해 실행 파일을 로드하여 프로그램이 실행된다.
이러한 컴파일 과정을 거치기 위해서는 gcc 명령어를 사용할 수 있다.
gcc는 CNU에서 개발된 ANSI C 표준을 따르는 C 언어의 컴파일러라고 한다.
이러한 gcc 명령어는 주요한 옵션이 있는데, 우리가 사용할 옵션은 다음과 같다.
1) gcc -c
: 목적 파일을 생성할 때 사용하는 옵션이다.
2) gcc -o
: 실행 파일의 이름을 지정하는 옵션으로 gcc - o [실행파일이름] [소스파일이름] 또는
gcc [소스파일이름] -o [실행파일이름] 과 같이 사용할 수 있다.
컴파일 과정에서 수행되어 생성되는 httpd에 대한 오브젝트 파일과
main에 대한 오브젝트 파일을 만들어야 한다.
그리고 나서 실행 파일을 만들게 되는데 이러한 과정은 몇 번의 명령어를
수행해야한다. 하지만, Makefile을 만들면 make 명령어를 통해 간단히 수행할 수 있다.
(참고: https://www.gnu.org/software/make/manual/make.html#Pattern_002dspecific-Variable-Values)
all: server
clean:
@rm -rf *.o
@rm -rf server
server: main.o httpd.o
gcc -o server $^
main.o: main.c httpd.h
gcc -c -o main.o main.c
httpd.o: httpd.c httpd.h
gcc -c -o httpd.o httpd.c
Makefile은 프로그램을 관리하는 하나의 프로그램으로 컴파일을
용이하게 만들어주는 파일이라고 볼 수 있다. (프로그램 빌드 시의 편리성)
Makefile은 기본적으로 구조가 다음과 같이 되어 있다.
%.o: %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
이렇게 된 코드는 대상에 대한 조건을 달고 그 밑에 명령어를 수행하게 된다.
%.o 자리에 오는 target에는 원하는 변수명을 정할 수 있다.
해당 자리에 우선 만들고 싶은 파일 명을 적게 된다.
%.c 자리에 오는 전제 조건(Prerequisite)은 target을 만들기 위해 필요한 것들을
작성하게 되는데, 앞선 코드를 보면 예를 들어 httpd.o 라는 오브젝트 파일을 위해
필요한 httpd.c 파일과 httpd.h 파일을 조건으로 입력하는 것이다.
그리고 나서 Tab을 사용하여 명령어(command)를 입력한다.
해당 입력에는 꼭 Tab을 사용해야하고, 명령어는 Shell 명령어,
C 언어, Python 등 다양한 언어로 작성이 가능하다고 한다.
이러한 Makefile을 만들기 위해서는 코드에 순서를 지켜야하는데,
해당 프로그램은 실행 파일을 만들기 위한 오브젝트 파일을 기준으로
오브젝트 파일이 없거나 있는데 오브젝트 파일보다 만들어진 시점이 최근인
.c 파일이 있을 경우 재컴파일 과정을 거쳐 target을 생성한다고 한다.
해당 코드 순서를 지키지 않고서도 target 파일을 생성하기 위해서는
all 이라는 target이 등장하게 되었는데, all을 target과 함께 명시해주면
순서와 상관없이 target에 명시한 부분부터 먼저 수행하기 때문에
정상적으로 작동한다고 한다. 그래서 해당 파일도 server라는 파일을
생성하기 위해서 all을 명시해주고 해당 부분부터 수행하게 된다.
그래서 이제 우리 코드를 보게 되면,
all에 server를 명시해두었기 때문에 server를 찾아가서 그 밑에 부분인
main.o 파일과 httpd.o 파일에 컴파일을 수행하여 오브젝트 파일을 만들게 되고,
해당 파일이 생성되었다면 최종 target인 server 파일을 생성하기 위해
생성된 main.o와 httpd.o 파일을 사용하여 만들게 된다.
여기서 사용되는 $^ 표시는 매크로를 의미하는데,
오브젝트 파일들을 링커로 전달하면서 server라는 파일을 만들기 위해
main.o 파일과 httpd.o 파일 모두 전달하겠다는 의미가 된다.
($^는 전제조건 영역을 의미한다고 한다.)
이렇게 하면 server라는 파일이 정상적으로 생긴다.
그렇다면, clean은 무엇인가?
clean에 작성되어 있는 것은 앞서 수행한 과정으로 생성된 파일을
찾아서 지울 수 있는데, make clean 명령어를 통해 작성 rm -rf 명령을 수행한다.
(rm -rf 명령어는 파일을 지우는 명령어와 옵션이다.)
해당 Makefile을 만들어준 폴더에서 make 명령을 수행하면
다음과 같이 오브젝트 파일들이 생성되면서 동시에 실행 권한이 부여된
server 파일을 획득할 수 있다.
그리고 나서 server 파일 실행하면 다음과 같이 성공할 수 있다!
로컬호스트 주소에 12000 포트로 연 페이지의 모습은 다음과 같고,
User-agent의 내용이 정상적으로 출력되는 모습을 볼 수 있다.
그런데.. 다른 공부를 하다가 vm 가상환경의 주요 폴더 권한을 잘못 설정해서
가상환경이 통채로 날라갔다.. 그래서 위에 나온 root 계정의 폴더 구조는
우분투의 recovery mode에서 수행한 모습이다..
이 공부를 위해 열심히 vim도 설정하고, (root와 계정의 vim 의존이 깨져서 엄청 삽질함..)
카카오 미러 사이트로 다운로드 주소도 바꾸고 다했던 몇 시간짜리의 가상환경이 날라갔기에..
다시 설치하여 다음에는 서버와 클라이언트의 CLI 환경에서의 소켓 통신을 공부해볼 생각이다..
화이팅 😥
'Study > C언어' 카테고리의 다른 글
[C언어] 스택 프레임.c (0) | 2022.10.11 |
---|---|
[C언어] HTTP 서버 구현.c (2) (0) | 2022.10.07 |
[C언어] HTTP 서버 구현.c (1) (0) | 2022.10.06 |
[C언어] 코딩도장 공부 (Unit 61~80) (0) | 2022.09.24 |
[C언어] 코딩도장 공부 (Unit 41~60) (0) | 2022.09.23 |