[P4C 4기] 4~5주차 - 과제C언어로 HTTP 서버 구현
HTTP
HTTP란 무엇인지 간단하게 살펴보겠습니다.
Hyptertext Trasnfer Protocol
여기서 Hypertext는 클라이언트의 선택에 따라서 이동이 가능한 조직화된 정보입니다.
어떠한 사이트에 접속 시 메인 페이지라 불리는 첫 번쨰 페이지 파일이 전송되어 브라웅저를 통해 출력합니다.
HTTP는 상태가 존재하지 않는 Stateless 프로토콜입니다.
서버는 클라이언트의 요청에 응답 후 연결을 끊습니다. 이러한 특징을 보완하고나 쿠키와 세션이라는 기술을 이용하여 상태 정보의 유지가 가능하게 됩니다.
요청 메시지(Request Message)
요청 메시지는 요청라인, 메시지 헤더, 메시지 몸체로 이루어져 있습니다.
요청 라인에는 요청방식에 대한 정보가 삽입되는데 대표적인 요청방식으로는 GET과 POST가 있습니다.
GET은 주로 데이터 요청, POST는 데이터 전송에 사용됩니다.
요청 라인의 GET /index.html HTTP/1.1은 다음과 같이 해석됩니다.
"index.html 파일을 요청(GET)하고 HTTP 프로토콜 버전 1.1 기준으로 통신하기를 원합니다."
요청 라인은 반드시 하나의 행으로 구성하여 전송하도록 약속이 되어 있으니, 전체 HTTP 요청 헤더 중 첫 번째 행을 추출해서 쉽게 요청 라인에 삽입된 정보를 확인할 수 있습니다.
메시지 헤더에는 요청에 사용된 브라우저 정보, 사용자인증정보 등 HTTP 메시지에 대한 부가적인 정보가 담깁니다.
메시지 몸체에는 클라이언트가 웹 서버에 전송할 데이터가 담기게 되는데, 이를 담기 위해서는 POST 방식으로 요청을 해야 합니다.
응답 메시지(Response Message)
응답 메시지는 상태 라인, 헤더 정보, 메시지 몸체로 이루어져 있습니다.
상태 라인에는 클라이언트의 요청에 대한 결과 정보가 담겨져 있습니다.
상태 라인의 HTTP1/1 200 OK는 다음과 같이 해석됩니다.
"HTTP 프로토콜 버전 1.1을 기준으로 응답하며, 당신의 요청은 성공적으로 처리되었습니다.(200 OK)"
메시지 헤더에는 전송되는 데이터의 타입 및 길이 정보 등이 담깁니다. 위의 예시에서는 다음과 같습니다.
"서버 이름: SimpleWebServer" 전송하는 데이터 타입은 text/html(html로 이루어진 텍스트 데이터)입니다. 그리고 데이터의 길이는 2048byte를 넘지 않습니다."
그리고 공백라인 이후에 메시지 몸체를 통해 클라이언트가 요청한 파일의 데이터가 전송됩니다.
참조 함수
sockaddr_in 구조체
bind 함수에 주소 정보를 전달하는 용도로 사용되는 구조체
int socket(int domain, int type, int protocol);
소켓 생성
int bind(SOCKET s, const struct sockaddr * name, int namelen); 구조체
소켓의 주소정보에 해당하는 것을 할당
IP주소와 PORT번호의 할당을 목적으로 호출되는 함수
int listen(int sockfd, int backlog);
연결 요청이 가능한 상태
int accept(int sockfd, struct sockaNddr *addr, socklen_t *addrlen);
연결요청에 대한 수락
ssize_t recv(int sockfd, void * buf, size_t nbytes, int flags);
성공 시 수신한 바이트 수(단 EOF 전송 시 0, 실패 시 -1 반환)
실습
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
#define SMALL_BUF 100
char* request_handler(char* request);
char* load_file(FILE* fp);
void error_handling(char* message);
int main(int argc, char *argv[])
{
int serv_sock;
int clnt_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;
char message[]="Hello World!";
int recv_len;
char request[BUF_SIZE];
int opt=1;
pthread_t t_id;
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");
else
printf("socket() success\n");
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
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\n");
else
printf("bind() sucess\n");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
else
printf("listen() success\n");
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");
else
printf("accept() success\n");
printf("Connection Request : %s: %d \n",
inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
printf("---------------------------------------------------------------\n");
recv_len=recv(clnt_sock, request, sizeof(request),0);
printf("<Request>\n%s\n", request);
printf("---------------------------------------------------------------\n");
char* response = request_handler(request);
printf("<Response>\n%s\n", response);
if(response != NULL)
send(clnt_sock, response,(int)strlen(response),0);
close(clnt_sock);
close(serv_sock);
return 0;
}
char* request_handler(char *request)
{
char *response = malloc(sizeof(char) * BUF_SIZE);
char *method, *path;
method = strtok(request, " \t\r\n");
path = strtok(NULL, " \t");
FILE * fp = fopen(path + 1, "r");
char* file = load_file(fp);
FILE * cl = fopen(path + 1, "r");
fseek(cl, 0, SEEK_END);
int file_size = ftell(cl);
char * content_l;
sprintf(content_l,"%d",file_size);
strcat(response, "HTTP/1.1 200 OK\r\n");
strcat(response,"Location: ");
strcat(response,path);
strcat(response,"\r\n");
strcat(response,"Contenti-Type: text/html; charset=utf-8\r\n");
strcat(response,"Content-Length: ");
strcat(response,content_l);
strcat(response,"\r\n\n");
strcat(response,file);
free(file);
return response;
}
char* load_file(FILE *fp){
int i,j;
char * buffer = malloc(sizeof(char) * BUF_SIZE);
memset(buffer,0,sizeof(buffer));
j=fgetc(fp);
for(i=0; j!=EOF; j=fgetc(fp), i++)
buffer[i] = j;
fclose(fp);
return buffer;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
|
cs |
많이 부족한 코드이지만 의도대로 Request를 받아오고 Response를 전달할 수 있었습니다.
좀 많이 애를 먹은 과제이지만 http의 원리를 통해 직접 만들어 볼 수 있는 과제였습니다.
※테스트를 진행하던 중 프로그램 실행이 되지 않고 bind error가 표시됩니다.
그 이유는 이미 쓰인 포트에 소켓이 다시 접속할 수 없기 때문입니다.(여기서 시간을 많이 잡아 먹었습니다...)
그래서 할당된 포트를 강제로 해제해야 합니다.
netstat -lntp 명령어를 통해 할당된 소켓 값, PID를 알아내어 해당 PID를 kill 합니다.sudo kill [PID]
저의 경우에는 netstat으로 PID를 확인한 경우 '-'(dash)가 나와 PID를 확인할 수 없었으나 관리자 권한으로 명령을 입력하니 확인할 수 있었습니다.
실행이 잘 되는 것을 확인할 수 있습니다.
참고
윤성우의 열혈 TCP/IP 소켓 프로그래밍
[multithreading] 컴파일하는 동안 -pthread와 -lpthread의 차이점 - 리뷰나라
의 차이 무엇입니까 gcc -pthread및 gcc -lpthread멀티 스레드 프로그램을 컴파일하는 동안 사용됩니까? 답변 -pthread 컴파일러에게 pthread 라이브러리에서 링크하고 스레드에 대한 컴파일을 구성하도록
daplus.net
https://kkalkkalparrot.tistory.com/51
[네트워크프로그래밍] 소켓 bind error 해결방법
이번 포스팅은 간단한 bind 에러 해결법이다. 네트워크프로그래밍을 하다보면 bind error가 나게 되는데, 그 이유는 이미 쓰인 포트에 소켓이 다시 접속할 수 없기 때문이다. 따라서 해결방법은 두
kkalkkalparrot.tistory.com
https://phoenixnap.com/kb/ubuntu-start-stop-restart-apache
Ubuntu: How To Start / Stop / Restart Apache Server
Updated Linux Tutorial on how to restart, start, & stop Apache web server on the Ubuntu operating system. Use the easy way now with a single shell command!
phoenixnap.com
https://reakwon.tistory.com/35
[C언어] 파일입출력 2 (fseek, ftell)
파일 입출력 2 간단하게 파일에서 읽고 쓰는 것은 이제 알겠습니다. 하지만 단순히 파일을 처음부터 차례대로 읽는 것이 아니라, 어떤 위치 이후에서 읽고 쓰는 것도 가능할까요? 할 수 있습니다
reakwon.tistory.com