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