크래프톤 정글/TIL

[webproxy-lab] 네트워크 프로그래밍 / 소켓 통신의 개념

양선규 2024. 5. 8. 19:27
728x90
반응형

파일 디스크립터(File Descriptor, FD)

- 유닉스/리눅스 시스템에서 프로세스가 파일을 다룰 때 사용하는 개념, 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값

- 이 값은 일반적으로 “0이 아닌 양수 정수값을 갖는다.

- 프로세스 실행 중 파일을 Open하면, 커널은 해당 프로세스의 파일 디스크립터 숫자 중 사용하지 않는 가장 작은 값을 할당해 준다.

- 이후 프로세스가 System Call을 이용해서 Open되어있는 파일에 접근할 때, FD값을 이용해서 파일을 지칭한다.

- 기본적으로 할당되는 FD는 표준입력(Standard Input), 표준출력(Standard Output), 표준에러(Standard Error) 3가지이며 각각 0, 1, 2가 할당된다.

 

 

네트워크는 일종의 I/O 장치라고 볼 수 있다

 

네트워크

- 호스트에게 있어 네트워크는 단지 또 다른 I/O 디바이스이다.

- I/O버스의 확장 슬롯에 꽂혀 있는 네트워크 어댑터(LAN카드)는 네트워크에 물리적인 인터페이스를 제공한다.

- 네트워크에서 수신된 데이터는 I/O, Memory bus를 거쳐 Main memory로 복사되며, 대개 DMA 전송으로 복사된다.

 

DMA

- 주변장치(주로 하드웨어)가 메모리에 직접 데이터를 읽거나 쓸 수 있는 기술

- DMA를 통해서 CPU의 개입 없이 데이터를 전송할 수 있다

- 데이터 전송 속도를 높이고, CPU를 다른 작업에 집중할 수 있게 해준다

- DMA를 이용하면 데이터 전송이 효율적으로 이루어지며, 전반적인 시스템 성능이 향상된다.

 

LAN(Local Area Network)

- 하위 수준 네트워크, 빌딩이나 캠퍼스에 설치됨

- 가장 대중적인 LAN 기술은 이더넷(Ethernet) 이다.

 

이더넷 세그먼트

 

이더넷 세그먼트

- 위 그림과 같이 몇 개의 전선과 허브라는 작은 상자로 구성됨

- 대개 방이나 빌딩의 층과 같은 작은 지역에 설치

- 전선의 한쪽 끝은 호스트의 어댑터에 연결, 다른 끝은 허브의 포트에 연결됨

- 허브는 각 포트에서 수신한 모든 비트를 종속적으로 다른 모든 포트로 복사함(브로드캐스트 한다)

 

브릿지형 이더넷

 

브릿지형 이더넷

- 전선들과 브릿지를 이용해서, 브릿지형 이더넷이라고 하는 더 큰 LAN을 구성할 수 있다.

- 빌딩 전체 또는 캠퍼스 규모로 설치될 수 있다.

 

네트워크 간 연결에 필요한, 라우터

- 다수의 LAN들은 라우터에 의해 연결될 수 있다.

- 라우터는 네트워크 간 연결을 구성한다.

- 라우터는 이들이 연결되는 각 네트워크에 대한 어댑터(랜카드)를 가지고 있다. ( 즉 여러개일 수 있다 )

- 라우터는 WAN(Wide-Area-Network)라고 한다. LAN보다 더 넓은 영역을 의미.

 

라우터로 연결된 인터넷

 

이렇게 라우터를 통해 서로 다른 네트워크들을 연결할 수 있다.

이 때, 비호환적인 네트워크들을 지나 목적지 호스트로 전송할 수 있는 이유는 프로토콜 때문이다.

프로토콜은 약속이자 규약이며, 비호환성을 가지는 서로 다른 네트워크들이 데이터를 주고받기 위해 협력할 수 있게 한다.

 

프로토콜이 제공해야 할 두 가지 기본 기능

1. 명명법(Naming Scheme)

- 서로 다른 LAN은 주소를 호스트에 할당하는 방법이 호환되지 않을 수 있다.

- internet 프로토콜(IP)는 호스트 주소를 위한 통일된 방법을 제공하여 이 차이점을 줄인다.

