응용 프로그램 프로토콜과 데이터 전송

bolee·2022년 4월 11일
0

응용 프로그램 프로토콜

응용 프로그램 프로토콜(application protocol)

  • 응용 프로그램 수준에서 주고받는 데이터의 형식과 의미 그리고 처리 방식을 정의한 프로토콜
  • TCP나 IP 와 같은 표준 통신 프로토콜에서 주고받는 패킷의 형식과 의미 그리고 처리 방식을 정의한 것과 비슷하나, 응용 프로그램마다 자유롭게 정의한다. 즉, 표준화되어 있지 않다.

응용 프로그램 프로토콜이 결정되면 데이터를 정해진 형시과 절차에 따라 주고받아 처리하도록 소켓 함수를 이용해 구현하면 된다.

데이터 전송

통신 양단이 주고받을 데이터를 정의하는 것만으로는 아직 충분하지 않다. 데이터를 전송할 때 고려할 사항을 알아보자

경계 구분

TCP 처럼 메세지 경계를 구분하지 안흔ㄴ 프로토콜을 사용할 경우 응용 프로그램 수준에서 메세지 경계를 구분하기 위한 추가 작업을 해야한다. 다음과 같이 4가지 방법을 고려할 수 있다.

  1. 송신자는 항상 고정 길이 데이터를 보내고 수신자는 항상 고정 길이 데이터를 읽는다.

    장점

    • 구현이 쉽다.
    • 주고받을 데이터의 길이 변동폭이 크지 않을 경우 편리한다.

    단점

    • 미래에 사용할 가장 긴 데이터를 감안해 고정 길이를 정해야 한다.
    • 길이가 짧은 데이터를 주고받을 때 낭비하는 부분이 생길 수 있다.
  2. 송신자는 가변 길이 데이터를 보내고 끝 부분에 특별한 표시(EOR, End Of Record)를 붙인다. 그리고 수신자는 EOR이 나올 때까지 데이터를 읽는다.

    장점

    • 생성될 데이터의 길이를 미리 알 수 없을 때 사용하기 적합하다.
    • 데이터가 생성되면 곧바로 전송하되 끝이라고 판단하면 EOR을 전송한다.

    단점

    • 데이터 중간에 EOR과 똑같은 패턴이 들어있으면 안된다.
    • 데이터를 효율적으로 수신하는 방식을 고려하지 않으면 성능이 떨어질 수 있다.
  3. 송신자는 보낼 데이터 크기를 고정 길이 데이터로 보내고, 이어서 가변 길이 데이터를 보낸다. 수신자의 경우 고정 길이 데이터를 읽어서 뒤따라올 가변 데이터의 길이를 알아내고, 이 길이 만큼 데이터를 읽는다.
    장점

    • 구현이 쉽다.
    • 효율성이 높다.

    해당 방법을 사용할 경우 메세지 구조
    (5-2 그림)

  4. 송신자는 가변 길이 데이터 전송 후 접속을 정상 종료한다. 수신자는 recv() 함수의 리턴값이 '0'(=정상종료)이 될 때까지 데이터를 읽는다.
    장점

    • 한쪽에서 다른 쪽으로 일방적으로 데이터를 보내는 경우 적당하다.

    단점

    • 데이터를 자주 전송하는 경우 [TCP 연결 설정/데이터 전송/TCP 연결 종료]를 반복해야 하므로 비효율적이다.

바이트 정렬

서로 다른 바이트 정렬 방식을 사용하는 시스템 사이에 데이터를 교환할 때는 바이트 정렬 방식을 통일해야 한다. 그렇지 않으면 데이터 해석에 문제가 생긴다.
특별한 전제가 없으면 빅 엔디안(네트워크 바이트 정렬) 방식으로 통일하는 것이 좋다.

구조체 멤버 맞춤

구조체 멤버 맞춤(structure member alignment)
- 구조체(C++의 클래스도 포함) 멤버의 메모리 시작 주소를 결정하는 컴파일러의 규칙

간단한 예를 들어 다음과 같이 구조체를 이용해 메세지를 정의하면 10(=4+1+4+1)바이트가 아닌 16바이트가 전송된다.

struct MyMessage
{
	int a;	// 4 바이트
    char b; // 1 바이트
    int c;	// 4 바이트
    char d;	// 1 바이트
};
MyMessage msg;
...
send(sock, (char *) &msg, sizeof(msg), 0);

(5-3 그림)

위 그림은 변수 msg의 메모리 구조인데, 기본적으로 C/C++ 컴파일러는 구조체 멤버에 대한 접근 속도를 빠르게 하려고 메모리를 약간 낭비하는 방식을 취하기 때문이다.

양쪽으로 프로그램이 동일한 구조체 멤버 맞춤을 사용한다면 이와 같이 메세지를 전송해도 문제가 되지 않는다. 만약 정확히 10바이트를 보내고자 한다면 다음과 같이 #pragma pack 지시자를 사용하면 된다.

#pragma pack(1)	// 구조체 멤버 맞춤을 1바이트 경계로 변경
struct MyMessage
{
	int a;	// 4 바이트
    char b; // 1 바이트
    int c;	// 4 바이트
    char d;	// 1 바이트
};
#pragma pack()	// 구조체 멤버 맞춤을 기본값으로 환원
MyMessage msg;
...
send(sock, (char *) &msg, sizeof(msg), 0);

이때 변수 msg의 메모리 구조는 아래와 같다.

(5-4 그림)

참고 자료
김성우 저, "TCP/IP 윈도우 소켓 프로그래밍", 한빛아카데미, 2018

0개의 댓글