
네트워크 상에서 중앙 서버는 최소한의 작업만 하고, 각 피어(호스트)가 동등한 위치에서 서로 직접 자원을 공유하는 분산 네트워크 구조
클라이언트-서버 파일 분배에서 서버는 파일 복사본을 각 클라이언트에게 보내려면 서버에게 커다란 부하를 주고, 많은 양의 서버 대역폭을 소비한다.
P2P 파일 분배에서 각 피어는 수신한 파일의 임의의 부분을 다른 피어들에게 재분배할 수 있어서 서버의 분배 프로세스를 도울 수 있다.
(peer는 각 클라이언트도 호스트도 될수있다)
중앙 서버는 노드 간의 연결을 관리하거나 메타데이터를 각 피어(호스트)에게 제공하여 서로 연결하여 자원을 공유할수 있도록 도와주는 역할만 한다.
(always-on X / on-demand)
p2p구조는 peer가 추가 연결/해제 되면, 서비스가 늘어나며 자체 확장/축소 한다.
peer 참여: 즉, 네트워크에 새로운 노드가 참여할 때마다 그 노드는 자신의 자원(파일, 처리 능력, 대역폭 등)을 네트워크에 공유하게 된다.
자원 증가: peer가 추가되면 네트워크에서 이용할 수 있는 처리 능력과 대역폭이 증가한다.
(각 노드는 자신이 가진 데이터를 제공하거나 다른 노드의 데이터를 다운로드하는 데 기여)
로드 분산: 네트워크가 커지면 데이터 요청을 여러 peer로 분산시킬 수 있어, 개별 피어에 과부하가 걸리는 것을 방지한다.
(더 많은 노드가 있을수록, 데이터 요청에 대한 응답 가능성도 상승.)
비용 효율성: 중앙 서버가 필요하지 않아 인프라 비용이 적다. (각 노드가 비용 분담)
ex) P2P file sharing (BitTorrent),streaming (KanKan), VoIP (Skype)
피어가 간헐적으로 연결되고, IP 주소가 자주 변경 -> 관리가 힘들다
보안 문제: 노드들이 서로 동등하게 자원을 공유하기 때문에 악의적인 노드가 침입할 가능성이 존재
신뢰성: 노드가 자율적으로 참여하고 떠날 수 있기 때문에, 신뢰할 수 없는 노드가 존재하거나 네트워크 성능이 불안정할 수 있다.
| 비교 항목 | P2P(Peer-to-Peer) 구조 | 클라이언트-서버(Client-Server) 구조 |
|---|---|---|
| 구조적 특성 | 모든 노드가 동등한 위치에서 서버와 클라이언트 역할을 동시에 수행 | 중앙 서버가 자원을 관리하고, 클라이언트는 서버에 요청하는 구조 |
| 서버 의존성 | 서버가 최소한만 관여 | 중앙 서버에 크게 의존하며, 서버가 네트워크의 핵심 역할을 수행 |
| 노드 역할 | 각 노드는 클라이언트이면서 동시에 서버 역할을 수행 | 클라이언트는 서버에 요청하고, 서버는 요청에 응답하는 역할 분리 |
| 확장성 | 노드 수가 증가할수록 네트워크 성능과 자원 공급도 증가 | 서버 용량에 제한이 있으며, 확장성은 서버 성능에 크게 의존 |
| 트래픽 분배 | 트래픽이 노드 간에 분산되어 네트워크 부하가 분산됨 | 대부분의 트래픽이 서버로 집중되며, 서버 부하가 커질 수 있음 |
| 내결함성 | 일부 노드가 실패해도 다른 노드들이 네트워크를 유지하며 동작 가능 | 서버가 다운되면 클라이언트는 서버에 접근할 수 없으므로 서비스 중단 가능 |
| 신뢰성 | 신뢰할 수 없는 노드가 있을 수 있으며, 데이터 무결성에 문제가 생길 수 있음 | 중앙 서버가 데이터를 관리하므로 데이터 무결성과 신뢰성이 높음 |
| 자원 관리 | 자원(파일, 대역폭, 처리 능력)을 각 노드가 분산적으로 제공 | 중앙 서버가 자원을 집중적으로 관리하며 배포 |
| 처리 성능 | 노드가 많아질수록 처리 능력이 높아질 수 있음 | 서버의 성능이 전체 시스템 성능을 결정하며, 부하가 집중될 경우 성능 저하 가능 |
| 네트워크 관리 | 분산된 네트워크 관리로, 중앙에서 관리하기 어려움 | 중앙 서버에서 네트워크를 쉽게 모니터링하고 관리 가능 |
| 데이터 전송 방식 | 노드들 간의 직접 데이터 전송 (다수의 노드가 동시에 데이터 전송 가능) | 클라이언트와 서버 간의 직접 데이터 전송 |
| 예시 | BitTorrent, kankan, 초기 Skype | 웹사이트, 이메일 서비스, 데이터베이스 서버 |
분배 시간??
모든 N개의 피어들이 파일의 복사본을 얻는데 걸리는 시간

