select()를 사용한 파일 전송중 네트웍 케이블을 뽑으면 어디서 block이 될까요?

superkkt의 이미지

대충 이런 코드로 파일 전송을 하고있습니다.

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의 사용법에서 잘못 이해하고있는 부분이 있나요?

wariua의 이미지

select()는 송신 버퍼의 남은 용량이 얼마나 될 때 쓰기가 가능하다고 알려줄까요? 달리 말하자면, select()에서 쓰기가 가능하다고 알려왔을 때 송신 버퍼에 최소한 얼마만큼의 공간에 있다고 보장할 수 있을까요?

말씀하신 대로 write()에서 블럭이 되려면 select()는 송신 버퍼에 10바이트의 여유 공간이 있어도 쓰기가 가능하다고 알려주어야 합니다. 하지만 실제로는 (아마도 성능 문제로) 공간이 꽤 남아있어야 쓰기가 가능하다고 알려주는 듯 합니다. 그 문턱값이 8192보다 크다면 적어주신 코드가 실행될 때 write()에서 블럭이 되는 경우는 없겠지요.

간단하게 테스트를 해보니 별다른 튜닝을 하지 않은 제 리눅스 박스에서는 대략 48 KB 정도 여유 공간이 있을 때 쓰기가 가능하다고 알려줍니다. 읽고 쓸 때 사용하는 버퍼(buf)의 크기를 많이 크게 해서 테스트 해보시면 write()에서 블럭되는 걸 관찰하실 수 있습니다. 그런데 48 KB라는 값의 근거는 찾을 수가 없네요. 생각해 보면 동적으로 변할 수 있는 값인 것도 같구요. 이 값(select()에서 쓰기 소켓이 쓰기 가능하다고 판단하는 남은 버퍼 공간의 크기)을 명시적으로 지정해 주는 방법을 아는 분 안 계신가요?

----

그런데 (바로 위의 내용은 무시하고서) 송신 버퍼에 10바이트가 남은 상태에서 20바이트를 write()하면 10이 반환되는 건가요? 20바이트 모두를 기록할 공간이 생기거나 접속이 끊겨서 인터럽트 될 때까지 블럭되지 않을까요? 다음은 send(p) man페이지의 일부입니다.

man send(p) wrote:
If space is not available at the sending socket to hold the message to be transmitted, and the socket file descriptor does not have O_NONBLOCK set, send() shall block until space is available.

$PWD `date`

superkkt의 이미지

wariua wrote:
그런데 (바로 위의 내용은 무시하고서) 송신 버퍼에 10바이트가 남은 상태에서 20바이트를 write()하면 10이 반환되는 건가요? 20바이트 모두를 기록할 공간이 생기거나 접속이 끊겨서 인터럽트 될 때까지 블럭되지 않을까요? 다음은 send(p) man페이지의 일부입니다.

처음에 바로 블럭되겠군요.. 신호를 받아서 인터럽트 되는거랑 햇갈렸습니다.

그럼 문제의 관건이 select()가 얼만큼의 공간이 남았을때 쓰기 가능으로 리턴하냐인데요.. 메뉴얼에서는 이렇게 적혀있네요.

Quote:
A descriptor shall be considered ready for writing when a call to an output function with O_NONBLOCK clear would not block, whether or not the function would transfer data successfully.

If a descriptor refers to a socket, the implied output function is the sendmsg() function supplying an amount of normal data equal to the current value of the SO_SNDLOWAT option for the socket. If a non-blocking call to the connect() function has been made for a socket, and the connection attempt has either succeeded or failed leaving a pending error, the socket shall be marked as writable.

SO_SNDLOWAT 크기만큼 공간이 있어야 쓰기 가능이라는 얘기같은데 영어가 딸려서 맞는지 모르겠네요..

======================
BLOG : http://superkkt.com

wariua의 이미지

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() 함수에서 이런 코드를 보았습니다.

if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk))
    mask |= POLLOUT | POLLWRNORM;

그리고 include/net/sock.h에 있는 두 함수의 정의 및 관련 필드의 의미는 다음과 같습니다.
/**
  * struct sock - network layer representation of sockets
  * ...
  * @sk_wmem_queued: persistent queue size
  * ...
  * @sk_sndbuf: size of send buffer in bytes
  * ...
 */
...
static inline int sk_stream_min_wspace(struct sock *sk)
{
    return sk->sk_wmem_queued / 2;
}

static inline int sk_stream_wspace(struct sock *sk)
{
    return sk->sk_sndbuf - sk->wmem_queued;
}

sk_wmem_queued는 "송신버퍼에 지금까지 채워진 데이터의 양" 정도의 의미로 쓰이는 것 같습니다. 그러면 tcp_poll()의 if문의 의미는 대략 "송신 버퍼 크기의 1/3 이상이 비어있을 때"가 됩니다. 제가 48 KB 결과를 얻었을 때 getsockopt(SO_SNDBUF)의 결과가 128 KB였으니 대략 코드의 내용과 일치하는 것 같습니다.

$PWD `date`

ssehoony의 이미지

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바이트라도 수신되면 읽기가 가능 함을 알려주는 것이지요.
이건 대부분 직접적으로 수정하는게 불가능합니다.
아마 이 값을 수정할려면 커널 소스를 수정해야만 하지 않을까 하네요.

wariua의 이미지

socket(7)의 man페이지에서는...

socket(7) wrote:
SO_RCVLOWAT and SO_SNDLOWAT

Specify the minimum number of bytes in the buffer until the socket layer will pass the data to the protocol (SO_SNDLOWAT) or the user on receiving (SO_RCVLOWAT). These two values are not changeable in Linux and their argument size is always fixed to 1 byte. getsockopt is able to read them; setsockopt will always return ENOPROTOOPT.


라고 돼 있습니다만, (2.4.? 및 이후 버전의 커널에서) SO_RCVLOWAT는 기본값이 1이고 변경이 가능합니다. 하지만 SO_SNDLOWAT는 1로 고정되어 있고 변경하려고 하면 ENOPROTOOPT 오류를 반환합니다.

커널 소스 내의 각 옵션의 처리 루틴은 다음과 같습니다.

[/]

$PWD `date`

ssehoony의 이미지

Quote:
SO_RCVLOWAT는 기본값이 1이고 변경이 가능합니다. 하지만 SO_SNDLOWAT는 1로 고정되어 있고 변경하려고 하면 ENOPROTOOPT 오류를 반환합니다.

네 그렇군요.
제가 참고한 것은 솔라리스였는데, 리눅스랑은 구현이 달랐군요.

리눅스의 소켓 개발자는 SNDLOWAT 를 임의의 특정 사이즈로 고정하는 것 보다는 send 버퍼의 사이즈에 따라 알맞게 변하는게 더 효율적이라고 생각했나 보군요.

if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk)) 
    mask |= POLLOUT | POLLWRNORM; 

근데 freesize >= sndbuf / 3 의 수식을 저렇게 어렵게 계산하다니.. 유별난 개발자군요 ㅡ.ㅡ;

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.