학교
[컴퓨터 네트워크] Chapter 3. Transport Layer (Part2) - (1)
일자 : 24-2 7주차 1차시 (2)
4. Principles of reliable data transfer
[1] Principles of reliable data transfer
1) reliable service abstraction

: 송신 프로세스와 수신 프로세스 사이에 신뢰성 있는 채널이 존재한다고 가정한다.
2) reliable service implementation (by unreliable channel)
: 송신 프로세스와 수신 프로세스 사이에 신뢰성 없는 채널이 존재한다고 가정한다.
이 문제를 해결하기 위해 신뢰성 있는 데이터 전송 프로토콜의 구현이 필요하다.
송신 측에는
신뢰성 있는 데이터 전송 프로토콜의 송신측 구현이 존재하여 데이터 손실이나 오류를 보완한다.수신 측에는
신뢰성 있는 데이터 전송 프로토콜의 수신측 구현이 존재하여 데이터가 정확하게 도착하도록 한다.
신뢰성 있는 데이터 전송 프로토콜의 복잡성은 불안정한 채널의 특성(데이터 손실, 손상, 순서 변경 여부)에 크게 의존한다.
송신자와 수신자는 서로의 “상태”를 알지 못한다. 예를 들어, 메시지가 수신되었는지 여부를 알 수 없다.
조건: 메시지를 통해 소통하지 않는 한
[2] 신뢰성 있는 데이터 전송: 시작하기
1) 우리는 다음을 수행할 것이다
신뢰성 있는 데이터 전송 프로토콜(rdt)의 송신 측과 수신 측을 단계적으로 개발한다.
단방향 데이터 전송만 고려한다.
하지만 제어 정보는 양방향으로 흐를 것이다!
송신자와 수신자를 명시하기 위해 유한 상태 머신(FSM)을 사용한다.
2) state

state (상태) : 현재 이 "상태"에 있을 때, 다음 상태는 다음 이벤트에 의해 고유하게 결정된다.
현재 시스템이 어떤 특정한 조건이나 상황에 있는지를 나타내는 지점이다.
예시: 예를 들어, 데이터 전송 중인지, ACK(확인 응답)를 기다리는 중인지 등
상태 전환을 일으키는 이벤트 / 상태 전환 시 수행되는 작업
상태 전환은 특정 이벤트가 발생할 때 일어난다.
예시: 데이터가 정상적으로 전송된 후 ACK를 받으면 "데이터 전송 완료" 상태로 전환되는 경우
[3] 신뢰성 있는 데이터 전송 프로토콜 (rdt): 인터페이스
rdt_send(): "소포 준비"비유: 마치 당신이
소포를 보내려고 준비하는 상황이다. 소포에 넣을 물건(데이터)을 준비하고, 그 소포를 택배 회사(프로토콜)에게 넘겨준다.설명:
rdt_send()는 상위 계층(보내려는 프로그램)에서 호출되어,전달할 데이터를 프로토콜에게 넘겨주는 역할을 한다.
udt_send(): "택배 회사의 트럭 운전사"비유: 택배 회사가 운전사를 불러서
소포를 먼 거리에 있는 수신자에게 보내는 것이다. 다만, 이 운전사는 불안정한 도로를 이용하기 때문에 소포가 손상되거나 길을 잃을 수도 있다.설명:
udt_send()는 불안정한 채널(운전사와 도로)을 통해 수신자에게 패킷(소포)을 보내기 위해 호출된다. 이 과정에서 신호가 손실되거나 문제가 생길 수 있는 부분이다.
deliver_data(): "수신자에게 소포 전달"비유: 택배 회사가
소포를 수신자 집 앞까지 안전하게 전달하고,수신자가 직접 물건을 받는 상황이다.설명:
deliver_data()는 수신자가 소포(데이터)를 최종적으로 받는 동작으로, 데이터를 상위 계층으로 안전하게 전달하는 역할을 한다.
rdt_rcv(): "소포 도착 알림"비유:
수신자 쪽에서 소포가 도착했다는 알림을 받는 것과 같습니다. 택배 회사가 알림을 주면, 수신자는 소포를 열어 확인한다.설명:
rdt_rcv()는 패킷이 수신 측에 도착했을 때 호출되며, 수신 측에서 데이터를 받기 시작할 준비를 하게 한다.
[4] rdt1.0: 신뢰할 수 있는 채널을 통한 신뢰성 있는 전송

