https://jinsang-2.tistory.com/87 링크 11.7의 문제의 연장선이라 볼 수 있다.
{IP 주소}.godzilla.jpg로 직접 요청하기
잘 나온다!!
❗문제발생
{IP 주소}.video.mp4 요청 시 SIGPIPE, Broken pipe가 뜨고 영상은 안나온다..
접속 시에 서버는 터진다..
GDB 디버깅 시도 rio_writen() 부분에서 터지는 것을 알 수 있다. SIGPIPE가 뜨넹
rio_writen()은 어디있는가?
void Rio_writen(int fd, void *usrbuf, size_t n)
{
if (rio_writen(fd, usrbuf, n) != n)
unix_error("Rio_writen error");
}
- Rio_writen 안에 있다! (csapp.c에 정의)
🤔문제 찾기
rio_writen에서 printf를 해보면서 어디서 터지나 확인해보자
- while문에서 첫번째까지는 잘 돌아간다.
- 두 번째 회차 때 터진다. 왜 그럴까??
ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nwritten;
char *bufp = usrbuf;
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
return -1; /* errno set by write() */
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
}
💡첫번째 문제해결 방법
while 문 없애기
ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nwritten;
char *bufp = usrbuf;
if ((nwritten = write(fd, bufp, nleft)) <= 0) {
if (errno == EINTR) /* Interrupted by sig handler return */
nwritten = 0; /* and call write() again */
else
return -1; /* errno set by write() */
}
return n;
}
잘 작동한다.
- 하지만 제대로 된 해결 방법은 아니다..
- 원인을 제대로 분석해보자.
★원인은 SIGPIPE이다.
위에 성공했을 때 스크린샷을 참고해보자. 처음에 브라우저는 video.mp4를 document 타입으로 보낸다. 브라우저 입장에서는 html이 우선순위로 response 받아야 하기 때문이다. 근데 서버에서 mp4 를 던져주는게 아닌가?! 여기서 문제가 발생한다. 브라우저는 document로 request보낸 것을 끊어버리고 video.mp4를 받을 파일디스크립터(fd)를 새로 판다.
rio_writen에서 while문을 통해 이전 fd(파일디스크립터)에 또 요청을 하니 SIGPIPE 신호를 받는다.
[SIGPIPE]
프로세스가 읽기가 안되는 파이프에 쓰려고 한다면, 커널로 부터 SIGPIPE 신호를 받게 된다.
- Client가 연결을 끊은 소켓에 서버가 재요청할 때 발생한다.
- 닫은 fd로 쓰기를 하려는 순간 발생했던 것!
💡SIGPIPE 해결
1. signal.h 헤더파일을 포함한다. (csapp.h에 이미 포함되어있긴함)
2. 첫번째 파이프를 열기 전에 시그널(SIGPIPE,SIG_IGN)을 호출한다. 이 호출은
리눅스 커널에게 만약 SIGPIPE 시그널이 발생하게 되면 무시
1. tiny.c의 main함수 맨 위에 signal(SIGPIPE, SIG_IGN); 쓰기
- signal(SIGPIPE, SIG_IGN); 를 사용함으로 SIGPIPE 에러 발생한 부분을 무시해버리기
2. Rio_writen안에 unix_error 처리해서 서버 다운 방지
Rio writen 안에 unix_error 함수 고치기
SIGPIPE로 인해 rio_writen에서 -1을 리턴하기 때문에 unix_error 부분에서 exit(0)을 통해 서버가 다운되니 에러넘버가 EPIPE가 아닐 때만 exit(0)을 실행하게 바꿔준다.
갑자기 EPIPE?
main에서 signal(SIGPIPE, SIG_IGN) 해주면 SIGPIPE를 만나면 쓰기의 호출이 에러(EPIPE)를
반환한다.
😊결과
잘 작동한다~
Tip
vsCode에서 ctrl 누른상태에서 마우스 좌클릭 누르면 클릭한 해당 함수를 찾아가요~