'programming'에 해당되는 글 387건

  1. 2013.04.05 select() > 0 인데, recv() < 0 - 어떤 경우에? SELECT RECV 관련

반응형

 

http://kldp.org/node/119462

 

select() > 0 인데, recv() < 0 - 어떤 경우에?

일반적인 TCP 소켓 프로그램에서 제목처럼

select() > 0 인데, recv() < 0 인 경우가 어떤 때 나오는지 궁금해서 질문드립니다.

RTSP 클라이언트 프로그램을 작성중인데요,

소켓에서 한 바이트씩 읽어서 /r/n/r/n이 나올 때까지 읽는 프로그램입니다.

소켓 옵션은 특별히 주지 않았구요. 그러니까 blocked socket 이네요.

뭐 이건 non-block 소켓으로도 바꿔 보고 이것저것 해 봤습니다.

아래 소스에서 block_until_readable()의 결과 값이 > 0 (그러니까... select() > 0) 인데,

recv() < 0 이어서

perror("Fail to recv :");

라인에 걸리는 경우가 있습니다.

errno = 104 (ECONNRESET - Connection reset by peer)

라고 나오는군요.

원래대로라면

RTSP/1.0 200 OK
CSeq: 4
Session: 135514808124572
Range: npt=now-

이 받아져야 하는데, 첫 번째 줄의

RTSP/1.0 200 OK (\r이나 \n 없이)

까지만 오고 나서 위와 같은 에러가 뜹니다.

항상 같은 결과...

(혹시 RTSP를 아시는 분을 위해 좀더 부언하면, OPTIONS ~ SETUP 까지는 주고받고 잘 하는데,

위의 응답은 PLAY에 대한 겁니다. 뭐 이건 질문의 요지와는 크게 관계는 없습니다만...)

그런데 이 경우 select()에서 한참을 머물다가 return 되어 돌아옵니다.

보통은 select()에서 바로바로 return되어 돌아오는데,

이 에러가 나기 직전에는 timeout 값보다 조금 안 되는 시간 만큼을 select()에서 머물렀다가

돌아오는 것 같습니다. 그리고서는 select()는 1을 리턴합니다.

직후에 recv()를 하면 -1이 리턴되면서 위와 같은 에러가 나는 것이구요.

불행히도 서버 쪽 소스는 저희가 볼 수 없네요.

다른 RTSP 클라이언트 (Live555의 openRTSP 같은 ...)

를 사용하면 정상적으로 주고받고 하는 것으로 보아 서버쪽이 문제가 없는 것 같기도 하고...

참고로 사용한 소스를 필요한 부분들을 추려 아래에 적습니다.

/* timeout을 두어 소켓의 상태가 읽기 가능한지 검사합니다. */
int block_until_readable(int s, struct timeval *p_timeout)
{
	int result = -1;
	fd_set rd_set;
 
	do
	{
		FD_ZERO(&rd_set);
		FD_SET(s, &rd_set);
 
		result = select(s+1, &rd_set, NULL, NULL, p_timeout);
 
		if (result == 0)
		{
			printf("Select timeout\n");
			break;
		}
		else if (result < 0)
		{
			if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
			{
				continue;
			}
			else
			{
				perror("select() error: ");
				break;
			}
		}
		else
		{
			if (!FD_ISSET(s, &rd_set))
			{
				printf("select() error - !FD_ISSET)\n");
				return -1;
			}
			else
			{	/* Socket is readable. Normal case */
				;
			}
		}
	} while (0);
 
	return result;
}
 