1) 특징
하위 채널은 완벽하게 신뢰할 수 있다.
비트 오류 없음
패킷 손실 없음
송신자와 수신자 각각에 대해 별도의 FSM이 존재한다.
송신자는 데이터를 하위 채널로 전송한다.
수신자는 데이터를 하위 채널에서 읽는다.
2) 명령어 순서
rdt_send(data)
송신자는 데이터를 전송하기 위해rdt_send함수를 호출한다. 이 함수는 데이터를 준비하고, 그 데이터를 패킷으로 만든다.packet = make_pkt(data)
make_pkt(data)함수가 호출되어 데이터를 포함한 패킷이 생성된다.udt_send(packet)
생성된 패킷을 하위 신뢰할 수 있는 채널을 통해 전송하는 함수인udt_send(packet)가 호출된다. 이때 패킷은 송신자의 하위 채널을 통해 전송된다.rdt_rcv(packet)
수신자는rdt_rcv(packet)함수로 수신한 패킷을 받는다. 이 함수는 수신된 패킷을 처리할 준비를 한다.extract(packet, data)
extract(packet, data)함수는 패킷에서 데이터를 추출하여data변수에 저장한다.deliver_data(data)
마지막으로deliver_data(data)함수가 호출되어 추출된 데이터를 상위 계층으로 전달한다. 이 데이터는 애플리케이션에 전달되어 최종적으로 처리된다.
[5-1] rdt2.0: 비트 오류가 있는 채널
1) 특징
하위 채널(underlying channel)은 패킷에서 비트를 뒤집을 수 있다.
체크섬(예: 인터넷 체크섬)을 사용하여 비트 오류를 감지한다.
질문: 오류가 발생했을 때 어떻게 복구할 것인가?
2) 인간은 대화 중에 "오류"를 어떻게 복구할까?
: 대화를 중단하고, 상대방이 잘못 말한 부분을 다시 말하거나 확인한다.
2) 오류가 발생했을 때 복구하는 방법
확인 응답 acknowledgements (ACKs): 수신자는 송신자에게 패킷이 정상적으로 수신되었음을 명시적으로(explicitly) 알린다.부정 응답 negative acknowledgements (NAKs): 수신자는 송신자에게 패킷에 오류가 있음을 명시적으로(explicitly) 알린다.송신자는 NAK을 받으면(on receipt of) 패킷을 재전송한다.
stop and wait: 송신자는 하나의 패킷을 전송한 후, 수신자의 응답을 기다린다.
[5-2] FSM 사양 (specification)
: 송신자와 수신자의 FSM 명령어 순서 및 역할
1) 송신자

wait for call from above -> wait for ACK or NAK
송신자는 데이터를 보내기 전에 위쪽 계층에서 호출을 기다린다. 데이터를 받으면 ACK 또는 NAK을 기다린다.rdt_send(data): 데이터 전송을 준비한다.snpkt = make_pkt(data, checksum): 데이터를 패킷으로 만들어 체크섬을 추가한다.udt_send(snpkt): 패킷을 하위 채널을 통해 전송한다.
wait for ACK or NAK: NAK일 때
송신자는 ACK(정상 전송 확인) 또는 NAK(오류 발생 알림)를 기다린다.rdt_rcv(rcvpkt) && isNAK(rcvpkt): 수신한 패킷이 NAK이면 오류가 발생한 것이므로 다시 전송한다.udt_send(sndpkt): 오류가 발생했으므로 패킷을 다시 전송한다.
wait for ACK or NAK -> Wait for call from above: ACK일 때
송신자는 다시 ACK 또는 NAK을 기다린다. NAK이 오지 않으면 정상적으로 전송이 완료된 것으로 판단하고 위쪽 계층으로 데이터를 전달하기 위해 기다린다.rdt_rcv(rcvpkt) && isACK(rcvpkt): ACK가 오면 정상적으로 데이터가 수신된 것이므로 전송을 완료하고 다시 호출을 기다린다.
2) 수신자

