SW 사관학교 정글(Jungle)/web proxy

[C언어][csapp] Tiny서버 구현 시 SIGPIPE 상황 발생 해결 (문제 11.7과 연결)

jinsang-2 2024. 9. 20. 01:06

https://jinsang-2.tistory.com/87  링크 11.7의 문제의 연장선이라 볼 수 있다. 

 

[CSAPP] 11장 숙제(Homework) 문제 정답

11.7) Tiny를 확장해서 MPG 비디오 파일을 처리하도록 하시오. 실제 브라우저를 사용해 여러분의 결과를 체크하시오(MPG 대신 mp4)1.get_filetype에 mp4 추가하기 //MIME type을 읽고 값을 *filetype에 저장void ge

jinsang-2.tistory.com

{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 누른상태에서 마우스 좌클릭 누르면 클릭한 해당 함수를 찾아가요~