- 개인적으로 공부하면서 지속적으로 정보를 추가, 수정, 삭제합니다.
- 정확하지 않은 부분 피드백 주시면 감사합니다.
- TIL 노트의 일부를 정리해서 적합한 카테고리의 노트 항목에 추가합니다.
컴퓨터 시스템 Chapter 11 네트워크 프로그래밍 개념 정리
참고:
무엇?
7주차 과제를 위한 컴퓨터 시스템 노트 정리!
☞ 컴퓨터 시스템 정리 (CSAPP) Chapter 11 네트워크 프로그래밍
왜?
7주차 커버리지
- 클라이언트-서버 모델 이해 ☞ 11.1장
- 네트워크 개념 리뷰 (특강자료) ☞ 11.2장
- 소켓 개요 이해(socket, bind, listen, accept, connect) ☞ 11.4장
- 웹컨텐츠 이해 (정적, 동적, CGI) ☞ 11.5장
- HTTP 이해 (요청/응답, 헤더, 메소드, 상태코드) ☞ 11.5장
- 프록시 서버 개념 이해
2022-12-12
echo - CSAPP 예제 Echo 클라이언트와 서버 구현하기
참고:
무엇?
컴퓨터 시스템 11.4.9 예제 Echo 클라이언트와 서버 구현하기
블로그의 다음 포스팅의 11.4.9 예제 Echo 클라이언트와 서버장의 내용으로 갈음
☞ 컴퓨터 시스템 정리 (CSAPP) Chapter 11 네트워크 프로그래밍#11.4.9 예제 Echo 클라이언트와 서버
왜?
나만의 웹서버를 만들어보기!
어떻게?
- echoclient.c 파일
#include "csapp.h"
/* Echo 클라이언트의 메인 루틴 */
int main(int argc, char **argv) // 인자: 호스트 네임(도메인명), 포트
{
int clientfd; // 클라이언트 식별자
char *host, *port, buf[MAXLINE];
rio_t rio;
// 인자를 세개 받지 않으면 종료 ("이 프로그램은 다음과 같은 인자가 필요해!")
// argv[0]는 프로그램의 이름
if (argc != 3)
{
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(0);
}
host = argv[1];
port = argv[2];
clientfd = Open_clientfd(host, port); // 클라이언트의 소켓; 서버와 연결을 설정
Rio_readinitb(&rio, clientfd);
while (Fgets(buf, MAXLINE, stdin) != NULL) // `Fgets`가 EOF 표준 입력을 만나면 종료
{
Rio_writen(clientfd, buf, strlen(buf));
// 서버는 `rio_readlineb` 함수에서 리턴 코드 0을 받으면 루프 종료
Rio_readlineb(&rio, buf, MAXLINE);
Fputs(buf, stdout);
}
Close(clientfd); // 클라이언트 식별자 닫음
exit(0);
}
- echoserveri.c 파일
#include "csapp.h"
/* 반복적 Echo 서버 메인 루틴 */
void echo(int connfd);
int main(int argc, char **argv) // argv[1] → port
{
int listenfd, connfd; // 듣기 식별자, 연결 식별자
socklen_t clientlen;
/* accept로 보내지는 소켓 주소 구조체
accept가 리턴하기 전에 clientaddr에는 연결의 다른 쪽 끝의 클라이언트의 소켓 주소로 채워짐
sockaddr_storage형으로 선언함으로써, 모든 형태의 소켓 주소를 저장하기에 충분히 큼
*/
struct sockaddr_storage clientaddr;
char client_hostname[MAXLINE], client_port[MAXLINE]; // 클라이언트 호스트이름, 클라이언트 포트
// 인자가 두개가 아니면 에러("이 프로그램은 다음과 같은 인자가 필요해!")
// argv[0]는 프로그램의 이름
if (argc != 2)
{
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
// 인자로 주어진 포트 번호로 듣기 식별자 소환 (소켓 생성, bind까지 다 됨)
listenfd = Open_listenfd(argv[1]);
while (1)
{
clientlen = sizeof(struct sockaddr_storage);
// 클라이언트로부터 연결 요청 기다리기
// Accept 함수; 듣기 식별자, 클라이언트 주소, 주소 길이 → 연결 식별자 생성
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
// Getnameinfo: 소켓 주소를 location, service name으로 번역해줌
Getnameinfo((SA *)&clientaddr, clientlen, client_hostname, MAXLINE,
client_port, MAXLINE, 0);
// "클라이언트 도메인 네임, 클라이언트 포트에 연결되었음"
printf("Connected to (%s, %s)\n", client_hostname, client_port);
echo(connfd); // echo.c의 echo 함수 호출
Close(connfd); // 연결 식별자 닫음
}
exit(0);
}
void echo(int connfd) // 연결 식별자
{
size_t n;
char buf[MAXLINE];
rio_t rio; // robust I/O (Rio);
Rio_readinitb(&rio, connfd);
// Rio_readlineb 함수가 EOF를 만날 때까지 반복적으로 읽고 print
while ((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0)
{
printf("server received %d bytes\n", (int)n);
Rio_writen(connfd, buf, n);
}
}
- echo.c
#include "csapp.h"
/* 텍스트 줄을 읽고 반복적으로 echo해주는 echo 함수 */
void echo(int connfd) // 연결 식별자
{
size_t n;
char buf[MAXLINE];
rio_t rio; // robust I/O (Rio)
Rio_readinitb(&rio, connfd);
// Rio_readlineb 함수가 EOF를 만날 때까지 반복적으로 읽고 print
while ((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0)
{
printf("server received %d bytes\n", (int)n);
Rio_writen(connfd, buf, n);
}
}
- Makefile 수정
CC = gcc
CFLAGS = -g -Wall
LDFLAGS = -lpthread
all: echo_server echo_client
# 에코서버 테스트------------------------------------------------------
echoclient: echoclient.c csapp.o
$(CC) $(CFLAGS) -o echo_client echoclient.c csapp.o $(LDFLAGS)
echoserver: echoserveri.c csapp.o
$(CC) $(CFLAGS) -o echo_server echoserveri.c csapp.o $(LDFLAGS)
csapp.o: csapp.c
$(CC) $(CFLAGS) -c csapp.c
# 에코서버 테스트------------------------------------------------------
csapp.o: csapp.c csapp.h
$(CC) $(CFLAGS) -c csapp.c
proxy.o: proxy.c csapp.h
$(CC) $(CFLAGS) -c proxy.c
proxy: proxy.o csapp.o
$(CC) $(CFLAGS) proxy.o csapp.o -o proxy $(LDFLAGS)
# Creates a tarball in ../proxylab-handin.tar that you can then
# hand in. DO NOT MODIFY THIS!
handin:
(make clean; cd ..; tar cvf $(USER)-proxylab-handin.tar proxylab-handout --exclude tiny --exclude nop-server.py --exclude proxy --exclude driver.sh --exclude port-for-user.pl --exclude free-port.sh --exclude ".*")
clean:
rm -f *~ *.o echo_client echo_server core *.tar *.zip *.gzip *.bzip *.gz
2022-12-13
TINY 웹 서버 - CSAPP 11.6장 TINY 웹 서버 기본 구현
참고:
무엇?
블로그의 다음 포스팅의 11.6 종합 설계 소형 웹 서버The Tiny Web Server장의 내용으로 갈음
☞ 컴퓨터 시스템 정리 (CSAPP) Chapter 11 네트워크 프로그래밍#11.6 종합 설계 소형 웹 서버The Tiny Web Server
어떻게?
- main 루틴
int main(int argc, char **argv) // 포트 번호 인자로 받음
{
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE];
socklen_t clientlen;
struct sockaddr_storage clientaddr;
/* Check command line args */
if (argc != 2)
{
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
// 듣기 소켓 오픈
listenfd = Open_listenfd(argv[1]);
// 무한 서버 루프
while (1)
{
clientlen = sizeof(clientaddr);
// 연결 요청 접수
connfd = Accept(listenfd, (SA *)&clientaddr,
&clientlen); // line:netp:tiny:accept
Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE,
0);
printf("Accepted connection from (%s, %s)\n", hostname, port);
// 트랜잭션 수행
doit(connfd); // line:netp:tiny:doit
// 연결 종료
Close(connfd); // line:netp:tiny:close
}
}
doit
함수
// HTTP transaction을 다루는 함수: 연결 식별자
void doit(int fd)
{
int is_static;
struct stat sbuf;
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
rio_t rio;
/* Read request line and headers */
Rio_readinitb(&rio, fd);
Rio_readlineb(&rio, buf, MAXLINE);
printf("Request headers:\n");
printf("%s", buf);
sscanf(buf, "%s %s %s", method, uri, version); // 파이썬의 map 같은거
// 11.11번 문제 때문에 수정한 if 조건문
// GET 메소드가 아니고, HEAD 메소드도 아니면 에러
if ((strcasecmp(method, "GET")) && (strcasecmp(method, "HEAD")))
{
clienterror(fd, method, "501", "Not implemented",
"Tiny does not implement this method");
return;
}
read_requesthdrs(&rio);
/* GET 요청으로 들어온 URI에서 파싱 */
is_static = parse_uri(uri, filename, cgiargs); // uri에 CGI인자가 없으면 1 반환 → 정적 컨텐츠
if (stat(filename, &sbuf) < 0)
{
clienterror(fd, filename, "404", "Not found",
"Tiny couldn’t find this file");
return;
}
/* Serve static content */
if (is_static)
{
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))
{
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn’t read the file");
return;
}
serve_static(fd, filename, sbuf.st_size, method); // 연결식별자, 파일명, 파일사이즈(?)
}
/* Serve dynamic content */
else
{
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))
{
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn’t run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs, method); // 연결식별자, 파일명, CGI 인자
}
}
clienterror
함수
// 클라이언트에게 에러 메시지를 보내는 함수
void clienterror(int fd, char *cause, char *errnum,
char *shortmsg, char *longmsg)
{
char buf[MAXLINE], body[MAXBUF];
/* Build the HTTP response body */
sprintf(body, "<html><title>Tiny Error</title>");
sprintf(body, "%s<body bgcolor="
"ffffff"
">\r\n",
body);
sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body);
/* Print the HTTP response */
sprintf(buf, "HTTP/1.%s %s\r\n", errnum, shortmsg);
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-type: text/html\r\n");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
Rio_writen(fd, buf, strlen(buf));
Rio_writen(fd, body, strlen(body));
}
read_requesthdrs
함수
// request 헤더를 읽고 무시하는 함수
void read_requesthdrs(rio_t *rp)
{
char buf[MAXLINE];
Rio_readlineb(rp, buf, MAXLINE);
// 요청 헤더를 종료하는 빈 텍스트 줄: `carriage return`과 `line feed`의 쌍
while (strcmp(buf, "\r\n")) // 두 문자열이 같으면 결과값이 0
{
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
}
return;
}
parse_uri
함수
// HTTP URI를 parsing 하는 함수: URI, 파일명, CGI인자
int parse_uri(char *uri, char *filename, char *cgiargs)
{
char *ptr;
// URI에 "cgi-bin" 문자열이 존재하지 않으면 정적 컨텐츠 → 포인터 반환
if (!strstr(uri, "cgi-bin"))
{
// 문자열 복사 함수: 복사한 문자열을 붙여넣기 할 주소, 복사할 문자열의 시작 주소
strcpy(cgiargs, ""); // CGI 인자 스트링 지움
strcpy(filename, "."); // 파일명을 .으로 바꿈
strcat(filename, uri); // 파일명과 uri를 합침 -> "./파일명.html" 같은 상대 경로가 됨?
// 만일 uri의 끝 문자가 '/'로 끝나면
if (uri[strlen(uri) - 1] == '/')
strcat(filename, "home.html"); // 파일명 + home.html(기본파일이름)
return 1;
}
// URI에 "cgi-bin" 문자열이 존재하면 동적 컨텐츠
else
{
/* 모든 CGI 인자 추출하기 */
ptr = index(uri, '?'); // URI에서 ?의 위치
// URI에 ?가 있으면
if (ptr)
{
strcpy(cgiargs, ptr + 1); // ? 다음에 오는 문자열을 전부 cgiargs에 복사
*ptr = '\0';
}
// URI에 ?가 없으면
else
strcpy(cgiargs, ""); // cgiargs 공백으로
/* 모든 CGI 인자 추출하기 끝 */
strcpy(filename, ".");
strcat(filename, uri);
return 0;
}
}
serve_static
함수
// 정적 콘텐츠를 클라이언트에게 serve하는 함수: 연결식별자, 파일명, 파일크기
void serve_static(int connfd, char *filename, int filesize, char *method)
{
int srcfd; // 소스 파일 식별자
char *srcp, filetype[MAXLINE], buf[MAXBUF];
// 클라이언트에게 응답 line과 응답 헤더를 보냄
get_filetype(filename, filetype); // 파일 이름의 접미어 검사 → 파일 타입 결정
sprintf(buf, "HTTP/1.2OK\r\n");
sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
sprintf(buf, "%sConnection: close\r\n", buf);
sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
Rio_writen(connfd, buf, strlen(buf));
// 클라이언트에 응답 line과 응답 헤더를 보냄 - 끝
printf("Response headers:\n");
printf("%s", buf); // 빈 줄로 헤더 종료
if (strcasecmp(method, "HEAD") == 0) // 메소드가 HEAD일 경우 응답 바디 출력X
return;
/* Send response body to client */
srcfd = Open(filename, O_RDONLY, 0); // read를 위한 소스 file 오픈 + 식별자 얻어옴
// 요청한 파일을 `mmap` 함수로 가상메모리 영역으로 매핑:
// private read-only 가상 메모리 영역으로 매핑
// 소스 파일 srcfd의 filesize 바이트의 가상메모리에서의 시작 주소 srcp
// srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
// 문제 11.9번을 위함
char *temp = Malloc(filesize); // malloc으로 filesize 만큼 메모리 할당
Rio_readn(srcfd, temp, filesize); // 소스파일에서 temp로 파일사이즈만큼 바이트 전송
// 파일을 메모리로 매핑한 후, 더이상 식별자 필요 없으니 파일 close (메모리 누수 방지)
Close(srcfd);
// `srcp`에서 시작하는 `filesize`를 클라이언트의 연결 식별자로 복사
// Rio_writen(connfd, srcp, filesize); // 원본
// 문제 11.9번을 위함 - 연결 식별자에 temp로부터 filesize만큼 바이트 전송
Rio_writen(connfd, temp, filesize);
// 매핑된 가상메모리 주소를 반환 (메모리 누수 방지)
Free(temp);
// Munmap(srcp, filesize);
}
get_filetype
함수
/* get_filetype - 파일명으로부터 파일 타입 추출 */
void get_filetype(char *filename, char *filetype)
{
if (strstr(filename, ".html"))
strcpy(filetype, "text/html");
else if (strstr(filename, ".gif"))
strcpy(filetype, "image/gif");
else if (strstr(filename, ".png"))
strcpy(filetype, "image/png");
else if (strstr(filename, ".jpg"))
strcpy(filetype, "image/jpeg");
else if (strstr(filename, ".mp4")) // 동영상 mp4 추가
strcpy(filetype, "video/mp4");
else
strcpy(filetype, "text/plain");
}
serve_dynamic
함수
// 동적 콘텐츠를 클라이언트에게 serve하는 함수: 파일 식별자, 파일명, CGI인자
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method)
{
char buf[MAXLINE], *emptylist[] = {NULL};
/* Return first part of HTTP response */
sprintf(buf, "HTTP/1.0 200 OK\r\n"); // 클라이언트에게 성공 메시지 전달
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Server: Tiny Web Server\r\n");
Rio_writen(fd, buf, strlen(buf));
// 새로운 자식 프로세스 포크
if (Fork() == 0)
{
/* 실제 서버는 모든 CGI 변수를 여기서 다 세팅함 */
// 자식 프로세스는 query_string 환경 변수를 요청온 URI의 CGI 인자로 초기화
setenv("QUERY_STRING", cgiargs, 1);
setenv("REQUEST_METHOD", method, 1); // "REQUEST_METHOD" 환경 변수에 주어진 메소드 설정
Dup2(fd, STDOUT_FILENO); // 자식 프로세스는 자식의 표준 출력(stdout)을 연결 파일 식별자로 재지정
Execve(filename, emptylist, environ); // CGI 프로그램 로드 후 실행
}
Wait(NULL); // 부모는 자식이 종료되어 정리되는 것을 기다리기 위해 `wait` 함수에서 블록
}
- adder.c 파일 → 동적 콘텐츠 생성을 위한 CGI 프로그램
/*
* adder.c - a minimal CGI program that adds two numbers together
*/
#include "csapp.h"
int main(void)
{
char *buf, *p, *a, *b;
char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
int n1 = 0, n2 = 0;
/* Extract the two arguments */
if ((buf = getenv("QUERY_STRING")) != NULL)
{
p = strchr(buf, '&'); // buf -> number-1=15000 \0 number-2=213
*p = '\0';
// 문제 11.10을 위해 수정한 부분
strcpy(arg1, buf + 9); // arg1 -> numbe`r-1=15000 -> 15000
strcpy(arg2, p + 10); // arg2 -> number-2=213 -> 213
// 문제 11.10을 위해 수정한 부분 - 끝
n1 = atoi(arg1);
n2 = atoi(arg2);
}
/* Make the response body */
sprintf(content, "QUERY_STRING=%s", buf);
sprintf(content, "Welcome to add.com: ");
sprintf(content, "%sTHE Internet addition portal.\r\n<p>", content);
sprintf(content, "%sThe answer is: %d + %d = %d\r\n<p>",
content, n1, n2, n1 + n2);
sprintf(content, "%sThanks for visiting!\r\n", content);
/* Generate the HTTP response */
printf("Connection: close\r\n");
printf("Content-length: %d\r\n", (int)strlen(content));
printf("Content-type: text/html\r\n\r\n");
printf("%s", content);
fflush(stdout);
exit(0);
}
C언어 strstr
함수 사용법
참고: [C언어] strstr 함수 (문자열 검색 함수) (tistory.com)
→ C언어 노트에 추가
무엇?
C언어 표준함수 strstr
은 문자열 안에서 특정 문자열을 검색할 때 사용함
왜?
CSAPP TINY 웹 서버 구현 中 URI 파싱하면서 사용된 함수
어떻게?
#include <string.h>
// 검색을 진행할 문자열, str 문자열에서 검색하려는 sub string
char* strstr(const char* str, const char* substr);
매개변수
- 검색을 진행할 문자열 : 종단 문자가 나올 때까지
str
의 내용을 검색함 substr
: 반드시 종단 문자로 종료되어야 함
반환값
str
에서substr
을 찾은 경우:str
기준으로 첫 번째substr
의 위치를 포인터로 반환str
에서substr
을 찾지 못한 경우: NULL 반환
추가
찾으려는 문자열이 여러개 존재하는 경우, 반환 값을 받아서 다시 검색하는 코드를 사용하면
순차적으로 substr
에 접근하거나 몇 개의 substr
이 있는지 체크할 수 있음
C언어 strcat
, strncat
함수 사용법
참고: [C언어,C++] 문자열 합치기 strcat, strncat 함수 사용법 & 예제 +구현 (tistory.com)
무엇?
C언어 표준함수 strcat
과 strncat
은 두 문자열을 이어 붙이는 기능을 함
strcat
은 첫 번째 주소에 저장된 문자열에 두 번째 주소에 저장된 문자열을 붙여서 반환
strncat
은 첫 번째 주소에 저장된 문자열에 두 번째 주소에 저장된 문자열을 붙여서 반환하는데, 일정 길이만큼 추가할 수 있음
왜?
CSAPP TINY 웹 서버 구현 中 URI 파싱하면서 사용된 함수
어떻게?
#include <string.h>
// 복사를 받을 대상의 시작 주소, 복사를 할 원본의 시작 주소
char *strcat(char *dest, const char *src);
// 복사를 받을 대상의 시작 주소, 복사를 할 원본의 시작 주소, 복사를 할 문자의 개수
char *strncat(char *dest, const char *src, size_t num);
매개변수
- 복사를 받을 대상의 시작 주소 : 반드시 종단 문자로 종료되어야 함
- 복사를 할 원본의 시작 주소 : 반드시 종단 문자로 종료되어야 함
- 복사를 할 문자의 개수 : size_t는 해당 시스템에서 어떤 객체나 값이 포함할 수 있는 최대 크기의 데이터를 표현하는 타입으로 반드시 unsigned 형으로 나타낼 것
추가
최종 결과가 나올 문자열의 배열 크기가 충분히 커야 overflow가 발생하지 않음.
C언어 strcpy
, strncpy
함수 사용법
참고: [C언어/C++] strcpy, strncpy 함수(문자열 복사)에 대해서 (tistory.com)
무엇?
C언어 표준함수 strcpy
과 strncpy
은 문자열을 복사하는 함수
왜?
CSAPP TINY 웹 서버 구현 中 URI 파싱하면서 사용된 함수
어떻게?
#include <string.h>
// 복사한 문자열을 붙여넣기 할 주소, 복사할 문자열의 시작 주소
char *strcpy(char *dest, const char *origin);
// 복사한 문자열을 붙여넣기 할 주소, 복사할 문자열의 시작 주소, 복사할 문자의 개수
char *strncpy(char *dest, const char *origin, size_t n);
매개변수
- 복사한 문자열을 붙여넣기 할 주소
- 복사할 문자열의 시작 주소 : 반드시 종단 문자로 종료되어야 함
- 복사할 문자의 개수
추가
strncpy
시n
의 크기는origin
의 크기보다 작거나 같아야 함.dest
의 길이보다n
은 작거나 같아야 함
HTML 폼 요소 삽입하기
참고: http://www.tcpschool.com/html-tag-attrs/form-action
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-action
#todo-html노트에정리하기
무엇?
폼 요소는 정보를 제출하는 상호적인 컨트롤을 포함하는 문서를 나타내줌.
- 속성
- action: submit을 했을 때 서식 데이터(form data)를 서버로 보낼 때 해당 데이터가 도착할 URL
- name: 빈 문자열이면 안 됨. 그리고 다른 form 요소의 name 속성과 겹치면 안 됨(즉, 고유한 값을 가져야 함)
- method: 서식을 제출할 메쏘드.
post
,get
,dialog
만 허용이며,get
이 디폴트!
왜?
CSAPP tiny 웹 서버 구현 11.10 A 문제를 위함.
어떻게?
<form action="" method="get" class="form-example">
<div class="form-example">
<label for="name">Enter your name: </label>
<input type="text" name="name" id="name" required>
</div>
<div class="form-example">
<label for="email">Enter your email: </label>
<input type="email" name="email" id="email" required>
</div>
<div class="form-example">
<input type="submit" value="Subscribe!">
</div>
</form>
TINY 웹 서버 - CSAPP 11.6장 TINY 웹 서버 확장하기
참고: video: 비디오 삽입 요소 - HTML: Hypertext Markup Language | MDN (mozilla.org)
무엇?
CSAPP 11.6장 숙제
- 11.7. TINY를 확장해서 MPG 비디오 파일을 처리하도록 하시오.
- 11.9. TINY를 수정해서 정적 컨텐츠를 처리할 때 요청한 파일을
mmap
과rio_written
대신에malloc
,rio_readn
,rio_written
을 사용해서 연결 식별자에게 복사하도록 하시오. - 11.10 A. CGI
adder
함수에 대한 HTML form을 작성하세요. 폼은 유저가 두 숫자를 기입할 수 있는 두 개의 텍스트 박스를 포함해야 함. 당신의 폼은 GET 메소드로 요청해야 함. - 11.10 B. 폼 요청을 브라우저로 체크하고, 제출하세요. 그리고 adder에 의해 생성된 동적 컨텐츠를 보여주어라.
- 11.11 TINY를 확장해서 HTTP HEAD 메소드를 지원하도록 하여라.
왜?
CSAPP TINY 웹 서버 기초 구현에서 더 나아가보자~
어떻게?
- 11.7. TINY를 확장해서 MPG 비디오 파일을 처리하도록 하시오.
※ mpg 확장자는 재생이 안 됨. legacy라 이제 지원이 안 되는 확장자인 듯? → mp4 확장자 동영상으로 대신함
- 구현하기
- 홈 디렉토리에 mp4 샘플 비디오 파일 추가
- home.html의
body
태그에 다음p
태그 추가
<p> <video src="sample_640x360.mp4" width="640" controls></video> </p>
tiny.c
파일의get_filetype
함수에 다음else if
구문 추가
else if (strstr(filename, ".mp4")) // 동영상 mp4 파일 타입 추가 strcpy(filetype, "video/mp4");
- 결과물
home.html에서 mp4 영상이 재생된다~
http://localhost:34533/sample_640x360.mp4
와 같이 접속해도 잘 나온다.
- 응답 헤더
Response headers:
HTTP/1.2OK
Server: Tiny Web Server
Connection: close
Content-length: 574823
Content-type: video/mp4
- 추가) 작동하지 않은 mpg 동영상 코드
<!-- 작동하지 않은 mpg 동영상 플레이 코드 -->
<p>
<video controls width="640">
<source src="sample_640x360.mpg" type="video/mpeg" >
</video>
</p>
- 11.9. TINY를 수정해서 정적 컨텐츠를 처리할 때 요청한 파일을
mmap
과rio_written
대신에malloc
,rio_readn
,rio_written
을 사용해서 연결 식별자에게 복사하도록 하시오.
※Rio_readn
,Rio_written
에 대해서 CSAPP 10.5.1절 참고!!
- 내가 변경한 알고리즘
malloc
으로filesize
만큼 메모리 할당Rio_readn
함수를 이용하여, 소스 파일에서 malloc으로 할당한 메모리에filesize
만큼 바이트 전송- 파일
close
Rio_written
함수를 이용해, 연결 식별자에filesize
만큼malloc
으로 할당한 메모리의 담긴 데이터 전송malloc
으로 할당 받은 메모리free
- 구현하기
tiny.c
파일의serve_static
함수의mmap
,Munmap
을 이용한 메모리 매핑 부분을 다음과 같이 수정
/* Send response body to client */
srcfd = Open(filename, O_RDONLY, 0); // read를 위한 소스 file 오픈 + 식별자 얻어옴
// 문제 11.9번을 위함
char *temp = Malloc(filesize); // malloc으로 filesize 만큼 메모리 할당
Rio_readn(srcfd, temp, filesize); // 소스파일에서 temp로 파일사이즈만큼 바이트 전송
// 파일을 복사한 후 더이상 필요 없으니 파일 close (메모리 누수 방지)
Close(srcfd);
// 문제 11.9번을 위함 - 연결 식별자에 temp로부터 filesize만큼 바이트 전송
Rio_writen(connfd, temp, filesize);
// temp 포인터 free
Free(temp);
- 11.10 A. CGI
adder
함수에 대한 HTML form을 작성하세요. 폼은 유저가 두 숫자를 기입할 수 있는 두 개의 텍스트 박스를 포함해야 함. 당신의 폼은 GET 메소드로 요청해야 함.
- 나의 슈도코드
- home.html에 작성하기
- submit을 누르면 get method로
/cgi-bin/adder?number-1=15000&number-2=213
을 요청
- 구현하기
- home.html의 body 태그 안에 다음과 같은 form 요소 추가하기
<form action="/cgi-bin/adder" method="get" class="form-example"> <div class="form-example"> <label for="number-1">number 1: </label> <input type="text" name="number-1" id="number-1" required> </div> <div class="form-example"> <label for="number-2">number 2: </label> <input type="number-2" name="number-2" id="number-2" required> </div> <div class="form-example"> <input type="submit" value="add!"> </div> </form>
- 결과
home.html 파일에서 다음과 같은 form 요소 삽입이 된 것을 볼 수 있다.
- 11.10 B. 폼 요청을 브라우저로 체크하고, 제출하세요. 그리고 adder에 의해 생성된 동적 컨텐츠를 보여주어라.
-
나의 슈도코드
- home.html의 form 요소에서 adder에 대한 request 받음
- adder.c 파일에서
/cgi-bin/adder?number-1=15000&number-2=213
uri에서number-1=
,number-2=
를 제외하고 숫자인자를 받을 수 있도록 파싱하는 코드 수정- 코드 리뷰 받은 내용 추가: '='의 위치를 찾아 인자를 추출하기
- 기존의 나의 방법: 간단하게 기존 코드 변경(유연성은 떨어지는…); arg1과 arg2를 구분 짓는 포인터에
number-1
,number-2
의 글자수만큼 주소를 더해줌
-
구현하기
-
기존의 adder.c의 main 루틴 中 두 개의 인자를 추출하는 코드를 다음과 같이 수정
- 코드 리뷰 후 추가한 부분
/* Extract the two arguments */ if ((buf = getenv("QUERY_STRING")) != NULL) { p = strchr(buf, '&'); // buf -> number-1=15000 \0 number-2=213 *p = '\0'; // 새로운 방법: '='의 위치를 찾아서 인자 추출하기! strcpy(arg1, buf); strcpy(arg2, p + 1); n1 = strtol(strchr(arg1, '=') + 1, NULL, 10); n2 = strtol(strchr(arg2, '=') + 1, NULL, 10); // 문제 11.10을 위해 수정한 부분 - 끝 n1 = atoi(arg1); n2 = atoi(arg2); }
- 기존의 내 방법
/* Extract the two arguments */ if ((buf = getenv("QUERY_STRING")) != NULL) { // buf -> number-1=15000 \0 number-2=213 p = strchr(buf, '&'); *p = '\0'; // 문제 11.10을 위해 수정한 부분 strcpy(arg1, buf + 9); // arg1 -> number-1=15000 -> 15000 strcpy(arg2, p + 10); // arg2 -> number-2=213 -> 213 // 문제 11.10을 위해 수정한 부분 - 끝 n1 = atoi(arg1); n2 = atoi(arg2); }
-
-
결과
그림처럼 URI에 주어진 인자를 잘 parsing해서 결과 값 2를 반환함을 확인
- 11.11 TINY를 확장해서 HTTP HEAD 메소드를 지원하도록 하여라.
※ HEAD는 HTTP 응답 시, body 없이 header만 받는 메소드로, 서버의 정상 작동 여부를 확인할 때 사용함
-
나의 슈도코드
- 에러 처리 조건문에서 HEAD 메소드도 허용하도록 변경
- 정적 콘텐츠, 동적 콘텐츠를 serve하는 함수에서 메소드를 인자로 추가
- 정적 콘텐츠 serve시; 응답 헤더 출력 후 메소드가 HEAD인 경우 함수 종료
- 동적 콘텐츠 serve시; 환경변수 "REQUEST_METHOD"에 메소드 인자 설정
-
구현하기
doit
함수의 조건문을 다음과 같이 변경
// GET 메소드가 아니고, HEAD 메소드도 아니면 에러
if ((strcasecmp(method, "GET")) && (strcasecmp(method, "HEAD")))
{
clienterror(fd, method, "501", "Not implemented",
"Tiny does not implement this method");
return;
}
read_requesthdrs(&rio);
serve_static
함수에char *method
매개변수 추가 + 헤더 출력문 다음에 아래 조건문 추가
if (strcasecmp(method, "HEAD") == 0)
return;
serve_dynamic
함수에char *method
매개변수 추가 + 자식 프로세스 포크하는 구문을 다음과 같이 변경
// 새로운 자식 프로세스 포크
if (Fork() == 0)
{
/* Real server would set all CGI vars here */
// 자식 프로세스는 query_string 환경 변수를 요청온 URI의 CGI 인자로 초기화
setenv("QUERY_STRING", cgiargs, 1);
setenv("REQUEST_METHOD", method, 1); // "REQUEST_METHOD" 환경 변수에 주어진 메소드 설정
Dup2(fd, STDOUT_FILENO); // 자식 프로세스는 자식의 표준 출력(stdout)을 연결 파일 식별자로 재지정
Execve(filename, emptylist, environ); // CGI 프로그램 로드 후 실행
}
Wait(NULL); // 부모는 자식이 종료되어 정리되는 것을 기다리기 위해 `wait` 함수에서 블록
}
- 결과
-
HEAD요청 시 응답 헤더만 보인다.
-
GET 요청시 데이터까지 다 보임
C언어 strchr
함수 사용하기
참고: https://blockdmask.tistory.com/389
무엇?
문자열 내에 일치하는 문자가 있는지 검사하는 함수
왜?
어떻게?
#include<string.h>
// 검색할 문자열, 존재하는지 확인할 문자 (아스키값으로 들어감)
char* strchr(const char*, int c);
매개변수
- 검색할 문자열
- 존재하는지 확인할 문자
반환값
- 첫번째 매개변수의 문자열에서 두번째 매개변수로 들어온 c가 존재하는지 검사
- 문자가 존재하면 해당 문자가 존재하는 곳의 포인터를 반환
- 존재하지 않으면 널 포인터 반환
C언어 strcasecmp
함수 사용하기
참고: https://badayak.com/entry/C언어-함수-대소문자-무시하고-문자열-비교-strcasecmp
https://www.it-note.kr/76
무엇?
두 문자열의 내용을 1바이트씩 unsigned char로 대소문자 구분하지 않고 크고 작음을 비교
어떻게?
#include <string.h>
// 비교할 문자열1, 비교할 문자열2
int * strcasecmp( const char *s1, const char *s2);
반환값
- 두 문자열을 순서대로 비교하다가 다른 문자(대소문자는 같은 값으로 인식함)를 처음 만났을 때, s1의 값이 작다 → 음수
- s1과 s2의 문자열의 길이와 내용(대소문자는 같은 값으로 인식함)이 모두 같다 → 0
- 두 문자열을 순서대로 비교하다가 다른 문자(대소문자는 같은 값으로 인식함)를 처음 만났을 때, s1의 값이 크다 → 양수
2022-12-14
네트워크 기초 이론
참고: https://www.youtube.com/watch?v=JDh_lzHO_CA&list=PLXvgR_grOs1BFH-TuqFsfHqbh-gpMbFoy&index=2
https://www.youtube.com/watch?v=4Sfned8HLzk&list=PLXvgR_grOs1BFH-TuqFsfHqbh-gpMbFoy&index=10
https://velog.io/@seul06/TIL-웹의-동작-방식-TCP와-UDP
https://www.youtube.com/watch?v=byR3BcrChT8&list=PLXvgR_grOs1BFH-TuqFsfHqbh-gpMbFoy&index=11
무엇?
-
MAC, IP주소, Port번호가 식별하는 것
- MAC: NIC마다 부여되는 것. 즉 LAN 카드마다 부여되는 것. 유선, 무선 두 개의 랜 카드가 있다고 하면 한 컴퓨터가 두 개의 MAC 주소를 갖게 됨.
- MAC 주소는 바꿀 수 있다.
- IP주소: 호스트를 식별함. n개의 IP를 가지고 있을 수 있음. NIC 하나에 여러개를 바인딩할 수 있음.
- Port번호: 소프트웨어 개발자 입장에서 Process 식별자
- 16비트 정보 → 2의 16승까지 경우의 수가 나오나, 0과 65535는 안 씀.
- 데이터가 네트워크로 유입되어 들어올 때, NIC → Driver → IP/TCP → Process까지 전달 됨. 이 때 어떤 Process로 갈 것인지를 알려주는 게 포트 번호!
- MAC: NIC마다 부여되는 것. 즉 LAN 카드마다 부여되는 것. 유선, 무선 두 개의 랜 카드가 있다고 하면 한 컴퓨터가 두 개의 MAC 주소를 갖게 됨.
-
초창기 웹 서비스 구조
- 티모시 버너스 리의 HTML, HTTP 고안 → 이것들의 연결이 거미줄 같다 해서 Web이라 함
- HTTP
- TCP/IP 연결을 대전제로 함 → stateful
- HTTP 프로토콜의 가장 중요한 점 → stateless
(※ 추후, 웹이 클라이언트의 서버로부터의 일방적인 문서 조회가 아닌, 클라이언트의 입력과 그에 따른 서버의 상호작용이 뒤따르는 양방향 서비스로 변하면서 맥락을 기록할 필요가 생겨났고, HTTP 프로토콜의 stateless한 특성은 맥락을 저장하지 못하므로 이 때문에 Database를 쓰게 되었음. → 이걸 클라이언트는 어떻게 하냐면 "쿠키"로 한다! )
[1]
-
웹 서비스의 3대 요소
- 구문 분석 - HTML, DOM
- 렌더링
- 연산(처리) - 자바 스크립트
Proxy 서버 이해
참고:
무엇?
- 네트워크 장치 설치의 두 가지 방식; inline과 out of path
- 인라인은 고속도로의 IC 같은 것. 패킷이 지나가거나 못 지나가거나 둘 중 하나.
- out of path는 따로 떨어져 나와 있는 가지 같은 것. 고속도로의 과속 감지 카메라같은 역할. 주로 포트 미러링으로 패킷을 그대로 복사하는 read-only sensor의 역할을 함.(장애 탐지, 해킹 탐지 등의 이유로)
- 애플리케이션 프록시
- Proxy = 대리자
- 패킷 단위가 아니라, Socket Stream을 다룸
- 프록시를 왜 쓸까?
- 우회
- 프록시 서버를 두고 프록시 설정을 함으로써 IP 세탁하게 됨.
- 조심할 점; 프록시 서버측에서 IP 세탁을 하려는 쪽의 모든 통신을 들여다 볼 수 있음.
- 분석
- HTTPS 통신 환경에서, 패킷 수준이 아니라 소켓 수준에서 암호화되기 전의 stream 단위의 데이터를 분석하고 싶을 때, 유저 단에서 프록시 서버를 두고 프록시 서버로 암호화 되기 전의 HTTP 평문 데이터를 받게끔해서 분석할 수 있음.
- Fiddler같은 툴을 사용
- 감시와 보호
- 프록시 서버를 두고, 나머지 호스트에 프록시 설정을 했다고 가정
- 프록시 설정이 되어있는 호스트가 악성코드가 있는 사이트에 접속하면, 프록시 서버가 악성코드를 먼저 발견함 ⇒ Virus Wall
- 프록시 설정이 되어있는 호스트가 어떤 사이트가 접속하는지 들여다 볼 수 있음 ⇒ 감시 역할
- 리버스 프록시
- 웹 서버 쪽에 웹 서버를 위해 두는 프록시. (1~3까지는 클라이언트를 감시, 보호하기 위함)
- 클라이언트가 해당 서버의 도메인을 입력하면 DNS가 프록시 서버의 IP를 알려 줌 → 프록시가 서버에서 응답을 받아 옴 → 프록시가 클라이언트에게 응답
- 클라이언트와 프록시는 Public 구간, 웹 서버와 그 웹 서버의 프록시는 Private 구간 ⇒ 외부에서 프록시를 거치지 않고서는 해당 웹 서버로의 직접 접근이 불가
- 이때 프록시는 접속한 클라이언트의 요청 내역을 감시(공격 식별 및 차단) + 웹 서버 보호 ⇒ WAF(Web Application Firewall)
- 우회
'크래프톤정글 > TIL & WIL' 카테고리의 다른 글
백준 10971번 외판원 순회 파이썬 (0) | 2023.01.23 |
---|---|
백준 9663번 N-Queen 파이썬 (1) | 2023.01.19 |
크래프톤정글 6주차; WIL - 동적 메모리 할당 개발 일지 (0) | 2022.12.08 |
크래프톤정글 6주차; TIL - 백준 9020 골드바흐의 추측 재도전 (0) | 2022.12.08 |
크래프톤정글 5주차; TIL 2 - Red-Black Tree의 insert, delete 연산, C typedef, enum 활용, 메모리에서 스택과 힙의 자라나는 방향 (0) | 2022.12.03 |