[컴퓨터 네트워크] UDP, TCP 네트워킹 / 패킷 유실 시 UDP와 TCP에서의 현상

Jin Hur·2022년 6월 24일
0

Server Programming

목록 보기
3/14

프로그램이 데이터를 보내거나 받는 과정 => UDP 또는 TCP 소켓 통신

소켓(socket)

  • 단말기 사이에 통신할 수 있게 OS에서 제공하는 자원
  • 파일을 읽고 쓰려면 파일 핸들(file handle) 혹은 파일 디스크립터(file descriptor, fd)를 생성해야 하듯이, 통신을 하려면 소켓을 생성해야 함.
  • 소켓을 생성하면 소켓 핸들 값이 반환되며, 이는 대부분 OS에서 int32 타입

source: http://jkkang.net/unix/netprg/chap2/net2_1.html


UDP 네트워킹

  • 사용자가 정의한 데이터그램(UDP의 PDU)을 상대방에게 보낼 수 있게 하는 통신 프로토콜
  • 데이터그램은 개발자 마음대로 정하는 약 64KB 이하의 이진 데이터로, 메시지 성질을 가짐.
  • 즉, 데이터 일부가 뭉치거나 쪼개지지 않음.
  • UDP는 패킷 유실 현상이 발생할 수 있음.
  • 받는 쪽에서 데이터그램 유실이나 순서 뒤바뀜 혹은 중복 수신 현상이 발생해도 괜찮을 때만 UDP를 사용하는 것이 좋음.
    • 동영상이나 음성 데이터를 보낼 때, 중간에 좀 잃어버리더라도 뒤이어서 오는 다른 데이터들을 계속 출력해 주면 크게 불편함을 느끼지 않음.
    • 네트워크 게임에서 캐릭터가 이동할 때마다 계속해서 보내주는 이동 정보(캐릭터 위치 등)를 보낼 때 UDP를 잘 씀. 중간에 데이터 유실이 생겨도 이어서 도착하는 캐릭터 이동 정보가 보완.

송신측 UDP 소켓 프로그래밍