- 각 호스트는 자신을 유일하게 식별하는 "IP주소"가 최소한 한 개 할당된다.

 

2. 전달기법(delivery mechanism)

- 또한 인코딩 방법과 패키징 방법이 다를 수 있다.

- IP 프로토콜은 데이터 비트를 패킷이라고 부르는 단위로 묶어 통일시켜 이 차이점을 줄인다.

 

 

 

클라이언트와 서버

- 클라와 서버는 소켓 인터페이스와 Unix I/O 함수들을 혼합하여 통신한다.

- 소켓 함수들은 일반적으로 System Call로 구현되며, System Call은 커널에서 트랩을 발생시키고 다양한 커널 모드 함수들을 호출하게 된다.

 

IP주소 구조체

 

IP주소

- 네트워크 프로그램은 IP주소를 위처럼 IP주소 구조체에 저장한다.

- TCP/IP는 네트워크 패킷 헤더에 포함되는 IP주소 같은 모든 정수형 데이터에 대하여, 통일된 빅 엔디안 바이트 순서를 정의한다.

- IP 주소 구조체는, 호스트의 바이트 순서가 리틀 엔디안이더라도 항상 빅 엔디안으로 저장된다.

 

바이트 순서 변환을 위해 제공되는 함수들

 

Unix에서 바이트 순서 변환을 위해 제공되는 함수들

(host to network, network to host)

htonl 함수

- 비부호형 32비트 정수를, 호스트 바이트 순서에서 네트워크 바이트 순서(빅엔디안)로 변환한다.

ntohl 함수

- 비부호형 32비트 정수를 네트워크 바이트 순서에서 호스트 바이트 순서(리틀엔디안)로 변환한다.

htons, ntohs 함수

- 각각 16비트 정수를 변환한다.

-> 64비트 값을 다루기 위한 동일한 함수는 없다.

 

dotted-decimal 표기법

- IP주소는 대개 사람들에게 dotted-decimal 표기라는 형식으로 제시된다.

- 각 바이트가 10진수 값을 사용하며, 다른 바이트들과는 점으로 구분된다.

ex) 128.2.210.175 -> 0x8002c2f2dotted-decimal 표현

 

dotted-decimal <-> 이진 IP주소 변환 함수

 

inet_pton, inet_ntop

- 응용프로그램들은 IP주소와 dotted-decimal 형태를 inet_pton, inet_ntop 함수를 이용해 상호 변환한다.

- n : network, p : presentation

- 32비트 IPv4 주소 또는 128비트 IPv6 주소를 처리할 수 있다.

 

inet_pton

- dotted-decimal 스트링을 네트워크 바이트 순서(빅엔디안)를 갖는 이진 IP 주소로 변환함.

- 만약 dotted-decimal이 유효하지 않다면, 0을 리턴하며, 다른 모든 에러에 대해선 -1을 리턴하고 errno를 설정한다.

 

inet_ntop

- 네트워크 바이트 순서를 갖는 이진IP주소를 dotted-decimal 스트링으로 변환하고, NULL로 끝나는 결과 스트링의 최대 sizedst로 복사한다.

 

 

 

소켓(socket)

- 연결의 종단점

- 각 종단점(클라이언트, 서버)은 소켓을 가지고 있고 이 소켓을 이용해 통신한다.

- 소켓은 IP주소와 포트번호를 가진다. ( address : port )

- 클라 -> 서버 연결할 때 클라는 1024번 이상 포트를 자동으로 할당하는데 이걸 단기 포트라고 한다

- 서버의 포트는 대개 Well-known port 이다.

- Well-known port와 이름의 매핑은 /etc/services 파일에 보관되어 있다. ( 1024 이상 포트도 보관되어 있는 것 같다 )

 

소켓 쌍

- 연결은 두 종단점의 소켓 주소에 의해 식별된다. 이 두 주소는 소켓 쌍이라고 하며, tuple로 나타낸다.

- (cliaddr:cliport, servaddr:servport)

 

소켓 인터페이스

- 네트워크 프로그램을 만들기 위한, Unix I/O 함수들과 함께 사용되는 함수들의 집합

 

소켓을 바라보는 관점

- 리눅스 커널의 관점 : 통신을 위한 끝 점

- Unix 프로그램 관점 : 해당 식별자를 가지는 열린 파일

 

