메뉴

문서정보

목차

yundream의 Endian 문서

endian 에 대해서

endian 에 대해서

윤 상배

dreamyun@yahoo.co.kr



1절. endian 에 대한 기초지식

아마 네트웍 프로그래밍을 조금 해보았다면, Little-Endian, Big-Endian 이라는 말을 들어 보았을것이다. 때로 order byte, byte order 라고 말하기도 하며, 작은 끝돌이, 큰 끝돌이(--;) 라고 말하기도 한다. 작은 끝돌이, 큰 끝돌이는 김치하 교수의 번역서에서 볼수 있는 단어들이다.

결론부터 말하자면 엔디안 이란 컴퓨터에서 데이타가 저장되는 순서를 말하는 것이다. 컴퓨터에서 데이타 저장은 여러분도 잘 아시다 시피 byte 단위로 저장이 된다. 그런데 이 단위 저장을 할때 각 제조업체(CPU)에 따라서 저장이 되는 순서가 서로 다르다. 예를들어 우리가 자주 사용하는 32bit 정수의 경우 한번에 4byte 의 데이타가 저장이 되는데, 이때 가장 낮은 바이트부터 저장을 하는 방식이 있고, 가장 높은 바이트부터 저장을 하는 방식이 존재한다. 전자를 Little Endian 이라고 하며, 후자를 Big Endian 이라고 한다.


1.1절. 자세한 데이타 방식

즉 Little Endian 저장방식이 적용될경우 다음과 같이 저장되게 된다.

    I : 32 bit int 형정수


    |   1byte   |
    +-----------+-----------+-----------+-----------+
    |    I1     |    I2     |    I3     |    I4     |   
    +-----------+-----------+-----------+-----------+

 addr A   addr A+1      addr A+2     addr A+3
			
반면 Big Endian 은 다음과 같이 데이타가 저장될 것이다.
    I : 32 bit int 형정수


    |   1byte   |
    +-----------+-----------+-----------+-----------+
    |    I4     |    I3     |    I2     |    I1     |   
    +-----------+-----------+-----------+-----------+

 addr A   addr A+1      addr A+2     addr A+3
			
보면 알겠지만 서로 반대되는 순서로 데이타가 저장이 됨을 알수 있을것이다.

그럼 좀더 실제적인 예를들어 보도록 하겠다. Little Endian 을 적용하는 가장 대표적인 CPU는 Intel 계열 CPU 이며, Big Endian 을 적용하는 가장 대표적인 CPU 는 Sparc 계열 CPU 이다.

우리는 Endian 의 테스트를 위해서 간단한 쏘쓰를 하나 제작할 것이다.

예제 : endian.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    int fd;
    int data=123456789;
    char c[4];
    fd = open("test_bin", O_CREAT|O_WRONLY);

    write(fd, (void *)&data, sizeof(int));
    memcpy(c, (void *)&data, sizeof(int));

    close(fd);
}
			
이 프로그램은 test_bin 이라는 이름의 파일을 open 한다음 int 형 정수를 쓰는 일을한다. 우리는 위의 프로그램을 Intel Linux 와, Sparc Solaris 에서 각각 실행시켜서 그 결과를 알아볼것이다. Sparck Solaris 에서 위의 프로그램을 컴파일,실행한후 생성되는 test_bin 파일을 ftp 등을 통해서 리눅스로 가져와서 테스트한다(동일한 환경에서 테스트하는게 혼동을 피할수 있음으로).

Linux 에서 위의 프로그램을 실행후 만들어진 파일을 test_bin_linux 로 이름을 바꾸고, Solaris 에서 만들어진 파일은 test_bin_solaris 로 이름을 바꾸고 나서 od(1) 프로그램을 이용해서 그값을 확인해 보았다.

[root@coco endian]# od -x test_bin_linux 
0000000 cd15 075b
0000004
[root@coco endian]# od -x test_bin_solaris 
0000000 5b07 15cd
			
위의 결과를 보면 Little Endian 과 Big Endian 의 차이점을 쉽게 이해할수 있을것이다. 저장방식이 바이트 단위로 서로 전혀 반대임을 알수 있다.