서버가 파일을 분배하는 시간(서버 transmission)
= (파일 사이즈 * 요청 피어 수) / 서버의 접속 링크 업로드 속도
= NF/u(s)
가장 낮은 다운로드 속도의 피어: d(min)
-> 가장 낮은 다운로드 속도의 클라가 다운받는데 걸리는 시간
= 최소 분배 시간 = F/d(min)
분배 시간 D(cs) = MAX( NF/u(s) , F/d(min))

N이 충분히 크면 클라이언트-서버 분배 시간 = NF/u(s) (N에 따라 선형 증가)
서버가 파일을 분배하는 시간(서버 transmission)
= (파일 사이즈) / 서버의 접속 링크 업로드 속도
= F/u(s)
가장 낮은 다운로드 속도의 피어: d(min)
-> 가장 낮은 다운로드 속도의 클라가 다운받는데 걸리는 시간
= 최소 분배 시간 = F/d(min)
NF/ (서버 업로드 시간 + 각 피어의 업로드 시간의 합)
= NF/u(total)
즉 분배시간은 다음과 같다.
분배시간 D(p2p) ≧ max {F/u(s) , F/d(min), NF/u(total)}


위 수식들에서 하한값은 서버-클라이언트 구조에서 서버가 전송을 스케줄링 하거나,
P2P 구조에서는 각 피어가 비트를 수신하자마자 그 비트를 재분배할 수 있다고 가정하면(실제로는 chunk가 재분배된다.),
식의 하한값을 최소 분배시간으로 채택할 수 있다.
트래커는 비트토렌트 네트워크에서 피어들의 위치 정보를 관리하는 서버.
트래커는 피어들이 어떤 파일 조각을 가지고 있는지, 어떤 피어들이 네트워크에 연결되어 있는지를 추적
특정 파일 분배에 참여하는 피어들의 그룹