wait for call from below (1)
수신자는 아래에서 데이터를 받기를 기다린다. 패킷이 손상되었을 경우 NAK을 송신자에게 보내어 오류를 알린다.rdt_rcv(rcvpkt) && corrupt(rcvpkt): 패킷이 손상되었으면 NAK을 보낸다.udt_send(NAK): 손상된 패킷에 대해 NAK을 송신자에게 전송한다.
wait for call from below (2)
수신자는 정상적인 패킷을 받으면 데이터를 추출하고, 정상적으로 수신되었음을 ACK으로 송신자에게 알린다.rdt_rcv(rcvpkt) && notcorrupt(rcvpkt): 패킷이 손상되지 않았으면 데이터를 추출하여 처리한다.extract(rcvpkt, data): 패킷에서 데이터를 추출한다.deliver_data(data): 추출한 데이터를 상위 계층으로 전달한다.udt_send(ACK): 정상적으로 수신된 패킷에 대해 ACK을 송신자에게 전송한다.
3) 참고
: 수신자가 송신자의 메시지를 정확히 받았는지에 대한 "상태"는 수신자가 송신자에게 이를 전달하지 않으면 송신자에게 알려지지 않는다.
: (받는 사람이 보낸 사람의 메세지를 정확히 받았는지에 대한 상태는, 받는 사람이 보낸 사람에게 이를 전달하지 않으면 보낸 사람에게 알려지지 않는다.)
그래서 우리는
프로토콜이 필요하다!
[5-3] rdt2.0: 오류가 없는 경우의 동작

1) 과정 설명
송신자:
rdt_send(data): 송신자는 위쪽 계층에서 호출을 기다린 후 데이터를 전송한다.snkpkt = make_pkt(data, checksum): 송신자는 데이터를 패킷으로 만들고 체크섬을 추가하여 오류를 검출할 준비를 한다.udt_send(snpkt): 패킷을 하위 채널을 통해 전송한다.
수신자:
rdt_rcv(rcvpkt): 수신자는 패킷을 받는다.notcorrupt(rcvpkt): 패킷이 손상되지 않았으면.extract(rcvpkt, data): 패킷에서 데이터를 추출한다.deliver_data(data): 상위 계층으로 데이터를 전달한다.udt_send(ACK): 데이터가 정상적으로 수신되었음을 송신자에게 알리기 위해 ACK를 보낸다.
송신자:
rdt_rcv(rcvpkt) && isACK(rcvpkt): 송신자는 ACK를 기다린다. ACK를 받으면 정상적으로 데이터 전송이 완료된 것으로 판단하고 다시 전송을 기다린다.
이 과정에서는 데이터가 손상되지 않으므로 송신자와 수신자는 패킷 전송과 응답을 정상적으로 주고받으며, 송신자는 정상적으로 ACK을 받으면 데이터 전송을 완료한다.
[5-4] rdt2.0: 패킷 손상 시 시나리오

1) 과정 설명
송신자:
rdt_send(data): 송신자는 데이터를 전송하기 위해 호출을 기다린다.snkpkt = make_pkt(data, checksum): 송신자는 데이터를 패킷으로 만들고 체크섬을 추가하여 오류를 검출할 준비를 한다.udt_send(snpkt): 패킷을 하위 채널을 통해 전송한다.
수신자:
rdt_rcv(rcvpkt): 수신자는 패킷을 받는다.corrupt(rcvpkt): 수신자는 패킷에 오류가 있음을 발견한다.udt_send(NAK): 수신자는 오류가 있는 패킷에 대해 NAK을 송신자에게 보낸다.
송신자:
rdt_rcv(rcvpkt) && isNAK(rcvpkt): 송신자는 NAK을 받으면 오류가 발생했음을 알게 된다.udt_send(sndpkt): 송신자는 패킷을 다시 전송한다.
수신자:
rdt_rcv(rcvpkt): 수신자는 다시 패킷을 받는다.notcorrupt(rcvpkt): 패킷이 손상되지 않았다면.extract(rcvpkt, data): 데이터를 추출한다.deliver_data(data): 데이터를 상위 계층으로 전달한다.udt_send(ACK): 정상적으로 수신된 패킷에 대해 ACK을 송신자에게 전송한다.
송신자:
rdt_rcv(rcvpkt) && isACK(rcvpkt): 송신자는 ACK을 받으면 전송이 완료되었음을 확인한다.
이 과정에서는 패킷이 손상되었을 때, 수신자가 NAK을 보내고 송신자가 패킷을 재전송하여 오류를 복구한다.
[6-1] rdt2.0에는 치명적인 결함(fatal flaw)이 있다!
1) 만약 ACK/NAK가 손상되면 어떻게 될까?
송신자는 수신자에서 무슨 일이 일어났는지 알 수 없다!
단순히 재전송할 수 없다: 중복 패킷이 발생할 수 있음
2) 솔루션 : 중복 처리
송신자는 ACK/NAK가 손상되면 현재 패킷을 재전송한다.
송신자는 각 패킷에
시퀀스 번호를 추가한다.수신자는
중복된 패킷을 폐기하고(상위 계층으로 전달하지 않음) 처리한다.
stop and wait: 송신자는 하나의 패킷을 전송한 후, 수신자의 응답을 기다린다.
[6-2] sender, handling garbled ACK/NAKs

