Guru

[알림판목록 I] [알림판목록 II] [글목록][이 전][다 음]
[ 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()의 용도를 아는데는 여기까지만 해도 될 거 같네요.
(위 코드가 최종 버젼이 아니라는 겁니다.)

[알림판목록 I] [알림판목록 II] [글 목록][이 전][다 음]
키 즈 는 열 린 사 람 들 의 모 임 입 니 다.