/* sock에서 \r\n\r\n이 나올때까지 읽어 buf에 저장합니다. */
int read_socket(int sock, char buf[], int buf_len)
{
	int	read_bytes, result, recv_len;
	char	*buf_ptr;
	struct timeval timeout;
 
 
	buf_ptr = buf;
	recv_len = 0;
 
        memset(buf, 0, buf_len);
 
	while (1)
	{
                /* Set timeout : 5 seconds */
	        timeout.tv_sec = 5;
	        timeout.tv_usec = 0;
 
		result = block_until_readable(sock, &timeout);
 
		if (result == 0)		// timeout
		{
			return -1;
		}
		else if (result < 0)	// select error
		{
			printf("Select error in read_socket()\n");
			return -1;
		}
 
		/* Socket is readable. Normal case. Read 1 byte */
		read_bytes = recv( sock, buf_ptr, 1, MSG_NOSIGNAL );
 
		if( read_bytes == 0 )
		{
			printf("0 returned in recv()\n");
			return -1;
		}
		else if( read_bytes < 0 )
		{
			perror("Fail to recv :");    // <== 여기서 걸림
			return -1;
		}
 
		/* Read success */
		recv_len += read_bytes;
		buf_ptr += read_bytes;
 
		/* String ends with "\r\n\r\n" - Find it! */
		if ( recv_len >= 4)
		{
			if (*(buf_ptr-4) == '\r' && *(buf_ptr-3) == '\n'
				&& *(buf_ptr-2) == '\r' && *(buf_ptr-1) == '\n')
			{
				*buf_ptr = '\0';	// I've got complete string. Terminate it.
				break;
			}
		}
		else
		{
			;	// I have to read more.
		}
 
		if (recv_len >= buf_len)
		{
			printf("Too long(%d)\n", recv_len);
			*buf_ptr = '\0';
			break;
		}
	}
 
	return recv_len;
}

질문이 길어졌는데...

요약하자면,

어떤 경우에 select() > 0 이면서 recv() < 0 이 나올까 하는 겁니다.

- 답을 안 주셔도 읽어 주신 모든 분께 감사드립니다.

음 ..

select() 가 return 하면, timeout 값이 남은 시간으로 변경됩니다.
그냥 쓰다보면 언젠가는 0 이 될테고, 그러면 호출되는 즉시 리턴되겠죠.
select() 에 들어가는 timeout 값은 매번 초기화 해줘야 합니다.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

select timeout 수정

음... 감사합니다.
그런 것도 있었군요. 그건 수정하겠습니다.

하지만 문제는 그게 아닌 것 같습니다.
perror ("Fail to recv:")
에 걸릴 때 select에 한참 머물다 왔습니다.
그리고 타임아웃 걸린 것도 아니고, select()는 1이 리턴되었습니다.

궁금한 건, select()가 1인데, recv()는 -1인 경우가 어떤 경우인지 ...

음 ..

recv() 는 socket 이 shutdown 이나 reset 일 때, 0 또는 -1 을 리턴합니다.
데이터를 읽어들이는 도중이나 다 읽었더라도, 서버에서 연결을 종료해 버리면..
read_socket() 에서 0 또는 -1 을 리턴할 가능성이 있을 것 같습니다.

그리고 recv() 의 두번째 인자로, buf 를 넘기시는데, 얘는 buf_ptr 로 바꾸는게 맞을 것 같네요.
전체 버퍼의 첫번째 바이트에만 데이터를 덮어 쓰고 있습니다.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

그 실수도 있었군요

제가 지금 작업하고 있는 소스를 그대로 올린 게 아니라,
질문을 위해 불필요한 부분을 걷어내고, 컴파일을 해보지 않은 상태로 올린 거라
그런 실수도 있었군요. 감사합니다.^^

recv()에서 -1이 리턴되고, ECONNRESET이 뜬 걸로 보아,
TCP 레이어에서 RST가 온 건 맞는 것 같습니다.
서버가 close() 외의 다른 방법으로 종료한 것 같습니다.
문제는 왜 그랬는가 하는 건데, 서버쪽 소스를 저희가 구할 수 없어 그 이유는 알 수가 없구요.

알려주신 댓글에 의하면,
select()가 읽기 버퍼를 보니 읽을 게 있고, 그래서 1이 리턴되었고...
상대편 소켓의 상태나 connection 상태는 모르고, 관심도 없고, select()의 임무도 아니고...
그런데, 읽는 도중 recv()가 상대편 소켓으로부터 RST를 받아서 -1이 리턴되었다...
그런 말씀이신가요?

항상

RTSP/1.0 200 OK