2절. Endian 이 실제 프로그래밍 환경에서 중요한가

Endian 에 의한 byte order 은 해당 시스템의 CPU 에서 신경을 쓰므로, 단지 하나의 시스템에서만 프로그래밍 작업을 한다면 Endian 에 대해서 전혀 신경 쓸필요가 없다.

그러나 네트웍프로그래밍을 할경우 이기종간의 (예를 들어 Sparc 과 Intel) 통신을 염두에 두어야만한다. 이럴경우 Endian 에 신경을 써주지 않으면 전혀 엉뚱한 결과를 가지고 오게 된다.


2.1절. 그럼 네트웍 상에서 어떤 문제가 발생하는가

이번에는 서버 클라이언트 프로그램을 만들어 보도록 하자. 서버 프로그램은 Sparc 솔라리스에, 클라이언트 프로그램은 Inten Linux 에서 각각 작동하도록 할것이다.

다음은 서버 프로그램이다.

server.c

	
#include <sys/time.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    int server_sockfd, client_sockfd, sockfd;
    struct sockaddr_in clientaddr, serveraddr;

    int fd_num;

    int state, client_len;
    int i, maxi, maxfd;
    int data;

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sockfd == -1)
    {
        perror("socket error : ");
        exit(0);
    }

    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(atoi(argv[1]));

    if(bind (server_sockfd, (struct sockaddr
             *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind error ");
        exit(0);
    }

    if (listen(server_sockfd, 5) < 0)
    {
        perror("listen error : ");
        exit(0);
    }

    client_sockfd = accept(server_sockfd, (struct
                    sockaddr *)&clientaddr, &client_len);
    read(client_sockfd, (void *)&data, sizeof(int));
    printf("%d\n", data);
    close(client_sockfd);
}
			

다음은 클라이언트 프로그램이다.

client.c

#include <sys/time.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    int client_sockfd;
    struct sockaddr_in clientaddr;
    int data = 123456789;
    int client_len;

    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    clientaddr.sin_family = AF_INET;
    clientaddr.sin_addr.s_addr = inet_addr("192.168.100.190");
    clientaddr.sin_port = htons(atoi(argv[1]));

    client_len = sizeof(clientaddr);

    if (connect(client_sockfd, (struct sockaddr *)&clientaddr, client_len) < 0)
    {
        perror("Connect error : ");
        exit(0);
    }

    write(client_sockfd, (void *)&data, sizeof(int));
    close(client_sockfd);
}
			

클라이언트는 123456789 를 보내고 서버는 이값을 받아서 출력하는 단순한 일을한다. 그럼 각각의 쏘쓰를 솔라리스와 리눅스로 옮겨서 컴파일후 작동시켜보도록 하자. 서버측에서는 다음과 같은 결과를 보여줄것이다.

[root@solaris test]# ./server 12345
365779719
			
분명 클라이언트는 123456789 를 보냈는데, 서버측에서는 전혀 다른 값을 출력시킨다. 이유는 간단한다 Endian 이 달라서 데이타의 byte 가 역전되어 있기 때문이다.

그럼 이문제를 어떻게 해결해야 할까. 인테넷 상에는 그야말로 다양한 시스템이 존재하고 시스템마다 Endian 차이가 존재할건데,


2.2절. 그럼 Endian 문제의 해결방법은 ?

가장 간단한 해결방법은 Endian 이 Big 이든 Little 이든 하나로 통일시키는 거겠지만 이건 사실 불가능하다. 그럼 생각할수 있는 방법이 공통되는 Endian 으로 변환 시킨다음에 자신의 데이타를 전송하는 방법이 될것이다. 그러면 전송하는 측이나 전송받는 측이나 이 공통되는 Endian 을 알고 있을 것임으로 byte order 를 시킬수 있을것이다.

