전문 구성 하기 (Fixed Length Format)

통신 프로그램에서 데이터 송수신 format을 정하는 것도 매우 중요합니다. 일반적으로 데이터 송수신하는 전문의 형태는 fixed length, xml, JASON 등 여러가지로 구성할 수 있습니다. 요즘은 Java나 Script언어들의 사용이 많아지면서, XML, JASON 등의 format을 사용하는 추세입니다만, 아직도 많이 사용하는 데이터 송수신 format으로 fixed length를 사용합니다. 특히, C언어에서 사용하기 가장 편리한 형태라고 할 수 있습니다. XML, JASON등은 parsing을 하는 부담과 parser가 필요합니다만, fixed length는 그런 부담없이 쉽게 사용할 수 있습니다.

 

fixed length format이란 전문을 구성하는 field들의 길이가 입력받을 수 있는 최대사이즈로 고정시키는 방식입니다.

예를들면, 이름은 최대 32바이트까지, 주소는 100바이트, 전화번호는 12자리, 주민번호는 13자리 등과 같이 항목의 길이를 fix시키는 것입니다. 따라서 C언어로 개발을 한다면 struct를 이용하여 구성하는 것이 좋습니다.


※ Java로 fixed length를 구현하는 것은 상당히 성가신 일입니다. C어어와 같은 struct를 제공하지 않기 때문에 byte 배열로 할당한 후에 데이터의 위치를 찾아서 일일이 copy해야하는 어려움이 있습니다.

 

전문의 구성

일반적으로 송수신 format을 정할 때에 header부와 body부로 구성합니다.

header부는 거래코드(Transaction code)와 관계없이 항상 일정한 크기를 갖는 부분입니다.

구성하기 나름이겠지만, 이 header부에 들어가야 할 내용으로는 
   - 거래코드

   - 거래추적을 위한 GUID(Globally Unique Identifier)
   - 접속자 정보(IP, User ID 등)

   - 오류코드, 오류메시지

   - body부의 데이터 길이

등이 들어가고 필요에 따라서 추가적으로 구성합니다.

 

필요에 따라서는 요청전문의 header와 응답전문의 header를 다르게 구성할 수도 있습니다.

 

body부에는 거래코드별로 데이터를 주고 받을 format을 정합니다.

 

전문 구성을 일일이 설명하기 보다는 예를들면서 설명하도록 하겠습니다. 

전문을 구성하는 데이터 type은 모두 C언어의 char로만 구성하는 것이 좋습니다. int, long 등과 같은 데이터는 이기종 시스템간에는 endian 문제가 있을 수 있습니다. 그리고 null값(0x00)값을 넣지 않도록 합니다. 원래 통신 상에서 null값은 문제가 없으나, 가끔 다른 통신 솔루션들과 연동 시에 오류가 발생할 수 있습니다.

 


전문 Header 구성

header의 전체 길이는 될 수 있으면, 8의 배수를 갖는 것이 좋습니다. 8의 배수를 갖는 것이 좋은 이유는 body부를 구성하는 데이터에 long type이 포함되어 있다면 body부의 구조체는 메모리 시작 위치가 8의 배수 위치에서 시작하고 body의 size도 8의 배수가 됩니다. 즉, 구조체는 메모리 상에 위치할 때에 최대 size의 데이터 type의 배수에 위치하고 sizeof( )로 했을 때의 구조체의 크기도 최대 size의 데이터 type의 배수를 갖습니다. 따라서 header의 끝부분이 8의 배수로 끝나지 않으면 8의 배수가 되는 부분은 버려지게 됩니다. (64bit 기준 long이 64bit로 가정)

typedef struct {
    char tx_code[10];     /* 거래코드 */
    char guid[32];        /* Global Unique Identifier */
    char err_code[8];     /* error code */
    char err_msg[256];    /* error message */
    char reserved[84];    /* 향후 확장을 위한 예약영역 */
    char length[10];      /* body length */
} header_t; 

 


 

전문 Body 구성 

- 만약 로그인 전문이고 거래코드가 COMLIN0010이면

 

NPUT 전문 Body 구성

typedef struct {
    char user_id[12];
    char passwd[20];
} comlin0010_in_t;

 

OUTPUT 전문 Body 구성

typedef struct {
    char user_id[12];
    char passwd[20];
} comlin0010_out_t;

 


Full 전문 구성

전문의 header와 body부를 합쳐서 packet_t라는 type을 선언합니다. 이렇게 header와 body를 합친 packet을 구성하면 데이터 송수신을 한번에 처리할 수 있어서 편리합니다.