토렌트에서는 파일을 일정 사이즈의 청크로 나눔
토렌트에 참여하는 피어들은 서로에게서 같은 크기의 청크(chunk)를 다운로드한다. (일반적으로 256KB)
처음으로 가입하면 그 피어에는 청크가 없지만, 시간이 지나면 점점 많은 청크를 쌓을 수 있다.
(accumulate chunks over time from other peers)
피어가 청크를 다운로드할 때 또한 청크를 다른 피어들에게 업로드한다.
일단 한 피어가 전체 파일을 얻으면 토렌트를 떠나거나, 토렌트에 남아서 다른 피어들로 청크를 계속해서 업로드할 수 있다.
피어는 다운로드와 동시에 다른 피어에 업로드를 하여 빠르게 파일을 전달하여 자원을 공유하는 상태를 만든다. (부하 분산 및 속도 향상)
Churn(변동성): 피어는 네트워크를 지유롭게 나가거나 들어올 수 있다.
피어는 파일을 다 받으면 나가거나(selfishly) 남아서 파일 공유를 도울 수 있다.(Altruistic)
가장 드문 것 부터 요청하는 희소 청크 우선 전략를 사용한다.(rarest first)
희소 청크 우선 전략은 네트워크 내에서 특정 파일 청크가 소수의 피어에게만 집중되지 않도록 하는 전략이어느 요청에 응답할지 결정할 때(데이터를 전달할 떄), 가장 빠른 속도로 나에게 데이터를 제공하는 이웃에게 우선순위를 주는 것이다.
1. 10초마다 가장 빠르게 전송하는 4개의 피어를 결정하고, 이 4개의 피어에게 청크를 보냄으로써 보답한다.
Unchoked 활성화되었다라고 한다.2. 30초마다 무작위로 다른 피어를 하나 선택하여 파일 전송 시작
이 전략은 새로운 피어들이 나와 데이터를 주고받을 수 있는 기회를 주기 위한 방법이다.
(계속해서 나와 주고받은 사람한테만 주게 되면 다른 피어에게는 데이터를 줄 기회가 없다)
이후 이 피어가 빠른 속도로 파일을 줬을 떄 Unchoked에 선택될수 있다
이떄 이 선택된 다른 피어를 Optimistic Unchoking 낙관적 활성화라고 한다.
비활성화(Choked)
- 자신에게 파일을 보내지 않거나 매우 느리게 보내는 피어들에게는 파일을 보내지 않는다. 이때 이 피어들을 Choked 피어라고 함
그리하여 총 5개의 피어와 빠르게 파일을 공유한다.

비디오 스트리밍 관련 통신이 전체 트래픽의 80퍼센트를 차지한다 (유튭 넷플..)
이러한 상황에서
수억명의 유저에게 비디오를 전달하려면 단일 대형 비디오 서버로는 불가하다
또한 각 유저마다 네트워크 상황이 다르다. (유선 VS 무선, 태블릿 VS 티비..)
이를 해결하기 위해 분산된 어플리케이션 레벨 인프라스트럭쳐를 사용해야한다. ex) CDN...
픽셀 단위로 구성되고, 각 픽셀은 휘도와 색상을 나타내는 여러 비트들로 인코딩된다.비트 전송률(bit rate - 초당 몇개의 비트를 포함하는지)로 압축해야한다.이미지 압축에는 공간적(spatial) 및 시간적(temporal) 중복성을 활용하여 사용되는 비트 수를 줄이는 방법이 있다.
1. 공간적 중복성 압축
단일 이미지 내에서 인접한 픽셀의 유사성을 줄여서 압축하는 방식.
2. 시간적 중복성 압축
연속적인 프레임 간의 차이를 이용하여 데이터를 압축하는 방식. 연속된 프레임에서 변하지 않는 부분은 재저장하지 않고 변한 부분만 저장
CBR(constant bit rate) - 고정된 bit rate를 사용하여 비디오를 전송
VBR(variable bit rate) - 비디오에서 공간적(Spatial) 또는 시간적(Temporal) 코딩의 요구 사항이 변화할 때마다 가변적으로 bit rate를 조정


빨간색 선 - 서버가 비디오를 보냄
파란색 선 - 클라이언트가 비디오를 받고 재생
- 클라이언트는 서버에서 데이터를 받으면서 동시에 초반 부분을 재생하고 있다.
(즉, 서버가 전송하는 동안 클라이언트는 먼저 받은 부분을 재생 -> )
비디오는 끊기거나 속도가 느려지지 않고, 실제로 제작된 비디오의 시간 흐름에 맞춰 재생되야 함
네트워크 지연 때문에 클라이언트 쪽에서 버퍼(buffer)를 두어 네트워크 지연이나 지터가 발생하더라도 부드럽게 재생될 수 있도록 데이터를 일시적으로 저장해 둠
Jitter - 네트워크 딜레이가 불규칙하게 변동하는상황