소켓 주소를 담는 구조체

 

소켓 주소는 위 그림의 sockaddr_in 타입의 16바이트 구조체에 저장된다.

 

sin_family : 항상 AF_INET

sin_port : 16비트 포트 번호

sin_addr : 32비트 IP주소

-> IP주소와 포트번호는 항상 빅 엔디안(네트워크 바이트 순서)으로 저장된다.

 

socket()

 

클라이언트와 서버는 소켓을 생성하기 위해 socket 함수를 사용한다.

매개변수들을 자동으로 생성하게 하여, 코드가 프로토콜에 무관하게 되도록 getaddrinfo()를 사용하여 인자를 넣는 게 좋다.

 

소켓의 오픈 과정이 어떻게 완료되는지는, 클라이언트인지 서버인지에 따라 다르다.

 

connect()

 

클라이언트는 connect() 를 호출해서 서버와의 연결을 수립한다.

connect가 성공한다면, clientfd 식별자는 읽거나 쓸 준비가 되었으며 소켓 쌍으로 규정된다.

소켓 쌍의 형태 : (클라 IP : 클라 port , 서버 IP : 서버 port)

 

bind()

 

bind() 는 addr에 있는 서버의 소켓 주소를 sockfd와 연결시키는 함수이다.

서버는 bind()함수로 지정된 IP와 포트번호를 사용하게 된다.

(클라이언트는 bind()를 따로 사용하지 않으며, 시스템에서 배정해준 IP와 port를 사용하게 된다.)

 

listen()

 

소켓 생성 시 소켓의 default 상태는 연결을 요청하는 입장이다.

listen()은 연결을 요청하는 입장(클라이언트)에서 연결을 받는 입장(서버)으로, 즉 능동 소켓에서 듣기 소켓으로 변경한다. listen상태의 소켓은 클라이언트로부터의 연결 요청을 수락할 수 있다. ( 실제로 연결하는 건 또 다르다. accept 에서 한다. )

 

backlog : 요청을 거절하기 전까지 큐에 저장해야 하는 연결 요청의 최대 수, 대개 1024같은 값으로 설정한다.

 

accept()

 

 

listen 상태의 서버는 accept() 를 호출하여,  클라의 연결 요청이 듣기 식별자 listenfd에 도달하기를 기다린다.

그 후 addr 내의 클라이언트 소켓 주소를 채우고, Unix I/O 함수를 사용해 연결 식별자를 리턴한다.

 

"연결 식별자" 는 소켓 쌍으로 이루어진, 연결된 서로를(클라, 서버) 식별할 수 있는 식별자이다.

 

getaddrinfo()

 

getaddrinfo()

호스트명, 포트번호 스트링을 소켓 주소 구조체로 변환한다.

성공 시, 대응되는 addrinfo 구조체 연결리스트의 포인터를 리턴한다.

클라이언트가 getaddrinfo()를 호출하면 addrinfo 연결리스트에 방문하며, 각 주소에 대하여 연결이 성공할 때까지 차례로 시험한다.

이 작업이 끝난 후엔 freeaddrinfo를 호출하여 리스트 메모리를 반환해야 한다.

 

hints 인자는 선택적이며, ai_family, ai_socktype, ai_protocol, ai_flags 필드만 설정할 수 있고 더 상세하게 addrinfo를 제어할 수 있다.

호스트이름 또는 포트번호 둘 중 하나를 NULL로 설정할 수 있다. 다만 둘 다 NULL로 설정해선 안 된다.

 

getnameinfo()

 

getnameinfo 함수

- getaddrinfo의 반대 역할

- 소켓 주소 구조체를, 대응되는 호스트와 서비스이름 스트링으로 변환한다.

- flags 인자로 기본 동작을 수정할 수 있다.

 

open_clientfd()

 

open_clientfd 함수

- "클라이언트"open_clientfd 함수를 호출해 서버와 연결한다.

- 연결할 서버의 이름과 포트번호를 인자로 입력한다.

- 서버와 연결된 소켓 식별자가 리턴된다.

-> 클라이언트 측의 socket(), connect() 를 한번에 해 주는 함수

 

open_listenfd()

 

open_listenfd 함수

- "서버"는 open_listenfd 함수를 호출해 서버와 연결한다

- 서버로써 사용할 포트번호를 인자로 입력한다.

