파일 디스크립터(File Descriptor, FD)
- 유닉스/리눅스 시스템에서 프로세스가 파일을 다룰 때 사용하는 개념, 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값
- 이 값은 일반적으로 “0이 아닌 양수 정수값”을 갖는다.
- 프로세스 실행 중 파일을 Open하면, 커널은 해당 프로세스의 파일 디스크립터 숫자 중 사용하지 않는 가장 작은 값을 할당해 준다.
- 이후 프로세스가 System Call을 이용해서 Open되어있는 파일에 접근할 때, FD값을 이용해서 파일을 지칭한다.
- 기본적으로 할당되는 FD는 표준입력(Standard Input), 표준출력(Standard Output), 표준에러(Standard Error) 3가지이며 각각 0, 1, 2가 할당된다.
네트워크
- 호스트에게 있어 네트워크는 단지 또 다른 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주소 구조체에 저장한다.
- 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 -> 0x8002c2f2의 dotted-decimal 표현
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로 끝나는 결과 스트링의 최대 size를 dst로 복사한다.
소켓(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 함수를 사용한다.
매개변수들을 자동으로 생성하게 하여, 코드가 프로토콜에 무관하게 되도록 getaddrinfo()를 사용하여 인자를 넣는 게 좋다.
소켓의 오픈 과정이 어떻게 완료되는지는, 클라이언트인지 서버인지에 따라 다르다.
클라이언트는 connect() 를 호출해서 서버와의 연결을 수립한다.
connect가 성공한다면, clientfd 식별자는 읽거나 쓸 준비가 되었으며 소켓 쌍으로 규정된다.
소켓 쌍의 형태 : (클라 IP : 클라 port , 서버 IP : 서버 port)
bind() 는 addr에 있는 서버의 소켓 주소를 sockfd와 연결시키는 함수이다.
서버는 bind()함수로 지정된 IP와 포트번호를 사용하게 된다.
(클라이언트는 bind()를 따로 사용하지 않으며, 시스템에서 배정해준 IP와 port를 사용하게 된다.)
소켓 생성 시 소켓의 default 상태는 연결을 요청하는 입장이다.
listen()은 연결을 요청하는 입장(클라이언트)에서 연결을 받는 입장(서버)으로, 즉 능동 소켓에서 듣기 소켓으로 변경한다. listen상태의 소켓은 클라이언트로부터의 연결 요청을 수락할 수 있다. ( 실제로 연결하는 건 또 다르다. accept 에서 한다. )
backlog : 요청을 거절하기 전까지 큐에 저장해야 하는 연결 요청의 최대 수, 대개 1024같은 값으로 설정한다.
listen 상태의 서버는 accept() 를 호출하여, 클라의 연결 요청이 듣기 식별자 listenfd에 도달하기를 기다린다.
그 후 addr 내의 클라이언트 소켓 주소를 채우고, Unix I/O 함수를 사용해 ”연결 식별자“를 리턴한다.
"연결 식별자" 는 소켓 쌍으로 이루어진, 연결된 서로를(클라, 서버) 식별할 수 있는 식별자이다.
getaddrinfo()
호스트명, 포트번호 스트링을 소켓 주소 구조체로 변환한다.
성공 시, 대응되는 addrinfo 구조체 연결리스트의 포인터를 리턴한다.
클라이언트가 getaddrinfo()를 호출하면 addrinfo 연결리스트에 방문하며, 각 주소에 대하여 연결이 성공할 때까지 차례로 시험한다.
이 작업이 끝난 후엔 freeaddrinfo를 호출하여 리스트 메모리를 반환해야 한다.
hints 인자는 선택적이며, ai_family, ai_socktype, ai_protocol, ai_flags 필드만 설정할 수 있고 더 상세하게 addrinfo를 제어할 수 있다.
호스트이름 또는 포트번호 둘 중 하나를 NULL로 설정할 수 있다. 다만 둘 다 NULL로 설정해선 안 된다.
getnameinfo 함수
- getaddrinfo의 반대 역할
- 소켓 주소 구조체를, 대응되는 호스트와 서비스이름 스트링으로 변환한다.
- flags 인자로 기본 동작을 수정할 수 있다.
open_clientfd 함수
- "클라이언트"는 open_clientfd 함수를 호출해 서버와 연결한다.
- 연결할 서버의 이름과 포트번호를 인자로 입력한다.
- 서버와 연결된 소켓 식별자가 리턴된다.
-> 클라이언트 측의 socket(), connect() 를 한번에 해 주는 함수
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를 보낸다.
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 라고 한다.
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언어 구현은 다른 글에 업로드되어 있다.
'크래프톤 정글 > TIL' 카테고리의 다른 글
[webproxy-lab] 웹 소켓 통신 / Tiny Web Server 구현 (C언어) (0) | 2024.05.08 |
---|---|
[webproxy-lab] 웹 소켓 통신 / Echo Server,Client 구현 (C언어) (0) | 2024.05.08 |
[malloc-lab] 동적 메모리 할당 / 명시적 가용 리스트 방식 구현 (C언어) (2) | 2024.05.01 |
[malloc-lab] 동적 메모리 할당 / 묵시적 가용 리스트 방식 구현 (C언어) (2) | 2024.05.01 |
[malloc-lab] 동적 메모리 할당의 개념 / 묵시적 가용 리스트 방식 (2) | 2024.05.01 |