body부는 거래코드에 따라서 format이 다르게 정의되고 길이도 알 수 없으므로 아래와 같이 char body[]; 형태로 정의합니다. body는 빈 char 배열로 구성하였습니다. 이렇게 하면 body는 pointer라는 의미만 있고 실제로 size가 없습니다.

typedef struct {
    header_t header;   /* header 부 */
    char     body[];   /* body 부 */
} packet_t;

위의 packet_t를 sizeof(packet_t)라고 하면 크기는 header_t와 같은 값이 됩니다. 위와 같이 구성을 하면 body의 크기는 header 부는 뺀 부분의 크기를 갖습니다. 

 

예를들면

packet_t  *packet = (packet_t *)malloc(sizeof(header_t) + 2048);

packet->body는 2048 바이트의 데이터를 할당받은 char *가 됩니다.

 

body는 문자열 배열이므로 사용하기 불편합니다. 이 때에 body 부분을 type casting합니다.

예를들면 body가 comlin0010_in_t 전문이라면

comlin0010_in_t *comlin0010_in = (comlin0010_in_t *)packet->body;

이후부터는 comlin0010_in변수를 사용하여 데이터를 처리합니다.

 

물론 packet_t를 정의하지 않고 header와 body를 따로따로 송수신을 해도 상관은 없습니다.

그리고 읽을 때부터 comlin0010_in변수를 선언하여 메모리 할당을 안하고 하면 되지 않느냐고 할 수도 있지만

전문의 종류가 한두개가 아니면 

처럼 일일이 비교해야 하며, 전문이 추가될 때마다 else if( )를 추가해야 합니다.

   if(strcmp("COMLIN0010", header->tx_code) == 0) {
        recv(accepted_socket, &comlin0010_in, sizeof(comlin0010_int_), 0);
   } else if(...) {
        ...
   } ...

그래서 그렇게 하지 않고, body의 길이만큼 먼저 데이터를 읽어두고 처리 함수 내에서 전문에 대한 type casting을 합니다.

 

데이터 수신 Sample

/* socket 서버 main 부분 */

/* 전역변수 */
int      accepted_socket;
header_t header;
packet_t *in_packet;
packet_t *out_packet;

......

    /* local 변수 */
    int size;
    char body_length[11] = {0x00, };
    int           ret;

    /* MSG_PEEK는 데이터를 읽습니다.
     * MSG_PEEK로 읽으면 socket의 buffer에서 데이터가 제거되지않으므로 다음에 읽을때에는 header도 
     * 다시 읽어야 합니다.
     */

    if(recv(accepted_socket, &header, sizeof(header), MSG_PEEK) == -1) {
         // 오류처리
         return -1;
    }

    /* header에서 body의 길이를 얻습니다. */
    strncpy(body_length, header.length, sizeof(header.length));
    size = sizeof(header_t) + atoi(body_length); 

    /* 전체 전문의 길이만큼 메모리를 할당합니다. */
    in_packet = (packet_t *)malloc(size);
    
    /* header + body 전체 데이터를 읽습니다. */
    if(recv(accept_socket, in_packet, size, 0) == -1) {
         // 오류처리
         return -1;
    }

    /* 거래코드에 따른 업무분기 */
    /* 업무 분기하여 함수 실행방법은 거래코드 단위로 shared lib를 형태로 만들어 실행함 */
    /* 이후 강좌에서... */
    ......

    /* header에서 body의 길이를 얻습니다. */
    strncpy(body_length, out_packet->header.length, sizeof(out_packet->header.length));
    size = sizeof(header_t) + atoi(body_length); 

    if(send(accept_socket, out_packet, size, 0) == -1) {
        // 오류처리
    }
    ......


-------------------------------------------------------------------------------

/* 업무 분기 소스에서는 */

int comlin0010(void)
{
    comlin0010_in_t  *comlin0010_in;
    comlin0010_out_t *comlin0010_out;

    comlin0010_in   = (comlin0010_in_t *)in_packet->body;
    out_packet      = (packet_t *)malloc(sizeof(header_t) + sizeof(comlin0010_out_t));
    memcpy(out_packet->header, in_packet->header, sizeof(header_t));
    comlin0010_out  = out_packet->body;
    out_packet->header.body_length = sizeof(comlin0010_out_t);

     // 업무처리 하기
     // 즉, output의 바디 채우기

      ....

      return 0;
}

 


 

see also :  TCP/IP 통신 프로그램   Socket 통신 관련 Library  

 

 

 

 

블로그 이미지

사용자 자연&사람

행복한 개발자 programmer since 1995.

Tag , , ,

댓글을 달아 주세요