출처 http://home.hanmir.com/~johnsonj/
소켓 프로그래밍 하우투
고든 맥밀란(Gordon McMillan)
gmcm@hypernet.com
한글판 johnsonj 2003.12.17
요약:
소켓은 거의 어느 곳에서나 사용되지만, 널리 퍼진 테크놀로지 중에서 가장 오해가 심한 테크놀로지 중의 하나이다. 이 문서는 소켓을 만 발자국 떨어져서 살펴 본 것이다. 실제로는 자습서가 아니다 – 여전히 공부를 더 해야 실제로 작동시킬 수 있을 것이다. 자세하게 요점을 살피지는 않지만 (요점도 너무나 많다), 모쪼록 충분한 배경지식을 얻어서 품위있게 사용하시길 바라는 바이다.
이 문서는 파이썬 하우투 페이지 http://www.python.org/doc/howto에서 얻을 수 있다.
목차
1 소켓
1.1 역사
2 소켓 만들기
2.1 프로세스간 통신(IPC)
3 소켓 사용
3.1 이진 데이터
4 접속종료
4.1 소켓이 죽을 때
5 비-블로킹 소켓
5.1 수행성능
이 문서에 관하여 …
1 소켓
소켓은 거의 어느 곳에서나 사용되지만, 널리 퍼진 테크놀로지 중에서 가장 오해가 심한 테크놀로지 중의 하나이다. 이 문서는 소켓을 만 발자국 떨어져서 살펴 본 것이다. 실제로는 자습서가 아니다 – 여전히 공부를 더 해야 실제로 작동시킬 수 있을 것이다. 자세하게 요점을 살피지는 않지만 (요점도 너무나 많다), 모쪼록 충분한 배경지식을 얻어서 품위있게 사용하시길 바라는 바이다.
INET 소켓에 관해서만 언급하겠다. 하지만 적어도 사용되는 소켓의 99%를 설명하는 것이다. 오직 STREAM 소켓에 대해서만 언급하겠다 – 무슨 일을 하고 있는지 정말로 모르지 않는 한 (이런 경우 이 하우투는 여러분에게 도움이 되지 않는다), 다른 어떤 것보다도 STREAM 소켓으로부터 더 좋은 수행성능과 행위를 얻을 것이다. 소켓이 무엇인지 그 신비를 깨끗이 벗겨내 보고, 뿐만 아니라 블로킹 소켓과 비-블로킹 소켓과 작동시키는 법에 관하여 약간의 힌트를 제공하겠다. 그러나 먼저 블로킹 소켓에 관하여 언급하겠다. 블로킹 소켓이 어떻게 작동하는지 먼저 이해해야만 비-블로킹 소켓을 다룰 수 있을 것이다.
이런 일을 이해하는데 어려움을 겪는 이유는 “소켓(socket)”의 의미가 상황에 따라 미묘하게 수 없이 다른 것을 의미할 수 있다는 것이다. 그래서, 먼저 “클라이언트(client)” 소켓과 “서버(server)” 소켓을 구별하도록 하자. 클라이언트 소켓은 대화의 종단점이고 서버 소켓은 전화 교환수와 상당히 닮았다. 클라이언트 어플리케이션(예를 들어, 브라우저)은 “클라이언트(client)” 소켓을 단독으로 사용한다; 대화의 상대방인 웹 서버는 “서버” 소켓과 “클라이언트” 소켓을 모두 사용한다.
1.1 역사
다양한 형태의 IPC (프로세스간 통신(Inter Process Communication)) 중에서, 소켓은 인기가 별로 없다. 어떤 플랫폼이 딱 주어지면, 다른 형태의 IPC가 더 빠를 가능성이 높다. 하지만 크로스-플랫폼 통신에 대해서라면, 소켓만이 추구할 만한 유일한 목표(only game in town)이다.
소켓은 버클리대에서 BSD 취향의 유닉스의 일부분으로 만들어졌다. 소켓은 요원의 불길처럼 인터텟을 타고 번졌다. 충분한 이유가 있었는데 — 소켓을 INET과 조합하면 (적어도 다른 체계와 비교하여) 전 세계에 걸친 어떤 머신과도 놀라울 정도로 쉽게 대화할 수 있기 때문이다.
2 소켓 만들기
대략적으로 이야기하면, 이 페이지로 오는 링크를 클릭할 때, 여러분의 브라우저는 다음과 같은 일을 한 것이다:
#INET을 만들고, 소켓을 스트림(STREAM)화 한다
s = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
# 이제 80번 포트로 웹 서버에 접속한다
# – 일반적인 http 포트
s.connect((“www.mcmillan-inc.com”, 80))
접속(connect)이 완료되면, 소켓 s는 이제 이 페이지의 텍스트에 대한 요청을 보내는데 사용될 수 있다. 같은 소켓이 그 응답을 읽고난 다음, 파괴된다. 바로 그거다 – 파괴된다. 클라이언트 소켓은 보통 한 번의 교환에만 (또는 연속적으로 조금씩 교환할 때만) 사용된다.
웹 서버에서 일어나는 일은 약간 더 복잡하다. 먼저, 웹 서버는 “서버 소켓”을 만든다”.
# INET을 만들어서, 소켓을 스트림(STREAM)화 한다
serversocket = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
# 소켓을 공개 호스트와,
# 잘 알려진 포트에 묶는다
serversocket.bind((socket.gethostname(), 80))
# 서버 소켓이 된다
serversocket.listen(5)
두 가지 주의 사항: 소켓을 외부 세계가 볼 수 있도록 하기 위해 socket.gethostname()을 사용했다. s.bind((”, 80))나 s.bind((‘localhost’, 80)) 또는 s.bind((’127.0.0.1′, 80))를 사용했다면, 여전히 “서버” 소켓이 필요하지만, 그 서버 소켓은 같은 머신 안에서만 볼 수 있다.
두 번째 주의사항: 낮은 포트 번호는 보통 “잘 알려진” 서비스에 예약되어 있다 (HTTP, SNMP 등등). 그저 놀아볼 요량이라면, 아주 높은 번호를 사용하자 (4 자리수).
마지막으로, listen에 넘겨지는 인자는 최대로 5 개의 요청만 큐에 담고 (보통 최대값) 그를 넘어서면 외부로부터의 접속을 거부하라고 소켓 라이브러리에 지시한다. 나머지 코드가 제대로 작성되었다면, 그 정도로 충분하다.
좋다, 이제”서버” 소켓을 확보하였고, 80번 포트에서 대기중이다. 이제 웹 서버의 주회돌이로 들어가 보자:
while 1:
# 외부로부터 들어온 접속요청을 받아들인다
(clientsocket, address) = serversocket.accept()
# 이제 클라이언트 소켓(clientsocket)에 무언가를 한다
# 이 경우에는, 이것이 쓰레드 서버라고 간주한다
ct = client_thread(clientsocket)
ct.run()
실제로 이 회돌이를 작동시키려면 일반적으로 세가지 방법이 있다 – 쓰레드를 분배해서 clientsocket을 다루거나, 새로 프로세스를 만들어서 clientsocket을 다루거나, 또는 이 어플리케이션이 비-블로킹 소켓을 사용하도록 구조를 바꾸어서, select를 사용하여 “서버” 소켓과 살아있는 클라이언트 소켓(clientsocket) 사이를 다중화(mulitplex)하는 것이다. 더 자세한 것은 나중에 다룬다. 현재 알아야 하는 가장 중요한 일은 이것이다: 다음은 “서버” 소켓이 하는 모든 것이다. 데이터를 보내지 않으며 데이터를 받지도 않는다. 그저 “클라이언트” 소켓을 만들어 낼 뿐이다. 각 clientsocket은 묶어 놓은 호스트와 포트에 connect()를 해 오는 다른 “클라이언트” 소켓에 대한 응답으로 만들어진다. clientsocket을 만들고 나면, 바로 다시 돌아가 더 많은 접속을 기다린다. 두 “클라이언트”는 자유롭게 대화할 수 있다 – 동적으로 할당되는 포트를 사용하는데 이 포트는 대화가 끝나면 다시 사용된다.
2.1 프로세스간 통신(IPC)
한 머신에서 두 프로세스간에 빠른 IPC가 필요하면, 플랫폼이 제공하는 공유 메모리의 형태를 살펴 보아야 한다. 공유 메모리와 메모리 잠금 또는 세마포어에 기반한 간단한 프로토콜은 빠른 테크닉과 거리가 멀다.
소켓을 사용하기로 결정하였다면, “server” 소켓을 ‘localhost’에 묶자. 대부분의 플랫폼에서, 이렇게 하면 대략 층 두개 정도는 가로지를 것이고 훨씬 더 빠를 것이다.
3 소켓의 사용
주의할 한 가지는 웹 브라우저의 “클라이언트” 소켓과 웹 서버의 “클라이언트” 소켓이 동일한 괴물이라는 것이다. 다시 말해, 이는 “동등 통신” 대화이다. 다른 방식으로 말해 보면, 디자이너로서, 대화에 어떤 규칙의 에티켓을 사용되어야 하는지를 결정해야 한다는 뜻이다. 보통은 소켓에 접속(connect)하면 요청 또는 허가를 보냄으로써 대화가 시작된다. 그러나 그것은 디자인 결정이다 – 그것은 소켓의 규칙이다.
이제 대화에는 두 가지 동사 집합이 사용된다. send와 recv를 사용하거나, 클라이언트 소켓을 파일-류의 어떤 것으로 변형을 한 다음 read와 write를 사용하면 된다. 자바는 뒤의 방법으로 소켓을 나타낸다. 여기에서 그에 관해 언급할 생각은 없으며, 단지 소켓에 flush를 사용할 필요가 있다는 점을 지적하고 싶다. 이런 소켓들은 버퍼화된 “파일”이어서, 자주 무언가를 쓴(write) 다음, 바로 대답을 읽으려는(read) 실수를 한다. 이 경우 flush를 사용하지 않으면, 대답을 영원히 기다려야할지도 모른다. 왜냐하면 요구한 것이 여전히 출력 버퍼에 있을 수 있기 때문이다.
이제 소켓에서 가장 넘기 힘든 장애물에 도착했다 – send와 recv는 네트워크 버퍼에서 작용한다. 이 두 함수는 반드시 건네 준 (또는 받으리라고 기대한) 모든 바이트를 처리하지는 않는데, 왜냐하면 초점이 네트워크 버퍼의 처리이기 때문이다. 일반적으로, 그 두 함수는 연관된 네트워크 버퍼가 꽉 차거나 (send) 텅 빌 때 (recv) 반환된다. 그리고나서 얼마나 많은 바이트를 처리했는지 알려준다. 메시지가 완전히 처리될 때까지 두 함수를 반복해서 호출하는 것은 여러분의 책임이다.
recv가 0 바이트를 돌려주면, 그것은 상대편이 접속을 끊었다는 뜻이다 (또는 접속 중단 상태에 있다는 뜻이다). 이 접속에서는 데이터를 더 이상 받지 못할 것이다. 영원히 말이다. 데이터는 성공적으로 보낼 수 도 있다; 다음 페이지에서 그에 관해 언급해 보겠다.
HTTP과 같은 프로토콜은 단 한 번의 전송에만 소켓을 사용한다. 클라이언트는 요청을 보낸다. 그리고 그 응답을 읽는다. 그게 다이다. 사용된 소켓은 폐기된다. 이는 곧 0 바이트를 수신하면 클라이언트가 응답의 끝을 탐지할 수 있다는 뜻이다.
그러나 소켓을 재사용하여 더 전송할 생각이라면, 소켓에는 “전송끝문자(EOT(End of Transfer))가 없다는 사실을 알아야 한다. 다시 한 번 강조한다: send 소켓이나 recv 소켓이 0 바이트를 처리한 후에 반환되면, 접속이 끊어진 것이다. 그 접속이 아직 끊어지지 않았다면, 영원히 recv 소켓을 기다려야 할지도 모르는데, 왜냐하면 그 소켓은 (지금 당장) 더 읽어야 할 것이 아무것도 없다는 사실을 말해 주지 않을 것이기 때문이다. 이러하므로 조금만 생각해 보면, 소켓의 근본적인 진실을 깨닫게 될 것이다: 메시지는 반드시 길이가 고정되어야 하거나 (안되), 반드시 구분되어야 하거나 (할 수 없지), 또는 얼마나 긴지 나타내어야 하거나 (훨씬 좋군), 또는 접속을 닫아서 끝내야 한다. 선택은 완전히 여러분의 책임이다 (그러나 몇가지 방법이 다른 방법보다 더 올바르다).
접속을 끊고 싶지 않다면, 가장 쉬운 해결책은 메시지 길이를 고정하는 것이다:
class mysocket:
”’demonstration class only
– coded for clarity, not efficiency”’
def __init__(self, sock=None):
if sock is None:
self.sock = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
else:
self.sock = sock
def connect(host, port):
self.sock.connect((host, port))
def mysend(msg):
totalsent = 0
while totalsent < MSGLEN:
sent = self.sock.send(msg[totalsent:])
if sent == 0:
raise RuntimeError, \r
“socket connection broken”
totalsent = totalsent + sent
def myreceive():
msg = ”
while len(msg) < MSGLEN:
chunk = self.sock.recv(MSGLEN-len(msg))
if chunk == ”:
raise RuntimeError, \r
“socket connection broken”
msg = msg + chunk
return msg
여기에 있는 전송 코드는 거의 모든 메시지 체계에 사용할 수 있다 – 파이썬에서 문자열을 보낸다면, (그 문자열에 문자들이 내장되어 있다고 할지라도) len()을 사용하여 그의 길이를 결정할 수 있다. 대부분 수신 코드가 더 복잡하다. (C에서는, 그렇게 사정이 나쁘지 않다. 메시지에 가 내장되어 있을 경우 strlen을 사용할 수 없다는 점만 제외하면 말이다.)
제일 쉽게 개선하는 방법은 메시지의 첫 문자가 메시지 유형을 나타내도록 만들어서, 그 유형으로 길이를 결정하는 것이다. 이제 두 개의 recv 소켓을 받았다 – 첫 번째 소켓은 (적어도) 그 첫 문자를 확보해서 길이를 알아 보도록 하는 것이고, 두 번째 소켓은 회돌이를 하면서 나머지를 받는다. 구분된 경로를 가기로 결정했다면, 크기가 일정하지 않은 메시지를 받으며 (4096바이트나 8192바이트가 보통 네트워크 버퍼 크기에 적당하다), 구분자로 무엇을 수신했는지 훓어 보아야 한다.
꼭 인지 해야할 복잡한 사실 한가지: 대화담당 프로토콜에서 다중 메시지가 (어떤 응답없이도) 다시 이리저리 되돌려져 보내지는 것을 허용한다면, 그리고 제멋대로의 길이로 recv를 보내는 것을 허용한다면, 아마도 따르는 메시지의 첫 부분을 읽어야 한다는 결론에 도달할 것이다. 그 첫 부분을 필요할 때까지 잠깐 옆에다 두고 잘 간직하고 있을 필요가 있다.
메시지 앞에다 (예를 들어, 5개의 숫자 문자와 같이) 그 길이를 두면 더 복잡해진다. 왜냐하면 (믿든 안 믿든), 한 recv에서 5개 문자 모두를 받지 못할 수도 있기 때문이다. 그냥 노는 정도라면, 무시해도 좋을 것이다; 그러나 네트워크 부담이 과도한 곳에서, 코드는 아주 빨리 망가져 버릴 것이다. 두 개의 recv 회돌이를 사용하지 않는 한 말이다 – 첫 번째는 길이를 결정하기 위한 것이고, 두 번째는 메시지의 데이터 부분을 얻기 위한 것이다. 피곤한 일이다. send가 언제나 한 번에 모든 것을 해결해 주지 않는다는 것을 알게 되면 역시 마찬가지다. 이를 읽었다고 할지라도, 결국에는 이 때문에 고민에 빠질 것이다!
여러분이 글을 쓸 기회를 줄 (그리고 나의 경쟁적 위치를 지킬) 공간이라는 관점에서, 이런 개선 방안은 독자들에게 숙제로 남긴다. 계속해서 다듬어 보자.
3.1 이진 데이터
소켓을 통하여 이진 데이터를 완벽하게 보낼 수 있다. 주요 문제는 모든 머신들이 이진 데이터에 대하여 똑 같은 포맷을 사용하는 것이 아니라는 것이다. 예를 들어, 모토롤라(Motorola) 칩은 값 1에 해당하는 16 비트 정수를 16진 바이트 00 01 두 개로 나타낼 것이다. 그렇지만 인텔(Intel)과 DEC에서는 바이트가 뒤집어져서 – 똑 같은 1이 01 00으로 나타난다. 소켓 라이브러리는 16비트와 32 비트 정수를 변환하는 함수들을 갖추고 있다 – ntohl, htonl, ntohs, htons이 그것인데 “n”은 네트워크(network)를 의미하고 “h”는 호스트(host)를, “s”는 단정도(short)를 의미하며 그리고 “l”는 장정도(long)를 뜻한다. 네트워크 순서가 호스트 순서와 같으면, 아무런 일도 하지 않지만, 뒤집어진 바이트를 머신이 사용하면, 바이트들을 적절하게 바꾼다.
오늘날의 32 비트 머신에서, 이진 데이터의 ascii 표현은 보통 이진 표현 보다 작다. 그 때문에 놀라울 정도로 많은 시간이, 장정도 정수 모두가 값으로 0 또는 1을 가지고 있는 것이다. 문자열 “0″은 2 바이트인 반면, 이진 데이터이면, 4 바이트가 될 것이다. 물론, 이는 고정-길이 메시지에는 잘 맞지 않는다. 잘 생각해서 결정하자.
4 접속 끊기
엄밀히 이야기해서, 소켓에 대하여 shutdown을 사용해야만 그 소켓을 닫을(close) 수 있다. shutdown은 상대편의 소켓에 대한 통고이다. 건네 준 인자에 따라, “더 이상 데이터를 보내지 않겠지만, 계속 듣고는 있겠다”, 또는 “듣지 않겠어, 이제 조용해졌군(good riddance)!”이라는 의미가 될 수 있다. 그렇지만 대부분의 소켓 라이브러리에서 보통 close가 shutdown(); close()와 똑 같이 사용되므로 프로그래머들은 이런 에티켓을 무시해도 좋다. 그래서 대부분의 상황에서, 명시적인 shutdown이 필요하지 않다.
shutdown을 효과적으로 사용되는 곳은 HTTP-류의 교환에 있다. 클라이언트가 요청을 보내고 그 다음에 shutdown(1)을 수행한다. 이렇게 해서 서버에게 ” 이 클라이언트가 전송을 마쳤지만, 여전히 받을 수 있다”는 사실을 알려준다. 서버는 0 바이트를 받으면 “EOF”를 탐지할 수 있다. 그 서버는 요청을 다 받았다고 간주할 수 있다. 그리고 서버는 응답을 보낸다. 전송(send)이 성공적으로 완수되었더라도, 실제로, 클라이언트는 여전히 수신중이다.
파이썬은 자동 해제통지를 한 단계 더 발전시켜서, 소켓이 쓰레기 수집되면 필요한 경우 자동으로 닫기(close)를 수행한다. 그러나 이에 의존하는 것은 아주 나쁜 습관이다. 소켓이 close 없이 그냥 사라진다면, 상대방의 소켓이 무한정 기다리게 될 수도 있다. 단지 느릴 뿐이라고 생각하면서 말이다. 일을 마치면 제발 소켓을 닫자(close).
4.1 소켓이 죽을 때
아마도 블로킹 소켓을 사용하는데 있어서 가장 나쁜 일은 상대방이 닫지(close) 않고 그대로 사망할 때 일어난다. 여러분의 소켓은 허공에 떠 버릴 것이다. SOCKSTREAM은 믿을만 한 프로토콜이다. 아주 오랜 시간동안 기다리다가 접속을 포기할 것이다. 쓰레드를 사용하고 있다면, 전체 쓰레드가 완전히 죽는다. 그에 관해서는 할 수 있는 일이 별로 없다. 블로킹 읽기를 하면서 잠금을 유지하는 것과 같이, 무언가 멍청한 짓을 하지 않는 한, 그 쓰레드는 실제로 자원을 많이 소모하지 않는다. 해당 쓰레드를 죽이려고 하지 말자 – 쓰레드가 프로세스보다 더 효율적인 이유는 부분적으로 쓰레드가 자동적인 자원 재생에 연관된 부담을 피하기 때문이다. 다른 말로 해서, 쓰레드를 어떻게 해서든 죽여 버린다면, 프로세스 전체가 아마도 망가져 버릴 것이다.
5 비-블로킹 소켓
절차를 이해하였다면, 이미 소켓 사용의 메커니즘에 관하여 알아야 할 것들을 이미 대부분 안 것이다. 여전히 똑 같은 호출을 거의 같은 방식으로 사용하면 된다. 단지 그 뿐이다. 제대로만 한다면, 어플리케이션을 거의 속속들이 알게 될 것이다.
파이썬에서, socket.setblocking(0)을 사용하면 소켓을 비-블로킹으로 만들 수 있다. C에서는, 더 복잡하다, (일례로, 두 가지 중의 하나를 선택할 필요가 있다. BSD 취향의 O_NONBLOCK과 거의 인정받지 못하는 Posix 취향의 O_NDELAY가 그것이다. O_NDELAY는 TCP_NODELAY와 전혀 다른 것이다). 그러나 그 아이디어는 정확하게 똑 같다. 소켓을 만들고 난 후에, 그러나 사용하기 전에 이렇게 해야 한다 (실제로, 조금만 신경쓰면, 이리 저리 전환할 수 있다.)
주요한 메커니즘의 차이는 send, recv, connect 그리고 accept가 아무것도 하지 않고 반환될 수 있다는 것이다. (물론) 여러가지 선택이 있다. 반환 코드와 에러 코드를 점검하여 미쳐 버리도록 머리를 혹사할 수 있다. 믿지 못하겠다면, 시간을 내서 한 번 시도해 보라. 어플리케이션은 자꾸 커지고, 버그 투성이가 되고, CPU를 질식사 시켜 버릴 것이다. 그러므로 머리를-혹사하는 해결책은 버리고 올바른 해결책을 시도하자.
select를 사용하자.
C에서, select의 코딩작업은 아주 복잡하다. 파이썬에서는 아주 쉬운 일이지만, 파이썬에서 select를 이해하면, C에서도 별 어려움이 없을 정도로 C 버전과 아주 비슷하다.
ready_to_read, ready_to_write, in_error = \r
select.select(
potential_readers,
potential_writers,
potential_errs,
timeout)
리스트 세 개를 select에 건네준다: 첫 번째 리스트에는 읽고 싶은 모든 소켓들이 담겨 있고; 두 번째 리스트에는 쓰고 싶은 모든 소켓들이 담겨 있으며, 그리고 (보통은 비어 있는) 마지막 리스트에 담긴 소켓은 에러를 점검하는데 사용한다. 꼭 알아야 할 것은 소켓은 하나 이상의 리스트에 들어갈 수 있다는 것이다. select 호출은 블로킹 함수이지만, 거기에다 시간제한을 줄 수 있다. 이는 일반적으로 취해야 할 합리적인 행위이다 – 특별히 다른 좋은 이유가 없는 한 충분히 긴 시간제한(timeout) (예를 들어 1분)을 주자.
응답으로 리스트 세개를 돌려 받는다. 돌려 받은 리스트에는 실제로 읽을 수 있으며, 쓸 수 있고, 에러인 소켓들이 들어 있다. 이 리스트 각각은 건네준 리스트에 상응하는 하부집합이다. 그리고 소켓을 하나 이상의 입력 리스트에 넣더라도, 그 소켓은 (많아야) 오직 하나의 출력 리스트에만 나타날 것이다.
소켓이 읽기가능 출력 리스트에 있다면, 그 소켓에 대하여 recv를 수행하면 무언가를 반환할 것이라고 확신할 수 있다. 같은 아이디어가 읽기가능(writable) 리스트에 적용된다. 여러분은 무언가를 보낼 수 있을 것이다. 원하는 모두는 아니지만, 아무것도 없는 것보다는 무언가 있는 것이 더 좋다 (실제로, 온전히 건강한 소켓이라면 쓰기가능으로 반환될 것이다 – 이는 단지 외부의 네트워크 버퍼 공간이 사용가능하다는 뜻일 뿐이다.)
“서버” 소켓이 있다면, potential_readers 리스트에 넣자. 그 소켓이 읽기가능(readable) 리스트에 나타나면, accept가 (거의 틀림없이) 작동할 것이다. 다른 누군가에게 접속하기(connect) 위하여 새로운 소켓을 만들었다면, ptoential_writers 리스트에 넣자. 그 소켓이 쓰기가능(writable) 리스트에 나타나면, 품위있게 접속할 기회를 얻는다.
select에서 아주 귀찮은 문제 하나: 그런 입력 리스트 중에 어디에선가 소켓 하나가 고약하게 사망하면, select는 실패할 것이다. 그러면 리스트를 모두 뒤져 소켓 하나하나를 회돌이 하면서 select([sock],[],[],0)를 수행하여 문제의 소켓을 찾아야 한다. 0 이라는 시간제한은 시간을 지체하지 않겠다는 뜻이지만, 보기에 안 좋다.
실제로, select는 블로킹 소켓에서도 편리할 수 있다. 블로킹을 할지 말지를 결정하는 한가지 방법이다 – 무언가 버퍼에 있으면 소켓이 읽기가능으로 반환된다. 그렇지만, 이는 여전히 상대편이 일을 끝마쳤는지, 아니면 그냥 다른 일로 바쁜 건지 결정하는 문제 해결에 도움이 되지 않는다.
이식성에 대한 주의: 유닉스에서, select는 소켓과 파일에 모두 작동한다. 윈도우즈에서는 이렇게 하지 말자. 윈도우즈에서, select는 소켓에만 작동한다. 또 주목할 것은 C에서 고급의 소켓 옵션 상당수가 윈도우즈에서는 다르게 수행된다는 것이다. 사실, 윈도우즈에서 나는 보통 소켓에 쓰레드를 사용한다 (아주, 아주 잘 작동한다). 현실을 직시하자, 조금이라도 수행성능을 원한다면, 코드는 유닉스보다 윈도우즈에서 아주 달라질 것이다 (매킨토시에서는 이런 일을 어떻게 처리하는지 나도 전혀 모른다).
5.1 수행성능
가장 빠른 소켓 코드는 비-블로킹 소켓을 사용하고 select를 사용하여 그 소켓들을 다중화하는 것이라는 데는 의문의 여지가 없다. CPU에 전혀 부담을 주지 않고서도 LAN 연결을 흠뻑 채울 무언가를 조립할 수 있다. 문제는 이런 식으로 작성된 어플리케이션이 크게 많은 일을 할 수 없다는 것이다. – 언제나 바이트를 뒤 섞을 준비가 되어 있어야 한다.
어플리케이션이 실제로 그 보다는 무언가를 더 할 수 있다고 가정하면, 쓰레드가 최적의 선택이다 (그리고 비-블로킹 소켓을 사용하면 블로킹 소켓을 사용하는 것보다 빠르다). 불행하게도, 유닉스에서 쓰레드의 지원은 API와 그 품질에 따라 다르다. 그래서 보통의 유닉스 해결책은 하부프로세스를 갈라서 각 접속을 처리하는 것이다. 이에 대한 부담은 심각하다 (윈도우즈에서 이렇게 하면 안된다 – 윈도우즈에서 프로세스를 만들어 내는 부담은 엄청나다). 이는 또한 하부프로세스가 따로따로 완전히 독립적이지 않는 한, 또다른 형태의 IPC를 사용할 필요가 있다는 뜻이다. 예를 들어 파이프나 공유 메모리 그리고 세마포어를 사용하여 부모 프로세스와 자손 프로세스 사이에 통신해야 한다는 뜻이다.
마지막으로, 기억할 것은 비록 블로킹 소켓이 비-블로킹 소켓보다 약간 느림에도 불구하고, 많은 경우 “올바른” 해결책이라는 것이다. 결론적으로, 어플리케이션이 소켓을 통하여 받는 데이터에 의하여 주도된다면, 로직을 복잡하게 만들어서 어플리케이션이 recv 대신에 select를 기다리게 만드는 것은 별로 의미가 없다.
이 문서에 대하여 …
소켓 프로그래밍 HOWTO
이 문서는 LaTeX2HTML 번역기를 사용하여 만들어졌다.
LaTeX2HTML은 다음에 복사권이 있다: Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds, and Copyright © 1997, 1998, Ross Moore, Mathematics Department, Macquarie University, Sydney.
LaTeX2HTML을 파이썬 문서화에 적용하면서 프레드 드레이크(Fred L. Drake, Jr)가 상당히 손을 보았다. 원래의 항해 아이콘은 크리스토퍼 페트릴리(Christopher Petrilli)가 공헌하였다.
