본문 바로가기

UNIX_LINUX_C_C++

Unix Socket 의미와 개념

출처 : http://blog.naver.com/jaeheun830/120008576570

- 목 차 -
1. 일반 정보와 개념
1.1. 이 FAQ에 대하여
1.2. 누구를 위한 FAQ 인가?
1.3. 소켓은 무엇인가?
1.4. 소켓은 어떻게 작동하는가?
1.5. 책에 있는 소스코드를 구할 수 있는데가 있는가?
1.6. 또 다른 정보들은 어디에서 구할 수 있는가?

2. 클라이언트와 서버 양쪽에 관한 질문 (TCP/SOCK_STREAM)
2.1. 언제 상대방쪽 소켓이 끊겼는지 알 수 있는 방법이 있는가?
2.2. bind()의 두 번째 파라메터는 무엇인가?
2.3. 주어진 서비스의 port번호를 얻는 방법은?
2.4. bind() 가 실패 했을 때 소켓 디스크립터를 가지고 할 수 있는 일은?
2.5. 정확하게 소켓을 닫는 방법은 무엇인가?
2.6. 언제 shutdown()을 써야 하는가?
2.7. TIME_WAIT 상태에 대해 설명해 달라.
2.8. 상대방쪽이 죽었는지 알아 내는데 왜 이렇게 오래 걸리나?
2.9. select(), non-blocking I/O 그리고 SIGIO의 잇점들은 무엇인가?
2.10. read()가 EPROTO 를 리턴했다. 무엇인가?
2.11. 소켓 버퍼에 있는 내용을 강제로 전달 할 수 있는 방법은 없나?
2.12. 소켓 프로그래밍을 위한 라이브러리가 있는가?
2.13. select는 데이터가 있다고 하는데 읽으면 0 을 리턴한다?
2.14. select() 와 poll()의 차이점은 무엇인가?
2.15. 어떻게 [this]를 소켓을 통해 보낼 수 있는가?
2.16. TCP_NODELAY는 어떻게 사용하는가?
2.17. Nagle 알고리즘은 어떤일을 하는 것인가?
2.18. read() 와 recv()의 차이점은?
2.19. send()/write()가 SIGPIPE 신호를 생성할 수 있던데. 이 신호를 무시하거나
EPIPE오류에 대한 검사를 하지 않고 다른 처리를 했을때의 어떤 잇점이 있는가?
시그널을 잡는 잡는 함수 에 넘겨지는 어떤 유용한 파라미터가 있는가?
2.20. chroot()후에 socket()이 실패했다. 왜 그런가?
2.21. 소켓 호출로 부터 전달되는 EINTR을 유지해야 하는 이유는 무엇인가?
2.22. 언제 나의 프로그램이 SIGPIPE 신호를 받게 되는가?
2.23. 소켓 예외는 있는가? out-of-band데이타란 무엇인가?
2.24. 어떻게 완전한 내 시스템의 호스트이름인 (FQDN)을 얻을 수 있는가?

3. 클라이언트 어플리케이션 만들기 (TCP/SOCK_STREAM)
3.1. 문자열을 인터넷 주소로 바꾸는 방법은?
3.2. firewall과 proxy서버를 통해서 클라이언트가 작동하도록 하려면 어떻게 하는가?
3.3. 어떻게 서버가 accept()하지않았는데도 connect()가 성공할 수 있는가?
3.4. 하나의 서버 이상을 사용하고 있을 때 왜 서버 주소를 가끔 잃어 버리는가?
3.5. connect()를 위한 타임아웃시간을 설정할 수 있는가?
3.6. 클라이언트 프로그램에서 포트번호를 갖기 위해 bind()를 써야 하는가 아니면
시스템이 connect()호출에서 선택하도록 해야 하는가?
3.7. 왜 서버가 동작하고 있지 않을 때 "connection refused" 라는 메시지가 나오
는가?
3.8. 소켓을 통해 들어오는 정보의 양을 모를 경우 어떻게 해야 하는가?
동적 버퍼를 사용할 수 있는 방법이 있는가?

4. 서버 만들기 (TCP/SOCK_STREAM)
4.1. bind()할 때 "address already in use" 메시지가 나왔는데 무슨의미인가?
4.2. 왜 소켓을 닫을 수 없는가?
4.3. 내 서버를 데몬으로 만들려면?
4.4. 한번에 여러 포트로부터 listen할 수 있는가?
4.5. SO_REUSEADDR 가 정확히 뭐하는 것인가?
4.6. SO_LINGER 가 정확히 뭐하는 것인가?
4.7. SO_KEEPALIVE 가 정확히 뭐하는 것인가?
4.8. 포트 번호를 1024 이하로 bind()할 수 있는 방법은?
4.9. 클라이언트의 주소와 호스트 이름을 알아내는 방법은?
4.10.나의 서버를 위한 포트번호를 어떻게 선택 해야 하는가?
4.11.SO_REUSEADDR 와 SO_REUSEPORT 의 차이는 무엇인가?
4.12.multi-homed server를 만드는 방법은?
4.13.한 번에 한 글짜식 읽는 방법은?
4.14.서버에서 exec()를 실행하고 그것에 소켓 I/O를 attach시키려고 하고 있다.
그런데 그것을 통하여 모든 그 데이터를 얻지 못하고 있다. 왜 그런가?

5. UDP/SOCK_DGRAM 어플리케이션 작성
5.1. TCP대신에 UDP는 언제 사용하는가?
5.2. "connected" 소켓과 "unconnected"소켓의 차이는 무엇인가?
5.3. connect() 호출이 소켓의 수신에 영향을 주는가?
5.4. "connected" UPD 소켓으로부터 어떻게 하면 ICMP error를 읽을 수 있는가?
5.5. 어떻게 UDP메시지가 수신 되었다는 것을 확신 할 수 있는가?
5.6. UDP 메시지가 순서대로 도착했음을 확신할 수 있는가?
5.7. 얼마나 자주 un-ack된 메시지에대해서 재전송 해야 하는가?
5.8. How come only the first part of my datagram is getting through?

6. 고급 소켓 프로그래밍
6.1. 현재의 소켓 프로그램을 비블록킹 모드로 바꾸는 방법은?
6.2. connect()의 타임아웃을 설정하는 방법은?

1. 일반 정보와 개념

1.1. 이 FAQ에 관하여

이 FAQ는 Andrew Gierth (andrew@erlenstar.demon.co.uk)의 도움과 더불어 vic Metcalfe( vic@acm.org)에 의해 유지 되고 있다. 난 지금 나의 많은 실수와 세부적인 것들을 채우기위해 마법의 지팡이에 의지하고 있는 중이다. 여기에 있는 코드 예제는 알아보기 쉽고 따라하기 쉽도록 만들어 졌다. 얼마냐 효율적으로 이것들이 쓰이는가는 독자에게 달려 있다. 나는 이 FAQ를 만드는 것을 comp.unix.programmer 를 잠깐 읽어 보고 FAQ가 필요하다고 생각되서 시작했다.
이 FAQ는 다음에서도 구할 수 있다.

Usenet: (매월 21일에 포스팅 된다.)
news.answers, comp.answers, comp.unix.answers,
comp.unix.programmer

FTP:
ftp://rtfm.mit.edu/pub/usenet/news.answers/unix-faq/socket
WWW:
http://www.ibrado.com/sock-faq
http://kipper.york.ac.uk/~ vic/sock-faq

만약 어떤 잘못된 점 등이 있으면 메일을 보내 주기 바란다. 여러분들이 내게 보내주는 질
문 또한 여기에 추가 할 것이다. 그것에 대한 대답을 내가 못할지라도 다른 누군가 대답해
줄거라는 기대를 가지고 여기에 추가 할 것이다. 매 시간 내가 좀 바쁘기 때문에 가끔식
대답이 좀 느릴 수도 있으니까, 참고 기다려 보라. 몇주가 지나도 대답이 없으면 다른 메일을
또 보내라 왜냐면 가끔씩 내가 실수로 잊는 경우도 있기 때문이다.

1.2. 이 FAQ는 누구를 위한 것인가?

이 FAQ는 유닉스 환경에서의 C 프로그래머를 위한 것이다. WinSock 프로그래머나 PERL,
Java나 기타에서 작업하는 프로그래머를 위해 의도된 FAQ가 아니다. 그렇다고 내가
Windows나 Perl 등을 거부하는 것은 아니고, 이 첫 번째 초안에 대해 범위를 제한 해야만 했다.
지금부터 나는 C 언어를 기준으로 이 FAQ 를 다룰 것이다. 이 FAQ의 버전은 오
직 AF_INET 계열의 소켓만을 다룰 것이다. 왜냐하면 이 AF_INET계열이 가장 일반적으로 쓰이고 있기 때문이다.
1.3. 소켓(Socket)은 무엇인가?

소켓(Socket)은 단지 과학소설에서 말하는 "벌레 구멍"이다. 어떤 물건이 한쪽 끝으로
들어가면 그건 다른쪽 끝으로 나오게 된다. 다른 종류의 소켓은 다른 속성을 가지고 있다
소켓은 "connection-oriented" 또는 "connectionless"의 두가지 종류가 있다.
connectionless가 connection open없이 한 번에 하나의 메시지만 전송할 수 있는데
비해서 connection-oriented소켓은 데이터가 필요에 따라 앞뒤로 흐를 수 있다. 가장
많이 쓰이는 것에는 인터넷 연결을 위한 AF_INET과 Unix IPC(interprocess
communication)를 위한 AF_UNIX의 두가지가 있다.
이미 말했듯이, 이 FAQ는 AF_INET 소켓을 다룬다.

1.4. 소켓(Socket)은 어떻게 동작 하는가?

실제 동작은 특정 유닉스 벤더에 의존하긴 하지만 프로그래머의 입장에서 보면
connection-oriented 소켓은 마치 파일 또는 PIPE 처럼 동작한다. 여러분이 여러분의 파일 디스크립터를
소유하게 되었을때에, 가장 주의해야할 점은 read()또는 write()호출이 실제로 요청한 바이트 보다 적은 바이트를 읽거나 쓰게 될 수 있다는 점이다. 만약 이
렇게 되면 나머지 데이터를 얻기 위해 두 번째 호출을 해야 한다. 이것에 대한 예제 소스코드 위치가 이 FAQ에 적혀 있다.

1.5. 시중의 네트웍프로그래밍관련 책의 소스코드를 어디서 얻을 수 있는가?

여기 내가 알고 있는 네트웍 프로그래밍 책에 대한 소스코드의 위치가 있다. 몇개 안되기
때문에 여러분들이 다른 소스코드 위치를 알려 주면 추가 하겠다.

Title: Unix Network Programming
Author: W. Richard Stevens (rstevens@noao.edu)
Publisher: Prentice Hall, Inc.
ISBN: 0-13-949876-1
URL: http://www.noao.edu/~rstevens