1) 과정 설명
위쪽에서 호출 0을 기다림 -> ACK 또는 NAK 0을 기다림
rdt_send(data): 데이터를 송신하기 위한 호출을 기다린다.snpkt = make_pkt(0, data, checksum): 패킷을 시퀀스 번호 0과 함께 생성하고, 체크섬을 추가한다.udt_send(sndpkt): 생성된 패킷을 전송한다.
ACK 또는 NAK 0을 기다림
rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || isNAK(rcvpkt)): 수신한 패킷이 손상되었거나 NAK인 경우udt_send(sndpkt): 손상된 패킷이거나 NAK인 경우, 송신자는 패킷을 재전송한다.
ACK 또는 NAK 0을 기다림 -> 위쪽에서 호출 1을 기다림
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt): 수신한 패킷이 손상되지 않았고, ACK가 정상적으로 오면 전송이 성공한 것으로 간주하여 처리한다.
위쪽에서 호출 1을 기다림 -> ACK 또는 NAK 1을 기다림
rdt_send(data): 송신자는 다시 데이터를 송신하기 위해 호출을 기다린다.snpkt = make_pkt(1, data, checksum): 이번에는 시퀀스 번호 1을 포함한 새로운 패킷을 생성하고 체크섬을 추가한다.udt_send(sndpkt): 생성된 패킷을 전송한다.
ACK 또는 NAK 1을 기다림 -> 위쪽에서 호출 0을 기다림
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt): 수신한 패킷이 손상되지 않았고, ACK가 정상적으로 오면 송신자는 데이터가 성공적으로 전달되었다고 판단하고 다시 호출을 기다린다.
2) 설명:
이 과정은 손상된 ACK나 NAK가 발생했을 때 송신자가 어떻게 이를 처리하는지에 대한 내용이다. 송신자는 각 패킷에 시퀀스 번호를 붙여서, 손상된 ACK나 NAK를 받았을 때 재전송을 통해 중복을 처리할 수 있다. 시퀀스 번호를 추가하면 수신자는 중복된 패킷을 구분할 수 있게 되어, 재전송된 패킷을 제대로 처리할 수 있다.
[6-3] rdt2.1: 수신자, 손상된 ACK/NAK 처리
: 이 내용은 rdt2.1에서 수신자가 손상된 ACK/NAK를 어떻게 처리하는지에 대한 설명
rdt2.1
에서는 수신자가 받은 패킷이 손상된 경우(예: 체크섬 오류) **NAK**(Negative Acknowledgment) 패킷을 보내고, 제대로 받은 경우에는 **ACK**(Acknowledgment) 패킷을 보냅니다.

