데이터 링크 계층에서는 두 호스트가 통신하려면 일대일 형식의 점대점 방식으로 연결해야합니다. 점대점하면 WAN이 생각이 나네요.
아무튼 데이터 링크 프로토콜에서 주로 기능하는 오류제어와 흐름제어가 있습니다.
오류제어는 송신한 데이터가 damage(손상)되거나 lost(손실)된 경우에 일어납니다. 이때 가장 간단한 해결책은 바로 재전송입니다.
첫번째는 송신한 데이터가 전송되는 과정에서 손상되었을 경우입니다. 그럼 수신측은 손상된 데이터를 받았겠죠. 손상된 데이터를 가지고 손상이 되었다고 송신 데이터에게 NACK(부정응답프레임)을 보냅니다. 그럼 송신측은 이를 알고 수신측에게 재전송을 합니다. 만약 손상되지 않고 잘 받았다면 송신측으로 ACK(긍정응답프레임)을 보내 데이터 전송이 잘 되었다고 알립니다.
두번째는 송신한 데이터가 전송되는 과정에서 손실되었을 경우입니다. 송신한 데이터가 손실되었다는 것은 수신측은 이를 받지도 못했다는 것을 의미합니다. 이럴때는 타임아웃 이라는 기능을 통해 수신측에서 ACK신호가 일정 시간 내로 오지 않으면 다시 재전송을 합니다. 이때, 수신측이 받게 되는 데이터는 똑같은 데이터가 여러개가 될 수 있습니다. 그래서 존재하는 것이 순서번호라는 것입니다. 순서번호를 통해 중복되는 번호는 삭제하고, 하나만 남겨 받을 수 있습니다.
세번째는 수신측에서 잘 받았다고 보낸 ACK신호가 분실되는 경우입니다. 이럴 경우에도 두번째 경우와 마찬가지로 타임아웃 기능을 통해 해결합니다. 따라서 ACK신호에도 순서번호가 존재하게 됩니다.
흐름제어는 수신측에 있을 버퍼에 수신된 데이터가 쌓여서 더 이상 적재할 공간이 없을 경우에 일어납니다. 마찬가지로 재전송이 가장 좋은 해결책이며, 수신측에서 송신측으로 ACK신호와 함께 남은 공간을 알립니다. 그러면 송신측이 수신측에게 남은 버퍼 공간이 얼마인지 알겠죠?
실제 세계에서의 통신은 양방향 통신을 지원합니다. 송신측이 수신측이 될 수 있고, 수신측이 송신측이 될 수 있습니다. 따라서 오류제어와 흐름제어의 기능을 모두 지원하는 통신이어야합니다.
이건 기본적인 슬라이딩 윈도우 코드입니다.
import java.util.Scanner;
public class SlidingWindow_base {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = 0;
System.out.print("프레임의 순서 번호 공간 크기 입력(bit단위) >> ");
n = sc.nextInt();
int N = 1; // 전송할 데이터의 크기 (2^n)
for(int i = 0 ; i < n ; i++) {
N *= 2;
}
int window_size = 0;
System.out.print("윈도우의 크기 >> ");
window_size = sc.nextInt();
int[] data = new int[N]; // 전송할 전체 데이터 배열
for(int i = 0 ; i < N ; i++) {
data[i] = i;
}
int[] window = new int[window_size]; // 윈도우 배열
for (int i = 0 ; i < window_size ; i++) {
window[i] = -1;
} // 윈도우 배열 -1로 초기화 (빈공간 나타냄)
int tmp_data = 0; // data 배열에 접근할 인덱스
int idx = 0; // window 배열의 0번째 인덱스가 되는 data배열의 인덱스
int err = 0;
int select;
do {
System.out.println("1. 전송(I) 2. 응답(ACK) 3. 종료");
select = sc.nextInt();
if (select == 1) {
err = 0;
// 윈도우에 data[tmp_data] 를 넣어야 함
for(int i = 0 ; i < window_size ; i++) {
if (window[i] == -1) {
window[i] = data[tmp_data];
//System.out.println(window[i]);
break;
}
err++;
}
if (err >= window_size) {
System.out.println("윈도우가 가득 찼습니다.");
}
else {
System.out.println("I >> " + data[tmp_data]);
tmp_data ++; // 다음 데이터
}
System.out.println();
}
else if (select == 2) {
if (window[0] == -1) {
System.out.println("윈도우가 비었습니다.");
}
else {
System.out.println("ACK >> " + data[idx+1]); // 다음 수신될 것으로 기대되는 번호 출력함
idx ++;
for (int i = 0 ; i < window_size ; i++) {
if (window[i] == -1) {
window[i-1] = -1; // 빈 인덱스라면 바로 이전 인덱스에 빈 공간 표시
break;
}
else if (i == window_size-1) {
window[i] = -1;
}
else {
if(idx + i <= tmp_data) {
window[i] = data[idx + i];
}
}
}
}
System.out.println();
}
else if (select == 3) {
System.out.println("종료합니다.");
break;
}
else {
System.out.println("잘못된 입력");
System.out.println();
}
} while (select != 3);
}
}
설명을 하고 싶어서 글을 쓴게 아니다 보니 설명이 많이 중구난방입니다. 강의 피피티 보기 귀찮아서 아는 내용대로 썼습니다... 정확도를 위해서라면 다른 글을 찾아보시는게.. 아니면 시간이 날 때 설명을 수정할 계획이니 그때 다시 찾아와주세요.. (꾸벅...)
학교에서 데이터 통신 강의 들으면서 이건 꼭 코드로 구현해보고싶다! 라는 생각이 들어서 해보았습니다. 작년에 백준 풀면서 '게으른 백곰'이라는 문제가 있었는데요, Sliding window 알고리즘을 이용해서 푸는 문제였습니다. 슬라이딩 윈도우 듣자마자 그 문제가 떠올라서 다시 한번 코드를 봤더니 파이썬 좀 안 봤다고 이해하는데 시간이 좀 걸렸습니다. 그런데 파이썬이랑 자바랑 배열 하는 부분에서 많이 다르다보니(...) 보다가 그냥 새로짜기로 했습니다.
코테 사이트만 하다가 이렇게 해보니까 재미있네요. 물론 해야 할 일들은 미루면서 했습니다 ㅋㅋㅋ 다음주에 죽었다 진짜..
다음에는 고백(Go-back)N 방식과 선택적 재전송과 피기배킹도 구현해보겠습니다 ㅎㅎ