클라이언트는 비디오 데이터를 수신한 후 바로 재생하지 않고, 일정량의 데이터를 버퍼에 저장
버퍼링 과정이 완료되면 클라이언트는 일정한 속도로 비디오를 재생할 수 있다.
(client playout delay - 버퍼링이 완료된 후 클라이언트가 비디오 재생을 시작하기까지의 시간 지연)
버퍼링은 네트워크의 변동하는 지연(jitter)을 보완하고, 비디오가 끊김 없이 재생될 수 있도록 함.
클라이언트와 서버 간의 동적인 네트워크 상태에 맞춰 비디오 스트리밍 품질을 조정하는 방식 -> 비디오의 끊임없는 재생 가능
이렇듯 클라이언트는 자신의 상태를 파악하여 지능적으로 다음과 같은 요청을 한다.
1. 언제 비디오 조각을 요청할지
2. 어떤 인코딩 속도를 요청할지:
DASH 프로토콜이 가능하게 함)3. 어느 서버에 비디오 조각을 요청할지:
그럼이제 서버 입장에서는
동시 접속하는 수천 수억명의 유저에게 비디오 스트리밍 컨텐츠를 어떻게 제공할수 있을까?
떄문에 기각.
CDN ??
다수의 지점에 분산된 서버들을 운영하며, 비디오 및 다른 형태의 웹 콘텐츠 데이터의 복사본을 이러한 분산 서버에 저장한다. (CDN)
사용자는 최적의 사용자 경험을 제공받을 수 있는 지점의 CDN 서버로 연결(가장 가까운 곳..등..)
최대한 서버를 사용자 근처에 위치시켜 링크 및 라우터를 거치는 횟수를 줄여 지연시간 및 처리율을 개선하는 것이다.
(억세스ISP의 접속 네트워크에 서버 클러스터를 구축)
더 적은 수의 핵심 지점에 큰 규모의 서버 클러스터를 구축하여 ISP를 Home으로 가져오는 개념이다.
(억세스ISP-지역ISP의 중간 부분(IXP)의 접속 네트워크에 서버 클러스터를 구축)
클라이언트는 네트워크의 혼잡 상황이나 CDN서버의 물리적 거리에 따라 다른 CDN에 요청을 보내게 된다.
그렇다면 OTT서비스에서는 어떤것을 고려해야 할까?
1. 어떤 CDN을 선택해야 하는지?
2. 받는 도중 혼잡 발생시 어떤 처리를 해야 하는지?
3. 어떤 컨텐츠를 어떤 CDN서버에 저장해야 하는지? (한국CDN서버엔 한국관련 컨텐츠..)
요청을 가로챌 때 CDN은 DNS를 활용한다. 이를 DNS redirection이라고 한다.

그럼 bob이 netcinema 라는 곳에서 스트리밍 서비스를 받게 되는 과정을 살펴보자
이때 netcinema는 자체 CDN 서버를 구축하지 않고 kingCDN이란 회사의 서버에 사용료를 내고 CDN 서비스를 제공한다
사용자가 URL을 입력한다.(netcinema.com)
사용자의 호스트는 URL의 host name에 대한 질의를 로컬 DNS로 보낸다.
-> 로컬 DNS는 의 netcinema의 책임 DNS 서버로 질의를 전달한다.
netcinema의 책임 DNS 서버는 해당 질의를 CDN 서버로 연결하기 위해 kingCDN의 CDN 서버의 책임 DNS 서버의 IP를 전달한다.
로컬 DNS는 kingCDN의 CDN 서버의 책임 DNS로 질의를 보내고, kingCDN의 CDN 콘텐츠 서버의 IP 주소를 로컬 DNS 서버로 응답한다.
(이때 클라이언트가 콘텐츠를 전송받게 될 서버가 결정된다.)
로컬 DNS 서버는 사용자 호스트에게 CDN 서버의 IP 주소를 알려준다.
클라이언트는 호스트가 알게된 IP 주소로 HTTP 혹은 DASH 프로토콜을 통해 비디오를 받아온다.
Geographically Closest
지리적으로 가장 가까운 클러스터를 할당한다.
Real-time Measurements
클러스터와 클라이언트 간의 지연 및 손실 성능에 대한 주기적인 실시간 측정을 통해 현재 네트워크 상황을 반영하여 최선의 클러스터를 선택
넷플릭스는 자체 CDN을 구축하기 위해 넷플릭스는 IXP 및 거주용 ISP 자체에서 서버 랙(rack)을 설치한다.