1. 아래로부터 0을 기다림 → 아래로부터 1을 기다림
패킷 수신:
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq0(rcvpkt)이 조건을 만족하면, 수신자는 패킷을 정상적으로 받았고, 시퀀스 번호가 0인 패킷을 받은 상태이다.
패킷 처리:
extract(rcvpkt, data): 받은 패킷에서 데이터를 추출한다deliver_data(data): 추출한 데이터를 상위 계층에 전달한다.
ACK 송신:
sndpkt = make_pkt(ACK, checksum): 정상적으로 받은 패킷에 대한 ACK를 만들어 송신 준비한다.udt_send(sndpkt): ACK 패킷을 송신합니다.
2. 아래로부터 1을 기다림
경우 1 (정상 패킷 받음, 시퀀스 번호 0):
rdt_rcv(rcvpkt) && not corrupt(rcvpkt) && has_seq0(rcvpkt)패킷이 손상되지 않았고, 시퀀스 번호가 0인 경우 정상적으로 ACK를 보낸다.
sndpkt = make_pkt(ACK, checksum): ACK 패킷을 만들어 송신 준비한다.udt_send(sndpkt): ACK 패킷을 송신한다.
경우 2 (패킷 손상됨):
rdt_rcv(rcvpkt) && corrupt(rcvpkt)패킷이 손상된 경우에는 NAK를 보내야 한다.
sndpkt = make_pkt(NAK, checksum): NAK 패킷을 만들어 송신 준비한다.udt_send(sndpkt): NAK 패킷을 송신한다.
3. 아래로부터 1을 기다린 후 → 아래로부터 0을 기다림
패킷 수신:
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq1(rcvpkt)이 조건을 만족하면, 수신자는 정상적으로 시퀀스 번호 1을 가진 패킷을 받은 상태이다.
패킷 처리:
extract(rcvpkt, data): 받은 패킷에서 데이터를 추출한다.deliver_data(data): 추출한 데이터를 상위 계층에 전달한다.
ACK 송신:
sndpkt = make_pkt(ACK, checksum): ACK 패킷을 만들어 송신 준비한다.udt_send(sndpkt): ACK 패킷을 송신한다.
4. 아래로부터 0을 기다림
경우 1 (정상 패킷 받음, 시퀀스 번호 1):
rdt_rcv(rcvpkt) && not corrupt(rcvpkt) && has_seq1(rcvpkt)정상적으로 시퀀스 번호 1을 가진 패킷을 받으면 ACK를 보내야 한다.
sndpkt = make_pkt(ACK, checksum): ACK 패킷을 만들어 송신 준비한다.udt_send(sndpkt): ACK 패킷을 송신한다.
경우 2 (패킷 손상됨):
rdt_rcv(rcvpkt) && corrupt(rcvpkt)패킷이 손상된 경우에는 NAK를 보내야 합니다.
sndpkt = make_pkt(NAK, checksum): NAK 패킷을 만들어 송신 준비한다.udt_send(sndpkt): NAK 패킷을 송신한다.
5. 설명
rdt2.1에서는 수신자가 손상된 ACK/NAK를 처리하는 방식이 포함된다. 송신자가 패킷을 보내고, 수신자는 이를 확인한 후:패킷이 손상되지 않았으면 ACK를 보내고,
패킷이 손상되었으면 NAK를 보낸다.
수신자가 받은 패킷이 손상되었거나 시퀀스 번호가 올바르지 않으면, 송신자에게 이를 알려주기 위해 NAK를 송신하고, 제대로 받은 패킷에 대해서는 ACK를 송신한다.
-이 방식은 신뢰성 있는 데이터 전송을 보장하고, 손상된 패킷이나 중복된 패킷을 처리할 수 있게 한다.
[6-4] rdt2.1: 토론
1) 송신자
패킷에 시퀀스 번호 추가
두 개의 시퀀스 번호(0, 1)만으로 충분하다.
왜?
수신한 ACK/NAK가 손상되었는지 확인해야 한다.
상태가 두 배 많음
상태는 "예상되는" 패킷의 시퀀스 번호가 0 또는 1이어야 하는지 기억해야 한다.
2) 수신자
수신한 패킷이 중복되었는지 확인해야 한다.
상태는 0 또는 1이 예상되는 패킷 시퀀스 번호를 나타낸다.
참고: 수신자는 자신의 마지막 ACK/NAK가 송신자에게 정상적으로 수신되었는지 알 수 없다.
[7-1] rdt2.2: NAK 없는 프로토콜
rdt2.1과 동일한 기능을 제공하지만, ACK만 사용
NAK 대신, 수신자는
마지막으로 정상적으로 수신된 패킷에 대한 ACK를 보낸다.수신자는
ACK되는 패킷의 시퀀스 번호를 명시적으로 포함해야 한다.
송신자에서 중복된 ACK를 받으면 NAK와 동일한 동작이 수행된다: 현재 패킷을 재전송한다.
우리가 볼 수 있듯이, TCP는 NAK 없는 방식으로 이 접근을 사용한다.
[7-2] rdt2.2 : 송신자