그래서 network byte order 이란것이 존재한다. 즉 network 로 데이타를 보낼때는 무조건 하나의 Endian 으로 통일을 시키는 것이다. network byte order 는 Big Endian 을 따른다. 그러므로 우리는 시스템에 관계없이 무조건 byte 를 Big Endian 에 맞도록 byte order 를 시킨다음에 네트웍을 통해서 외부로 보내면 된다. 받는 측에서는 자기에게 들어오는 Endian 이 Big 으로 통일되어 있음으로 Big Endian 을 자신의 Endian 에 맞도록 byte order 시켜주면 될것이다.

C 는 이러한 byte order 를 위한 함수를 제공한다. 함수가 하는 일은 자신의 host byte order 을 network byte order 에 맞게 변경시켜주는 것과, network byte order 을 자신의 host byte order 에 맞게 변경시켜주는 2가지 군의 함수를 제공한다.

#include <netinet/in.h>

unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);

unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
			
htonl 과 htons 는 host to network 즉 host byte order 를 network byte order 로 변경시켜주며, ntohs 는 network to host 로 그 반대로 변경 작업을 한다.

그렇다면 위의 쏘스들에서 port 지정을 위해서 왜 htons 를 사용하는지를 알수 있을것이다.

serveraddr.sin_port = htons(atoi(argv[1]));
			
자신의 host byte order 를 network byte order 로 변경 시키는 작업이다. linux 의 경우 little endian 을 따르는 byte order 를 하는데 만약 nework byte order 로 변경시키지 않는다면, 자기가 입력한것과 전혀 다른 port 가 전달될것이다. 예를 들어 349 번 포트를 htons 없이 지정했다면 34049 가 지정된것으로 전달될것이다. 결국 원하는 포트를 찾지 못하는 결과를 가져올 것이다.

이제 위의 코드를 어떻게 변경시켜야 할지 감을 잡았을 것이다. data 를 보내는 측에서는 htonl 을 이용해서 host byte order 로 변경시켜주고, 받는 측(서버) 에서는 ntohl 을 이용해서 network byte order 를 자신에게 맞도록 변경시켜주면 된다. 다음과 같이 코드를 각각 변경시키면 된다.

client.c
========
data = htonl(data);
write(client_sockfd, (void *)&data, sizeof(int));

server.c
========
printf("%d\n", ntohl(data));
close(client_sockfd);
			


2.3절. 또 다른 해결책은 없나요

byte order 함수를 사용해서 Little/Big Endian 문제의 해결은 했지만, 조금만 생각해보면 이 방법이 꽤 불편할수 있다는걸 알수 있다. 데이타를 보낼적엔 반드시 Endian 변환을 해줘야 한다. 꽤 귀찮은 작업이 될수 있다. 혹시라도 실수로 변환을 하지 않을경우에는 어떻게 될지 알수 없다.

그렇다면 byte order 에 신경쓸필요 없이 byte 단위로 데이타를 보내면 될것이다. char 를 사용하는 것이다. char 은 1byte 의 크기를 가짐으로 byte order 에 신경쓸 필요가 없이 사용가능하다. 그래서 많은 경우 네트웍 통신 프로그래밍을 할경우 char 만을 이용해서 통신을 하도록 하는 경우도 있다. 예를 들어 위의 12345678 을 int 형으로 보내는 대신 문자열로 변환시켜서 전송하는 것이다. 이럴경우에는 Endian 문제에 신경쓸필요가 없을것이다.


2.4절. Endian 체크하기

다음과 같은 간단한 코드를 이용해서 시스템의 Endian 을 체크할수 있다.

예제 : endian_check.c

int main()
{
    int i = 0x00000001;
    if( ((char *)&i)[0] )
        printf( "Littile Endian\n" );
    else
        printf( "Big Endian\n" );
}
			


3절. 결론

이상 Endian 에 대해서 간단하게 알아보았다. 보통 int 형과 같이 byte order 가 필요한 데이타 통신을 "binary 통신을 한다" 라고 말한다. 최근에는 binary 통신을 사용하지 않고 단지 text(char) 만을 이용해서 통신을 하는게 좀더 일반적인 추세이다. 그러나 어느 방식을 채택하든지 사용하기에 간편한 방식을 택하면 될것이다.

참고문헌