- 클라이언트와 연결된 소켓 식별자가 리턴된다.

- 서버 측의 socket(), bind(), listen()을 한번에 해 주는 함수

 

 

 

 

클라이언트 <-> 서버 소켓 통신 과정 간단 정리

소켓 기반 네트워크 프로그램 개요

 

서버

- open_listenfd() 함수에 서버로써 사용할 포트번호를 넣고 listen상태의 소켓을 생성한다. 그리고 accpet() 상태로 대기한다.

 

클라이언트

- open_clientfd() 함수에 서버의 IP와 포트번호를 넣고 서버와 연결된 클라이언트 소켓을 생성한다.

 

클라이언트는 서버에게 연결 요청을 하며, listen() + accept() 중이던 서버는 연결 요청을 수락하고 새로운 연결 식별자 소켓을 만들어, 해당 소켓으로 통신하게 된다.

 

 

MIME(Multipurpose Internet Mail Extensions) Type

- 이메일/웹 등에서 다양한 데이터 형식을 표현하고 전송하는 데 사용된다.

- 미디어 유형/서브타입  형태로 구성되며, 일반적으로 서버가 클라이언트에게 전송할 때 HTTP Header의 Content-type 부분에 기록되어 사용된다.

- 클라이언트는 이 헤더를 보고 서버에서 받은 파일을 적절하게 처리할 수 있다.

- text/html , image/gif 등이 MIME 타입이며, 확장자에 의해 결정되기도 하지만 명시적으로 결정할 수도 있다.

 

 

## 헤더는 없을 수도 있다. ( 요청 라인/상태 라인만 존재할 수도 있다 )

HTTP Request

- HTTP 에서 클라이언트가 서버에게 데이터를 요청할 때 HTTP Request를 보낸다.

 

Request

 

1번 줄(그림에선 3줄로 표현되어 있지만 좌측 숫자를 기준으로 본다)

- Request Line(요청 라인)

- 메소드, 요청 경로(URI), HTTP 버전으로 구성된다.

 

2번 줄 부터는 Request Header 이다. Header는 Empty Line이 나올 때까지 계속되며, Empty Line은 Header와 Body를 구분짓는 기준이다. Empty Line으로 Header와 Body는 구분된다.

Empty Line은 말 그대로 빈 줄이다. 엔터 친거.

 

HTTP Response

- 클라이언트의 Request에 대한 서버의 답장을 HTTP Response 라고 한다.

Response

 

1번 줄

- Status Line(상태 라인)

- HTTP버전과 상태코드로 구성된다.

- 2번 줄 부터는 Response Header 이다. Request와 마찬가지로 Header와 Body는 Empty Line으로 구분된다.

 

HTTP 상태 코드

- HTTP Response의 첫 줄(status Line)에 존재하는 코드.

- Request의 처리 상태를 코드로써 쉽게 알아볼 수 있게 한다.

 

대표적인 상태코드

200 OK : 요청이 정상적으로 처리됨

301 Moved permanently : 요청한 자원이 이동되었음, 리다이렉트 된다

400 Bad request : 잘못된 요청

403 Forbidden : 요청에 대한 권한이 없음

404 Not found : 요청 페이지/파일을 찾을 수 없음

500 Internal server Error : 서버 문제로 요청을 수행할 수 없음

 

 

CGI(Common Gateway Interface)

- 웹 서버와 외부 프로그램 간의 표준화된 인터페이스

- CGI를 통해 웹 서버는 클라이언트의 요청을 받은 후 이를 외부 프로그램으로 전달하여 "동적 콘텐츠"를 생성하여 반환할 수 있다.

- 사용자 입력에 영향을 받는 응답, DB 조회 결과 등 동적인 콘텐츠를 CGI를 통해 외부 프로그램을 이용하여 생성해 반환할 수 있다.

- CGI 프로그램은 CGI 환경 변수(QUERY_STRING 등)를 통해 요청과 관련된 정보(인자)를 얻고 실행될 수 있다.

- CGI는 현재 웹 프레임워크(Flast, Spring 등)가 널리 사용되며 중요성이 줄어들고 있다.

 

================

 

echoserver, echoclient, tiny web server 의 C언어 구현은 다른 글에 업로드되어 있다.

728x90
반응형