Wait for call 0 from above -> Wait for Ack 0
rdt_send(data): 송신자는 데이터를 받으면, 패킷을 만들기 위해 호출한다.
sndpkt = make_pkt(0, data, checksum): 데이터와 시퀀스 번호 0을 포함한 패킷을 생성한다.
udt_send(sndpkt): 생성된 패킷을 전송한다.
Wait for Ack 0
rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || isACK(rcvpkt, 1)): 송신자는 수신된 패킷이 손상되었거나, 잘못된 ACK(시퀀스 번호 1을 가진 ACK)를 받았을 경우, 재전송을 해야 한다.
udt_send(sndpkt): 잘못된 ACK를 받으면 이전에 전송한 패킷을 재전송한다.
Wait for Ack 0 -> Wait for call 1 from above
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt, 0): ACK 0이 손상되지 않고 정상적으로 수신되면, 송신자는 1번 시퀀스 번호의 패킷을 보낼 준비를 한다.
Wait for call 1 from above -> Wait for Ack 1
rdt_send(data): 송신자는 새로운 데이터를 받으면, 1번 시퀀스 번호를 가진 새로운 패킷을 보내기 위해 호출한다.
sndpkt = make_pkt(1, data, checksum): 데이터와 시퀀스 번호 1을 포함한 패킷을 생성한다.
udt_send(sndpkt): 생성된 패킷을 전송한다.
Wait for Ack 1
rdt_rcv(rcvpkt) && corrupt(rcvpkt) || isACK(rcvpkt, 0): 송신자는 ACK 1을 기다리는 동안 패킷이 손상되었거나, ACK가 0을 포함한 잘못된 ACK를 받았을 경우, 재전송을 해야 한다.
udt_send(sndpkt): 잘못된 ACK를 받으면 이전에 전송한 패킷을 재전송한다.
Wait for Ack 1 -> Wait for call 0 from above
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt, 1): ACK 1이 손상되지 않고 정상적으로 수신되면, 송신자는 0번 시퀀스 번호의 패킷을 보낼 준비를 한다.
[7-3] rdt2.2: 수신자

