삽질/델파이

in_addr 구조체

rokwha 2025. 3. 26. 04:43

2025.03.24 - [삽질/델파이] - 아이피 구하기 에서 윈도우 소켓 을 이용해서 로컬 아이피를 구하는 방법을 알아보았는데 이데 대해서 조금 더 알아본다.

gethostbyname 은 PHostEnt 라는 구조체의 포인터를 넘겨주는데 이 구조체는 다음과 같다. ( 원형과 라자루스, 델파이로 포팅된것은 버전별로 약간 상이할 수도 있는데 기본적으로는 같으니 차이점은 알아서 잘... 특별히 언급하지 않으면 라자루스 4.0 rc2 - fpc 3.2.2를 기준으로..)


https://learn.microsoft.com/ko-kr/windows/win32/api/winsock/ns-winsock-hostent

 

HOSTENT(winsock.h) - Win32 apps

HOSTENT(winsock.h) 구조체는 함수에서 호스트 이름, IPv4 주소 등과 같은 지정된 호스트에 대한 정보를 저장하는 데 사용됩니다.

learn.microsoft.com

 

    type
       hostent = record
          { official name of host  }
          h_name: pchar;
          { alias list  }
          h_aliases: ^pchar;
          { host address type  }
          h_addrtype: SmallInt;
          { length of address  }
          h_length: SmallInt;
          { list of addresses  }
          case byte of
             0: (h_addr_list: ^pchar);
             1: (h_addr: ^pchar)
       end;
       THostEnt = hostent;
       PHostEnt = ^THostEnt;


h_addrtype 은 프로토콜 종류 로 AF_ 로 시작되는 값들이 있는데 AF_INET (2) 은 ipv4, AF_INET6 (23)는 ipv6 그리고 AF_UNSPEC (0) 정도만 기억해 두고 다른 값들은 winsock2.pas 에 선언되어 있으니 참고하도록.

마지막 가변으로 선언되어져 있는 h_addr_list에 주소 데이터가 들어가져 있다. 여러 개의 주소들이 있을 수 있다. h_addr 은 h_addr_list [0]으로 접근되어 사용할 수 있다고 하는데 예전 버전의 호환성 때문에 유지되고 있는 것 같다. 자세한 건 모르겠고 그냥 그렇다고 한다. 

h_addr_list 가 ^pchar 로 포인터로 되어 있는데 in_addr 구조체로 되어 있다.
 

    type
       SunB = packed record
          s_b1,s_b2,s_b3,s_b4 : u_char;
       end;

       SunW = packed record
         s_w1,s_w2 : u_short;
       end;

       in_addr = record
          case integer of
             0 : (S_un_b : SunB);
             1 : (S_un_w : SunW);
             2 : (S_addr : u_long);
       end;
       TInAddr = in_addr;
       PInAddr = ^TInAddr;

 
주소값은 하나지만 바이트(S_un_b) , 워드(S_un_W) , 롱(S_addr) 타입으로 각각 상황에 따라 적당한 것을 골라 쓸 수 있다. 

S_addr 은  u_long 은 부호없는 정수 Cardinal이다. 8바이트니깐 SunW는 4바이트 2개, SunB는 1바이트 4개로 이루어져 있다. Sun 뒤에 W는 워드, B는 바이트를 뜻하는 걸 알 수 있다.

SubB는 1바이트 4개인데 우리가 흔히 보는 192.168.0.1과 같은 닷 데시멀 노테이션 ( Dot-decimal notation ) 이라는 4개의 옥텟 ( octet ) 으로 사용될 수 있다. s_b1, s_b2, s_b3, s_b4 가 각 옥텟에 1:1 대응을 한다. 1~255 사이의 값이 들어가 있어서 그냥 쓰면 된다.

다음은 간단한 예이다.

  var
    aInAddr4: TInAddr;
    apInAddr4: PInAddr;
    cIP: string;
    cStr: string;
    nB1, nB2, nB3, nB4: char;
    nW1, nW2: Word;
    nL1: ULONG;    