까지만 읽히는데, 항상 서버가 정확히 거기까지만 send()하고
close()가 아닌 다른 어떤 식으로 소켓을 끊어 버렸다는 그런
얘기가 되나요?

갑자기 궁금해 지는 게 있네요.
select()는 정말 connection의 상태는 관심없고, 오로지 읽기(또는 쓰기) 버퍼만 쳐다보는 건가요?
이런 경우처럼 상대편이 10바이트 정도를 보내고 (Peer 프로그램 crash 등으로 인해) 갑자기 연결이 끊어지면
select() 리턴값이 -1이 되어 주면 좋겠는데
그렇지 않은 것 같군요.

제가 이해하고 있는 게 맞는지 모르겠는데,
그 외에 select() 의 리턴값이 1이 되고, recv()의 리턴값은 -1이 되는 또 다른 어떤 시나리오가 있을까요?

하여간 ymir님 계속 관심 가져 주셔서 고맙구요.
혹시 생각나시는 거 있으면 한 번 더 댓글 주시면 감사하겠습니다.

음 ..

select 는 fdset 의 상태 변화를 모니터링 합니다. socket close 시에도 이벤트가 발생한 fd 수를 리턴하는 걸로 알고 있습니다.
그래서 이벤트가 발생한 fd 에 read/write 등의 리턴값을 통해 close 여부를 확인할 수 있구요.
select 가 -1 을 리턴하는 경우는 man page 보시면 아시겠지만.. 몇 개 없습니다.
socket close 역시 socket 의 상태가 바뀐것이지, 에러가 아니니 -1 을 리턴할 이유가 없는거죠.

buf 의 첫번째 바이트에만 계속 덮어쓰다보니, 프로토콜 상의 terminate 조건을 만나지 못하고 계속해서 누적됩니다.
데이터 전송 후 상대가 socket 을 종료할 텐데, graceful shutdown 이라면 recv 가 0 을 리턴할 것이고...
그 이외의 상황이라면 -1 을 리턴하겠죠.. 어쨌든 둘 다 socket close 라고 보시면 됩니다.

읽은 데이터가 항상 "RTSP/1.0 200 OK" 라고 하셨는데...
이전 버퍼를 클리어 하지 않아서 우연히 남은 값이거나 하지 않을까 추정됩니다.
strace 로 시스템 콜을 떠 보시면 recv 가 어디까지 읽었는지 확인할 수 있을텐데..
적어도 다른 클라이언트로 테스트 했을때 서버에서 아무런 문제를 발견하지 못했다면...
위의 코드 역시 데이터를 읽기는 다 읽어들일겁니다. 단지 읽어들인 값을 정확하게 리턴하지 못했을 뿐..

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

음냐;

헛소리 써놓아서 죄송합니다. -_-;
몇시간 지나고 보니 헛소리 써놓았더군요.

감사합니다.

select()를 다시 뒤져 읽어 보았습니다.

"A socket is ready for reading"의 의미 중에 "socket error is pending"

도 있군요.

select()는 recv() 호출을 해도 block되지 않을 것이다. 라는 것만 알려 주는 것 같습니다.

소켓에 문제가 생긴 걸 감지하는 게 아니라...

(그나저나 서버 쪽에서는 왜 소켓을 reset 시켜 버렸을까... 쩝...)

그리고 한 가지 재미있는 것도 알게 됐는데,

select() 호출시 마지막 파라미터인 timeout 값이 리눅스에서는 남은 타임아웃 값으로 업데이트 되는데, 유닉스에서는 그런 것 같지 않네요.

Stevens 의 Unix Network Program 2nd Ed.의 p.150에 나오는 함수 원형에는 마지막 파라미터 앞에 const가 있습니다.

Linux man 페이지에는 없구요.

하여간 ymir님 여러 모로 감사합니다.

* 지적해 주신 버그들은 수정하여 본문의 코드를 다시 올렸습니다. (자꾸 마음에 걸려 하시는 것 같아서... ^^; 누가 다시 볼 일은 별로 없겠지만요...)

반응형
Posted by 공간사랑
,