main() {
	// (1) UDP 사용 선언, 소켓 핸들 반환
    s = socket(UDP);	
    // (2) 데이터를 주고 받기 위해 포트를 하나 할당받음. 
    // any_port(0)을 넣으면 OS가 알아서 사용 중인 포트를 할당해줌
    s.bind(any_port);	
   	// (3) 상대방의 주소와 포트, 즉 끝점(endpoint)에 원하는 데이터를 보내면 됨
    s.sendTo("55.66.77.88:5959", "hello");	
    // (4) 소캣 핸들 닫기
    // 소캣 핸들이 닫히면 할당받은 포트도 다른 곳에서 사용 가능한 상태가 됨
    s.close();

수신측 UDP 소켓 프로그래밍

main() {
    s = socket(UDP);
    // (1) bind() 시 포트 번호를 명시적으로 지정
    // 송신측에서 포트 5959번으로 데이터를 보냈기 때문
    s.bind(5959);
    // (2) recvfrom()을 호출하여 데이터를 받음
    // 데이터가 아직 도착하지 않았으면 함수는 리턴하지 않음 => "블로킹 상태"
    r = s.recvfrom();
    print(r.srcAddrPort, r.data);
    // (출력) => 11.22.33.44:53234 hello
    s.close();
}
  • UDP 소켓을 이용하면 데이터 송신 뿐 아니라 받는 것도 가능. 즉 수신용 소켓, 송신용 소켓을 따로 만들지 않는다.
  • UDP의 또 다른 특징은 "다대다 통신"이 가능하다는 점. 상대방의 끝점(호스트 이름 혹은 IP 주소와 포트 값)만 알면 우편 보내듯이 보내면 됨.

TCP 네트워킹

  • 보내는 쪽 데이터가 받는 쪽에서 완전히 동일함을 보장해주는 프로토콜
  • TCP는 데이터를 주고받기 전 연결
  • 연결 후 보낸 데이터가 받는 쪽에서 정확히 모두 도착한다는 것을 보장
  • 메시지 형태가 아니라 스트림 형태
  • 스트림 형태이기에 a, bb, ccc, dddd 뿐 아니라 abbcccdddd로 받을 수도 있고 abb, cccd, ddd로 받을 수도 있다. => 즉, 데이터를 뭉치거나 쪼갤 수 있음.
  • UDP는 IP 패킷이 유실이 발생할 경우 UDP 데이터그램도 덩달아 드롭, TCP는 똑같은 상황에서도 데이터가 상대방에게 정확히 전송됨. 이것이 가능한 이유는 TCP 자체가 가지고 있는 흐름 제어 기능(data flow control)때문임.
  • TCP에서 보낼 스트림 데이터는 세그먼트라는 IP 패킷에 넣을 수 있는 크기의 단위로 쪼개짐. 그리고 IP 패킷 안에 세그먼트를 넣어 수신자에게 전송
  • 수신자는 IP 패킷을 받으면 여기서 세그먼트를 꺼내 받은 세그먼트 응답을 송신자에게 반송. 응답(ack)
  • 보낸 쪽에서는 일정 시간 안에 세그먼트에 대한 ack가 회신되지 않으면, 상대방이 받았다는 응답이 올 때까지 다시 세그먼트를 보냄.

송신측 TCP 소켓 프로그래밍

main() {
    s = socket(TCP);
    s.bind(any_port);
    // (1) connect() 함수는 연결에 성공할 때까지 블로킹됨.
    s.connect("55.66.77.88:5959");
    // (2) 이미 연결이 되었기에 sendTo가 아닌 send 호출
    s.send("hello");
    s.close();
}

수신측 TCP 소켓 프로그래밍

main(){
    s = socket(TCP);
    // (1) s는 listen 소켓 역할
    // listen 소켓은 TCP 연결을 받아들이는 역할
    s.listen(5959);
    // (2) accept()를 실행하면 상대방에게 TCP 연결을 받을 때까지 블로킹됨
    // TCP 연결이 받아지면 새로운 소켓이 생성되며, 소켓의 핸들 값이 리턴됨
    // 상대방과의 통신은 이 새로운 소켓으로 함
    // 새로운 소켓은 listen 소켓이 점유한 포트 값과는 다른 포트 값을 가짐
    s2 = s.accept();
    // (3) 상대방 끝점 확인
    print(getpeeraddr(s2));
    while(true) {	// 네트워크에서의 스트림 특성상 send() 호출 횟수와 recv() 호출 횟수가 일치하지 않을 수 있음
        r = s2.recv();
        // (4) UDP 메시지는 0바이트 메시지도 허락
        // TCP에선 0바이트를 수신하면 '연결 종료'를 의미 (like eof)
        if(r.length == 0)
            break;
        print(r);
    }
    s2.close();
}
  • UDP 소켓과 마찬가지로 수신용 소켓, 송신용 소켓을 따로 만들지 않는다.

패킷 유실 시 UDP와 TCP에서의 현상

  • 앞서 설명하였듯 OSI 모델의 계층 1~3에서는 패킷 유실이 발생할 수 있음.

UDP: 데이터그램이 유실됨

  • 패킷 유실 시 UDP에서는 데이터그램 자체가 유실
  • UDP 데이터그램의 크기가 클수록 유실될 확률도 높아짐.
    • IP 패킷이 1300바이트일 때, 데이터그램이 1만 바이트라면 대략 8개정도로 나뉘어 IP 패킷 8개가 상대방에게 전송된다는 뜻. 이 중 하나라도 유실되면 수신자는 8개를 모두 받지 못하는 것과 같음.

TCP: 중간에 지연시간이 발생함

  • 송신측에서 ack가 오지 않으면 기다리다 다시 패킷을 보냄.
  • 수신측에서는 도착하지 않는 패킷이 있으므로 다른 패킷이 다 도착하더라도 데이터 스트림을 조립하지 못함.

평균 레이턴시

  • UDP 레이턴시 = 네트워크 기기의 레이턴시
  • TCP 레이턴시 = 네트워크 기기의 레이턴시 + 패킷 유실율 * 재전송 대기시간
  • UDP는 주로 레이턴시가 민감하거나 패킷 유실이 있어도 괜찮은 곳에서 주로 사용
    • 게임 캐릭터 이동, 기관총 난사, 음성이나 화상 데이터 전송

0개의 댓글