티스토리 뷰

str_cli함수는 사용자로부터 입력을 받아서 서버에게 보내고, 서버는 받은 문자열을 그대로 돌려주고 그 돌려준 문자열을 다시 화면에 출력하게 된다.

Readline은 내부적으로  read시스템콜을 사용하게 되는데, read시스템콜은 상대방으로부터 EOF(End of File)이벤트가 발생했을때 0을 리턴하게 된다. EOF이벤트는 상대방이 FIN메시지를 보냈을때 발생하게 된다.

Ctrl+D를 누르게 되면 EOF이벤트가 발생하게 되고 그렇게 되면 Fgets는 NULL을 리턴하게 된다. 그렇게 되면 반복문이 종료되고 

str_cli함수가 종료되게 되면 그다음 exit(0)를 호출해서 상대방에게 FIN메시지를 보내게 된다. 


서버 프로그램과 클라이언트 프로그램을 같은 기계에서 실행시키는 상황을 생각해보자.

서버 프로그램은 백그라운드 프로세스로 실행시켜야 한다. 

./tcpserv01 & (끝에 &를 입력하게 되면 백그라운드 프로세스로 실행되게 된다.)

백그라운드 프로세스란 stdout,stdin(모니터출력,키보드입력)을 사용하지 않는 프로세스이다. 그렇게 실행 시키면

[1] 17870 이 화면에 찍히게 되는데 백그라운드 프로세스로 실행시킨 tcp서버의 프로세스 아이디가 출력되게 된것이다.


 

그리고 나서 netstat을 찍어보면 위와 같이 나오게 된다.

Local Address는 소켓 Address를 나타내게 된다.

*:9877 -> 어떤 프로세스가 사용하고 있는 소켓의 주소는 *(와일드카드,커널이 알아서 지정=INADDR_ANY) 이고 포트번호는 9877이다.

*:* -> 상대방의 어떤 ip주소나 포트번호 모두 가능

state -> LISTEN : 리슨 소켓이다.(서버 프로세스라는것을 알 수 있다.)


지금 현재는 백그라운드 프로세스로 서버 프로그램만 실행시켰으므로 당연히 nestat에 리슨 소켓만 뜨게 되는게 맞는것이다.


그리고 나서 클라이언트 프로그램을 실행시키는데 아이피주소는 127.0.0.1이다.(루프백 주소 == 나 자신을 가리킴)

제일 먼저 connect 시스템콜이 실행 될 것이다. 네트워크 카드(이더넷 카드)까지 내려 간다음에 다시 응용계층까지 올라올 것이다.

따라서 위의 문장을 실행 시키면 백그라운드 프로세스로 돌고 있는 서버 프로그램에 SYN메세지를 보내게 될것이다.


지금 까지의 상황을 정리해보면 서버 부모 프로세스(리슨소켓 보유)을 백그라운드 프로세스로 실행시켰고 클라이언트 프로그램을 실행시켜서 SYN메시지를 서버프로세스에게 보낸 상황이다. 이후에 벌어질 일들은 TCP 3 WAY 핸드쉐이킹이 일어나서 연결이 설정되고 클라이언트 프로그램에서 connect시스템콜이 정상적으로 리턴 될것이며 서버프로그램에서는 accept가 리턴되면서 커넥트 소켓이 만들어 질 것이다.

커넥트 소켓이 만들어진 이후에 fork시스템 콜을 통해서 서버 자식 프로세스는 클라이언트와 데이터를 주고 받기 위해서 str_echo.c프로그램이 실행 될 것이고, 서버 부모 프로세스는 다시 accept에서 블럭 될것이다.


그다음 , 클라이언트 프로그램에서는 서버프로그램에 어떤 문자를 입력하기 위해서 Fgets에서 블럭이 될것이고,

서버 프로그램에서는 커넥트 소켓에서 readline을 호출하게 되는데 readline은 내부적으로 read 시스템콜을 호출하게 되고, read는 현재 버퍼에 아무 문자열도 없으므로 블럭 되게 될것이다.