begin
  cIP:= '192.168.0.108';

  aInAddr4.s_addr:= inet_addr( PChar(cIP) );
  apInAddr4:= @aInAddr4; //굳이 포인터형으로 쓰지 않아도 되지만 실제로 사용할때는 포인터로 접근하는경우가 많아서...

  Log('IP : '+cIP+#13#10);

  nB1:= apInAddr4^.S_un_b.s_b1;
  nB2:= apInAddr4^.S_un_b.s_b2;
  nB3:= apInAddr4^.S_un_b.s_b3;
  nB4:= apInAddr4^.S_un_b.s_b4;

  cStr:= '';
  cStr:= cStr + Format('S_un_b.s_b1 : %.2x : %d', [Ord( nB1 ), Ord( nB1 ) ]) +#13#10;
  cStr:= cStr + Format('S_un_b.s_b1 : %.2x : %d', [Ord( nB2 ), Ord( nB2 ) ]) +#13#10;
  cStr:= cStr + Format('S_un_b.s_b2 : %.2x : %d', [Ord( nB3 ), Ord( nB3 ) ]) +#13#10;
  cStr:= cStr + Format('S_un_b.s_b3 : %.2x : %d', [Ord( nB4 ), Ord( nB4 ) ]);
  Log( cStr );
  //ord 대신 byte( value ) 나 integer( value )로 형변환 해도 된다.	
end;

{*  출력
IP : 192.168.0.108

S_un_b.s_b1 : C0 : 192
S_un_b.s_b1 : A8 : 168
S_un_b.s_b2 : 00 : 0
S_un_b.s_b3 : 6C : 108

*}

 

SubW는 2개의 워드로 이루어져 있는데 우선 값을 한번 살펴보자

 

  
  nW1:= apInAddr4^.S_un_w.s_w1;
  nW2:= apInAddr4^.S_un_w.s_w2;
  cStr:= '';
  cStr:= cStr + Format('S_un_w1 : %.4x : %d', [ nW1, nW1 ])+#13#10;
  cStr:= cStr + Format('S_un_w2 : %.4x : %d', [ nW2, nW2 ]);
  Log( cStr );
  
  {* 출력예 
  S_un_w1 : A8C0 : 43200
  S_un_w2 : 6C00 : 27648
  *}

 

워드니 2바이트씩 A8C0 와 6C00 으로 이루어져 있다. 그런데 C0A8 이 아니고 A8C0 이다. 이는 엔디안 ( Endianness ) 또는 바이트 정렬(순서) 이라고 하는데 간단히 알아보자면 빅 엔디안 은 최상위(MSB) 부터, 리틀 엔디안 은 최하위(LSB) 로 부터 저장한다. 이는 cpu 와 운영체제마다 다르다. 

 

방식이 통일되지 않다 보니 다른 곳과 통신을 하다보니  아이피 주소, 포트번호 와 같은 프로토콜 정보 와 응용 프로그램의 데이터 의 교환에 심각한 문제가 발생한다. 

현재 통신쪽은 빅 엔디안 방식으로 흔히 네트워크 바이트 정렬 이라고 한다. 자세한것은 더 찾아보길 바라며 참고로 인텔 시퓨(x86, x64 ), AMD 는 리틀 엔디안 방식을 사용하고 있다.


그러면 어떻게 하나? 이미 winsock 에 준비된 것이 있다.  

 

htons , htonl 

ntohs , ntohl 

 

호스트 정렬을 네트워크 정렬로 ( hton - host to network ) , 네트워크 정렬을 호스트로 ( ntoh - network to host ) 변경해주고 s 와 l 은 각각 short ( 워드 ), long ( 롱) 을 뜻한다. 이 밖에 d, f, ll 같은 함수가 더 생긴것 같으니 msdn 을 참고하고... 위 함수는 WSAStartup 으로 초기화를 하지 않아도 사용할수 있고 반드시 WSAStartup 으로 초기화 한 에 사용할수 있는 WSAhtons 와 같은 함수군이 존재한다. ( winsock2 )

다음은 htons 함수로 사용해본 예이다.

 

  cStr:= '';
  cStr:= cStr + Format('S_un_w1 : %.4x : %.4x', [ nW1, htons(nW1) ])+#13#10;
  cStr:= cStr + Format('S_un_w2 : %.4x : %.4x', [ nW2, htons(nW2) ]);
  Log( cStr );
  
  {* 출력예 
  S_un_w1 : A8C0 : C0A8
  S_un_w2 : 6C00 : 006C
  *}

 

보면 앞뒤가 변경이 된걸 확인할수 있다. 이를 각 바이트별로 나눠서 출력해보면...

 

  nW1:= htons(nW1);
  nW2:= htons(nW2);
  cStr:= cStr + Format('%d.%d.%d.%d', [nW1 shr 8, nW1 and $ff, nW2 shr 8, nW2 and $ff]);
  Log( cStr );    
  
  {*
   192.168.0.108
  *}

 


마지막으로 long 타입은 S_Addr 를 살펴보자.

var
  cStr: string;
  nL1: Cardinal;
begin

  nL1:= APInaddr4^.S_addr;
  cStr:= '';
  cStr:= cStr + Format('S_Addr : %.8x : %d', [ nL1, nL1 ])+#13#10;
  Log( cStr );
  
  {*
  	S_Addr : 6C00A8C0 : 1811982528
  *}

 

역시 2개의 워드 가 서로 바뀌여져 있는걸 알수가 있다. 위에서 변환함수 중에 htonl 이 있어 이걸 한번 사용해볼까?

 

  cStr:= cStr + Format('S_Addr : %.8x : %.8x', [ nL1, htonl(nL1) ])+#13#10;
  Log( cStr );
  
  {*
  S_Addr : 6C00A8C0 : C0A8006C
  *}

 

예상했던 결과인가? 다시 2개의 워드로 나눠서 위의 SunW 에서 했듯이 각 바이트별로 출력을 해보도록 하자.

  nW1:= nL1 and $ffff;
  nW2:= nL1 shr 16;

  nW1:= htons( nW1 );
  nW2:= htons( nW2 );
  cStr:= '';
  cStr:= cStr + Format('S_Addr : %.4x : %.4x', [ nW1, nW2 ] )+#13#10;
  cStr:= cStr + Format('%d.%d.%d.%.d', [nW1 shr 8, nW1 and $ff, nW2 shr 8, nW2 and $ff]);
  Log(cStr);
  
  {*
  S_Addr : C0A8 : 006C
  192.168.0.108
  *}

 

비트연산은 간결하긴 하지만 이 역시 엔디안 방식에 따라서 결과값이 달라질수 있다. 비트연산 말고도 자료형을 쪼개는 방법은 많이 있으니 한번 해보도록 하자

반응형