
2025.05.06
오늘한 내용 : C - 네트워크 프로그래밍 - echo server, tiny web server 구현
WEEK08: BSD소켓, IP, TCP, HTTP, file descriptor, DNS
echo_client.c, echo_server.c, csapp.h, csapp.c 넣어 놓는다.gcc -o echo_server echo_server.c csapp.c
gcc -o echo_client echo_client.c csapp.c
./echo_server 9190(포트번호)./echo_client 127.0.0.1(localhost) 9190(포트번호)#include "csapp.h"
int main(int argc, char **argv)
{
int clientfd;
char *host, *port, buf[MAXLINE];
rio_t rio;
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)
{
Rio_writen(clientfd, buf, strlen(buf));
Rio_readlineb(&rio, buf, MAXLINE);
Fputs(buf, stdout);
}
Close(clientfd);
exit(0);
}
#include "csapp.h"
void echo(int connfd);
int main(int argc, char ** argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
char client_hostname[MAXLINE], clinet_port[MAXLINE];
if (argc != 2){
fprintf (stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
listenfd = Open_listenfd(argv[1]);
while (1){
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
Getnameinfo((SA* )&clientaddr, clientlen, client_hostname, MAXLINE,
clinet_port, MAXLINE, 0);
printf("Connected to (%s, %s)\n", client_hostname, clinet_port);
echo(connfd);
Close(connfd);
}
exit(0);
}
void echo(int connfd)
{
size_t n;
char buf[MAXLINE];
rio_t rio;
Rio_readinitb(&rio, connfd);
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0){
printf("server received %d bytes\n", (int)n);
Rio_writen(connfd, buf, n);
}
}
make 실행./tiny 8000(port)localhost:8000serve_static() 수행 → godzilla 사진 등장http://localhost:8080/cgi-bin/adder?x=1&y=2 접속serve_dynamic 수행 → adder 실행됨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
}
}
단일 연결(iterative) 서버 패턴
Accept에서 블로킹되어 한 클라이언트의 연결 요청이 올 때까지 대기doit(connfd)로 요청을 처리Close(connfd)로 소켓 닫고Accept에서 대기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;
Rio_readinitb(&rio, fd);
Rio_readlineb(&rio, buf, MAXLINE);
printf("Request headers:\n");
printf("%s", buf);
sscanf(buf, "%s %s %s",method, uri, version);
if (strcasecmp(method, "GET")){
clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
return;
}
read_requesthdrs(&rio);
is_static = parse_uri(uri, filename, cgiargs);
if (stat(filename, &sbuf) < 0){
clienterror(fd, filename, "404", "Not found","Tiny couldn't find this file");
return;
}
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);
}
else{
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs);
}
}
| 상태 코드 | 명칭 | 분류 | 발생 조건 |
|---|---|---|---|
| 400 | Bad Request | 클라이언트 오류 (4xx) | 잘못된 요청 라인(구문 오류) 혹은 서버가 이해할 수 없는 요청 형식일 때 (추가 구현 시) |
| 403 | Forbidden | 클라이언트 오류 (4xx) | 파일이 존재하지만 읽기·실행 권한이 없을 때– 정적: serve_static 전 권한 검사 실패– 동적: serve_dynamic 전 권한 검사 실패 |
| 404 | Not Found | 클라이언트 오류 (4xx) | 요청한 파일이 서버에 존재하지 않을 때 (stat 호출 실패) |
| 500 | Internal Server Error | 서버 오류 (5xx) | CGI 실행 중 오류 발생 시(예: execve 실패 등 추가 구현 시) |
| 501 | Not Implemented | 클라이언트 오류 (4xx) | GET 이외의 HTTP 메서드가 들어왔을 때 |
int stat(const char *path, struct stat *buf);buf에 채워 넣음.struct stat {
dev_t st_dev; /* 파일이 속한 디바이스 ID */
ino_t st_ino; /* 파일의 i-node 번호 */
mode_t st_mode; /* 파일 종류 및 접근 권한 비트 */
nlink_t st_nlink; /* 하드링크 개수 */
uid_t st_uid; /* 소유자 사용자 ID */
gid_t st_gid; /* 소유 그룹 ID */
off_t st_size; /* 파일 크기 (바이트 단위) */
/* 그 외 타임스탬프, 블록 수 등 */
};
HTTP 프로토콜 상으로는 아래 순서대로 바이트 스트림을 보내야 함:
왜 나눠 쓰느냐
malloc/sprintf로 거대한 버퍼를 만들 필요 없이, 필요한 만큼만 작은 버퍼(buf)에 찍어서 바로 보내는 방식.빈 줄(\r\n) 은 “헤더의 끝”을 알리는 중요한 구분자.
이 세 번의 Rio_writen 호출이 모여서 완전한 HTTP 응답을 만듦.
HTTP/1.0 404 Not Found\r\n
Content-type: text/html\r\n
Content-length: 123\r\n
\r\n
<html>…error body…</html>
parse_uri 함수는 클라이언트가 보낸 URI를 분석해서filename)cgiargs) 를 각각 설정해 주는 역할<string.h> 헤더에 선언된 ****C 문자열 함수들| 함수 | 원형 | 설명 |
|---|---|---|
strstr | char *strstr(const char *hay, const char *ndl); | 문자열 hay 안에서 처음으로 ndl이 나타나는 위치의 포인터를 반환. 없으면 NULL. |
strcpy | char *strcpy(char *dest, const char *src); | src 문자열 전체(널 종료 포함)를 dest에 복사하고 dest 반환. |
strcat | char *strcat(char *dest, const char *src); | dest 문자열 끝(널 바이트 위치) 뒤에 src를 붙이고 dest 반환. |
strlen | size_t strlen(const char *s); | 문자열 s의 길이(널 종료 문자 제외) 반환. |
index | char *index(const char *s, int c); (BSD) | 문자열 s에서 문자 c를 처음 만나는 위치의 포인터를 반환. 없으면 NULL. ※ POSIX 표준 함수명은 strchr. |
sprintf로 차례로 버퍼에 붙인 뒤 Rio_writen으로 전송.open → mmap으로 메모리에 맵핑Rio_writen으로 매핑된 영역 전체(파일 내용)를 한 번에 전송munmap으로 매핑 해제void *mmap(void *addr, size_t length,
int prot, int flags,
int fd, off_t offset);
fd)로 열린 파일(또는 장치)의 내용을 프로세스 가상 주소 공간에 “매핑”하여, 마치 메모리 배열인 것처럼 접근할 수 있게 해 줌.read/write 반복 호출 없이,mmap 후 char *p로 파일 내용을 바로 p[i] 처럼 인덱싱할 수 있어 코드가 깔끔.read/write는 커널 ↔ 유저 버퍼를 매번 복사해야 함mmap은 페이지 테이블만 설정해 주면 부가 복사 없이 바로 동일 페이지를 공유할 수 있음.Fork() 호출로 부모/자식 프로세스를 분기Wait(NULL))