서버 부모 프로세스(리슨 소켓), 서버 자식 프로세스(커넥트 소켓), 클라이언트 프로세스가 각각 accept,read,Fgets에서 블럭이 된 상태가 된다.



이 상태에서 네트워크의 상태를 알아보고 싶어서 netstat -a를 입력 해보고 싶다. 근데 지금 상태는 백그라운드 서버를 실행시킨 상태에서 클라이언트 프로그램까지 실행 시켰다. 서버는 문자열이 오기를 read 시스템콜에서 기다리고 있고 클라이언트는 키보드로부터 입력을 Fgets에서 블락된 상태로 기다리고있다.(커서가 깜빡깜빡 거리면서 사용자의 입력을 기다리는 상태.)


그런데 이상태에서 netstat -a 를 입력 하면 어떤 쉘 명령어가 아니라 저 문자열 자체를 에코 서버에게 보내게 되는것이므로 우리는 새롭게 쉘을 켜서 netstat -a를 실행 시켜봐야 위의 그림처럼 나오게 된다.

위의 그림을 살펴보면 아까랑은 다르게 와일드 카드(*)가 사라졌다. 


상태를 보니 ESTABLISHED이므로 TCP연결이 완료된 서버 자식프로세스 소켓(커넥트 소켓)의 아이피 주소는 localhost고 포트번호는 9877이며 클라이언트 프로그램의 소켓 아이피주소는 마찬가지로 localhost이며 포트번호는 커널이 임의로 지정해준 42758이라는 번호라는것을 netstat명령어로 알 수 있게 된다. 마지막줄에는 리슨소켓(서버 parent 프로세스의 소켓)의 아이피 포트번호를 나타내며 리슨상태라는것이 state에 나와있다.


bash는 쉘의 이름이다.

2번째줄 : 17870이 프로세스 아이디이고 부모 프로세스의 아이디는 22038이다.(쉘의 자식 프로세스)

즉 17870은 wait_for_connect라는 것으로 보아, 리슨 소켓임을 알 수 있다.

3번째 줄 : 19315번 프로세스 아이디는 부모 프로세스 아이디가 17870이다. 한마디로 이 19315번 프로세스는 서버 자식 프로세스

(커넥트소켓을사용)임을 알 수 있다. 상태를 보니 tcp_data_wait, 데이터를 기다리고 있는 커넥트 소켓이 맞다.

4번째 줄 : 13914번 프로세스아이디는 쉘이 부모이며, ./tcpcli01 127.0.0.1이 커맨드인걸 보아하니 클라이언트 프로세스이다.

(read에서 블럭되서 키보드로 부터 입력을 기다리고 있다)


Q.클라이언트 프로세스(19314) 서버 자식 프로세스(19315) 서버 부모 프로세스(17870)인데, 여기서 클라이언트 프로세스가 항상 서버 자식 프로세스보다 프로세스 아이디가 1개 앞설까?

A.그렇다, 왜냐면 서버 부모 프로세스(리슨소켓사용)을 백그라운드 프로세스로 실행시키고, 그다음에 클라이언트 프로그램을 루프백 주소로 실행시키기 때문에 이때 클라이언트 프로세스가 생성된다. 그다음에 이 클라이언트 프로그램에서 connect 시스템 콜을 통해 TCP연결이 서버와 설정이 되면 그다음에 서버에서 accept가 리턴되고 커넥트 소켓이 만들어지며 fork시스템콜을 통해 서버 자식 프로세스가 만들어진다.

그렇기 때문에 항상 서버 부모 프로세스(리슨소켓) -> 클라이언트 프로세스(TCP 연결 요청) -> 서버 자식 프로세스(연결 완료후 커넥트 소켓)

순서대로 프로세스가 생성 될것이다.


CTRL+D를 입력하게 되면 클라이언트 프로그램의 Fgets에서 NULL이 리턴되기 때문에 while문이 종료되게 되고,exit(0)을 호출함으로써