넷플릭스 회원 서버에서 로그인
사용자가 재생할 영화를 선택한다.
아마존 클라우드에서 실행 중인 넷플릭스 소프트웨어가 영화 사본을 갖고 있는 CDN 서버를 결정한다.
영화가 있는 서버 중에서 클라이언트 요청에 대한 최적의 서버를 결정한다.
일반적으로 로컬 ISP가 CDN 서버 랙(rack)이 있다면 해당 CDN을 사용하거나, 근처 CDN 서버가 있는 IXP를 사용한다.
아마존 클라우드 서버는 요청된 영화의 다른 버전에 대한 URL을 가진 메니페스트 파일과 특정 서버의 IP 주소를 클라이언트에 보낸다.
클라이언트와 해당 CDN 서버는 독점 버전의 DASH를 이용하여 직접 상호작용한다.

송신 프로세스가 데이터의 패킷을 소켓 문 밖으로 밀어내기 전에, 먼저 패킷에 목적지 주소인 IP를 붙이고,
이 패킷이 송신자의 소켓을 통과한 후 인터넷은 이 목적지 주소를 이용하여 그 패킷을 인터넷을 통해 수신 프로세스에 있는 소켓으로 라우트(route)할 것이다.
패킷이 수신 소켓에 도착하면 수신 프로세스는 소켓을 통해 그 패킷을 추출하여 데이터를 받는다.
이 통신을 할때 UDP TCP 중 무슨 프로토콜을 사용하느야에 따라 소켓 프로그래밍 방식이 달라진다.
UDP는 사전연결이 없고, 전송시 loss가 발생할 수 있고, 순서가 바뀔수 있다.
UDP의 패킷을 Datagrams 라고 한다.

클라이언트
# socket module이다. 이 module을 통해 소켓을 생성할 수 있다.
from socket import *
#서버의 IP 혹은 서버의 호스트 이름을 할당한다.
serverName = ’hostname’
# 목적지 port 번호를 나타낸다.
serverPort = 12000
# 클라이언트 소켓을 생성한다. AF_INET은 IPv4를 사용하고 있음을 나타내고, SOCK_DGRAM은 UDP 소켓임을 의미한다.
clientSocket = socket(AF_INET, SOCK_DGRAM)
# 보낼 메시지를 입력 받는다.
message = Input(’Input lowercase sentence:’)
# 소켓으로 바이트 형태를 보내기 위해 먼저 문자열을 encode()를 통해 바이트 타입으로 변환한다.
# sendTo() 메서드는 목적지 주소를 메시지에 붙이고 그 패킷을 프로세스 소켓인 clientSocket으로 보낸다.
# 클라이언트 주소도 같이 보내지는데 이는 자동으로 수행된다.
clientSocket.sendto(message.encode(),(serverName, serverPort))
# 패킷 데이터는 modifiedMessage에 저장되고, 패킷의 출발지 주소(IP, port)는 serverAddress에 할당된다.
# recvfrom() 메서드는 2048의 버퍼 크기로 받아들인다.
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
# 출력
print(modifiedMessage.decode())
# 소켓 닫기
clientSocket.close()
서버
from socket import *
# 포트 번호
serverPort = 12000
# UDP 소켓 생성
serverSocket = socket(AF_INET, SOCK_DGRAM)
# 12000 포트 번호를 소켓에 할당한다. 이를 통해 서버 IP 주소의 12000 포트로 패킷을 보내면 해당 소켓으로 패킷이 전달된다.
serverSocket.bind((’’, serverPort))
print(”The server is ready to receive”)
while True:
# 패킷이 서버에 도착하면 데이터는 메세지에 할당되고 패킷의 출발지 주소는 clientAddress에 저장된다.
# 해당 주소로 서버는 응답을 어디에 보내야할지 알 수 있다.
message, clientAddress = serverSocket.recvfrom(2048)
# 바이트 데이터를 decode()하고 대문자로 변환한다.
modifiedMessage = message.decode().upper()
# 클라이언트 주소를 대문자로 변환된 메시지에 붙이고, 그 결과로 만들어진 패킷을 서버에 보낸다.
# 서버의 주소도 같이 보내지는데 이는 자동으로 수행된다.
serverSocket.sendto(modifiedMessage.encode(), clientAddress)
TCP는 통신전에 클라의 요청으로 사전연결을 시작하고,
데이터 신뢰성과flow-control지원
클라이언트 소켓은 서버 소켓과 TCP 연결을 하고 연결된 소켓을 통해 데이터를 주고 받는다.
이때 연결된 서버는 다른 사용자의 요청이 오면 새로운 소켓을 생성해서 통신한다.

