select 함수로 여러 소켓의 상태를 감시할 경우.
글쓴이: byulparan / 작성시간: 목, 2012/07/05 - 12:51오전
구글에서 여러번 검색을 해봤는데 비슷한 질문은 많은데 시원한 답변이 없어서
질문드려봅니다.
select 함수로(혹은 다른 멀티플렉싱 도구들로..) 여러 소켓의 입출력을 관리할때
대게 소켓은 논블로킹으로 설정한다는 설명을 보았습니다.
libevent 의 예제들을 봐도 소켓을 논블록으로 설정을 하더군요.
그 이유가 무엇일까요?
select 라면, readable 혹은 writable 하기때문에 리턴되고, 때문에 해당 읽기/쓰기가
블로킹 되지 않는다는 걸 보장 하지 않나요?
검색중에 다음을 보고
http://superkkt.com/81
아..이경우 write 가 블로킹 될 수도 있겠구나…..함을 알았습니다.^^;;
그런데 읽기 전용의 경우 select 가 readable 하다고 판단하여 리턴되었음에도
read 가 블로킹 될 수 있는 예가 있는지요? 소켓버퍼에 데이터가 들어오고, 감시하던
select 가 리턴되었다면 read 함수를 한 번 만 호출할 경우 현재 버퍼에 있는 데이터 만큼은,
혹은 read 함수에 지정한 사이즈 만큼은 읽어올 수 있음을 보장하지 않나요?
자바의 nio 가 논블로킹 소켓을 강제하고(맞나요??), 많은 분들이 멀티플렉싱을 이용할 경우 논-블로킹 소켓과
함께 쓰기를 권장하는 이유가 궁금합니다.
Forums:
read 블로킹은 상당히 흔합니다.
블로킹 모드에서 read 함수로 10바이트를 읽어오게 할 경우, 버퍼에 5바이트만 있다면?
소켓 버퍼에 10바이트가 찰때까지 대기하게 됩니다.
시스템이 read 함수에서 멈추는거죠.
반면에 논블로킹으로 read 함수를 호출 할 경우, 버퍼에 데이터가 적건, 많건, 없건 일단 read 함수는 결과값을 리턴하게됩니다.
즉 논블로킹일 경우 read 함수에서 멈추는 일은 없죠.
여기에 select 의 개념을 더한다면
블로킹 read 의 경우
1. socket 버퍼에 1 바이트 데이터가 옴
2. select 에서 감지 -> read 함수 호출
3. read 함수에서 10바이트를 읽으려 함
4. 9바이트가 모자라기 때문에 read 함수에서 9바이트가 socket 버퍼에 찰 때까지 기다림
논 블로킹 read 의 경우
1. socket 버퍼에 1 바이트 데이터가 옴
2. select 에서 감지 -> read 함수 호출
3. read 함수에서 10바이트를 읽으려 함
4. 9바이트가 모자라지만 read 함수에서 대기를 안하고 적절한 리턴값을 주고 계속 진행
다만 논블로킹을 쓰게 될 경우, read 함수에서 리턴된 값은 어떤식으로 리턴될지 보장 못하기에, 예외처리는 프로그래머의 몫이됩니다.
아! 감사합니다!
바보같이 read() 함수의 동작을 잘못 파악하고 있었네요.
감사합니다 :-) 확실하게 이해했습니다.
>>수정
가 아니라...=.= 집에 오는 길에 생각해봤는데
read() 함수는 블로킹/논블로킹 상관없이 읽을 게 1바이트라도 있으면 1바이트만 읽고, 읽은
만큼을 리턴하지 않나요? 그래서 tcp 의 경우 항상 read 함수의 리턴값으로 얼마만큼을 읽었는지
확인해가며 진행해야 하는 걸로 알고 있었는데.....
10바이트를 읽으려고 시도 할때 소켓버퍼에 1바이트만 있으면 나머지 9바이트를 기다리나요?
recv() 함수의 MSG_WAITALL 플래그를 준 경우가 아니라도요?
커널 소스 net/ipv4/tcp.c 의
커널 소스
net/ipv4/tcp.c 의 tcp_recvmsg() 함수를 참조하시면 recv() 작동방식을 알 수 있습니다.
요약하면,
MSG_WAITALL 가 아니고 특별히 지정한 recv water mark 가 없는 이상 1입니다.
1 바이트만 수신을 해도 리턴한다는 말입니다. (수신했을 당시 백로그 큐에 더 이상 세그먼트가 없으면..)
즉, recv 는 한번 요청에 많은 바이트를 수신하려고 노력하지만 여의치 않으면 그냥 리턴 해버립니다.
즉 harion01 님이 말씀하신 블록 모델일 때 recv(10) 요청한다고 무조건 10바이트 수신 때 까지 기다린다는 건 틀린 내용입니다.
그런 방식으로 작동하게 하려면 MSG_WAITALL 이나 SO_RCVLOWAT 소켓 옵션을 수정해야 합니다.
read 함수와 블로킹에 관해...
막연히 socket 옵션을 non-block 으로 설정 안한상태에서 read 함수를 쓸 경우 블록된다고 생각하고 있다가, 익명님의 답변을 보고 좀더 검색해봤습니다.
그결과, non-block 으로 설정 안한상태에서 read 함수를 쓸 경우 socket 옵션에 따라 블록될 수도, 안될 수도 있다는걸 알게됬습니다.
검색한 내용 링크입니다.
http://kldp.org/node/26136 - read / recv(msg_waitall) 비교
http://kldp.org/node/57755 water mark 관련
http://211.221.225.175/~comsys/NP_PDF/chapter5.pdf 소켓 옵션 pdffile
http://www.nxmnpg.com/ko/2/setsockopt get/setsockopt 메뉴얼
테스트를 위해 제작해본 소스입니다.
*************************************
int main(){
struct sockaddr_in addr, addr_cla;
int fd, fd_cla, select_ret, get_ret;
int ret_buf, opt_len, change_size;
int ret = 0;
char read_buf[100];
fd_set read_fds;
fd = socket(AF_INET, SOCK_STREAM, 0);
opt_len = sizeof(int);
get_ret = getsockopt(fd, SOL_SOCKET, SO_RCVLOWAT, &ret_buf, &opt_len);
printf("get_ret (%d) ret_buf (%d)\n", get_ret, ret_buf);
change_size = 5;
get_ret = setsockopt(fd, SOL_SOCKET, SO_RCVLOWAT, &change_size, sizeof(int));
printf("SO_RCVLOWAT size changed get_ret (%d)\n", get_ret);
get_ret = getsockopt(fd, SOL_SOCKET, SO_RCVLOWAT, &ret_buf, &opt_len);
printf("get_ret (%d) ret_buf(%d)\n", get_ret, ret_buf);
memset((void *)&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons((u_short)9090);
ret = bind(fd , (struct sockaddr *)&addr, sizeof(addr)) ;
printf("bind ret (%d) \n", ret);
if(ret < 0 )
return 0;
ret = listen(fd, 5);
printf("listen ret (%d) \n", ret);
while(1){
fd_cla = accept(fd, NULL, NULL);
printf("fd_cla (%d) \n", fd_cla);
memset(read_buf, 0x00, sizeof(read_buf));
ret = read(fd_cla, read_buf, 100);
printf("errno (%d) errmsg (%s) \n", errno, strerror(errno));
printf("read buf (%s) ret (%d)\n", read_buf, ret);
}
}
********************************************************
테스트는 다음과 같았습니다.
1. 테스트용 클라이언트로 3바이트(5바이트 이하) 데이터를 전송한 경우.
-> read 함수에서 블록되고, 한번 더 데이터를 보내서 버퍼에 5바이트 이상 차게 될 경우 read 함수는 블로킹에서 풀린다.
2. 테스트용 클라이언트로 6바이트(5바이트 이상) 데이터를 전송한 경우.
-> read 함수에서 블록되지 않고, 수신한 6바이트 데이터를 출력.
즉, read 함수에서의 block 은 read 함수 인자값이 아니라 생성된 socket 의 SO_RCVLOWAT 옵션에 의존됩니다.
잘 모르는상태에서 답변을 적어서 헷갈리게해서 죄송합니다.
댓글 달기