FIN메세지를 서버 자식 프로세스에게 전달한다. 그러면 그 서버 자식 프로세스 에서 read시스템콜을 호출해서 블럭된 상태에서 FIN을 받게 되면 마찬가지로 서버에서도 EOF가 발생하게 되면서 read시스템콜이 0을 리턴하기 때문에 str_echo함수가 리턴되고 exit(0)을 호출하면서 서버 자식 프로세스를 없앤다. 


위의 프로그램같은 경우에는 클라이언트가 먼저 ctrl+d로 fin메세지를 보내고 ack를 받음으로써 먼저 종료되고

그 다음에 fin을 받은 서버 자식 프로세스가 read시스템콜에서 0을 리턴하고 나서 exit(0)를 호출함으로써 종료되게 된다.

클라이언트 종료 -> 서버 자식 프로세스 종료

ctrl+d를 입력하고 나서 곧 바로 netstat을 시켰다. 

클라이언트 커넥트 소켓이 TIME_WAIT상태인 이유는 커널이 마지막 하프 클로즈에서 바로 소켓을 없애는게 아니라 일정한 시간동안 기다린 다음에 소켓을 없애버린다. 


왜냐면 내가 마지막에 보낸 ACK가 유실될경우 서버가 다시 FIN을 재전송 할거기 때문에  정상적인 TCP종료 과정이 이루어 지게 하기 위해서 클라이언트 커넥트 소켓이 TIME_WAIT상태에 머물러 있는것이다.


다시말해서 클라이언트 커넥트 소켓을 현재 사용할수는 없지만 정상적인 TCP종료를 위해서 일정시간 동안 기다리는 상태에서 우리가 netstat을 입력했기 때문에 클라이언트 커넥션 소켓이 TIME_WAIT라고 뜬것이다.


현재 클라이언트 커넥트 소켓과 서버 자식 프로세스의 커넥트 소켓이 종료된 상태이고 서버 부모 프로세스의 리슨 소켓만 남아있는 상태이다.

그렇기 때문에 2번째줄에 17870이 wait_for_connect 하고 있는것은 이해가 된다.

하지만 3번째 줄에 보면 종료 되었어야할 서버 자식 프로세스가 종료되지 않고 살아있는것을 볼 수 있다. stat을 보아하니 z(zombie)이다.

좀비 프로세스는 사용할수 없는 죽은프로세스라는 뜻이다. tcpserv01에 의해 만들어진 프로세스 중 좀비 프로세스가 있다는 의미이다.


우리가 마음대로 종료시킬수없는 init이라는 프로세스는 pid가1번이다. 

모든 프로세스는 어떤 프로세스에서 fork를 통해 만들었건간에 최종 조상은 init프로세스다.

구현을 간단하게 하기 위해서 이런식으로 만들었는데, 프로세스와 프로세스 사이에 통신(데이터송수신)이 필요하다.


자식 프로세스가 종료 되었으면 부모 프로세스에게 그 사실을 알려야 한다.

 프로세스에게 어떤 이벤트가 발생했다고 알려주는것을 시그널이라고 한다.

자식 프로세스의 종료를 부모 프로세스에게 통보하는것도 시그널의 한 종류이다.


처음에는 10몇개의 시그널이 존재했지만 현재는 32개 정도의 시그널이 존재한다.

이해하기 쉽게 하기 위해서 시그널이라는 말을 사용했지만 실제로는 어떤 이벤트가 발생했을때 커널이 그 프로세스에게 알려준다.


지금 까지의 서버 프로그램에서는 자식 프로세스가 종료되었을때 부모 프로세스에서 어떤 처리를 해주는 메소드(시그널 핸들러)

를 정의를 하지 않았다.

그렇기 때문에 좀비 프로세스가 발생한것이다.













'컴퓨터 공학과 졸업 > 소켓 프로그래밍' 카테고리의 다른 글

wait waitpid 좀비프로세스  (1) 2017.10.19
시그널과 좀비 프로세스  (0) 2017.10.18
TCP 3 Way Handshaking 상태표  (0) 2017.10.16
Reference Count  (0) 2017.10.15
소켓 프로그래밍 교재  (0) 2017.10.15
댓글
최근에 올라온 글
최근에 달린 댓글
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
글 보관함