Wait for 0 from below -> Wait for 1 from below
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq0(rcvpkt): 수신자는 시퀀스 번호가 0인 패킷을 정상적으로 받았는지 확인한다.extract(rcvpkt, data): 받은 패킷에서 데이터를 추출한다.deliver_data(data): 추출한 데이터를 상위 계층에 전달한다.sndpkt = make_pkt(ACK, 0, checksum): 수신자는 시퀀스 번호 0에 대한 ACK 패킷을 생성한다.udt_send(sndpkt): 생성된 ACK 패킷을 송신자에게 전송한다.
Wait for 1 from below
rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || has_seq0(rcvpkt)): 수신자는 수신된 패킷이 손상되었거나, 시퀀스 번호 0을 가진 패킷을 받았을 때 확인한다.sndpkt = make_pkt(ACK, 0, checksum): 이 경우, 수신자는 시퀀스 번호 0에 대한 ACK 패킷을 다시 생성하여 송신자에게 전송한다.udt_send(sndpkt): ACK 패킷을 송신자에게 전송한다.
Wait for 1 from below -> Wait for 0 from below
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq1(rcvpkt): 수신자는 시퀀스 번호 1을 가진 정상적인 패킷을 받았는지 확인한다.extract(rcvpkt, data): 받은 패킷에서 데이터를 추출한다.deliver_data(data): 추출된 데이터를 상위 계층에 전달한다.sndpkt = make_pkt(ACK, 1, checksum): 수신자는 시퀀스 번호 1에 대한 ACK 패킷을 생성한다.udt_send(sndpkt): 생성된 ACK 패킷을 송신자에게 전송한다.
Wait for 0 from below
rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || has_seq1(rcvpkt)): 수신자는 수신된 패킷이 손상되었거나, 시퀀스 번호 1을 가진 패킷을 받았을 때 이를 확인한다.sndpkt = make_pkt(ACK, 1, checksum): 수신자는 시퀀스 번호 1에 대한 ACK 패킷을 생성하여 송신자에게 전송한다.udt_send(sndpkt): 생성된 ACK 패킷을 송신자에게 전송한다.
[8-1] rdt3.0: 오류와 손실이 있는 채널
새로운 채널 가정: 기저 채널(underlying channle)은 패킷(데이터, ACKs)을 잃어버릴 수 있다.
체크섬, 시퀀스 번호, ACKs, 재전송은 도움이 되지만 완전히 해결되지는 않는다.
Q: 인간은 대화에서 송신자-수신자 간의 단어 손실을 어떻게 처리하는가?
A: 인간은 대화에서 단어를 잃어버린 경우, 일반적으로 다시 묻거나 반복하여 확인한다. 예를 들어, "무슨 말씀이셨죠?"라고 묻거나, 이해가 되지 않은 부분을 다시 설명해달라고 요청한다
[8-2] rdt3.0: 오류와 손실이 있는 채널
접근법: 송신자는 ACK를 기다리는 "합리적인(reasonable)" 시간을 설정
이 시간 내에 ACK를 받지 못하면 패킷을 재전송한다.
만약 패킷(또는 ACK)이 단지 지연된 경우(손실되지 않은 경우):
재전송은 중복될 수 있지만, 시퀀스 번호가 이미 이를 처리한다!
수신자는 ACK된 패킷의 시퀀스 번호를 명시해야 한다.
"합리적인" 시간이 지나면 타이머가 종료되도록 카운트다운 타이머를 사용한다.
[8-3] rdt3.0 송신자 (1)

Wait for call 0 from above -> Wait for ACK0
rdt_send(datA): 송신자가 데이터를 보내기 위해 호출sndpkt = make_pkt(0, data, checksum): 시퀀스 번호 0을 가진 패킷 생성udt_send(sndpkt): 패킷을 전송start_timer: 타이머를 시작하여 일정 시간 내에 ACK가 도착하지 않으면 재전송
Wait for ACK0 -> Wait for call 1 from above
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt, 0): 시퀀스 번호 0인 ACK 패킷이 손상되지 않고 수신되면 ACK 수신 처리stop_timer: 타이머를 중지
Wait for call 1 from above -> Wait for ACK1
rdt_send(datA): 송신자가 데이터를 보내기 위해 호출sndpkt = make_pkt(1, data, checksum): 시퀀스 번호 1을 가진 패킷 생성udt_send(sndpkt): 패킷을 전송start_timer: 타이머를 시작하여 일정 시간 내에 ACK가 도착하지 않으면 재전송
Wait for ACK1 -> Wait for call 0 from above
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt, 1): 시퀀스 번호 1인 ACK 패킷이 손상되지 않고 수신되면 ACK 수신 처리stop_timer: 타이머를 중지
알겠습니다! 다시 포맷을 유지하여 작성해드리겠습니다.
[8-4] rdt3.0 송신자 (2)

