select()를 사용한 파일 전송중 네트웍 케이블을 뽑으면 어디서 block이 될까요?
글쓴이: superkkt / 작성시간: 일, 2006/02/26 - 1:27오후
대충 이런 코드로 파일 전송을 하고있습니다.
while() { select(); if(읽기가능) { ... } if(쓰기가능) { read(fd, buf, 8192); writen(connfd, buf, 8192); } }
그리고 아래는 writen의 코드입니다.
int writen(int connfd, char *buf, size_t len) { int nwrite, twrite = 0, olen = len; char *ptr = NULL; ptr = buf; while(len > 0) { again: if((nwrite = write(connfd, ptr, len)) < 0) { if(errno == EINTR) goto again; else return -1; } len -= nwrite; ptr += nwrite; twrite += nwrite; } return (twrite == olen) ? twrite : -1; }
메뉴얼에서 write()는 fd가 NONBLOCK이 아닌 상태에서 소켓 SND_BUF가 사용 불가능하면 블럭된다고 되어있습니다.
만약 SND_BUF가 10바이트 남은 상태에서, select는 쓰기 가능으로 리턴할겁니다. 그리고 writen()이 20바이트를 쓰려고 시도하면 첫번째 write에서 10바이트를 쓰고 리턴하면 writen이 나머지 10바이트를 쓰기위해 다시 write를 호출합니다. 그럼 SND_BUF는 꽉 차있고 두번째 write에서 블럭이 됩니다.
여기까지가 제가 생각한 시나리오인데요.. 실제로 파일 전송중에 네트웍 케이블을 뽑으면 select()에서 블럭이 되어있습니다. wrapping 함수인 writen을 쓰지않고 그냥 write를 사용했다면 select에서 블럭되는게 맞겠지만 writen을 사용하게되면 위와 같은 상황에서 두번째 write를 할때 블럭이 되야하는데..
왜 select에서 블럭이 되는지 이해가 안되네요.. 제가 select와 write의 사용법에서 잘못 이해하고있는 부분이 있나요?
Forums:
select()는 송신 버퍼의 남은 용량이 얼마나 될 때 쓰기가 가능하다
select()는 송신 버퍼의 남은 용량이 얼마나 될 때 쓰기가 가능하다고 알려줄까요? 달리 말하자면, select()에서 쓰기가 가능하다고 알려왔을 때 송신 버퍼에 최소한 얼마만큼의 공간에 있다고 보장할 수 있을까요?
말씀하신 대로 write()에서 블럭이 되려면 select()는 송신 버퍼에 10바이트의 여유 공간이 있어도 쓰기가 가능하다고 알려주어야 합니다. 하지만 실제로는 (아마도 성능 문제로) 공간이 꽤 남아있어야 쓰기가 가능하다고 알려주는 듯 합니다. 그 문턱값이 8192보다 크다면 적어주신 코드가 실행될 때 write()에서 블럭이 되는 경우는 없겠지요.
간단하게 테스트를 해보니 별다른 튜닝을 하지 않은 제 리눅스 박스에서는 대략 48 KB 정도 여유 공간이 있을 때 쓰기가 가능하다고 알려줍니다. 읽고 쓸 때 사용하는 버퍼(buf)의 크기를 많이 크게 해서 테스트 해보시면 write()에서 블럭되는 걸 관찰하실 수 있습니다. 그런데 48 KB라는 값의 근거는 찾을 수가 없네요. 생각해 보면 동적으로 변할 수 있는 값인 것도 같구요. 이 값(select()에서 쓰기 소켓이 쓰기 가능하다고 판단하는 남은 버퍼 공간의 크기)을 명시적으로 지정해 주는 방법을 아는 분 안 계신가요?
----
그런데 (바로 위의 내용은 무시하고서) 송신 버퍼에 10바이트가 남은 상태에서 20바이트를 write()하면 10이 반환되는 건가요? 20바이트 모두를 기록할 공간이 생기거나 접속이 끊겨서 인터럽트 될 때까지 블럭되지 않을까요? 다음은 send(p) man페이지의 일부입니다.
$PWD `date`
[quote="wariua"]그런데 (바로 위의 내용은 무시하고서) 송신
처음에 바로 블럭되겠군요.. 신호를 받아서 인터럽트 되는거랑 햇갈렸습니다.
그럼 문제의 관건이 select()가 얼만큼의 공간이 남았을때 쓰기 가능으로 리턴하냐인데요.. 메뉴얼에서는 이렇게 적혀있네요.
SO_SNDLOWAT 크기만큼 공간이 있어야 쓰기 가능이라는 얘기같은데 영어가 딸려서 맞는지 모르겠네요..
======================
BLOG : http://superkkt.com
socket(7), getsockopt(3p) 등의 설명을 보자면 SO
socket(7), getsockopt(3p) 등의 설명을 보자면 SO_SNDLOWAT(Send low water mark)는 "어플리케이션이 쓰기 버퍼이 최소한 그 만큼의 데이터는 채워줘야 커널의 프로토콜 처리 루틴에서 실제로 전송 동작을 수행한다"라는 의미인 것 같습니다. 현재 리눅스 버전(2.6.15 기준)에서는 1로 고정되어 있으며 변경이 불가능합니다...
라고 적으려고 했는데 인용해 주신 내용(select(3p))과 이전의 어느 포스팅을 보니 SO_SNDLOWAT의 의미가 헛갈리네요...
다시 본래의 문제로 돌아와서, 커널 소스를 좀 뒤져서 select() 호출을 따라가다 보니 net/ipv4/tcp.c의 tcp_poll() 함수에서 이런 코드를 보았습니다.
그리고 include/net/sock.h에 있는 두 함수의 정의 및 관련 필드의 의미는 다음과 같습니다.
sk_wmem_queued는 "송신버퍼에 지금까지 채워진 데이터의 양" 정도의 의미로 쓰이는 것 같습니다. 그러면 tcp_poll()의 if문의 의미는 대략 "송신 버퍼 크기의 1/3 이상이 비어있을 때"가 됩니다. 제가 48 KB 결과를 얻었을 때 getsockopt(SO_SNDBUF)의 결과가 128 KB였으니 대략 코드의 내용과 일치하는 것 같습니다.
$PWD `date`
[b]transmit low watermark[/b] SO_SNDLO
transmit low watermark
SO_SNDLOWAT 는 select 나 poll 이 socket에 쓰기가 가능하다고 알려주는 최소 free space 사이즈 입니다.
1바이트라도 공간이 남았을때 마다 쓰기 가능하다고 알려주면, write 함수가 1바이트를 위해 call 해지는 비효율적 상황을 막고자 함이지요.
SO_SNDLOWAT 이건 소켓별로 값을 셋팅할 수가 있습니다.
위의 글을 보니 리눅스의 기본값은 송신버퍼의 1/3 인가보군요.
receive low watermark
리눅스에서 1바이트로 고정되어 있다고 하는 것은 혹시 SO_RCVLOWAT 이거 아닐까요?
이건 SO_SNDLOWAT 와 반대로 select 나 poll 이 socket 에서 읽기가 가능하다고 알려주는 최소버퍼 사이즈 인데 보통 이건 1 이지요.
즉, 1바이트라도 수신되면 읽기가 가능 함을 알려주는 것이지요.
이건 대부분 직접적으로 수정하는게 불가능합니다.
아마 이 값을 수정할려면 커널 소스를 수정해야만 하지 않을까 하네요.
socket(7)의 man페이지에서는...[quote="socket(
socket(7)의 man페이지에서는...
라고 돼 있습니다만, (2.4.? 및 이후 버전의 커널에서) SO_RCVLOWAT는 기본값이 1이고 변경이 가능합니다. 하지만 SO_SNDLOWAT는 1로 고정되어 있고 변경하려고 하면 ENOPROTOOPT 오류를 반환합니다.
커널 소스 내의 각 옵션의 처리 루틴은 다음과 같습니다.
- setsockopt(SO_RCVLOWAT)
- setsockopt(SO_SNDLOWAT)
- getsockopt(SO_RCVLOWAT)
- getsockopt(SO_SNDLOWAT)
[/]$PWD `date`
[quote]SO_RCVLOWAT는 기본값이 1이고 변경이 가능합니다.
네 그렇군요.
제가 참고한 것은 솔라리스였는데, 리눅스랑은 구현이 달랐군요.
리눅스의 소켓 개발자는 SNDLOWAT 를 임의의 특정 사이즈로 고정하는 것 보다는 send 버퍼의 사이즈에 따라 알맞게 변하는게 더 효율적이라고 생각했나 보군요.
근데 freesize >= sndbuf / 3 의 수식을 저렇게 어렵게 계산하다니.. 유별난 개발자군요 ㅡ.ㅡ;
댓글 달기