클라이언트
from socket import *
serverName = ’servername’
serverPort = 12000
# 클라이언트 소켓을 의미한다. SOCK_STREAM으로 TCP 소켓임을 명시했다.
# UDP 때와 마찬가지로 따로 출발지 주소를 명시하지 않는다. (운영체제가 대신 해준다.)
clientSocket = socket(AF_INET, SOCK_STREAM)
# 클라이언트가 TCP 소켓을 이용하여 서버로 데이터를 보내기 전에 TCP 연결이 먼저 클라이언트와 서버 사이에 설정되어야 한다.
# 해당 라인으로 TCP 연결을 시작하고, connect() 메서드의 파라미터는 연결의 서버 쪽 주소이다.
# 이 라인이 수행된 후에 3-way handshake가 수행되고 클라이언트와 서버 간에 TCP 연결이 설정된다.
clientSocket.connect((serverName, serverPort))
sentence = raw_input(’Input lowercase sentence:’)
# 클라이언트 소켓을 통해 TCP 연결로 보낸다. UDP 소켓처럼 패킷을 명시적으로 생성하지 않으며 패킷에 목적지 주소를 붙이지 않는다.
# 대신 클라이언트 프로그램은 단순히 문자열에 있는 바이트를 TCP 연결에 제공한다.
clientSocket.send(sentence.encode())
# 서버로부터 바이트를 수신하기를 기다린다.
modifiedSentence = clientSocket.recv(1024)
print(’From Server: ’, modifiedSentence.decode())
# 연결을 닫는다. 이는 클라이언트 TCP가 서버의 TCP에게 TCP 메시지를 보내게 한다.
clientSocket.close()
서버
from socket import *
serverPort = 12000
# TCP 소켓 생성
serverSocket = socket(AF_INET, SOCK_STREAM)
# 서버의 포트 번호를 소켓과 연관시킨다.
serverSocket.bind((’’, serverPort))
# 연관시킨 소켓은 대기하며 클라이언트가 문을 두드리기를 기다린다.
# 큐잉되는 연결의 최대 수를 나타낸다.
serverSocket.listen(1)
print(’The server is ready to receive’)
while True:
# 클라이언트가 TCP 연결 요청을 하면 accept() 메소드를 시작해서 클라이언트를 위한 연결 소켓을 서버에 생성한다.
# 그 뒤 클라이언트와 서버는 핸드셰이킹을 완료해서 클라이언트의 소켓과 연결 소켓 사이의 TCP 연결을 생성한다. (addr는 클라 주소)
connectionSocket, addr = serverSocket.accept()
sentence = connectionSocket.recv(1024).decode()
capitalizedSentence = sentence.upper()
connectionSocket.send(capitalizedSentence.encode())
# 응답을 보내고 연결 소켓을 닫는다. 그러나 환영소켓인 serverSocket이 열려있어 다른 클라이언트가 서버에 연결을 요청할 수 있다.
connectionSocket.close()