Wait for call 0 from above -> Wait for ACK0
rdt_send(datA): 송신자가 데이터를 보내기 위해 호출sndpkt = make_pkt(0, data, checksum): 시퀀스 번호 0을 가진 패킷 생성udt_send(sndpkt): 패킷을 전송start_timer: 타이머를 시작하여 일정 시간 내에 ACK가 도착하지 않으면 재전송
Wait for ACK0
경우 1
rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || isACK(rcvpkt, 1)): 수신된 ACK 패킷이 손상되었거나 시퀀스 번호가 1인 경우 → 재전송을 위한 타이머가 작동
경우 2
timeoutudt_send(sndpkt): 재전송start_timer: 타이머를 다시 시작
Wait for ACK0 -> Wait for call 1 from above
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt, 0): 시퀀스 번호 0인 ACK 패킷이 손상되지 않고 수신되면 ACK 수신 처리stop_timer: 타이머를 중지
Wait for call 1 from above
rdt_rcv(rcvpkt): 데이터 수신 대기
Wait for call 1 from above -> Wait for ACK1
rdt_send(datA): 송신자가 데이터를 보내기 위해 호출sndpkt = make_pkt(1, data, checksum): 시퀀스 번호 1을 가진 패킷 생성udt_send(sndpkt): 패킷을 전송start_timer: 타이머를 시작하여 일정 시간 내에 ACK가 도착하지 않으면 재전송
Wait for ACK1
경우 1
rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || isACK(rcvpkt, 0)): 수신된 ACK 패킷이 손상되었거나 시퀀스 번호가 0인 경우 → 재전송을 위한 타이머가 작동
경우 2
timeoutudt_send(sndpkt): 재전송start_timer: 타이머를 다시 시작
Wait for ACK1 -> Wait for call 0 from above
rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt, 1): 시퀀스 번호 1인 ACK 패킷이 손상되지 않고 수신되면 ACK 수신 처리stop_timer: 타이머를 중지
Wait for call 0 from above
rdt_rcv(rcvpkt): 데이터 수신 대기
[8-5] rdt3.0 in action
1) 손실이 없는 경우 (no loss)

Sender가 pkt0를 전송 → Receiver가 수신하고 ack0로 응답
Sender가 pkt1을 전송 → Receiver가 수신하고 ack1로 응답
Sender가 다시 pkt0를 전송 → Receiver가 수신하고 ack0로 응답
모든 패킷과 ACK가 정상적으로 전송됨
2) 패킷 손실이 있는 경우 (packet loss)

Sender가 pkt0를 전송 → Receiver가 수신하고 ack0로 응답
Sender가 pkt1을 전송 → 패킷 손실 발생
타임아웃 발생 → Sender가 pkt1을 재전송
Receiver가 재전송된 pkt1을 수신하고 ack1로 응답
Sender가 pkt0를 전송 → Receiver가 수신하고 ack0로 응답
3) ACK 손실이 발생한 경우 (ACK loss)

Sender가 pkt0를 전송 → Receiver가 수신하고 ack0로 응답
Sender가 pkt1을 전송 → Receiver가 수신하고 ack1로 응답하지만 ACK가 손실됨
타임아웃 발생 → Sender가 pkt1을 재전송
Receiver가 중복된 pkt1을 감지하고 다시 ack1로 응답
Sender가 pkt0를 전송 → Receiver가 수신하고 ack0로 응답
4) (d) 조기 타임아웃/지연된 ACK의 경우 (premature timeout/delayed ACK)

Sender가 pkt0를 전송 → Receiver가 수신하고 ack0로 응답
Sender가 pkt1을 전송 → Receiver가 수신하고 ack1로 응답하지만 지연됨
타임아웃이 조기 발생 → Sender가 pkt1을 재전송
Receiver가 중복된 pkt1을 감지하고 ack1로 응답
원래의 지연된 ack1이 도착하지만 Sender가 무시
Sender가 pkt0를 전송 → Receiver가 수신하고 ack0로 응답
[9] Reliable Data Transfer Protocol(신뢰성 있는 데이터 전송 프로토콜): 1.0 ~ 3.0
rdt 버전 | 문제점 | 해결방안 |
|---|---|---|
1.0 | 완벽하게 신뢰할 수 있는 채널 | 필요 없음 |
2.0 | 비트 오류가 있는 채널(패킷의 비트 전환) | - ACK와 NAK 사용- NAK 수신 시 발신자가 패킷 재전송 |
2.1 | - 비트 오류가 있는 채널- ACK/NAK가 손상된 경우는? | - ACK/NAK가 손상된 경우 발신자가 현재 패킷 재전송- 발신자가 각 패킷에 순차번호 추가- 수신자는 중복 패킷을 폐기(상위 계층에 전달하지 않음) |
2.2 | - 비트 오류가 있는 채널- NAK 없는 프로토콜을 만들고 싶은 경우 | NAK 대신, 마지막으로 정상 수신된 패킷에 대한 ACK 전송 |
3.0 | 오류와 손실이 있는 채널 | - 발신자가 ACK를 위해 "적절한" 시간만큼 대기- 이 시간 내에 ACK를 받지 못하면 재전송 |
[10] Summary
