본문 바로가기

Linux

TCP/IP 기본

[socket]

 

소프트웨어적인 데이터 송수신 방법은 이미 대부분의 운영체제에서 지원하고 있다.

소켓이란 물리적으로 연결된 네트워크상에서의 데이터 송수신에 사용하는 소프트웨어적 장치를 의미한다.

데이터를 주고 받기 위해서는 소켓 디스크립터라는 파일 시스템을 이용해야 하는데, 

이를 가능하게 해주는 것이 소켓이라고 이해하면 편하다.

 

리눅스에서는 모든 장치를 dev 즉, 장치로 인식한다. 따라서 어떤 식의 장치든 결국 열고 닫는 것이 기본이 된다.


 

[TCP]

 

응답을 주고 받기 때문에 신뢰성이 있다. 데이터를 누락시키는 일이 없도록 해야하는 작업에 사용한다.

응답을 주고 받기 때문에 성능이 저하된다. 여기서의 성능은 속도 등을 의미한다.

 


 

[UDP]

 

일방적으로 데이터를 전송하고 전송한 데이터에 대한 신뢰성 검증은 최소한의 작업만 수행한다.

신뢰성이 낮으나 전송 속도가 빠르기 때문에 실시간 스트리밍 서비스에 최적화되어 있다.

 


 

[SERVER & CLIENT]

 

클라이언트는 손님이다. 서버에게 무언가를 요청하는 주체이다. 

서버는 클라이언트가 원하는 것을 주거나 클라이언트가 주길 원하는 것을 받는다.

 

- 가장 기본적인 예제 코드로 EchoServer와 EchoClient가 있다.

 

정말 안타깝게도 현재는 코드를 원활히 해석하는 것조차 불가능하기 때문에 코드에 집착하지 말고

돌아가는 원리를 파악하는 데 중점을 두자.

 


 

<입출력 버퍼>

 

TCP소켓 데이터의 송수신에서, 서버가 한 번의 write함수 호출로 40바이트를 전송해도

클라이언트는 네 번의 read함수 호출을 통해 10바이트씩 수신하는 것이 가능하다.

그런데 여기서 한번에 전송한 40바이트를 어떻게 10바이트씩 나눠서 수신할 수 있을까.

- write 함수가 호출되는 순간 데이터가 전송되는 것이 아니다.

- read 함수가 호출되는 순간 데이터가 수신되는 것이 아니다.

- wirte 함수가 호출되는 순간 데이터는 출력버퍼로 이동한다.

- read 함수가 호출되는 순간 데이터는 입력버퍼에 저장된 데이터를 읽어들인다.

 

- 입출력버퍼는 TCP소켓 하나하나에 대해 별도로 존재한다.

- 입출력버퍼는 소켓 생성 시에 자동으로 생성된다.

- 소켓을 닫아도 출력버퍼에 남아있는 데이터는 계속해서 전송이 이뤄진다.

- 소켓을 닫으면 입력버퍼에 남아있는 데이터는 소멸된다.

 

- 입력버퍼의 크기를 초과하는 분량의 데이터 전송은 발생하지 않는다.

 


 

[fork 함수]

 

멀티프로세스 기반 서버의 구현에 사용된다.

fork 함수는 호출한 프로세스의 복사본을 생성한다.

원본 프로세스와 복사본 프로세스 모두 fork 함수의 호출&반환 이후 문장을 실행하게 된다.

이 둘은 완전히 동일한 프로세스이므로, 메모리 영역까지 동일하게 복사하는 특징이 있다.

따라서 fork 함수의 호출&반환 이후의 프로그램 흐름은, fork 함수의 반환 값을 기준으로 나뉘도록 프로그래밍해야 한다.

- 부모 프로세스: fork 함수의 반환 값 = 자식 프로세스의 ID

- 자식 프로레스: fork 함수의 반환 값 = 0

 

fork 함수의 반환 값을 기준으로 나눈다는 것은 아래의 예시와 같다.

예를 들어

 

pid_t pid;

 

pid = fork( );

if(pid == 0) //자식 프로세스

else           //부모 프로세스

 


[ 좀비 프로세스 ]

 

파일(모든 장치 포함)은 여는 것만큼 닫는 것 또한 중요하다.

 

좀비 프로세스의 생성 원인

- exit 함수로 전달되는 인자 값과, main함수의 return문에 의해 반환되는 값 모두 운영체제로 전달된다.

운영체제는 이 값이 자식 프로세스를 생성한 부모 프로세스에게 전달될 때까지 자식 프로세스를 소멸시키지 않는다.

바로 이런 상황에 놓인 프로세스를 좀비 프로세스라고 한다. 결국 좀비 프로세스 상태를 유발하는 주체는 운영체제이다.

 

좀비 프로세스의 소멸

- 해당 자식 프로세스를 생성한 부모 프로세스에게 exit 함수의 인자 값이나 return문의 반환 값이 전달될 때 소멸한다.

그렇다면 부모 프로세스에게 값을 어떻게 전달해야할까. 운영체제는 자기가 알아서 부모 프로세스에게 반환 값을 전달하지 않는다. 부모 프로세스가 운영체제에 적극적으로 요청해야 그 값을 전달해준다.

 


[ 시그널 핸들링 ]

 

프로세스는, 자식 프로세스의 종료라는 상황이 발생할 때, 운영체제에게 특정 함수의 호출을 요청한다.

이것이 '시그널 등록'에 해당한다.

 

이 요구는 아래 함수의 호출을 통해서 이루어진다.

- 시그널 등록 함수

#include<signal.h>

void (*signal(int signo, void (*func)(int)))(int); //시그널 발생 시 호출되도록 이전에 등록된 함수의 포인터 반환.

 

시그널 등록 함수를 보고 정신이 아득해졌기 때문에 '아 이런 것이 있구나. 다음에 다시 만나자.'

속으로 생각하고 바로 다음으로 넘어간다.

 

아래는 alarm 함수이다.

include<unistd.h>

unsigned int alarm(unsigned int seconds);

이 함수를 통해 0 또는 SIGALRM 시그널이 발생하기까지 남아있는 시간을 초 단위로 반환한다.

 

여기까지만 알아보자.

나머지는 다음에 알아보자.

 


이후 가장 앞쪽에 소개되었던 기본 에코서버와 에코클라이언트를 조금씩 확장(?)하면서

여러 형태의 예제 서버와 클라이언트 코드를 돌려보았다.

다중 접속 에코 서버&클라이언트, TCP 입출력 루틴의 분할 등.

 

C언어의 함수 파트와 포인터 파트의 기본 내용을 부실하게 알고 있으니 완전히 개박살이 나버렸다.

어떻게든 코드가 작동하는 분위기는 알 수 있었지만, 누군가가 어느 부분을 단 한 칸만 확대해서 물어본다면

나는 아마 눈 뜬 채로 죽은 사람이 될 것이다.

프로세스와 쓰레드 내용을 내일 수업을 통해 다시 복습하고 정리하여 글로 작성해보기.

'Linux' 카테고리의 다른 글

argc / argv가 뭔데?  (0) 2021.09.29
기본 패키지 설치  (0) 2021.09.09