참조:
https://github.com/stkang9409/TinyWebServer
문제 개요
1. 클라이언트 측에서 내가 보낸 비디오 파일을 읽을 수 있도록 하기 위해, URI parse 시 처리할 수 있는 file type에 mp4 형식을 추가하였다. 이로써 응답 헤더에 content type으로 video/mp4를 보내줄 수 있게 되었다.
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"))
strcpy(filetype, "video/mp4");
else if (strstr(filename, ".mpeg"))
strcpy(filetype, "video/mpeg");
else
strcpy(filetype, "text/plain");
}
이후 서버에 7Mb짜리 mp4 영상을 올리고, home.html을 수정하여 동영상을 확인할 수 있게 되었다.
- 동영상 관련 요청 및 응답 헤더.
- 브라우저에서 띄운 페이지
- 처음 브라우저에 띄우면 동영상이 자동적으로 재생되지 않는다.
그러나 URI에서 직접 동영상 주소를 입력하면 문제가 발생한다.
- 응답 요청 헤더
- 브라우저 상 화면
- 브라우저 상에 동영상이 아예 뜨지를 않는다.
- 콘솔 창에 확인을 해보면 서버가 그냥 꺼져버리는 것이 문제임을 알 수 있다.
어디서 서버가 터지는 것일까..
- 일단 파일을 클라이언트 측으로 보내는 코드를 보자
srcfd = Open(filename, O_RDONLY, 0); //PROT_READ -> 페이지는 읽을 수만 있다. // 파일을 어떤 메모리 공간에 대응시키고 첫주소를 리턴 srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); Close(srcfd); Rio_writen(fd, srcp, filesize); //대응시킨 녀석을 풀어준다. 유효하지 않은 메모리로 만듦 // void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); // int munmap(void *start, size_t length); Munmap(srcp, filesize);
- rio_writen 관련
ssize_t rio_writen(int fd, void *usrbuf, size_t n) { size_t nleft = n; ssize_t nwritten; char *bufp = usrbuf; printf("filesize: %d\n", n); while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) <= 0) { if (errno == EINTR) /* Interrupted by sig handler return */ nwritten = 0; /* and call write() again */ else { printf("end2\n"); return -1; /* errno set by write() */ } } nleft -= nwritten; printf("leftsize: %d\n", nleft); bufp += nwritten; } printf("end1\n"); return n; }
프린트 해보다 알게된 것
- 처음 write를 하면 file size가 좀 남는다.
- rio_writen에 적혀있는대로면 남았을 떄는 반복문으로 다시 들어가는데, 출력이 사진 상의 두 문장이 끝인 것으로 미루어 보아, 두번째로 반복문에 진입했을 때 정상적으로 함수가 종료되지도 않고 write(2) 시스템 콜에서 문제가 발생해서 프로그램이 종료되어버리는 것 같다.
그래서 while을 if로 바꾸었더니..
- 잘된다.
- 나는 파일이 잘려서 파일 상에 쓰이게 되니 잘린 만큼만 재생될 줄 알았는데, 끝까지 재생이 잘 됐다.
- 그래서 콘솔을 확인해보니..
그냥 파일을 끝까지 다 썼다.
현재 코드 그대로 홈페이지에서는 어떻게 되는가 확인을 해봤다.
- 콘솔
- 홈화면과 콘솔 모두 전과 동일하다.
- 홈화면에는 동영상이 멈춘 상태로 로딩이 끝났고 콘솔에서 확인하길 파일은 쓰다 말았다.
그래서 동영상 재생 버튼을 눌러보았다.
- 콘솔
파일을 처음부터 끝까지 다 쓴다.
지금까지 알게된 것
이 실험을 통해 두가지 사실을 알게 되었다.
- 처음 동영상이 로딩될 때 서버측에서 write를 해줄 때 시그널 인터럽트를 당하는 것 같다. 그래서 쓰다가 만다.
- 동영상 로딩과, 동영상 재생이 각각 두번의 요청을 보내는 것으로 판단된다.
- 그래서 처음 코드상에선 동영상이 자동으로 재생되는 주소 host:8000/happy.mp4 는 서버가 꺼져버렸고, 자동으로 재생이 안되는 홈화면에선 서버가 안꺼지고 있었다고 추측된다.
- 뭔가 두개의 요청이 충돌을 일으키는 느낌이다.
그래서 if를 다시 while로 바꾸고 대신 nleft가 남았을 때 sleep을 좀 하다가 다시 반복문에 들어가도록 코드를 바꾸어보았다.
- 코드
ssize_t rio_writen(int fd, void *usrbuf, size_t n) { size_t nleft = n; ssize_t nwritten; char *bufp = usrbuf; printf("filesize: %d\n", n); while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) <= 0) { if (errno == EINTR) /* Interrupted by sig handler return */ nwritten = 0; /* and call write() again */ else { printf("end2\n"); return -1; /* errno set by write() */ } } nleft -= nwritten; printf("leftsize: %d\n", nleft); bufp += nwritten; if (nleft) usleep(50); } printf("end1\n"); return n; }
- 이렇게 했더니 굉장히 잘된다.
- 여전히 홈 화면에선 처음에 파일을 쓰다 말고, 재생을 눌리면 다시 처음부터 끝까지 전부 쓴다.
- 이상한 점은 홈 화면이 떴을 때 바로 재생 버튼을 누르면 쓰다 말지 않고 바로 끝까지 다 써버린다는 것이다. 어디선가 인터럽트 하던 동작이 사라지는 것처럼 보인다.
내가 생각하는 정상적인 동작
- 내가 파악한 바에 따르면 브라우저에서 보내는 모든 요청은 서버 측에서 각각의 요청으로 받아들여져서 연결 대기 큐에 들어있다가 순차적으로 수행된다.
- 동영상 로딩과 재생이 각각 다른 요청이라고 치면 ( 홈화면에서 보여주었던 동작을 바탕으로 추측한 사항 - 재생버튼을 누르면 요청이 하나 더 보내졌던.. ) 일단 동영상의 일부분만 페이지 띄우는 요청과, 다시 동영상을 재생시키는 요청이 둘다 콘솔에 떠야한다.
- 그러나 usleep(50)을 추가하고 나니, 홈화면을 로딩하고 다시 재생을 눌렀을 때 요청이 하나밖에 안들어온다. (usleep을 추가하기 전엔 그렇게 하면 서버가 꺼졌다..)
원인에 대한 나의 추측
- 하나의 파일 디스크럽터에 서버와 클라이언트가 동시에 뭔가 작성하기 시작해서 (동영상 파일과 요청헤더(재생하면 한번 더 보내지는)) 오류가 발생하는 것 같다.
- 시스템 콜에 대해 이해가 부족해 좀 더 공부하고 생각해봐야할 것 같다..