티스토리 뷰

앞선 예제의 서버는 iterative server이다. 이는 daytime server같이 단순한 것에는 괜찮다. 

그러나 클라이언트의 요청을 서비스하는 데 긴 시간이 걸릴 때는, 서버가 한 클라이언트에 얽매이는 것 보다 동시에 많은 클라이언트를 처리하는 것이 바람직하다. 

유닉스에서 다중접속(concurrent)서버 를 작성하는 간단한 방법은 각각의 클라이언트를 처리할 자식 프로세스를 fork하는 것이다. 

다음 예제는 전형적인 다중접속(concurrent)서버의 틀을 보여주고 있다. 




연결이 설정되면 accept에서 반환되고, 서버는 fork를 호출한다. 자식 프로세스는 클라이언트와 데이터를 주고 받고 부모 프로세스는 다시 다른 연결요청을 accept하기 위해서 반복문의 맨 위로 돌아가게 된다. 자식 프로세스는 리슨소켓이 필요가 없으므로 close(listenfd)를 해주고 데이터를 주고받은뒤 커넥트소켓도 닫은뒤 프로세스를 종료시키는 것이다(exit(0)) 또한 부모 프로세스에서는 리슨소켓만 필요하므로 connect socket은 닫아줘야 한다. 


사실 다음 문장이 exit 이므로 close는 필요가 없다. 프로세스가 종료하면 커널에 의해 모든 open descriptor는 닫히게 된다. 이 close를 포함하는 것은 개인의 취향에 달려 있다. TCP 소켓에서 close를 호출하면 FIN을 보내게 되고 정상적인 연결 종료 절차가 이어진다.


예제에서 부모가 호출한 connfd에 대한 close는 클라이언트와의 연결을 끊지 못할까? 

이유를 이해하려면 모든 파일이나 소켓은 reference count를 가지고 있음을 알아야 한다. Reference count는 file table entry에 있으며, 파일이나 소켓을 참조하는 프로세스의 현재 개수를 센다. 


fork에서 반환된 후에는 부모와 자식이 리슨,연결소켓을 공유하므로 리슨소켓과 연결소켓의 file table entry에서 reference count는 2가 된다. 

(descriptor라는 것은 파일을 대표하는 숫자이다. 여기서는  listenfd 와 connfd를 나타낸다. 리슨소켓번호와 연결소켓번호)


file table entry  = > (각각의 파일들을 참조하는 프로세스의 수가 몇개인지 적어놓음)


부모 프로세스에서 리슨소켓만 필요하기 때문에 연결소켓을 close한다고 하더라도 연결소켓은 자식 프로세스에서 사용하고 있기 때문에 

아직 연결 소켓에 대한 reference count가 1이다 그렇기 때문에 클라이언트와 연결이 끊기지 않게 되는 것이다.


close를 했을때 reference count값이 0보다 크면 TCP 연결 종료 절차를 실행 시키지 않는다.


만약에 부모 프로세스에서 맨마지막에 close(connfd);를 호출하지 않는 다면 어떻게 될까?


부모 프로세스에서는 리슨소켓만 필요하기 때문에 연결소켓은 종료를 시켜주어야 한다. 그렇지 않으면 한 프로세스가 할당할수있는 파일디스크립터 번호를 모두 사용하게 되서 fd가 고갈되게 될수 있고 ,그보다 더 심각한 문제는 자식프로세스에서 데이터 송수신을 마치고 나서 연결소켓을 종료하게 되는데 자식에서 연결소켓을 종료한다고 하더라도 reference count는 2->1로 줄어드는게 끝이다. 실질적으로  TCP연결 종료 절차를 시작하지 않는다. 그렇기 때문에 부모 프로세스에서 반드시 연결소켓을 닫아서 reference count를 2->1로 만들어 놓아야 나중에 자식이 데이터 송수신 절차를 마쳤을때 정상적으로 TCP 연결을 종료 시킬수 있게 된다.




아래 그림은 서버가 accept를 호출하여 블록(block)되고 클라이언트로부터 연결 요청이 도착하는 동안의 서버와 클라이언트의 상태를 보여준다. 



댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함