Title: Power Programming with RPC
Author: John Bloomer
Publisher: O'Reilly & Associates, Inc.
ISBN: 0-937175-77-3
URL: ftp://ftp.uu.net/published/oreilly/nutshell/rpc/rpc.tar.Z
Recommended by: Lokmanm Merican (lokmanm#pop4.jaring.my@199.1.1.88)
Title: UNIX PROGRAM DEVELOPMENT for IBM PC'S Including OSF/Motif
Author: Thomas Yager
Publisher: Addison Wesley, 1991
ISBN: 0-201-57727-5

1.6. 또 다른 정보를 얻으려면 어디를 가봐야 하나?
나는 내가 알고 있는 것에 대한 복사본을 내 홈페이지에 보관하고 있다. 나는 대부분의 그것들을 어디서 얻었는지 기억이 없다. 언제 한 번 그 정보들의 소스를 찾아보고 ftp정보를 여기에 추가 하겠다. 지금까지의 것들을 여러분들은 여기에서 얻을 수 있다.

http://www.ibrado.com/sock-faq.

George Neville-Neil(gnn@wrs.com)에 의해 유지되는 TCP/IP FAQ가 다음에 있다.
http://www.visi.com/~khayes/tcpipfaq.html

2. 클라이언트와 서버 양쪽에 관한 질문 (TCP/SOCK_STREAM)

2.1. 언제 상대방쪽 소켓이 끊겼는지 알 수 있는 방법이 있는가?

AFAIK:
만약 SO_LINGER상태로 엉망이 된 peer가 close()를 호출 하거나 exit하면 read()하기 위한 호출이 0 을 리턴한다. 이 경우에 write() 호출을 하기 위한 어떤 일이 일어났는지에 대해서는 불확실 하다. 다음 호출이 아니라 계속적인 호출에서 EPIPE가 생길 것이다.만약 peer가 리부팅 하거나 또는 l_onoff를 1로 그리고 l_linger를 0으로 설정하고 close하면 우리는 read()로 부터 ECONNRES ET(결국은)를 얻게 되거나 write()로 부터 EPIPE를 얻게 될 것이다.

나는 또한 다음을 지적하고 싶다.
write()가 EPIPE를 리턴했을 때 그것은 또한 SIGPIPE로 함께 되돌린다는 것이다. 여러분 은 이 시그널을 무시하거나 핸들하는 것을 구현하지 않는한 이 EPIPE 오류를 결코 볼 수 없다.

만약 peer가 도달할 수 없는(unreachable) 상태로 지속된다면, 또다른 몇몇의 오류를 얻게될 것이다. 나는 write()가 합법적으로 0 을 리턴할 수 있다고 생각지 않는다.
read()는 peer로부터 FIN을 받았을 때 그리고 다음의 모든 호출에서 0 을 리턴해야 한다.
그러면 read()가 0 을 리턴하게 될 것이다.

예제로, TCP link로부터 파일을 다운 받는다고 가정하자;
여러분은 read()로 부터 리턴값을 다음과 같이 핸들 할 것이다.

rc = read(sock,buf,sizeof(buf));
if (rc > 0)
{
write(file,buf,rc);
/* error checking on file omitted */
}
else if (rc == 0)
{
close(file);
close(sock);
/* file received successfully */
}
else /* rc < 0 */
{
/* close file and delete it, since data is not complete
report error, or whatever */
}

2.2. bind()의 두 번째 파라미터는 무엇인가?

매뉴얼 페이지는 "struct sockaddr *my_addr"이라고 되어 있다. 이 sockaddr 구조체
는 원하는 구조체에 대한 홀더 일 뿐이다. 물론 가지고 있는 소켓의 종류에 따라 다른 구
조체를 써야 한다. AF_INET소켓에서는 sockaddr_in 구조체를 써야 한다 .

구성요소에는 세가지가 있는데

sin_family
이건 AF_INET로 설정하라.

sin_port
byte-order된 16 bit 포트 번호

sin_addr
호스트 ip 번호. 이것은 in_addr구조체인데 u_long형태의 s_addr이라는 하나의 필드만 포함되어 있다.

2.3. 주어진 서비스에 대한 포트번호를 어떻게 얻을 수 있는가?

getservbyname()을 쓰면 된다. 이것은 servent구조체를 리턴할 것이다. 여러분은 포트
번호를 가지고 있는 올바른 바이트 순 서로된 s_port필드에 흥미 있을 것이다.(여러분은
htons()를 호출할 필요가 없다.) 예제를 보라.

/* 서비스 이름과 서비스 형태를 받고 포트 번호를 리턴한다. 만약 서비스 이름이 발견되지 않으면 그것은 10진 수로 다시 시도 한다. 리턴된 숫자는 네트웍을 위한 바이트 순서(byte ordered)화된 것이다. */

int atoport(char *ser vice, char *proto) {
int port;
long int lport;
struct servent *serv;
char *errpos;

/* 우선 /etc/ser vices로 부터 읽는다 */
serv = getservbyname(ser vice, proto);
if (serv != NULL)
port = serv->s_port;
else { /* 서비스에 없으면 숫자인가 */
lport = strtol(ser vice,&errpos,0);
if ( (errpos[0] != 0) || (lport < 1) || (lport > 5000) )
return -1; /* 잘못된 포트 번호이다 */
port = htons(lport);
}
return port;
}

2.4. bind()가 실패했다면 소켓 디스크립터로 뭘 해야 하는가?

exit하고 있다면, 모든 유닉스 시스템은 파일 디스크립터를 닫을 것이다. 만약 exit하고 있는 것은 아니라면 일반 close()로 닫아야 한다.

2.5. 정확하게 소켓을 닫는 방법은?

이 질문은 보통 close()함수를 쓰려고 하는 사람들이 하는데, 그들은 그렇게 해야 한다고
보고 있기 때문이다. 보통 그렇게 하고 netstat등으로 보면 소켓이 여전히 활동상태
(active)라는 걸 알게 될 것이다. 그러나 close()를 쓰는건 맞다. TIME_WAIT상 태 값이 왜 중요한지 그 이유가 여기에 있다. 2.7의 "TIME_WAIT 상태에 대해 설명해 달라"를 참조하라.

2.6. 언제 shutdown()를 써야 하는가?

Michael Hunter (mphunter@qnx.com)로부터:

shutdown()은 여러분은 TCP를 사용하여 서버로 요청 하는일을 언제 끝냈는지 알아 내는데 유용하다. 전형적인 사용예는 서버 로 요청이 전달되고 나후에 shutdown()이 이루어 졌을 경우에 유용하다. 서버는 여러분의 호출을 EOF일때까지 받는다. 즉 이것 은 서버가 여러분 의 완전한 요청을 받았다는 것을 의미한다. 그리고 나서 소켓에서의 read는 블록된다. 서버는 요청을 처리하고 필요한 데이터를 여러분에게 보내고 소켓을 닫게 된다. 여러분이 필요한 모든 내용을 소켓에서 읽었다면 마지막에 EOF를 읽게 될 것이다. 이것은 TTCP(TCP for Transactions)가 보다 나은 트랜잭션 처리를 위한 도구 라는 것을 보여준다.

S.Degtyarev (deg@sunsr.inp.nsk.su)는 이것에 관한 상세한 메시지를 내게 보내 주었다. 그는 하나는 "reader"이고 다른 하나는 "write" 프로세스인 클라이언트 프로세스의 동기화를 도와주는 shutdown()에 대한 실제 예제까 들었다. 여기 그것의 일부를 여기 싣는다.

소켓은 데이터 전달과 클라이언트/서버 트랜잭션에 사용된다는 점에서 파이프와 매우 비슷
하다 그러나 파이프와 다른 점은 양 방향 성이라는 것이다. 소켓을 사용하는 프로그램은 자주 fork()하고 각 프로세스는 소켓 디스크립터를 상속받는다. 파이프에 기 본을 둔 프로그램은 데이터 상실이나 deadlock을 피하는 단방향성의 데이터 스트림으로 pipe line을 만들기 위해 사용되지 않는 모든 끝은 닫도록 엄격히 권고되고 있다. 그러나 소켓의 경우에는 한쪽은 쓰기만 하고 한쪽은 받기만 하는 것은 허용되지 않기 때문에 여러분들은 그 순서에 유념해야 할 필요가 있다.

일반적으로 close()와 shutdown()의 차이점은: close()는 프로세스에 대해 소켓 ID를 닫지만 만약 다른 프로세스가 소켓 ID를 공유하고 있다면 연결은 여전히 열려 있게 된다. 그 연결은 읽기,쓰기를 위해 유지 되며, 어떤때, 이것은 매우! 중요하다. shut down()은 그 소켓 ID를 공유하고 있는 모든 프로세스에 대해 연결을 끊어버린다. 커널 소켓 버퍼가 FULL 이면 읽기를 시도 하는 프로세스는 EOF를 발견하게 되고, 쓰기를 시도하는 프로세스는 SIGPIPE를 받게 된다. 게다가 shutdown()은 그 연결을 어떻 게 끊을지를 나타내는 두 번째 파라메터를 가지고 있는데, 0은 이후의 읽기를 못하게하고, 1은 쓰기를, 2는 양쪽 모두를 못하게 한다.

이 쉬운 예제는 단순한 클라이언트 프로세스를 자른 것이다. 연결을 설립한 후에 서버는
fork한다. 그리고 나서 child는 EOF 를 받거나 부모프로세스가 서버로부터 응답을 받을 때까지 키 입력을 서버에게 전송한다.

/*
* 예제 클라이언트 쪼가리..
* 변수선언과 에러 핸들링은 제외 됐음
*/
s=connect(...);

if( fork() ){ /* 자식은 그 stdin을 소켓으로 복사한다 */
while( gets(buffer) >0)
write(s,buf,strlen(buffer));

close(s);
exit(0);
}

else { /* 부모가 응답을 받는다 */
while( (l=read(s,buffer,sizeof(buffer)){
do_something(l,buffer);

/* 서버가 연결을 끊는다 */
/* ATTENTION: deadlock here */
wait(0); /* 자식이 나갈때까지 기다림 */
exit(0);
}

무엇을 기대할 수 있는가? 자식프로세스는 stdin으로부터 EOF를 받으면 소켓을 닫고 빠져
나간다. 서버는 EOF를 받으면 연결 을 끊고 빠져 나간다. 부모 프로세스는 EOF를 받으면
wai()을 호출하고 빠져 나간다. 이것 대신 우린 뭘 할 수 있을까? 비록 부 모 프로세스가
쓰기를 하지 않을지 모르지만 부모 프로세스에 있는 소켓 인스턴스는 읽기와 쓰기를 위해
여전히 열려 있다. 서버 는 EOF를 발견하지 못한다. 그리고 영원히 클라이언트로부터의 데
이터를 기다린다. 그리고 서버도 죽~. (여기서 deadlock이 발생 된다.)

여러분은 클라이언트 프로그램을 다음과 같이 바꿔야 한다.

if( fork() ) { /* 자식 */
while( gets(buffer) }
write(s,buffer,strlen(buffer));

shutdown(s,1); /* 쓰기를 위해 연결을 끊는다
서버는 EOF를 받게 된다. Note: 소켓으로부터의 읽기는 여전히 허용된다.
서버는 EOF를 받은 후에 몇 데이터를 보낼 것이다.
*/
exit(0);
}

나는 이 다듬어 지지 않은 예제가 여러분들이 가지고 있는 클라이언트/서버의 동기화의 문
제를 설명해 줄 수 있기를 바란다. 일반적으로 여러분들이 모든 프로세스의 특정 소켓 인스턴스에서 기억하고 있어야 할 것이 있는데, 만약 여러분이 하나의 프로세 스 안에서 연결을 끊기 위해 close()또는 shutdown()을 사용하길 바란다면, 그들 모두를 공유하고 있다가 한 번에 닫아라.

2.7. TIME_WAIT 상태에 대해 설명해 달라.

TCP는 모든 전송 데이터에 신뢰성을 가진다. 소켓을 닫을 때 서버는 TIME_WAIT상태가 되고 모든 데이터는 사라지게 된다는 것 을 *정말*정말*. 소켓이 닫혀지면 양쪽으로 전송되던 데이터는 더 이상의 전송은 되지 않게 된다. 문제는 두 개다. 첫째, 마지막 ack가 성공적으로 전달 되었는지 알 수 있는 방법이 없다는 것이다. 두 번째, 전송되었으면 처리 되었을 "헤메는 복사본 "이 네트웍에 남아 있게 된다는 것이다.

Andrew Gierth (andrew@erlenstar.demon.co.uk)는 다음의 유즈넷 포스팅에서의 close순서에 대한 설명을 도와줬다.

다음을 가정하자. 연결은 ESTABLISHED상태에 있고, 클라이언트는 순종한다. 클라이언트의 순서는 없고 Sc라고 표시하며 서버 는 Ss라고 한다. 파이프는 양쪽방향으로 비어있다.

클라이언트 서버
========= ======
ESTABLISHED ESTABLISHED
(client closes)
ESTABLISHED ESTABLISHED
<CTL=FIN+ACK><SEQ=Sc><ACK=Ss> ------->>
FIN_WAIT_1
<<-------- <CTL=ACK><SEQ=Ss><ACK=Sc+1>
FIN_WAIT_2 CLOSE_WAIT
<<-------- <CTL=FIN+ACK><SEQ=Ss><ACK=Sc+1> (server closes)
LAST_ACK
<CTL=ACK>,<SEQ=Sc+1><ACK=Ss+1> ------->>
TIME_WAIT CLOSED
(2*msl elapses...)
CLOSED
Note: 순서번호에서 +1 는 FIN이 데이터를 1바이트로 세기 때문이다.(위의 diagram은 작은 것이다. RFC 793의 13.)

이제 만약 이 패킷의 마지막것이 발생했을 때 어떤 일이 일어나는지 보자. 클라이언트는 연결을 마쳤다. 이제 더 이상 보낼 제어 데이터가 없고 앞으로도 없을 것이다. 그러나 서버는 클라이언트가 모든 데이터를 잘 받았는지 알 수 없다. 그 이유은 마지 막 ack 때문이다. 이제 서버는 클라이언트가 그 데이터를 갖고 있는지가 걱정거리다. 그러나 TCP에 문제가 있는건 아니다. 왜냐. . TCP는 신뢰성 있는 프로토콜이기 때문이다. 그리고 모든 데이터 전송이 제대로 됐으면 close되며, 데이터를 잃어 버리게 되면 연결은 중지(abort)된다.

그래서 만약 마지막 패킷이 날라가 버리면, 서버는 그것을 다시 전송하고(결국 이 패킷은
acknowledge되지 않은 세그먼트이다 .) 적당한 ACK 세그먼트가 되돌아 오길 기다린다. 만약 클라이언트가 바로 CLOSED되면 되돌아 올 수 있는 재전송은 데이터를 잃 어 버린 서버를 지정하게되는 RST가 된다.

명심해야 할 것은 서버의 FIN세그먼트는 추가적으로 데이터를 포함하고 있다는 사실이다.

포기: 이것은 RFC의 TCP관련된 것을 읽은 것에 대한 번역일 뿐이다. 그러나 이 것을 소스
코드를 이용해 테스트를 하고 확인 해보지는 않았다. 비록 나는 이 로직이 옳다는 것에 대
해 만족하지만.

vic의 추가적인 설명:

두 번째 논쟁은 "Unix Network Programming"의 저자인 Richard
Stevens(rstevens@noao.edu) 의 말이다.

Richard Stevens (rstevens@noao.edu):

만약 TIME_WAIT의 반복 상태가 단지 TCP의 양방향 close를 핸들링 하기 위한 것이라면 시간은 더 작게 될 것이다. 그리고 그 것은 MSL(packet lifetime)이 아니라 현재의
RTO(retransmission timeout)의 몇몇 함수로 될 수 있을 것이다.

TIME_WAIT에 대한 몇 개의 관점을 소개한다.
첫번째 FIN을 보내는 목적은 그것이 마지막 ACK을 보내는 목적이기 때문에 TIME_WAIT상태에 도달한다. 만약에 또다른 목적 의 FIN이 분실되거나 마지막 ACK이 분실되면, 접속에 관한 첫번째 FIN의 유지상태를 보낸 결과가 그것이 마지막 ACK을 재전송할 정보를 충분히 가지고 있음을 보증해 준다.(미치겠군)

TCP 시퀀스 번호는 2의 32승 바이트 정도의 수이다.
A.1500(호스트 A, 포트 1500)와 B.2000사이에 연결이 되었다고 가정하면 한 세그먼트 접속은 상실(lost)되고 재전송 된다. 그러나 세그먼트가 정말 상실된 건 아니고, 중간 라우터에 보류 중이며, 다시 네트웍 괘도에 올라가게 된다.(이것을 "헤메 는 복사본:wandering duplicate"라고 부른다.) 그러나 패킷이 상실되고 재전송되고 다시 보이게(reappearing)되는 사이에 접속은 닫혀지고 그래서 다른 연결이 그 같은 호스트의 같은 포트에서 만들어 진다. 이것을 또다른 "실현:incarnation "이라고 부른다. 그러나 새로운 incarnation을 위해 선택된 시퀀스 번호도 이전에 나타났던 헤메는 복사본의 시퀀스 번호와 중복 되게 된다. (이건 정말 가능한 일이다. 주어진 시퀀스 번호 선택 방법에 따라 TCP 에 대한 연결을 만들기 때문이다.) 여러 분이 헤메는 복사본을 새로운 연결의 incarnation에 보내려 한다면 이 방법은 피하라. 이 방법을 피하기 위해 재 설립될 접 속의 incarnation을 TIME_WAIT상태가 끝날 때 까지 허용하지 말라. 비록 TIME_WAIT상태가 "TIME_WAIT암살"이라고 불리우 는 이 두 번째 문제를 완벽히 해결해 주지는 못할 지라도. 자세한건 RFC 1337을 보라.

TIME_WAIT상태의 반복이 2*MSL 인 이유는 패킷이 네트웍을 헤매는 시간의 최대량이 MSL초가 된다는 가정 때문이다. '2'는 왕복을 위한 때문이다. MSL을 위한 추천값은 120초이나 버클리로부터 구현된 제품의 경우 30초를 사용한다. 이것은 1과 4분 사이 의
TIME_WAIT딜레이를 의미한다. 솔라리스 2.x는 추천사항인 120초를 사용한다.

"헤매는 복사본"은 상실된 것으로 보이고 재전송되는 패킷을 말한다. 그러나 이것이 정말
상실된 것은 아니고... 몇몇 라우터들이 문제가 있어서 그 패킷을 얼마동안(몇초 정도, TTL이 크면 몇분이 될 수도 있다) 보유하고 있기 때문이고 그리 고나서 패킷을 다시 네트웍에 보낸다. 그러나 다시 그 패킷이 보이는 순간에는 이미 어플리케이션이 원래의 데이터를 재전송 한 상태이다.

TIME_WAIT암살로 인한 이런 잠재적인 문제 때문에 사람들은 SO_LINGER옵션을 설정하여 일반 TCP연결 끝내는것(FIN/ACK/FIN/AC K)에 대신한 RST를 보내는 것으로 TIME_WAIT상태를 피하려 하지는 않는다. TIME_WAIT상태는 여러분의 친구이도 도움을 준다.
나는 나의 "TCP/IP Illustrated, Volume 3"에서 이 주제에 관해서만 오랜 토론을 벌였다. TIME_WAIT상태는 정말로 TCP의 가장 오해할 만한 부분이다.

나는 지금 "Unix Network Programming"을 다시 쓰고 있다. 그리고 이 주제에 관해 더 자세한 내용을 다룰 것이다.
추가로 Andrew의 설명:
소켓을 닫는거: 만약 SO_LINGER가 소켓에서 호출되지 않았다면 close()는 데이터를 버리는 것을 가정하지 않을 것이다. 이것 은 SVR4.2에서 사실이고, SVR4에서는 아직 확인 안해봤다.
shutdown() 또는 SO_LINGER 둘다 모든 데이터의 전달을 보장하는 것을 요구하는 듯하다.

2.8. 왜 상대편의 죽었는지 확인하는데 그렇게 오래 걸리나?

Andrew Gierth (andrew@erlenstar.demon.co.uk)로부터:

기본적으로, 전달할 데이터가 없거나 ack가 없으면 TCP 연결은 아무런 데이터도 보내지 않는다. 그래서 만약 단지 여러분이 반대쪽으로부터의 데이터를 기다리는 것이라면 반대peer가 가만 히 있는건지 데이터를 전송할 준비 가 아직 안된 경우인지 알 수 가 없다. 이것은 문제가 될 수 있다.(특히, 만약 그 peer가 PC라면 그리고 그 사용자가 그냥~ 전원 을 내려버린거라 면) 하나의 해결책은 SO_KEEPALIVE 옵션을 사용하는 것이다. 이 옵션은 Peer가 여전히 존재하는지 정기적으로 시험해 준다.

경고: 이 옵션에 대한 기본 타임아웃시간은 *적어도* 2시간이다. 이 타임아웃시간은 바뀌어질 수 있다.(시스템 의존적이다.) 그러나 보통 매번의 접속에 기본한 것은 아니
다.(AFAIK)

RFC1122는 이 타임아웃 시간이 설정 가능하다고 하고 있다. 주요 유닉스에서 이 설정은 전역적으로 설정되어 모든 TCP연결에 영향을 준다. 더욱이 이 값을 변경하는 방법은 어렵고매뉴얼도 별로 없으며, 어떤 경우에도 존재하는 거의 모든 버전이 다 틀리 다

만약 굳이 바꾸어야 한다면 tcp_keepidle이라는 말 과 같은 것이 kernel 설정파일이나 네트옵션에 있는지 확인해 보라.
Peer로 전송하면, 좀더 나은 보장을 받게 된다. 왜냐면 전송되는 데이터 자체가 peer로부
터의 ACK를 받는 것을 의미하기 때문 이다. 그리고 나면 알 것이다. 재전송 타임아웃 후에 그 peer가 여전히 살아 있는지 그렇지 못한지. 그러나 재전송 타임아웃은 T CP연결은 작은 네트웍 문제의 결과로서 전송이 취소되지 않는 것을 의도하여 우발적인 사고를 허용하도록 설계가 되었다.
그래서 실패 통지를 받기 전에 몇분의 지연은 있을 수 있는 일이다.

이 접근 방식은 현재 인터넷에서 쓰이고 있는 대부분의 응용 프로토콜에서 서버로의 읽기 타임아웃을 구현하기 위해 사용되고 있다.(FTP, SMTP 등등); 서버는 단순히 정해진 시간동안(보통 15분) 클라이언트로부터의 요청이 없으면 포기한다. 연결이 오랜 동안 놀고 있는 경우의 프로토콜은 두가지 선택이 있다.

1. SO_KEEPALIVE를 사용
2. 고수준의 keepalive 체제를 사용(매번 자주 null요청을 서버에게 보냄)


2.9. select(), non-blocking I/O 그리고 SIGIO의 잇점들은 무엇인가?

non-blocking I/O를 사용하면 소켓으로부터 읽을 것이 있는지 알아보기 위해 poll해야 한다. polling은 보통 그것이 다른 기술들 보다 CPU시간을 많이 소모 하기 때문에 피해야 한다.

SIGIO를 사용하면 어플리케이션이 무엇을 하고 OS는 소켓에 대기중인 데이터가 있다는 것을 시그널을 통해 알려 준다. 한가 지 혼돈될 수 있는 결점이라면 만약 여러개의 소켓을 사용 한다면 읽을 준비가 된 소켓을 찾아 내는데 select()를 사용 해야만 한다는 것이다.

select()를 사용하면 여러개의 소켓으로부터 동시에 데이터를 받을 수 있을 때 아주 좋다.
왜냐면 소켓들중의 하나에 데이터 가 준비될 때까지 다른 것들은 block되기 때문이다.

다른 하나의 select()사용에 대한 잇점은 소켓이 데이터를 가지고 있는지를 여러분에게 알려주는 타임 아웃 시간을 설정할 수 있다는 것이다.


2.10. read()가 EPROTO 를 리턴했다. 무엇인가?

Steve Rago (sar@plc.com)로부터:
EPROTO는 프로토콜이 복구할 수 없는 에러를 만났을 시점에 발생한다. EPROTO는 EPROTO이외
의 다른 적당한 에러 코드가 없을 때 STREADM에 기반한 드라이버에 의해 사용되는 다목적용(catch-all)의 에러코드들 중의 하나이다.

Andrew (andrew@erlenstar.demon.co.uk)의 추가 :
read()로 부터 EPROTO를 받는건 그리 대단한 일은 아니다. 그러나 몇몇 STREAM기반의 구현(implementation)들에서 EPROTO는 만약 incoming연결이 accept가 완전히 이루어 지기 전에 reset되었을 경우 accept()에 의해서도 리턴될 수 있다.

이것이 발생된다면 다른 몇몇 구현에서도 accept는 블록킹 될 수 있다. select()가 듣고있는(listening)소켓이 읽기 가능한 상태고 그래서 accept() 호출에서 일반적으로 블록되지 않는다고 말해지기 때문에 이것은 중요하다. 물론 이 고정(fixing)은 만 약 그것에서 select()를 사용 하려고 할 때 listening 소켓에서의 비블록킹 모드를 설정하기 위함이다.


2.11. 소켓 버퍼에 있는 내용을 강제로 전달 할 수 있는 방법은 없나?

Richard Stevens (rstevens@noao.edu):

강제로 할 수는 없다. 시간이 지나야 한다. TCP는 자신의 데이터를 언제 보낼 것인가를 생각하고 있다. 보통 TCP소켓에서 wr ite()를 호출 했을 때, TCP는 실제로 세그먼트를 보낸다. 그러나 이것을 강제로 시킬 수 있는 방법이나 보증이 없다. 왜 TCP가 세그먼트를 보내지 않는가에 대한 많은 이유가 있다. : "윈도우 담힘:closed window"와 Nagle 알고리즘은 즉시 마음에 와 닫는 그런 이유중의 두가지이다.

(Andrew Gierth의 TCP_NODELAY을 사용 하는 것에 대한 작은 제안)

이것을 설정하는 것은 많은 시험의 하나, Nagle알고리즘를 사용불가능하게 한다. 그러나 만약 그 원래의 포스터의 문제가 사 실이라면 이렇게 옵션을 설정하는 것도 도움이 된다.

tcp_output()을 잠시 보면 11번의 테스트 정도로 TCP가 세그먼트를 보내야 할질 말아야 할지를 결정 해야 한다.

Dr. Charles E. Campbell Jr. (cec@gryphon.gsfc.nasa.gov):

우리가 추측 했을 때, Nagle 알고리즘을 불가능하게 만들어 놔도 아무런 문제가 없었다. 그것은 기본적인 버퍼링 수단이다. 거기엔 작고의 문제가 아니라 모든 패킷에 대하여 혼합된오버헤드가 발생된다. 그러므로 Nagle알고리즘은 작은 패킷을 모으고( 기껏해야 0.2초정도의 딜레이발생) 그러므로 전송하는 것에 대한 오버헤드를 줄이게 된다. 이 접근방식은 rcp 에 대해서 잘 작동 한다. 예를 들어 0.2초 딜레이는 인간에게는 별로 못느끼는 시간이다. 그리고 다중 사용자 환경에서 사용자들은 자신의 패킷을 보다 효율적으로 전송할 수 있다. 대학교 같은 대부분의 일반 사용자가 표준툴, 예를 들어 rcp나 ftp나 telnet, 그런것들을 사용 하는데서는 도움을 줄 수 있을 것이다. 그러나 Nagle알고리즘은 리얼타임
시스템에서의 제어와 특히나 인터렉티브한 키 입력을 하는 어플리케이션에서는 안좋은 영
향을 미친다. 선택적으로 Nagle알고리즘을 통과하는 한가지 방법은 Out-of-bind메시지
시스템을 쓰는 것이다. 그러나 이것은 내 용물에 제약이 있고 또 다른 문제(순서의 상
실:loss of sequentiality)를 일으킬 수 있다.

vic의 또다른 내용:
요약하면, 만약 여러분이 문제가 있어서 소켓을 플러시하기를 원한다면, 보통은
TCP_NODELAY를 설정함으로써 문제를 해결할 수 있다. 그러나 Andrew에 의하면 "out-
of-bind 데이타는 그 자신의 문제를 가지고 있고, 버퍼링 딜레이의 문제를 해결해 주 는
충분한 수단으로 생각지 않는다. 이것은 몇몇 다른 프로토콜에도 존재한다는 점에서 '재촉된 데이터(expedited data)'가 아니 다.; 이것은 스트림 형태로 그러나 어디로 가는지의 포인터를 가지고 전송된다" 라고 한다.

나는 Andrew에게 "TCP가 데이터를 네트웍에 쓸 때 TCP가 무슨약속을 하는가?"의 효과를 요청했다. 그의 대답이 이 것의 대답이 될 거라고 생각한다. 많은 약속은 아니만 약간..
나는 시도하고 이것에 대해 출처를 명확히 하겠다.
참조:

RFC 1122, "Requirements for Internet Hosts" (also STD 3)
RFC 793, "Transmission Control Protocol" (also STD 7)

1. 소켓 인터페이스는 TCP PUSH flag를 접근하는 것을 지원하지 않는다.
2. RFC1122 는 다음과 같이 적고 있다 (4.2.2.2):

TCP는 PUSH flag를 SEND 호출에서 구현하고 있다. 만약 PUSH flag가 구현되지 않았다면 그 전송 TCP는: 1. 불명확하게도, 데이터를 버퍼링 하지 않을 것이다. 그리고 2. 마지막 버퍼링된 세그먼트에 PSH bit를 설정할 것이다.(예를들어, 전송될 데이터가 큐에 더 이상 없을경우)


3. RFC793 는 다음과 같이 적고 있다. (2.8):

수신하고 있는 TCP가 PUSH flag를 보게 된다면, 수신 프로세스로 데이터가 전달되기 전에 전송 TCP로부터 더 이상의 데이 터를 기다리지 않을 것이다.

[RFC1122에 써있는 내용]
4. 그러므로 protocol의 고려에 의해서 방해 되는 것을 제외 하고는 write()호출에 전달되는 데이터는 한정된 시간안에 peer 로 전달 되어야 한다.

5. 데이터를 전달하는데의 약 11번의 테스트 지연시간이 있다.(앞에서의 Steven의 말에 의하면) 그러나 내가 봤을 때, 프로그 래머의 제어하에 있지 않고 한정된 시간에 해결되어야하거나 연결이 끊기게 되는 재전송 포기와 같은 것으로 오직 2번의 의미있 는 테스트 지연이 있다.

첫 번째 재미있는 것은 "윈도우 닫힘:window closed"(예를 들어 수신자에게 버퍼가 없을 경우 이것은 데이터를 무한 정 지연 시킬 수 있다. 수신 프로세스가 이용할 수 있는 데이터를 실제 읽을 수 없을 때만)

vic의 질문:
좋다. 만약 클라이언트가 읽지 않는다면 또 데이터는 연결된 것을 통하여만 성공 할 경우에 의미가 있다. 이것은 전송자가 하 여금 수신큐가 차있을 경우 블록된다는 것으로 받아 들이면 되는가?

소켓 전송버퍼가 차 있을 때 송신자는 블록한다. 그래서 버퍼는 양쪽으로 차게 될 것이다.
윈도우가 닫혀진 동안에 전송 TCP는 윈도우 시험 패킷을 보낸다. 이것은 윈도우가 마침내 다시 열리게 되었을 때 그 전송 TCP 가 그 사실을 알도록 해준다.
두 번째 흥미있는 것은 "Nagle알고리즘"(키 입력 같은 작은 세그먼트, 예를 들어 만약 peer
로 부터 ACK가 올거 같 으면 키 입력같은 것은 큰 세그먼트를 만들기 위해 지연된다. 이것은 TCP_NODELAY로 무력하게 만든 것이다)

vic 질문:
나의 tcpclient예제가 TCP_NODELAY로 하여금 EOL코드가 네트웍을 통해 전달 되었다는 것을 보증 해야함을 의미합니까?

아니다. tcpclient.c는 옳게 작동하고 있다. 가능한 많은 데이터를 write()하기 위한 가능한 적은 호출로 써라. 많은 양의 데 이터는 소켓 전송버퍼에 비해 상대적으로 작기 때문에모든 요청은 오직 한 번의 write()로 충분하며, TCP 레이어는 즉시 그 요 청을 하나의 세그먼트로 떼어낸다.(PSH flag을 이용해서, 위의 2.2를 보라)

Nagle 알고리즘은 두 번째 write()이 데이터가 여전이 ack되지 않은 동안에 호출될 때 영향을 준다. 일반적인 경우에, 이 데 이터는 ack된 데이터가 없거나 full-size세그먼트를 떼어내는 것이 가능한 충분한 데이터가 있을 때까지 버퍼에 남아 있게 된다. 이 지연은 무한정 계속 될 수는 없는데 왜냐면, 재전송 타임아웃이 생기거나 또는 연결이 끊게 되면 조건이 참이 되기 때문이다 .

이 지연이 어떤 응용프로그램에 대해 부정적인 결과를 나타낼 때, 일반적으로 작은 요청의
스트림은 response없이 전송되어 진다. 예를 들어 마우스 움직임의 표준은 마우스 움직임을 불가능하게 하기 위한 옵션이 존재해야 한다고 하고 있다. [RFC1122, ss 4.2.3.4]
추가적인 기록: RFC1122 또한 이렇게 되어 있다.:


[토론]:
PUSH flag가 SEND호출에서 구현되지 않았을 때, 예를 들어 어플리케이션/TCP인터페이스가 순수한 스트림 모델을 사용할 때 어 떤 작은 데이터 조각들을 합리적인 크기의 세그먼트로 모우는 것에 대한 책임은 부분적으로 어플리케이션 레이어에 있다.
그래서 프로그램은 작은 데이터 길이(MSS에 상대적으로 작다는 의미)로 write()를 호출하는 것을 피해야 한다. 버퍼 내에서 요청을 형성 시키고 그런후에 sock_write()등과 같은 호출을 하는게 더 좋겠다. 다른 TCP에서 지연에 대한 원인은 프로그램에서 제어 할 수 있는 것이 아니며, 그들은 데이터를 임시 지연시킬 뿐이다.

vic 질문:
임시적으로, 당신은 데이터가 갈수 있는 만큼 간다는 것을 의미하는가? 한쪽은 대답을 기다리고 한쪽은 요청을 받지도 않은 상태에서 이런 시점에 내가 더 이상 가만히 있을 수만은
없다. 적어도 이러고 영원히 기다릴 수는 없지 않은가?

당신은 어떻게든지 양쪽 방향의 모든 버퍼를 채우도록 하기위해 관리한다면.. 쉽지 않다.
데드락(deadlock)에 걸릴 수 밖에 없다.

다음은 가능하다.(좋은 예는 아니지만)
해결책은 비블록킹 모드를 사용하는 것이다. 특히 write를 위해. 그러면 필요에 따라 프로그램에서 버퍼를 넘치게 할 수 있다 .
2.12. 어디스 소켓에 관한 라이브러리를 구할 수 있는가?
Charles E. Campbell, Jr. PhD. 와 Terry McRoberts에 의해 만들어진 간단한 소켓 라이브러리가 있다. 이 파일은 ssl.tar.gz 이라고 불린다. 그리고 그것은 faq홈페이지에서 구할 수 있다. c++를 위해 socket++라이브러리가 있다.

(ftp://ftp.virginia.edu /pub/socket++-1.10.tar.gz.)

또한 C++ Wrapper도 있다.
ftp://ftp.huji.ac.il/pub/languages/C++/C++_wrappers.tar.gz.
이걸 알려준 Bill McKinnon에게 고맙다. http://www.cs.wustl.edu/~schmidt에 가면 여러분은 ACE 툴킷를 구할 수 있다.

PING소프트웨어그룹은 몇몇 소켓 라이브러리를 가지고 있다.
http://love.geology.yale.edu/~markl/ping.

이 라이브러리를 써본 경험은 없다. 그래서 뭐 다른 추천해줄만한 내용은 없다.
2.13. select는 데이터가 있다고 하는데 읽으면 0 을 리턴한다?

select가 리턴하는 데이터는 EOF이다. 왜냐면 반대편이 연결을 끊었기 때문이다.
이러면 read는 0 을 읽는다. 자세한 내용은 2 .1을 읽어봐라.

2.14. select()와 poll()의 차이점은?

Richard Stevens (rstevens@noao.edu):

기본적인 차이는 select()의 fd_set은 bit mast이고 그러므로 어떤 고정된 크기라는 점이다. 그것은 커널이 컴파일될 때 어플 리케이션이 원하는 크기로 FD_SETSIZE를 정해줄 수 있도록 이 크기를 제한하지 않는 것이 가능하다. 그러나 그렇게 하면 더 많을 일을 하게 된다. 4.4BSD의 커널과 솔라리스 라이브러리 함수둘은 이 한계를 가지고 있다. 그러나 나는 BSD/OS 2.1 이 이 한계를 피하고 있어서 프로그래밍에서는 약간의 수고만 하면 된다. 어떤 사람은 솔라리스의 버그리포트에 이것을 넣고 언제 이게 고쳐 지나~ 보는 경우도 있다.

그러나 poll()의 경우에 사용자는 pollfd 구조체 배열을 할당해야한다. 그리고 이 배열의 개수를 넘겨줘야 한다. 그래서 기본 적인 한계는 없다. Casper가 말하는 바에 의하면,
select보다 poll()을 가지고 있는 시스템은 거의 없다고 한다. 그래서 후자(se lect())가 좀더 호환성이 있다라고 한다. 또한 원래 시스템(SVR3)에서는 pollfd구조체에있는 한 엔트리를 커널이 무시 하도록 하 기위해 디스크립터를 -1로 설정하는 것이 불가능 했었다. SVR4에서 이것이 해결 되었다. 개인적으로 나는 항상 select()를 사용 하며 poll()은 거의 사용하지 않는다. 왜냐하면 나는 나의 코드를 BSD환경에도 포팅하기 때문이다. 이러한 이유로select()를 사 용하는 poll()을 구현할 수도 있겠다. 그러나 난 이런 사람은 한 번도 본적이 없다. 어쨋든 POSIX 1003.1g에서 이 둘은 표준으로 채택되었다.

2.15. 어떻게 [this]를 네트웍을 통해 보낼 수 있는가?

주의하지 않으면 1바이트의 데이터가 아니면 아마 산산조각날 수 있다. 정수형 숫자의 경우 htons()를 사용할 수 있고 1바이 트의 묶음들은 잘 전송된다. 포인터형 데이터를 문자열처럼 보내지 않도록 주의하라. 왜냐면 포인터는 다른 기계에서는 아무 의 미가 없기 때문이다. 만약 구조체를 보내는게 필요하면 sendthisstruct()를 전송하는데,
그리고 readthisstruct()를 수신하는함 수로 사용하라. 만약 실수를 보내야 한다면 많은 작업을 해야한다. RFC1014는 호환되는 방법으로 다른 기계에 데이터를 전송하는 방법에 대해 기술하고 있다.(이걸 알려준 Andrew Gabriel에게 감사를 표한다.)

2.16. TCP_NODELAY를 사용하는 방법은?

우선, 처음부터 이것을 써야 하는지에 대해 먼저 생각해 봐라. 이것은 Nagle알고리즘을 못쓰게 만들어 버릴 것이다.(2.11의 소켓 버퍼에 있는 내용을 강제로 전달 할 수 있는 방법은 없나? 부분을 읽어봐라) 이것은 작은 패킷들을 양산하여 네트웍 트래픽 을 높일 수 있다.또한 내가 말해줄 수 있는 것은 속도 증가는 미미하다는 것이다. 그러므로 일단은
TCP_NODELAY를 먼저 고려해 보고 그래도 안되면 써야 한다.

코드 예제가 여기 있다. Andrew Gierth의 이것의 사용에 대한 경고 메시지와 함께
Gierth:

int flag = 1;
int result = setsockopt(sock, /* 영향받을 소켓 */
IPPROTO_TCP, /* TCP레벨에 옵션은 선택 */
TCP_NODELAY, /* 옵션이름 */
(char *) &flag, /* 이 경우는 역사적인 cruft 이다 */
sizeof(int)); /* 옵션값의 길이 */
if (result < 0)
... handle the error ...

TCP_NODELAY는 Nagle 버퍼링 알고리즘을 중지시킬 특별한 목적으로 사용된다.
이것은 전송은 작은 단위로 자주 이루어 지지만 즉각적인 응답은 필요 없는 어플리케이션에
서만 사용 되어야 한다.(마우스 움직임 같은)

2.17. Nagle 알고리즘이 도데체 뭐하는 건가?

접 속의 다른 끝으로부터 ACK사이의 가능한한 많은 데이터를 한데 묶는 것을 말한다.
Andrew Gierth (andrew@erlenstar.demon .co.uk)가 아래의 그림을 통해서 설명한 것을 보면쉽게 이해할 수 있을 것이다.

설명:
이 그림은 완벽한건 아니다. 단지 잘 설명하기 위한것일 뿐이다.

첫 번째 경우: 클라이언트는 한 번의 write()에 1바이트를 write한다.
B 호스트에서 이 프로그램은 FAQ 예제의 tcpserver.c 이다.

클라이언트 서버
APP TCP TCP APP
[연결 설정의 제거됨]

"h" ---------> [1 byte]
------------------>
-----------> "h"
[ack 지연됨]
"e" ---------> [Nagle alg. .
효과를 나타냄] .
"l" ---------> [ditto] .
"l" ---------> [ditto] .
"o" ---------> [ditto] .
"\n"---------> [ditto] .
.
.
[ack 1 byte]
<------------------
[큐에 쌓인 데이터를
보냄]
[5 bytes]
------------------>
------------> "ello\n"
<------------ "HELLO\n"
[6 bytes, ack 5 bytes]
<------------------
"HELLO\n" <----
[ack 지연됨]
.
.
. [ack 6 bytes]
------------------>

총 세그먼트: 5. (만약 TCP_NODELAY 가 설정되었다면, 10까지 증가될 수 있다.)
응답시간: 2*RTT에 ack 지연시간을 더한 합.

두 번째 경우: 클라이언트는 한 번의 write()요청에 모든 데이터를 write한다.

클라이언트 서버
APP TCP TCP APP
[연결설정은 제거됨]

"hello\n" ---> [6 bytes]
------------------>
------------> "hello\n"
<------------ "HELLO\n"
[6 bytes, ack 6 bytes]
<------------------
"HELLO\n" <----
[ack 지연됨]
.
.
. [ack 6 bytes]
------------------>
총 세그먼트: 3.

응답시간 = RTT (최소시간이 걸린다).

이 그림이 좀 쉽게 알아보는 계기가 됐음 한다.
두 번째 경우에서, 데이터를 전송하는데 필요없는 지연이 구현되길 바라지는 않을 것이다.
응답시간에 곧바로 더해진다면.

2.18. read()와 recv의 차이점은?

Andrew Gierth (andrew@erlenstar.demon.co.uk):

read()는 파라메터 flag로 0 을 넘겨주면 recv()와 같게 동작한다. flag파라미터에 대한 수를 넣으면 recv()는 다르게 동작한 다. 마찬가지로 write()도 flag가 0이면
send()와 같게 동작한다.

이것은 send()와 recv()가 drop될 수 있다는 점에서 다르다.
아마 POSIX드래피트의 복사본을 가지고 있는 사람은 이것을 확인 해 볼 수 있을 것이다.
호환노트: unix가 아닌 시스템은 read()/write()가 소켓에서 동작하는 것을 허용하지 않을 것이다. 그러나 recv()/send()는 보통 허용된다. 보통 Window와 OS/2에서 가능하다.

2.19. send()/write()가 SIGPIPE 신호를 생성할 수 있던데 이 신호를 무시하거나 EPIPE오류에 대한 검사를 하지 않고 다른 처리를 했을때의 어떤 잇점이 있는가?
시그널을 잡는 잡는 함수 에 넘겨지는 어떤 유용한 파라미터가 있는가?

Andrew Gierth (andrew@erlenstar.demon.co.uk)가 말하기를:

일반적으로 시그널 핸들러에 전달되는 유일한 파라메터는 그것이 구동되도록 만드는 시그널
번호이다. 몇몇 시스템은 추가 선택사항 파라미터를 가지고 있지만 이 경우에 사용되지는 않는다.

나의 충고는 단지 여러분이 제안한 것처럼 SIGPIPE를 무시하라는 것이다. 그것은 나의 소켓코드에서 할 수 있는 것이다. error값은 시그널보다 다루기 쉽다(사실, FAQ의 첫 번째 판은 이러한 전후 관계를 가지고 SIGPIPE를 설명하는데 실패했다.

SIGPIPE를 무시해서는 안되는 한가지 상황이 있다; 만약 여러분이 소켓으로 redirect되는 stdout를 가진 다른 프로그램을 exec() 하려고 한다면, 이 경우 exec()하기 전에 SIGPIPE를 SIG_DFL로 설정하는게 현명할 것이다.


2.20. chroot()실행후에 socket() 호출이 실패했다. 왜 그런가?

Andrew Gierth (andrew@erlenstar.demon.co.uk):

소켓이 스트림의 최상위에서 구현된 시스템(예를 들어 시스템V기반의 시스템, 솔라리스등)
에서 socket()함수는 실제로 /dev에 의 특수파일을 여는 것으로 socket()이 실행된다. 그러므로 chroot()를 실행하기 전에 그 root디렉토리에 /dev/디렉토리를 만들 어 줘야 하며, 필요한 디바이스 장치들도 만들어 줘야 한다(오직 필요한 것만).

시스템 매뉴얼에 그 필요한 디바이스 장치를 지정하고 있을 것이다. (편집자노트: Adrian Hall (adrian@waltham.harvard.net) 은 ftpd 매뉴얼 페이지에 있는 부분을 참조하라고 권한다. 거기에 있는 /dev 장치들을 사용할 수 있을 것이다.

좀 관련이 적은 얘기지만, 많은 daemon들이 그렇게 하듯이 syslog()함수를 호출 했을 경우 syslog()는 UDP와 소켓, FIFO또는 유닉스도메인 소켓을 열게 된다. 그래서 만약 여러분들이 chroot() 실행 후에 그것을 사용할 경우 chroot()전에 openlog()를 호 출하는 것을 잊지 말기 바란다.

2.21. 왜 소켓 호출로부터 EINTR가 오는 것을 계속 기다리고 있어야 하는가?

exit 조건에서 처럼 이건 그렇게 많은 문제가 있는건 아니다. 이것은 그 호출이 시그널에
의해 중지될 수 있음을 의미한다. 블록될수 있는 모든 호출은 EINTR를 검사하는 루틴으로
싸여 있어야 한다. 예제코드가 그것을 보여준다(6. 예제코드를 보라)

2.22. 언제 내 프로그램이 SIGPIP 시그널을 받게 되는가?

Richard Stevens (rstevens@noao.edu):

매우 간단하다. TCP를 사용할 경우 여러분의 연결(connection)이 다른 쪽으로부터 RST를 받게 되면 여러분은 SIGPIPE를 받게 된다. 이것은 RST가 여러분들이 읽도록 거기에 존재하고 있기 때문에 만약 write() 대신에 select를 사용한다면, 그 select는 읽 기 가능한 소켓을 지정하게 될 것이다.(read는 errno에 ECONNRESET을 설정한 오류코드를 리턴할 것이다.)

기본적으로 RST는 더 이상 다룰 수 없거나 그럴것으로 예상되는 어떤 패킷에 대한 TCP의 응답니다. 일반적인 경우는 peer가 연결(여러분에게 FIN을 보내는)을 끊을때이다. 그러나 여러분이 쓰고있고 읽고 있지는 않을 경우 여러분은 이것을 무시한다. (여 러분은select를 사용해야 한다.) 그러므로 여러분은 반대편에 의해 끊긴 연결과 다른쪽은 RST를 가진 TCP응답에 write 한다.

2.23. 소켓 예외(exception)는 있는가? out-of-band데이타란 무엇인가?

C++에서의 예외와는 다르게, 소켓 예외는 오류가 발생했다는 것을 지칭하지는 않는다. 소켓 예외는 보통 out-of-bind데이터가 도착 했다는 것을 알려 주는 것이다. out-of-band데이터( TCP에서 "긴급데이터(urgent data)"라고 불리우는)는 주 데 이터 스트림으로부터의 분리된 스트림과 같이 응용프로그램에 의지한다. 이것은 분리된 두가지 종류의 데이터의 경우에유용하다 . "긴급데이타(urgent data)"라는 것은 in-band데이타 스트림보타 중요성이 더 크다거나, 더 빠른 전송이 요구된다는 것을 뜻하는 건 아니다라는 것을 알아두라. 그리고 또한 주 데이터 스트림과는 다르게 out-of-band데이터는 여러분의 어플리케이 션이 그것을 유지하지 않을 경우 상실되게 된다는 것을 알아둬야 한다.
2.24. 어떻게 완전한 내 시스템의 호스트이름인 (FQDN)을 얻을 수 있는가?

Richard Stevens (rstevens@noao.edu):

몇몇 시스템은 호스트 이름을 FQDN으로 설정하고 어떤 다른 시스템들은 단지
unqualified된 호스트 이름을 그냥 사용하기도 한다. 현재 BIND FAQ는 FQDN를 사용하도록 추천하고 있지만 예를 들어, 대부분의 솔라리스 시스템들은 unqualified된 호스트 이 름을 사용한다.

그럼에도 불구하고, 이 방법은 처음 호스트 이름을 알아내는 방법이다. 대부분의 시스템은
uname()이라는 posix의 방법을 지 원한다. 그러나 오래된 BSD시스템은 단지 gethostname()
을 지원하기도 한다. 여러분의 IP 주소를 얻으려면 gethostbyname()을 사 용하라. 그리고 그 IP주소를 가지고 gethostbyaddr



3. 클라이언트 어플리케이션 만들기 (TCP/SOCK_STREAM)

3.1. 문자열을 인터넷 주소로 바꾸는 방법은?
만약 명령라인으로부터 호스트의 주소를 읽는다면 여러분이 xxx.xxx.xxx.xxx 형태의 주소를 가지고 있는지 알 수가 없고, host.domain.com 형태의 주소도 가지고 있는지 알
수가 없다. 그럼 여기서 어떻게 해야 하는가. 우선 xxx.xxx.xxx.xxx의 형태로 주소를
쓰고 그것이 실패하면 name lookup을 이용하라. 여기 예가 있다.

/* 아스키 텍스트를 in_addr구조체로 변경. 주소가 별견되지 않으면 NULL을 반환한다 */
struct in_addr *atoaddr(char *address) {
struct hostent *host;
static struct in_addr saddr;

/* 우선 aaa.bbb.ccc.ddd. 형태로 시도한다*/
saddr.s_addr = inet_addr(address);
if (saddr.s_addr != -1) {
return &saddr;
}
host = gethostbyname(address);
if (host != NULL) {
return (struct in_addr *) *host->h_addr_list;
}
return NULL;
}

3.2. firewall과 proxy서버를 통해서 클라이언트가 작동하도록 하려면 어떻게 하는가?

만약 서비스들이 여러 분리된 프록시를 통하여 운영되고 있다면, 어떤것도 할 필요가 없다.
만약 sockd를 통하여 운영되고 있다면, "socksify"를 여러분의 시스템에서 사용해야 한다.
이것을 사용하는 자세한 것은 그 패키지에 있다.

ftp://ftp.net.com/socks.cstc/socks.cstc.4.2.tar.gz

그 sock faq는 다음에 있다.

ftp://coast.cs.purdue.edu/pub/tools/unix/socks/FAQ
3.3. 어떻게 서버가 accept()하지않았는데도 connect()가 성공할 수 있는가?

Andrew Gierth (andrew@erlenstar.demon.co.uk):
한 번 소켓에 대하여 listen()을 호출하면, 커널은 그 소켓 위에 연결을 accept하도록 된다. 일반적인 유닉스들은 내부로 들어오는 합당한 SYN 세그먼트들을 위한 SYN 핸드쉐이크를 즉시 완결 하는 것에 의해 동작하며, 그 새로운 소켓에 대한 accept()호출을 위해 내부 큐를 준비시키도록 구현되었다. 그러므로 소켓은 accept가 완결되기 전에 완전히 open된다. 이것의 다른 원인은 listen()의 'backlog' 파라미터 때문이다. 그것은 한 번에 얼마나 많은 완전히 연결된 소켓들이 큐에 들어갈 수 있는지를 나타낸다. 만약 이 번호가 넘치게 되면 새로운 접속은 무시된다.(그 소켓들은 아웃된다.)

3.4. 하나의 서버 이상을 사용하고 있을 때 왜 서버 주소를 가끔 잃어 버리는가?

Andrew Gierth (andrew@erlenstar.demon.co.uk):
hostent구조체를 주의깊게 보면, 그 안의 모든 것들이 포인터 형이라는 것을 알 수 있게
될것이다. 모든 이 포인터는 정적으로 할당된 데이터들을 가르킨다.

예를들어, 만약 다음과 같이 한다면:
struct hostent *host = gethostbyname(hostname);

gethostbyname()을 하는 다음 호출은 'host'에 의해 그 포인터는 덮어써질 것이다.

그러나 다음과 같이 하여:
struct hostent myhost;
struct hostent *hostptr = gethostbyname(hostname);
if (hostptr) myhost = *hostptr;

위 처럼 덮어써 지기 전에 hostent의 복사본을 만들려고 하면, 그것은 여전히 그것은
gethostbyname()을 호출하는 것에 의해 타격을 입게 된다. 왜냐하면 비록 myhost자체
가 덮어써 지지는 않지만 포인팅 하려는 모든 데이터는 덮어써 질 것이기 때문이다.

여러분은 적당한 hostent의 'deep copy'를 해서 다룰 수 있다. 그러나 이렇게 하면 지루한 감이 있다. 내가 추천할 만한 것은 hostent의 필요한 필드만 추출하여 그들을 여러분들 자신의 방법대로 저장하는 것이다.
3.5. connect()를 위한 타임아웃시간을 설정할 수 있는가?

Richard Stevens (rstevens@noao.edu):

보통 이걸 바꿀수는 없다. 솔라리스는 이것을 커널하에서 ndd
tcp_ip_abort_cinterval파라미터를 통해 허용한다.

연결 시간을 짧게 하는 가장 쉬운 방법은 alarm() 호출을 사용하는 것이다. 가장 어려운 방법은 소켓을 비블록킹 상태로 만든 후에 select()를 사용하는 것이다. 또한 알아둬야 할 것은 이 시간은 짧게 만드는 것은 가능 하지만 늘리는 것은 불가능 하다는 것이다.
Andrew Gierth (andrew@erlenstar.demon.co.uk):

우선, 소켓을 만들고 그것을 비블록 모드로 만들어라, 그런다음 connect()를 호출한다. 3개의 가능성이 있다.

connect succeeds : 연결이 성공적으로 만들어 진 것이다.(이것은 일반적으로 같은 기계에 연결되었을 때 발생한다.)

connect fails:

connect returns -1/EINPROGRESS. 연결 시도가 시작 되었으나 아직 완결되지는 않았다.

만약 연결이 성공하면:
소켓은 쓰기 가능한지로서의 select()를 호출한다.( 그리고 또한 데이터가 도착하면 읽기가 가능한지 호출한다.)

연결이 실패하면:
소켓은 읽기 가능인지, 쓰기가능이지를 select 한다. 그러나 이 read 또는 write은 연
결 시도 로부터 오류 코드를 리턴할 것이다. 또한 getsockopt(SO_ERROR)를 사용하여
오류 상태를 얻을 수 있다. 그러나 주의해야 한다. 몇몇 시스템은 getsockopt의 결과 파라미터에 포함하여 리턴한다. 그러나 몇몇은(잘못된 것이지만) getsockopt호출이 *자신(itself)*을 호출하여 저장된 값에 에러를 저장하기도 한다.(?)

3.6. 클라이언트 프로그램에서 포트번호를
갖기 위해 bind()를 써야 하는가 아니면 시스템이 connect()호출이 선택하도록 해야 하
는가?
Andrew Gierth (andrew@erlenstar.demon.co.uk):
** 시스템이 클라이언트의 포트 번호를 선택하게 하라 **
이것에 대한 예외는 만약 서버가 연결이 될 클라이언트 포트가 어떤 것인지 선택 하도록
되어 있을 경우이다. Rlogind와 rshd가 좋은 예이다. 이것은 보통 유닉스만의 인정시스템의 한 부분이다. 이것의 의도는 서버가 root 권한의 프로세스로 부터만 연결을 허용할 의도록 만들어진 것이다.(이 시스템의 약점은 많은 OS(예를 들어 MS-DOS)들이 어떠한 사람이라도 어떤 포트든지 bind할 수 있도록 한다는데있다.)

rresvport() 루틴은 이러한 시스템을 사용하는 클라이언트에 도움을 주고자 만들어 졌다.
이것은 기본적으로 socket()+bind()와 같고 512부터 1023까지 범위의 포트 번호를 선택한다. 만약 서버가 클라이언트의 포트 번호에 대해서 세심하게 주의하지 않는 것이라면, 이것을 여러분들의 클라이언트에 적용하지 마라, 단지 connect()가 그것을 선택하도록 하라. 만약 클라이언트에서 여러분이 그것이 작동하는 한 고정된 포트번호와 bind()호출을 연속적인 값으로 호출한다면 여러분은 엄청난 곤란을 겪게 될 것이다.

이 문제는 만약 여러분의 접속에 대해 서버쪽이 능동적인 접속 해제를 할 경우이다(예를
들어 'QUIT'명령을 서버에게 보내면 서버는 연결을 해제한다). 이것은 그 연결의 클라
이언트쪽을 CLOSED상태로 만든다. 그리고 서버쪽 끝은 TIME_WAIT상태로 둔다. 그래서 클라이언트가 exit 한 후에 연결에 대한 흔적은 없게 된다.

지금 클라이언트를 다시 실행해 보라. 그것이 그 포트를 볼 수 있는 한은 그 같은 포트번
호를 선택할 것이다. 그러나 connect() 을 호출하자마자 그 서버는 여러분이 존재하고
있는 접속(연결:connection)을 복제하려고 시도한다는 것을 발견할 것이다.(비록
TIME_WAIT상태에 있을지라도). 이것은 이것을 하는것에 대해 거절하는 것에 대한 완전한 권한을 주는 것이다. 그러므로 여러분은 이렇게 될 때 (내가 생각하건데) connect()로 부터 ECONNREFUSE를 리턴 받게 될 것이다.(몇몇 시스템들은 때로 어떻게 연결 되었던지 간에 그것에 대해 응답을 받을 수 없는 경우도 있다.)

이것은 클라이언트와 서버가 다른 기계가 아닐 경우에 특히 더 위험하다. (같은 기계가있을때 만약 전에 그랬던 것 처럼 클라이언트가 같은 포트번호를 선택하지 않는다면) 그래서
만약 프로그램을 개발한다면 먼저 같은 기계에서 클라이언트와 서버를 테스트 해 보는게 좋
다. 비록 여러분의 프로토콜이 클라이언트를 우선 끊는다면, 여전히 이 문제를 발생시킬 방법이 있다.(예를 들어 서버를 죽여라)


3.7. 왜 서버가 동작하고 있지 않을 때 "connection refused"라는 메시지가 나오는가?

connect()는 그것이 연결을 설정하려고 대기중에만 블록된다. 반대쪽에 기다리는 서버가
없을 경우엔 연결을 설립할 수 없다는 것을 에러 메시지를 통해 알게 된다. 존재하지도 않는 않는 서비스에 대해 계속 클라이언트가 기다리면 안되기 때문에 좋은 방법이다. 사용자
는 그들이 설립될 연결에 대해서 기다리다가 어느정도 후에는 포기한다는 것은 별로 좋은
방법이라 생각지 않을 것이다.

3.8. 소켓을 통해 들어오는 정보의 양을 모를 경우 어떻게 해야 하는가? 동적 버퍼를 사
용할 수 있는 방법이 있는가?

Niranjan Perera (perera@mindspring.com).

들어오고 있는 데이터의 양을 모를 경우에, 가능한한 크게 버퍼를 만들 수 있다. 또한 읽고있는 도중에 존재하는 버퍼의 크기를 임의로 늘릴 수도 있다. 큰 버퍼를 malloc()할 때 대부분의 유닉스 시스템들은 주소 공간을 할당한다. 그러나 실제 램의 패이지를 할당하는 것은 아니다. 좀더 많은 양의 버퍼가 사용되면, 커널은 실제 메모리를 할당한다. 이것은 큰버퍼를 malloc하는 것이라 해도 메모리가 사용되지 않는다면 자원을 낭비하는 것은 아니라는 것을 의미한다. 그렇기 때문에 단지 몇K의 데이터가 올것으로 생각될 때 몇메가의 램을할당하는 것도 좋은 일이라는 것이다. 반면에, 좀더 품위있는 해결책은 realloc을 사용하여퍼를 4K 씩 늘리도록 하는 커널의 내부 작업에 의존하지 않는 것이다. 나는 이것과 비슷한것을 예제코드의 sockhelp.c에 포함 시켜 놨다.



4. 서버 만들기 (TCP/SOCK_STREAM)

4.1. bind()할 때 "address already in use" 메시지가 나왔는데 무슨 의미인가?

이건 그 주소가 이미 사용중일 때 발생한다. 가장 일반적인 이유는 서버를 중지시킨 후에
그것을 곧바로 다시 실행했을 경우에 그런일이 발생하는 경우가 있다. 첫 번째 만들어진 서버에 의해 사용된 소켓이 여전히 active상태이기 때문이다. 이것은 2.7에 더 깊이 설명되고 있는 TIME_WAIT와 2.5."어떻게 소켓을 제대로 닫을 수 있는가"를 참조하라.

4.2. 왜 소켓을 닫을 수 없는가?

close() 시스템 콜을 발생시키면, 여러분은 소켓 자체가 아니라 소켓에 대한 인터페이스를닫게 되는 것이다. 소켓을 닫는 것은 커널이 하는 일이다. 때때로 기술적인 이유로 소켓은여러분이 그것을 닫은 후에도 계속 살아 있는 경우가 있다. 소켓이 서버쪽에서 TIME_WAIT 상태로 들어가 보통 몇분간 살아 있는건 보통이다. 표준에 의하면 보통 4분이다. 리눅스에서는 약 2분정도이다. 이것은 2.7 "TIME_WAIT상태에 대해 설명해 달라"를 참고하기 바란다.

4.3. 나의 서버 데몬을 어떻게 만드나?

두가지 방법이 있는데, 첫 번째는 inetd로 하여금 어려운일을 맏기는 것이고. 두 번째는 힘들일 까지 모두 여러분 자신이 하는 것이다.

만약 inetd를 사용한다면. 여러분은 단지 stdin, stdout, 또는 stderr을 여러분의 소켓을 위해 사용 하고, 이것을 여러분의 코드에서 이것들을 프로그램에서 소켓처럼 사용하면 된다.( stdin을 비롯한 이 세가지는 실제 소켓으로부터 dup()를 호출함으로써 만들 수 있다.) inetd프로세스는 프로그램이 다 끝났을 때 소켓을 닫아주는 일까지 해준다.

만약 자신의 서버를 만들기 원한다면, "Unix Network Programming"을 보라. 그리고 만약 SIGPIPE를 무시하는 코드를 추가하길 원할 수도 있다. 왜냐하면 만약 이 시그널을
그냥 두면 프로그램이 exit될 수도 있기 때문이다.

나는 이 모든 것들을 GNU C library 매뉴얼로 부터 얻었다. 여기에 몇몇 코드가 있다.
필요하면 이것을 여러분 코드에 삽입할 수도 있을 것이다.



#include
#include
#include
#include
#include
#include
#include

/* 전역변수 */
volatile sig_atomic_t keep_going = 1; /* 제어 프로그램 종료 */

/* 함수 프로토타입 */
void termination_handler (int signum); /* 종료전에 clean up 한다. */

int
main (void)
{
...

if (chdir (HOME_DIR)) /* 데이터파일을 포함하고 있는 디렉토리로 이동 */
{
fprintf (stderr, "`%s': ", HOME_DIR);
perror (NULL);
exit (1);
}

/* 데몬이 된다. */
switch (fork ())
{
case -1: /* fork 실패*/
perror ("fork()");
exit (3);
case 0: /* 자식, 프로세스가 데몬이 된다: */
close (STDIN_FILENO);
close (STDOUT_FILENO);
close (STDERR_FILENO);
if (setsid () == -1) /* 새로운 세션 요청 (job control) */
{
exit (4);
}
break;
default: /* 호출 프로세스로 부모는 리턴한다 */
return 0;
}

/* 종료전에 clean up하기 위해 시그널 핸들러를 설립한다. */
if (signal (SIGTERM, termination_handler) == SIG_IGN)
signal (SIGTERM, SIG_IGN);
signal (SIGINT, SIG_IGN);
signal (SIGHUP, SIG_IGN);
/* 주 프로그램 루프 */
while (keep_going)
{
...
}
return 0;
}

void
termination_handler (int signum)
{
keep_going = 0;
signal (signum, termination_handler);
}

4.4. 한번에 여러 포트로부터 listen할 수 있는가?

이것을 위한 가장 좋은 방법은 select()를 사용하는 것이다. 이것을 이용하면 커널은 언제소켓이 사용가능한지를 알려 준다. 이 호출을 이용하면 다중 소켓 에서 하나의 프로세스를통하여 i/o 하는게 가능하다. 만약 소켓 4,6,10로부터의 연결을 기다리기를 원한다면 다음의 코드를 실행해야 한다.
fd_set socklist;
FD_ZERO(&socklist); /* 처음에 구조체를 clear시킨다 */
FD_SET(4, &socklist);
FD_SET(6, &socklist);
FD_SET(10, &socklist);
if (select(11, NULL, &socklist, NULL, NULL) < 0)
perror("select");

커널은 (select()의 첫 번째 파라미터인) 11보다 작은 파일 디스크립터가 우리의 쓰기 가능상태가 되는 socklist의 구성원이 되자마자 우리에게 그것을 알려 준다. 자세한 것은
selec()의 매뉴얼 페이지를 보라.

4.5. SO_REUSEADDR 가 정확히 뭐하는 것인가?

이 소켓 옵션은 커널에게 비록 이 포트가 busy상태일 지라도 그것을 계속해서 사용하도록 하게 한다. 이것은 서버가 셧다운 되고 소켓이 그것의 포트에서 아직 active상태인 동안에바로 재구동 되었을 경우에 유용하다. 하지만 만약 소켓이 active된 상태에서 어떤 예기치 않은 데이터가 들어오면 이것이 서버를 혼란스럽게 할 수 있다라는 점을 알아 둬야 한다.

"소켓은 5개의 튜플(proto, local addr, local port, remote addr, remote
port)이고, SO_REUSEADDR는 단지 로컬 주소를 다시 사용할 수 있으며. 이 5튜플은 계
속해서 유일해야 한다"라는 것을 Michael Hunter (mphunter@qnx.com)이 지적했다.

이것은 사실이고, 이것은 매우 예기치 않은 데이터가 서버에 거의 들어오지 않을 것이란
것의 이유가 된다. 위험한 것은 이 5 튜플이 네트웍에 여전히 떠다니고, 여기저기 튀어 다
니는 동안 새로운 연결이 같은 기계의 클라이언트로 부터 들어오게 되면 똑같은 리모트의
포트를 얻게된다. 이것은 2.7의 "TIME_WAIT 상태에 대해 설명해 달라" 에 설명되어 있다.

4.6. SO_LINGER 이 정확히 뭐하는 것인가?

몇몇 유닉스에서 이것은 아무 의미도 없다. 또 다른 유닉스에서 이것은 커널로 하여금 tcp
연결을 정확하게 닫게 하는 것 대신에 tcp연결을 취소(abort)하게 명령한다. 이것은 위험한 일이다. 만약 여러분이 이것에 대해 잘 이해가 안가면 2.7장을 보라.


4.7. SO_KEEPALIVE 이 정확히 뭐하는 것인가?
Andrew Gierth (andrew@erlenstar.demon.co.uk):

SO_KEEPALIVE옵션은 만약 오랜시간동안(default로 2시간 이상) 패킷이 수신 또는 송신되지 않을 때, 패킷으로 하여금 리모트 시스템으로 전달되도록 한다. 이 패킷은 peer가 ACK 응답을 만들도록 설계 되었다. 이것은 unreachable된 peer를 알아 낼 수 있도록 해준다.(예를 들어 파워가 꺼져 버렸거나 네트웍이 끊겼을 때). 2.8 에 이것에 대한 더 상세한 내용이 있다.

2시간이라는 수치는 RFC1122의 "Requirements for Internet Hosts"에 근거한 숫자이다. 설정되어야 할 중요한 숫자이지만 자주 난 이것이 어려운 일임을 발견하게 된다. 계속 살아있어야 할 시간이 각 연결마다 설정되는 것이 가능 한 유일한 것이 SVR4.2이다.

4.8. 포트 번호를 1024 이하로 bind()할 수 있는 방법은?

Andrew Gierth (andrew@erlenstar.demon.co.uk):

1024보다 작은 수치의 포트 번호에 접근하도록 제한 하는 것은 유닉스의 보안 체계에 안좋
은 영향을 주는 아주 않좋은 방법이다. 이 의도는 서버가(예를 들어 rlogind, rshd) 클라이언트의 포트번호를 체크할 수 있도록 하고, 만약 그것이 1024보다 작으면 그 요청은 아마도 클라이언트쪽에서 인증된 것임을 가정한다.

실제적인 이것의 요지는 포트번호를 1024 보다 작게 바인딩 하는 것은 프로세스가 유효한
UID==root를 갖도록 프로세스에게 남겨둔다(reserved)는 것이다.

이것은 때로 그 자체가 보안의 문제를 만들 수 있다. 예를 들어 서버프로세스가 잘 알려진
포트로 바인딩 할 필요가 있고, root 접근이 필요한 경우는 아닐 경우(예를 들어 news
server)이다. 이것은 단순하게 소켓을 바인드 하고 실제 사용자 ID를 반환하는 작은 프로
그램을 만들어 실제 서버에서 exec()를 실행함으로써 해결 될 수 있다. 이 프로그램은
root 소유로 setuid될 수 있다.

4.9.클라이언트의 주소나 이름을 나의 서버가 알 수 있는 방법이 있는가?

Andrew Gierth (andrew@erlenstar.demon.co.uk):
접속을 accept()한 후에, getpeername()을 사용하여 클라이언트의 주소를 얻을 수 있다. 클
라이언트의 주소는 물론 accept()로부터도 리턴된다. 그러나 이것에 대한 accept()호출이 잘 동작하기 전에 이것은 주소 길이 파라메터를 초기화 하는데 필요한 것일 뿐이다.
Jari Kokko (jkokko@cc.hut.fi)은 클라이언트 주소를 알아내는데 다음의 코드를 사용할 것을 추천하였다.

int t;
int len;
struct sockaddr_in sin;
struct hostent *host;

len = sizeof sin;
if (getpeername(t, (struct sockaddr *) &sin, &len) < 0)
perror("getpeername");
else {
if ((host = gethostbyaddr((char *) &sin.sin_addr,
sizeof sin.sin_addr, AF_INET)) == NULL)
perror("gethostbyaddr");
else printf("remote host is '%s'\n", host->h_name);
}

4.10. 나의 서버를 위한 포트번호를 어떻게 선택 해야 하는가?

등록된 포트할당 리스트는 STD2나 RFC1700에 있다. 거기에서 아직 등록되지 않은 것을 선택 하면 된다. 그리고 여러분의 시스템의 /etc/ser vices에 없는 것으로 선택하면 된다. 다른 등록되지 않은 다른 서버의 포트 번호를 그 서버의 사용자가 선택할 수 있도록 해주기위해 포트번호 설정을 사용자가 할 수 있도록 해 주는 것도 좋은 방법이다. 가장 좋은 방법은 서비스 이름을 하드코딩 하고 getservbyname()을 사용하여 실제 포트번호를 찾도록 하는것이다. 이 방법은 사용자가 자신들의 포트 번호를 단지 /etc/ser vices파일을 바꿈으로 해서 마음대로 바꿀 수 있다는 잇점이 있다.

4.11. SO_REUSEADDR 와 SO_REUSEPORT의 차이는 무엇인가?

SO_REUSEADDR는 여러분의 서버로 하여금 TIME_WAIT상태에 있는 주소를 바인드 하게 해
준다. 이것은 같은 주소로 하나 이상의 서버가 바인드 하는 것를 허용하지 않는다. 이것은
보안의 위험성 있다고들 말한다. 왜냐하면 다른 서버도 INADDR_ANY에 반대되는 것 처럼 주
소를 지정하여 같은 포트로 바인드 할 수 있기 때문이다. SO_REUSEPORT는 하나의 포트로
여러 프로세스가 바인드(bind) 하는 것을 허용한다.

Richard Stevens (rstevens@noao.edu):

이것은4.4BSD의 멀티캐스팅 코드에서 새로 생긴 flag이다. (비록 이 코드가 다른 어떤 곳으로부터 생긴 것일지라도, 그래서 이 새로운 SO_REUSEPORT flag를 누가 개발했는지는 몰라도)이 flag는 여러분들이 이미 사용중인 하나의 포트를 재바인드 할 수 있도록 해준다. 그러나 모든 이 포트의 사용자들이 그 flag를 지정할 경우에만이다. 이것은 멀티캐스팅 어플리케이션을 위한 의도라고 생각된다. 왜냐하면 만약 여러분이 같은 어플리케이션을 하나의호스트에서 실행할 때 모든 어플리케이션은 같은 포트를 바인드할 필요가 있기 때문이다. 그러나 이 flag는 다른 용도도 가지고 있다. 예를 들어 다음의 이 사람이 올린 글을 보면...

Stu Friedberg (stuartf@sequent.com):

SO_REUSEPORT는 ftpd의 데이터 연결 설정에서 try-10-times-to-bind 해킹을 제거하기위해 유용하다. SO_REUSEPORT가 없으면 오직 하나의 ftpd thread만이 준비하고 있는 클라이언트쪽의 TCP(lhost, lport, INADDR_ANY, 0)와 연결 할 수 있다. 이런 많은 로드가 발생하는 상황에서는 try-10-times해킹에서보다 더 많은 충돌 thread들이 발생한다.
SO_REUSEPORT로 모든 것이 잘 돌아가고, 해킹은 무의미 하게 된다.

나는 또한 DEC OSF가 이 flag를 지원한다는 말을 들었다. 또한 4.4BSD하에서 만약 여러분이 멀티캐스트 주소를 바인딩 하는지, 그리고 SO_REUSEADDR이 SO_REUSEPORT와 같다고 생각되는지를 주의하라.(p. 731 of "TCP/IP Illustrated, Volume 2").
나는 솔라리스하에서 여러분들은 단지 SO_REUSEPORT를 SO_REUSEADDR로 바꾸어 놓을 수 있다고 생각한다.

Stevens이 보낸 글중 약간 고친것:

기본적으로 SO_REUSEPORT는 멀티캐스팅이 BSD에 추가되면서 생긴 것이다. 비록 그것이
Steve Deering코드에서 사용되지 않을 지라도. BSD 파생시스템에는 OSF 도 해당된다. 그래서 SO_REUSEPORT는 여러분들이 같은 주소와 포트를 바인드 할 수 있도록 해준다. 그러나 모든 바인더가 그것을 지정 했을 경우에만 그렇다는 얘기다. 그러나 멀티캐스팅 주소를 바인드 할 때, SO_REUSEADDR은 완전히 SO_REUSEPORT 와 같다.(P 731, "TCP/IP Illustrated,
Volume 2". 그래서 멀티캐스팅 어플리케이션의 호환성을 위해 나는 항상 SO_REUSEADDR를 사용한다.


4.12. 어떻게 multi-homed 서버를 만들 수 있는가?

원래의 질문은 사실 Shankar Ramamoorthy(shankar@viman.com)이 한거다. 나는 multi-homed
호스트에서 사용하길 원한다. 그 호스트는 두 네트웍의 부분에 물려 있으며, 두 개의 랜카드가 있다. 나는 이 기계에서 미리 정해진 포트 번호에 서버를 실행 시키고 싶다. 나는 각
각의 이더넷 클라이언트들이 그 포트로 브로드캐스트 패킷을 보내고 서버가 그것을 받을 수 있도록 하고 싶다.

Andrew Gierth (andrew@erlenstar.demon.co.uk):

이 시나리오에서 당신의 첫 번째 질문에서 패킷이 오는 서브넷이 어느 서브넷인지 알아야 한다. 나는 이것이 정말 모든 경우에 결정될 수 있는것인지 전혀 확신할 수가 없다.
만약 당신이 주의깊게 할 수 있다면, 당신이 필요한 것은 INADDR_ANY로 하나의 소켓 바운
드를 하는 것이다. 그것은 모든 것을 매우 간단하게 만들어 준다.

만약 당신이 주의깊게 할 수 있다면, 다중 소켓을 바인드 해야 한다. 이것을 분명히 당신의 코드에 적용시켜라.

나는 아래와 같은 것이 제대로 동작하길 바란다. 그럴까요?
그건 Solaris 2.4/2.5가 돌아가고 있는 Sparc에서 제대로 동작한다.
나는 솔라리스를 써본적이 없다. 그러나 나의 다른 유닉스 사용 경험에 비추어 코멘트 해건데.

[Shankar의 실제 코드는 제거됨]

당신이 하고 있는 것은현재 호스트의 모든 unicast 주소를 hosts/NIS/DNS 호스트에 나와 있는 모든 호스트에 바인드 시키려는 것이다. (이것이 실제 상황을 반영했든 안했든 간에.), 그러나 더 중요한건, 브로드캐스트 주소를 무시하는 것이다. 이것은 소켓이 목적지 브로드캐스트 주소를 가지고 들어오는 패킷을 보지 않을 unicast주소로 바운드 하는 구현
(implementation)의 대다수의 경우 인 듯 보인다.

내가 선택한 방법은 SIOGIFCONF를 사용해서 활동중인 네트웍인터페이스의 리스트를 얻어 내고 SIOCGIFFLAGS와 SIOCGIFBRDADDR를 브로드캐스트 가능한 인터페이스를 알아내고 브로드캐스트 주소를 얻는데 사용할 수 있다. 그리고 나서 각각의 unicast주소와 브로드캐스트주소에 바인드하고, 그리고 또한 INADDR_ANY에도 바인드 한다. 그 마지막은 목적지에서INADDR_BROADCAST로 애태우고 있는 패킷을 잡는게 필요하다.
(SO_REUSEADDR는 INADDR_ANY
뿐만 아니라 지정된 주소도 바인드 하는데도필요하다.)
이것은 거의 내가 원하는 바를 제공해 준다. 모책은 다음과 같다.
나는 특정 소켓을 통하여 패킷을 취하는 것이 반드시 그것이 실제 그 인터페이스에 도달했
다는 것을 가정하지 않는다.

만약 그것의 목적지가 INADDR_BROADCAST 일 때 나는 패킷이 발생한 어떤 서브넷에 관해서도 알 수가 없다. 외관상 멀티캐스트를 지원하는 몇몇 스택들 위에서 나는 INADDR_ANY 을 통하여 들어오는 메시지를 복제한다.

4.13. 어떻게 한 번에 한문자만 읽을 수 있는가?

이 질문은 보통 그들의 서버를 telnet으로 테스트를 하거나 키 입력에 따른 프로세싱을 원하는 사람들에 하는 질문이다. 올바른 기법은 psuedo 터미널(Pty)를 사용하는 것이다.

Roger Espel Llima (espel@drakkar.ens.fr)에 의하면 여러분은 여러분의 서버가 다음과 같은 제어문자의 순서를 보내도록 할 수 있다. (0xff 0xfb 0x01 0xff 0xfb
0x03 0xff 0xfd 0x0f3) 이것은 다음과 같이 해석된다.(IAC WILL ECHO IAC WILL
SUPPRESS-GO-AHEAD IAC DO SUPPRESS-GO-AHEAD) 이것이 뭘 의미하는지에 대한 더
자세한 정보는 std8, std28, std29 를 참조하라. 그리고 Roger는 또한 다음과 같은
tip을 줬다.

이 코드는 echo를 막을 것이다. 그래서 만약 사용자가 그것들을 보게 하길 원한다면 사용자가 클라이언트에게 다시 입력하는 그 문자를 보내야 할 것이다.

캐리지 리턴은 널문자 다음에 넘어 올 것이다. 그래서 그것들을 예상하고 있어야 할 것이다. 만약 당신이 0xff를 받게 되면, telnet 탈출 코드인 그 다음 두 코드를 받게 될 것
이다. pty의 사용은 또한 자식 프로로세스를 실행시키고 i/o를 소켓으로 전달하는 올바른 방법이 될 것이다.
나는 faq에 포함하려고 하는 예제소스에 pty부분을 포함시켰다.
만약 어떤 사람이 이 faq에 넣어서 좋을 소스(저작권이 없는)를 가지고 있으면 나에게
email로 좀 보내 달라.

4.14. 서버에서 exec()를 실행하고 그것에 소켓 I/O를 attach시키려고 한다.
그런데 그것을 통하여 모든 데이터를 얻지 못하고 있다. 왜 그런가?

만약 당신이 printf()이나 다른 stdio.h에 정의되어 있는 스트림 함수를 사용하여 실행시키고 있는 그 프로그램이 있을 경우 당신은 두 개의 버퍼를 가지고 있어야 한다. 커널은 모
든 소켓 I/O를 버퍼링 시킨다. 이것은 2.11에 설명되어 있다. 두 번째 버퍼가 그 재난의
버퍼(?)인데 이것은 stdio버퍼이다. 그리고 이문제는 Andrew에 의해서 잘 설명되고 있다.:
(이 질문에 간단하게 대답하면 당신은 소켓 보다는 pty를 사용해야 한다. 이 문서의 나머지에서 왜 그런지를 설명하려 한다)

우선, setsockopt()에 의해 제어되는 소켓 버퍼는 stdio버퍼에 대해 아무런 관계가 없다. 그것을 1로 설정하는 것은 잘못되도록 한다(tm)

아마 아래 그림이 보다 잘 설명 할 것이다.

프로세스 A 프로세스 B
+---------------------+ +---------------------+
| | | |
| mainline code | | mainline code |
| | | | ^ |
| v | | | |
| fputc() | | fgetc() |
| | | | ^ |
| v | | | |
| +-----------+ | | +-----------+ |
| | stdio | | | | stdio | |
| | buffer | | | | buffer | |
| +-----------+ | | +-----------+ |
| | | | ^ |
| | | | | |
| write() | | read() |
| | | | | |
+-------- | ----------+ +-------- | ----------+
| | User space
------------|-------------------------- | ---------------------------
| | Kernel space
v |
+-----------+ +-----------+
| socket | | socket |
| buffer | | buffer |
+-----------+ +-----------+
| ^
v |
(AF- and protocol- (AF- and protocol-
dependent code) dependent code)

이 두 프로세스가 각각 통신한다고 가정하자. 여러분은 A프로세스가 stdio 버퍼에 의해서
write 되는 데이터를 볼 수 있을 것이다. 그러나 이 버퍼는 B프로세스에 의해서는 접근이
불가능하다. 그리고 그 버퍼의 내용을 커널로 플러시 할 경우에만 그 데이터는 실제로 다른 프로세스에 전달 된다.

A프로세스 안에서 버퍼링에 영향을 주는 한가지 확실한 방법은 코드를 바꾸는 것이다.
그러나 stdout을 위한 기본 버퍼링은 근원인 FD가 터미널을 가리키느냐 아니냐에 의해 제어된다. 일반적으로 터미널 출력은 line-buffered되어 있다. 그리고 터미널이 아닌 곳으로의출력은(파일이나 파이프, 소켓 그리고 non-tty 장치등) 완전한 버퍼링이 된다. 그래서 바라는 효과는 보통 pty장치를 사용함으로써 얻을 수 있다. 예를 들어 이것은 'expect' 프로그램이 하는 방법이다.

stdio버퍼가 사용자레벨 데이터라면 exec()호출을 통해 보존되지 않는다. 그러므로 exec가효력을 발휘하기 전에 setvbuf()를 사용하라.

다른 몇 개의 대안은 Roger Espel Llima(espel@drakkar.ens.fr)에 의해 제안 되었다.:

만약 이것이 선택사항이라면, 어떤 standalone프로그램이 그것의 입출력버퍼와 pty안에
서 어떤것인가를 수행 시킬수 있을 것이다. pty.tar.gz이라는 프로그램이 있는데 이게 그런 일을 하는 것 같다. 한 번 AltaVista같은걸로 찾아봐라.
다른 선택사항(**경고, 나쁜 해킹이다.**), 만약 여러분이 이것을 지원하는 시스템
(SunOS, Solaris, Linux ELF 가 지원한다. 다른 것은 잘 모르겠다.)에서 여러분의 주 프로그램에 putenv()을 이용해 LD_PRELOAD 환경변수에 공유 라이브러리(*.so)를 넣어라, 그리고 나서 .so 안에서 몇몇 일반적으로 쓰이는 libc 함수(당신의 exec하는 것이 일찍 실행될 것이라고 알려진) 를 재정의(redefine) 한다. 여기서 그 실행되는 시스템에서 'get control'을 할 수 있다. 그리고 그것을 얻은 처음에, setbuf(stdout, NULL)을 여러분의 프로그램 중간에 실행하라, 그리고 나서 원래의 libc함수를
dlopen()+dlsysm()과 함께 호출하라. 그리고 dlsym()값을 static 변수로 유지한다.
그러면 당신은 다음의 몇번 이것을 호출 할 수 있다.

5. UDP/SOCK_DGRAM 어플리케이션 작성

5.1. 언제 TCP대신 UDP를 사용해야 하는가?

UDP는 다른 시스템으로 가게 되는 메시지의 순서가 중요하지 않을 경우에 메시지를 한 시스템에서 다른 시스템으로 전송하는 좋은 방법이다. 이것은 이것은 faq에서 내가 예제 소소코드에서 UDP는 한 번만 사용한 이유이다. 보통 TCP가 더 좋은 솔루션이다. 그것은 메시지가 다른 호스트로 전달 된 것에 대한 신뢰를 줄 수 있고 순서에도 확신을 할 수 있게 전달 되었는지를 확인하는 소스코드의 추가 작성을 줄여 준다. 꼭 알고 있어야 하는 것은 소스코드의 길이가 늘어나면 늘어날수록 버그는 증가하게 된다는 것이다.

만약 TCP이 여러분이 바라는 것보다 무척 드리다는 것을 발견하게 되면 UDP를 사용하면 좀더 나은 성능을 얻을 수 있다. 물론 메시지 순서와 신뢰성은 좀 희생 될 것이다.

UDP는 동시에 한 시스템 이상에게 메시지를 전달하는 멀티캐스트 메시지로 사용될 수 있다.
TCP에서 어플리케이션은 목적지 시스템 각각에 대해 연결을 설정해야 하고 각각의 시스템에메시지를 보내야 한다. 이것은 여러분의 어플리케이션이 오직 여러분이 알고 있는 시스템에게만 메시지를 전달 할 수 있다는 것을 의미한다.

5.2. "connected" 소켓과 "unconnected"소켓의 차이는 무엇인가?

Andrew Gierth (andrew@erlenstar.demon.co.uk):

만약 이용가능한 목적지 주소가 없어서, bind()호출 후에 일반적인 상태인 UDP소켓이
unconnect된 상태는 send()또는 write()가 허용되지 않고 오직 sendto()호출에 의해서만 데이터를 전송할 수 있다.

소켓에서 connect()를 호출하는 것은 지정된 주소 레코드와 포트번호를 원하는 통신 파트너로 변환된다. 그것은 send() 또는 write()가 허용되지 않는다는 것을 의미한다. 그들은 connect호출에 의해서 주어진 목적 주소지와 포트를 사용한다.

5.3. connect() 호출이 소켓의 수신에 영향을 주는가?

Richard Stevens (rstevens@noao.edu):

그렇다 두 가지다.
첫 번째, 당신의 "connected peer"로부터 오는 데이터그램은 리턴된다. 모든 당신의 포
트에 다른 도착하는 것들 모두는 당신에게 전달되지 않는다. 그러나 가장 중요한 것은,
UDP소켓은 ICMP 에러를 받기 위해 연결되어야만 한다는 것이다.
"TCP/IP Illustrated, Volume 2"의 Pp. 748-749에 보면 왜 이것이 이런지 설명하고 있다.

5.4. "connected" UPD 소켓으로부터 어떻게 하면 ICMP error를 읽을 수 있는가?

만약 상대 기계가 메시지를 버리게 되면(요청된 포트 번호를 읽는 프로세스가 없어서) 그
서버는 당신의 서버에 ICMP메시지를 보내서 그 소켓에서 동작하는 다음 시스템 호출이
ECONNREFUSED를 리턴하도록 만든다.

기억할 것은 당신의 소켓은 ICMP에러를 받기 위해서는 "connected"되어 있어야 한다는 것이다. 예전에 얘기 했지만, Alan Cox는 리눅스에서는 "unconnected"된 소켓에서도 리턴 하는 것을 확인 했다는데, 이것은 당신의 어플리케이션에 포팅 문제를 일으킬 수 있다. 따라서 그런 어플리케이션에는 리눅스 커널 2.0.0 이후 버전을 위해 그것에
SO_BSDCOMPAT flag를 설정하도록 하고 있다.

5.5. 어떻게 UDP메시지가 수신 되었다는 것을 확신 할 수 있는가?

당신은 상대방이 메시지를 수신했음에 대한 confirm을 받을 수 있도록 당신의 프로토콜을
디자인 해야 한다. 물론 confirm은 UDP에 의해서 전송된다. 그리고 그것도 신뢰성이 없는것이고 송상자로 부터 수신자에게 제대로 전달된다는 보장을 못한다. 만약 전송자가 일정시간 동안 confirm을 받지 못하면 최소 한 번 이상 그 메시지를 재전송해야 한다. 그리고 재전송에 따른 복제된 데이터를 원래 데이터 다음에 다시 받게되는 경우 그 복제된 데이터는 무시 할 수 있도록 구현 되어야 한다. 대부분의 프로토콜은 이미 메시지를 받았는지를 알아내기 위해 메시지 넘버링 체계를 가지고 있다. confirmation은 또한 전송자가 어느 메시지가 confirm되었는지 알 수 있기 위해 그 메시지 번호를 참조해야 한다. 정신 없다고? 이래서 TCP를 쓰는 거다.

5.6. UDP 메시지가 순서대로 도착했음을 확신할 수 있는가?

할수 없다. 할 수 있는건 메시지가 순서대로 처리 되었는지만을 알 수가 있다. (5.5를 보
라) 만약 메시지가 순서대로 도착하는 것을 확신할 수 있어야 한다면 TCP를 쓰라. 그게 훨
씬 시간도 절약해 주고 프로그램을 신뢰성 있게 할 수가 있다.

5.7. 얼마나 자주 un-ack된 메시지에대해서 재전송 해야 하는가?

해야할 가장 간단한 것은 적당하게 작은 지연시간(약 1초 정도)를 갖는 것이다. 이 문제는
이것이 여러분의 네트웍이 랜 또는 다른 기계 등의 문제가 있을 경우에 더 큰 트래픽 문제
를 일으킬 수 있다.

더 좋은 기법은 "UNIX Network Programming"책에 기술되어 있는데, 적당한 timeout를 잠재적인 backoff(포기)로 사용하는 것이다. 이 기법은 호스트에 도달고자 하는 메시지와 그에 따른 타임아웃 조정을 획득하는 시간의 통계정보를 유지시켜준다. 그것은 또한 불필요한 데이타그램으로 네트웍이 넘치지 않도록 하기위한 각 타임아웃 시간을 두배로 만든다. Richard는 친절하게도 이것에 관한 소스코드를 다음의 웹사이트에 등록해 놓았다.

http://www.noao.edu/~rstevens.

5.8. 왜 데이터 그램의 첫 부분만 들어오고 있나?

이것은 두 연관된 기계에서 최대 데이터그램 크기를 가지고 해봐야 한다. 이것은 관련된 시스템과 MTU(Maximum Transmission Unit)에 의존되는 것이다. "Unix Network Programming"책에 의하면 MTU의 크기에 상관 없이 모든 TCP/IP 구현들은 데이터그램 사이즈가 최소 576바이트이어야 한다고 되어 있다. 20바이트의 IP헤더 그리고 8바이트의 UDP헤더, 그리고 나머지 548바이트는 UDP메시지의 안전한 최대 크기이다. 최대 크기는
65516바이트이다. 몇몇 플랫폼에서는 IP조각을 허용하여 이것에 의하여 데이터 그램이 조각나고(MTU값 때문에) 그리고 나서 상대쪽에서 가서 다시 재조립된다.

그러나 모든 implementation들이 이것을 지원하는 것은 아니다.

Andrew는 다음의 큰 UDP메시지에 관해서 눈을 돌리고 있다.

다른 이슈는 조각(fragmentation)이다. 만약 데이터그램이 네트웍 인터페이스(랜카드)를 통해 전송하기에는 너무 크면 전송 호스트는 이것을 조각내서 상대쪽 호스트에서 재조립하도록 한다.

그리고 만약 그 호스트 사이에 라우터가 있다고 하면 그들은 또한 하나 또는 그 이상의 조
각들을 잃게 될 수 있다.(이렇게 되면 전체 데이터그램이 상실된다.) 그러므로 인터넷의 여러 라우터를 거칠 가능성이 있는 어플리케이션에서는 큰 UDP메시지는 왠만하면 피해야 한다.
5.9. 기대했던 것 보다 빨리 소켓의 버퍼가 꽉 차는데 왜 그런가?
Paul W. Nelson (nelson@thursby.com):

전통적인 BSD소켓시스템에서 UDP처럼 작은 소켓은 수신된 데이타를 mbufs의 리스트에 저장한다. mbuf는 다양한 프로토콜 스택에 의해 공유된 고정된 크기의 버퍼이다. 수신 버퍼 크기를 설정할때, 프로토콜 스택은 실제 바이트수의 수가 아닌 mbuf공간의 바이트를 수신 버퍼에 유지하고 있다. 이 방법은 정말 여러분이 제어하고 있는 자원들이 소켓 버퍼에 얼마나 많이 포함되어 있는지가 아니라 얼마나 많은 mbuf가 사용 되는지를 제어함을 위한 것이다.

(소켓 버퍼는 전통적인 의미의 버퍼가 아니다. 이것은 mbuf의 리스트이다.)

예를 들어: 여러분의 유닉스에 mbuf 크기가 256바이트 의 작은 크기라고 하자. 만약 수신
소켓버퍼가 4096으로 설정되어 있다면, 여러분은 소켓 버퍼위에 16 mubf를 고정시킬 수 있다. 만약 각각이 10바이트인 16개의 UDP 패킷을 받았다고 하면 여러분의 소켓 버퍼는 full이 될 것이다. 그리고 여러분은 160바이트의 데이터를 갖게 될 것이다. 만약 각각 200바이트인 16개의 UDP패킷을 받았다고 하면 여러분의 소켓 버퍼는 역시 full이 될 것이지만 3200바이트의 데이터를 갖게된다. FIONREAD는 메시지 또는 mbuf의 바이트 수가 아닌 총 바이트 수를 리턴한다. 이렇기 때문에 이것은 여러분의 수신버퍼가 어느정도 차 있는지를 측정하는 좋은 지시자는 못된다.

게다가, 만약 여러분이 260바이트의 UDP메시지를 받으면, 여러분은 두개까지 mbuf를 사용할 수 있고 버퍼가 가득차기 전에 오직 8개의 패킷을 받을 수 있다. 이 경우 4096 바이트 중에서 2080바이트만이 소켓 버퍼에 들어가게 된다.

이 예는 아주 간단한 것이다. 그리고 실제 소켓 버퍼 알고리즘은 또한 몇가지 다른 파라메터를 포함하고 있다. 예전의 몇몇 소켓 시스템은 128바이트의 mbuf를 사용했음을 주지하라.

6. 고급 소켓 프로그래밍

6.1. 현재의 소켓 프로그램을 비블록킹 모드로 바꾸는 방법은?

Andrew Gierth (andrew@erlenstar.demon.co.uk):

기술적으로, fcntl(soc, F_SETFL, O_NONBLOCK), 이런식으로 쓰면 다른 파일 flag에 문제를
일으키게 되므로 쓰면 안된다. 일반적으로 이건 별 문제 없이 잘 동작하는데 이것은 이것
말고 다른 flag들(예를 들어 O_APPEND)등은 소켓에 별 다른 영향을 주지 않기 때문이다.
비슷하게, fcntl(soc, F_SETFL, 0)을 이용하여 블록킹 모드로되돌릴 수 있다.

이것을 제대로 하려면, F_GETFL을 이용해 현재의 flag를 얻고, O_NONBLOCK flag를 설정하고 삭제한다.

그리고 이 flag는 원하는 방법으로 변경할 수 있다.


6.2. connect()의 타임아웃을 설정하는 방법은?

Gierth (andrew@erlenstar.demon.co.uk)

connect()에서 select()를 사용하는 방법을 이용하면 connect()호출에서 타임아웃을 설정할 수 있다.

우선, 소켓을 만들고 비블록킹 모드로 만들고, 그런다음 connect()를 호출하라.
3개의 가능성이 있다.

connect succeeds: 연결이 제대로 됨. (이것은 보통 같은 기계로 연결될때 발생된다)
connect fails: 뻔하죠?
connect 가 -1/EINPROGRESS를 리턴한다. 연결 시도가 시도 됐으나, 아직 완결되지 않
았다.

만약 연결이 성공하면
소켓은 쓰기가능으로 select()한다.(그리고 데이터가 도착하면 일기 가능하게 된다)
연결이 실패하면 소켓은 읽기와 쓰기로 select 할 것이다. 그러나 read 또는 write는 연결 시도로부터 에러코드를 리턴할 것이다. 또한 getsockopt(SO_ERROR)를 사용하여 에러 상태를얻을 수 있다-그러나 조심하라; 몇몇 시스템은 getsockopt()의 결과 파라메터안에 있는 에러코드를 리턴한다. 그러나 또한 다른 시스템들은(옳지 못하지만) getsockopt 호출 자체가 에러로 저장된 값을 에러로하여 실패하게 된다.

이것에 관한 예제 소스파일은 connect.c이다.