[ Guru ] in KIDS 글 쓴 이(By): belami (- 커피 -) 날 짜 (Date): 1998년 7월 30일 목요일 오전 12시 14분 52초 제 목(Title): Re: [Q]소켓에서 select()와 accept()의 차� '구체적인' 것은 책을 찾아보시면 되고요. 대강은 이렇습니다. 먼저 accept()와 select()는 같은 목적을 위한 두 가지 방법으로 존재하는 것이 아니라 각각의 다른 목적을 위해 존재합니다. select()는 I/O 멀티플렉싱 어쩌고... 하고 나오지만 쉽게 말하면 여러 개의 i/o descriptor의 상태만 체크해보는 라이브러리입니다. 일반적인 소켓 서버는 socket(), bind(), listen(), accept()의 순서대로 함수를 호출하여('소말리아'라고 암기합니다.) 클라이언트의 접속을 처리합니다. socket() bind() listen() while true { s = accept() if (fork() > 0) { // optional read(s) write(s) close(s) exit() } // optional } 대략 이런 구조를 가지게 되죠. telnet이나 ftp와 같이 하나의 클라이언트와 (그러니까 socket descriptor가 1개인 경우) 상호작용하는 경우에는 accept()나, read(), write()가 blocking call이라는 게 별 문제되지 않습니다. 즉, select()가 필요없습니다. 이제 select()가 반드시 필요한 경우의 예를 들어보면 N개의 클라이언트와 상호작용하는 소켓 서버가 있습니다. 새로운 접속 처리도 하면서 이미 접속된 클라이언트와 통신도 수행하는 경우죠. listenfd = socket() bind() listen(listenfd) while true { if conn. request from listenfd exist s[i] = accept() i++ else if s[i] have pending data read(s[i]) write(s[i]) } 문제는 accept(), read(), write() 이런 함수들이 전부 blocking call들이라는 겁니다. 이 blocking call들은 이름 그대로 할일을 할 때까지 block 된다는 겁니다. 일단 호출하고 나면, 성공하든 에러가 발생하여 에러코드를 리턴하든 둘 중 한 가지를 해야 하죠. 하지만 accept()를 할지, read()를 할 지는 conn. request가 있는지 또는 기존 연결된 descriptor인 s[i]에 읽을 데이터가 있는지를 본 후에야 결정이 가능하다는 문제가 있죠. 이때 이 결정을 위해 select()가 사용되는데요. 그래서 마지막으로 '구체적으로' 고쳐보면. listenfd = socket() bind() listen(listenfd) while true { FD_ZERO(&rfds) FD_SET(listenfd, &rfds) FD_SET(s[i], &rfds) // for all i n = select(FD_SETSIZE, &rfds) if n > 0 { // ok, conn. request or i/o request(s) exist if FD_ISSET(listenfd, &rfds) is nonzero { // new conn. request s[i] = accept(listenfd) i++ } else { if FD_ISSET(s[i], &rfds) is nonzero { // test if s[i] have data read(s[i]) write(s[i]) } } } } 여러 파라미터는 생략을 했는데도 좀 복잡해졌군요. n = select(FD_SETSIZE, &rfds) FD_SETSIZE는 검사할 디스크립터의 갯수이고, rfds는 검사할 디스크립터가 세팅된 구조체입니다. n은 읽을 데이터가 있는 디스크립터의 갯수입니다. n이 0보다 크면, 프로그램은 무언가 해야 한다는 뜻이고 FD_ISSET(listenfd, &rfds)를 수행해서 listenfd가 세트되어 있으면 새로운 커넥션 요청이 들어왔다는 것을 알게 되므로 안전하게 accept()를 호출할 수 있습니다. 실제로는 한 프로세스가 accept와 i/o를 모두 담당하는 것은 위험한 일이라 더 튼튼하게 만들기 위해 둘을 분리시킵니다만 accept()와 select()의 용도를 아는데는 여기까지만 해도 될 거 같네요. (위 코드가 최종 버젼이 아니라는 겁니다.) |