Application layer는 email, Web, Voice over IP (VoIP), Facetime, Google, Video, Instagram, KakaoTalk 등 End host에서 사용하는 많은 응용 프로그램들을 제공해줄 수 있는 레이어이다. Web applications의 경우, User's host에서 동작하는 Browser program과 Web Server host에서 동작하는 Web Server Program으로 나뉜다. 이러한 프로그램들은 또한 서로 통신이 가능해야 한다.
주로 이런 Client-Server 아키텍처를 사용하고, 여기서 Client는 Web browser, Server는 Web Server를 가리키고 Client는 다른 Client와 직접 통신하지 않는다. P2P 아키텍처를 사용하면 직접 통신이 가능하다
여기서 통신한다라는 것은 Program 간의 통신을 말한다. 즉, Process들의 통신을 책임진다는 것이다. 동일한 End system 내에 있다면 IPC(Inter-process communication)을 사용해서 통신하고, 다른 End system에 있다면 네트워크를 통해 메시지를 교환하게 된다.
이때 네트워크와 process를 연결해주는 인터페이스를 Socket이라고 한다. 이런 Socket을 제어하고 사용하기 위한 것이 바로 API라고 할 수 있다.
Web에서의 프로토콜로 Client-Server model을 사용한다.
Client가 서버로 Request를 보내면 서버는 이에 대해 Response를 보내주는 방식으로 통신한다.
HTTP는 기본적으로 TCP를 사용한다. 이 말은 Web을 켜서 화면이 나오기 전 과정에서 TCP 3-way handshaking 과정이 발생하여 TCP Connection이 이루어지고, 데이터가 오고간다는 말이 된다. 또한, HTTP는 Well-known port로 80번을 사용한다.
이런 HTTP는 1.0부터 1.1, 2.0, 3.0까지 여러 버전의 프로토콜이 존재한다. 이에 대한 차별점을 알아보기 전에 HTTP에서 가지는 몇 가지 특성들을 알아보자.
Stateless라는 것은 서버가 Client의 요청을 받았을 때 이 Client에 대한 정보를 가지고 있지 않는다. 즉 서버는 각 Client에 대한 State 정보들을 저장하거나 유지하지 않아 Client는 서버에 요청을 보낼 때마다 보내야 하는 모든 정보를 Request에 포함해야 한다.
반면, Stateful한 방식은 서버가 각 클라이언트의 상태를 저장하고 있다는 것이다. 이는 Cookie, Session, Token과 같은 기술들을 사용해 각 클라이언트의 State를 유지하거나 식별할 수 있다.
Non-Persistent는 연결이 계속해서 유지되지 않는다는 의미로, Client가 서버에게 필요한 각각의 데이터를 얻고자 할 때마다 TCP Connection을 계속 새로 연결하고 종료시키고 해야 한다. 예를 들어, 4개의 데이터를 받고자 한다면 3-way handshaking을 4번 수행하고, Terminaion handshaking도 4번 수행해야 한다. 이렇게 되면 오버헤드 Segment가 늘어나고 Round Trip 횟수가 많아진다는 특징이 있다.
반면, Persistent는 한 번의 연결에서 여러 개의 데이터(Object)를 보낼 수 있다. Non-persistent가 페이지 하나를 로드하기 위해서 여러 번의 Connection을 생성하고 종료시켜야 하는 반면, Persistent는 한 번의 Connection으로 충분하다.
초기 HTTP인 1.0은 1996년에 고안되었다. 이 HTTP 1.0은 Stateless하고 Non-persistent한 방식을 사용했다. 왜냐하면 서버에서 각 클라이언트의 State를 관리하는 것 자체가 TCP에서 State Transient Diagram을 가지고 있어야 하므로, 즉 History 정보를 계속 가지고 있는 등 복잡해지고, 메모리 관리도 필요하고, DDos 공격에도 약해지고,... 등의 이유로 초기에 사용되었던 HTTP 1.0은 Stateless를 사용했다.
그런데, 이렇게 Non-persistent를 사용하다보니 너무 Segment의 개수가 많아지게 되었고, 결국 로직이 복잡하더라도 Stateful을 사용하여 2014년에 HTTP 1.1 프로토콜을 만들게 되었다. 기존 HTTP 1.0은 데이터 4개를 얻기 위해서 SYN -> SYN+ACK -> ACK -> Object 1 -> ACK -> FIN -> FIN+ACK -> ACK 이 과정을 4번 반복해서 총 Segment가 8 * 4 = 32개가 필요했는데, Stateful하게 이용하면 SYN -> SYN+ACK -> ACK -> Object 1 -> ACK -> Object 2 -> ACK, .... -> FIN -> FIN+ACK -> ACK 까지 하여 14개의 Segment로 더 효율적으로 데이터를 전달받을 수 있게 되었다.
또한, HTTP 1.1은 Stateful하게 동작한다. 예를 들어, 기존 HTTP 1.0을 사용하는 쇼핑몰 서버에 사용자 A가 접속했다고 해보자. 그 후로 A가 접속을 끊고 다시 접속을 한다면, 서버는 이 접속을 요청한 것이 A임을 알 수 없었다. 그렇지만 쇼핑몰에서는 이 사용자에 대한 사용자 정보, 선호하는 품목, 장바구니 항목 등을 다시 A에게 보여줘야 하기 때문에 이러한 정보들을 저장하고 있는 등의 관리가 되어야 한다. 이를 Cookie를 통해 관리한다.
Cookie를 사용하는 과정은 다음과 같다. 클라이언트가 처음 서버에 접속하면 서버는 해당 클라이언트에 대한 내용을 DB에 저장해두고 Response에 Cookie값을 담아 보내준다. 시간이 지나 연결이 끊어지고 난 후 해당 클라이언트가 서버에 다시 접속할 때는 Cookie를 Request에 포함시켜서 보내주고, 이를 받은 서버는 이 쿠키값을 토대로 사용자의 정보를 Response 해준다.
이렇게 쿠키를 이용하는 것은 약간 Privacy가 침해되는 문제가 생길 수도 있기 때문에 최근 많은 서비스들에서는 Cookie 사용 여부를 묻고 있다.
현재 사용되고 있는 HTTP가 바로 이 1.1 버전이 되겠다.
HTTP 1.1은 Web cache도 사용한다. 이 웹 캐시라는 것은 사실 Proxy Server의 종류 중 하나이다.
내가 넷플릭스로 영화를 보는 상황을 가정해보자. 넷플릭스는 많은 사람들이 이용하는 서비스이니까 내가 접속할 때 다른 사용자 100명도 함께 접속하여 나와 같은 영화를 본다고 생각해보자. 이러면 넷플릭스 서버에서는 이 모든 사용자들에 대한 트래픽을 부담해줘야 한다는 우려가 생길 수 있고, Client와 서버 사이에 존재하는 네트워크의 Bandwidth가 이 모든 사용자들의 트래픽을 부담하지 못할 수 있다. 예를 들어, 네트워크는 1Gbps의 속도를 가지는 Bandwidth를 보장할 때 한 사용자가 영화를 보려면 100Mbps의 속도가 필요하다면 10명 이상의 사용자에 대해서 넷플릭스 서버는 처리해줄 수 없게 된다. 이렇게 되면 서버의 성능이 아무리 뛰어나도 네트워크의 속도로 인해 동시 접속 사용자의 수가 제한되는 상황이 발생한다.
이걸 어떻게 해결할까? 그냥 Bandwidth를 늘려야 할까? Bandwidth를 늘리는 작업은 쉬운 작업이 아니다. 대역폭 자체는 매우 비싼 자원이기 때문에 비용이 많이 들 뿐더러 바로 설치도 불가능하다.
이를 해결하기 위해서 나온 방법이 Proxy 서버를 두어 트래픽을 분산시키는 것이다. 넷플릭스 메인 서버가 미국에 있다고 할 때 전 세계 모든 사용자가 메인 서버에 접속하려 한다면 당연히 해당 서버에 부하가 가해지고, 네트워크의 용량을 넘어선 트래픽이 들어오면 역시 문제가 생긴다. 이를 여러 지역에 Proxy 서버를 두고 Client는 메인 서버가 안닌 Proxy 서버에 Request를 보내면 자연스레 트래픽이 분산되게 되어 문제를 해결할 수 있다.
이렇게 사용자의 위치에서 가장 가까운 서버에 Resource를 캐싱해두고 보다 빠르게 가져오는 네트워크를 CDN(Content Distribution Network)이라고 한다. 이런 CDN을 전문적으로 제공하는 회사들도 존재한다.
CDN을 구축하여 사용하면 그만큼 효율, 즉 Utilization이 좋아진다.
그러나, 이런 문제가 발생할 수 있다. 메인 서버에 최신 내용이 업데이트 되었는데, 이 내용이 Proxy 서버에는 아직 반영이 안 되었을 수 있다. 물리적으로 장치가 떨어져있으니 최신 변경 사항이 Proxy 서버로 전파되는데에 시간이 걸리기 때문이다.
이를 해결하기 위해 Proxy 서버는 메인 서버에 수정된 내용이 있는 지에 대해 질의하여 현재 Proxy 서버가 가진 내용이 최신 정보인 지를 확인할 수 있다. 이를 Conditional GET이라고 한다.
HTTP Request Message
Request는 이렇게 Carriage return 과 개행 기호로 line을 구분하여 사용된다.
Method
POST, GET, DELETE, PUT, HEAD 등의 메소드가 있고, 이 중에서 POST와 GET을 가장 흔히 사용한다. 이때, GET은 요청에 필요한 정보를 모두 Header에 담아야 하지만, POST의 경우 필요한 정보들을 Body에 숨기고 이 Body를 암호화한다면 외부에서 이 정보들을 알 수 없기 때문에 GET에 비해 보안성이 훨씬 강하다.
HTTP Response Message
Status Code
- 200 OK
- 301 Moved Permanently
- 400 Bad Requeest
- 404 Not Found
- 505 HTTP Version Not Supported
과 같은 상태 코드로 Response의 상태를 알려줄 수 있다.
이번에는 1.1에서 이런 문제가 생겼다. O_1가 일반 트래픽이고 O_2가 실시간성 트래픽이라고 해보자. HTTP는 TCP를 사용하기 때문에 O_1을 서버가 전부 다 보내야만 그 다음 트래픽인 O_2를 보낼 수 있다. 심지어 혹시 전송 중에 문제가 생기면 에러를 복원하기 전까지 다음 트래픽들을 계속 보내지 못한다. 그러나, 사용자의 입장에서는 좀 느리더라도 다른 것을 먼저 받는 것이 더 빠르다고 느낄 수도 있고 앞서 말한 것처럼 실시간성이 요구되는 트래픽의 경우 먼저 받고 싶을 수도 있다.
이런 문제는 Layer 3에서도 볼 수 있었다. Message Switching 방식으로 데이터를 보내기에는 너무 데이터가 커질 수 있어서 이를 방지하고자 여러 개의 Packet으로 데이터를 쪼개서 보내는 식으로 해결했었다.
이를 해결하기 위해 구글이 2015년에 HTTP 2.0을 제안했다. Packet switching으로 Message switching 문제를 해결한 것처럼 여러 프레임으로 Object data를 나누어서 전송한다.
이렇게 하면 Object가 프레임 단위로 나뉘어 병렬로 전송되기 때문에 크기가 큰 Object가 아직 전송될 동안 다른 Object들은 전송되어 페이지에 먼저 보이게 할 수 있다. -> UX 향상
HTTP 3.0은 비교적 최근인 2022년에 등장한 프로토콜이다. HTTP 2.0까지는 TCP를 사용했다. 하지만, 저번 포스트에서 다룬 것처럼 TCP는 약간의 문제점들이 있었다. 성능을 향상시키고 싶어도 너무 인터넷 상에 존재하는 장치들이 많아서 이 장치들을 일괄적으로 업데이트하기 힘들다는 점이 있었고, Connection-oriented, Reliable한 서비스이기 때문에 이를 위한 기능들이 많아 성능이 느려진다는 문제도 있었다.
이를 해결하기 위해서 저번 포스트에서 설명한 것처럼 UDP 위에서 TCP 기능을 Application으로 구성하는 QUIC을 사용할 수 있다. 실질적으로는 UDP 위에 TCP 기능이 구현되고(QUIC) 그 위에 HTTP가 동작하게 된다.
Application Layer까지 다루었다면 이제 브라우저에서 구글 화면을 내 기기에 띄우는 과정을 기술할 수 있다.
DHCP Ack를 받은 Client는 Default gateway IP 주소를 알았지만, Default gateway의 MAC 주소를 모르기 때문에 ARP request를 브로드캐스트하여 Default gateway를 찾는다.
ARP reply를 통해 Router의 Mac 주소를 알게 되었다면 이 주소를 Cache Table에 넣고 Destination으로 보낼 Packet 앞에 Default gateway의 MAC address를 붙여서 보내게 된다.