성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>직렬화로 설명하는 Little/Big Endian</h1> <p> 아래와 같은 질문이 있는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c# socket 통신할때 빅엔디언으로 바꿔줘야 하나요? ; <a target='tab' href='https://www.sysnet.pe.kr/3/0/5759'>https://www.sysnet.pe.kr/3/0/5759</a> </pre> <br /> 마침 한 번도 엔디언 관련한 이야기를 꺼낸 적이 없어서 이렇게 글로 남깁니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> <a target='tab' href='https://ko.wikipedia.org/wiki/%EC%97%94%EB%94%94%EC%96%B8'>걸리버 여행기에서 유래한 엔디언(Endianness)</a>이라는 단어는 컴퓨터 업계에서는 바이트의 배열 방법을 일컫습니다.<br /> <br /> 예를 들어 볼까요? '0', '1', '2'라는 문자 데이터는 0x30, 0x31, 0x32에 해당합니다. 그럼, 이 값을 "파일"에 저장한다고 가정해 보겠습니다. 딱히 이에 대해서는 생각할 여지가 없이 그대로 데이터를 저장할 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // text.txt에 저장된 바이트 0x30 0x31 0x32 </pre> <br /> 문제는, 이러한 데이터의 크기가 단순히 1바이트 짜리가 아닌, 2바이트 이상이 되었을 때 발생합니다. 가령, 숫자 24592는 바이트로 바뀌어 16진수로 표현된 경우에는 0x6010이 됩니다. 그리고 이 값을 파일에 저장하기 위해서는 2가지 방법이 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [숫자 0x6010을 저장하는 방법] 1) 숫자의 상위 바이트 영역을 먼저 저장 (Big Endian) 0x60 0x10 2) 숫자의 하위 바이트 영역을 먼저 저장 (Little Endian) 0x10 0x60 </pre> <br /> 걸리버 여행기의 소인국 사람들의 논쟁을 보면서 뭐 저런 걸로 다 싸우냐고 할 텐데요, 재미있게도 소설이 아닌 현실에서도 (싸움까지는 안 했겠지만) 저런 식의 결정 장애를 겪고 있는 사람들이 정말 있었던 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 유명한 Intel 아키텍처에서는 Little Endian 방식으로 바이트를 배열합니다. 그래서 숫자 24592를 Intel CPU가 채택된 시스템에서 메모리에 저장하면 0x10, 0x60과 같이 저장이 됩니다. 비주얼 스튜디오 + C#을 이용해 실제로 다음과 같은 코드를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > internal class Program { static unsafe void Main(string[] args) { Console.WriteLine(BitConverter.IsLittleEndian); <span style='color: blue; font-weight: bold'>short x = 24592; IntPtr ptr = new IntPtr(&x);</span> Console.WriteLine($"{ptr:x16}"); } } </pre> <br /> 디버깅 모드로 실행(F5)하면 메모리 창을 이용해 저장 순서를 확인할 수 있습니다.<br /> <br /> <img alt='endian_byte_order_1.png' src='/SysWebRes/bbs/endian_byte_order_1.png' /><br /> <br /> 보는 바와 같이 변수의 메모리 주소(위의 경우 0xfd1f17e5e8) 위치에 0x10, 0x60 순으로 2바이트 short 데이터가 저장돼 있습니다.<br /> <br /> 반면 PowerPC 아키텍처에서는 그 반대로 Big Endian을 채택했으므로 동일한 숫자를 메모리에 저장할 때 0x60, 0x10으로 저장합니다.<br /> <br /> 그런데, 사실 CPU로 인해 달라지는 사례가 유명해서 그렇지, 엄밀히 엔디언은 CPU에 종속된 단어는 아닙니다. 제가 이 글의 처음에 쓴 것처럼, 2바이트 이상의 데이터 타입을 특정 미디어에 저장할 때, 즉 I/O 장치에 전송할 때 어디에서나 발생할 수 있는 선택의 문제입니다.<br /> <br /> 가령, 파일로 데이터를 저장할 때를 예로 들어보겠습니다. C#으로 다음과 같이 숫자를 저장하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > internal class Program { static unsafe void Main(string[] args) { short x = 0x6010; // 10진수 24592 byte[] buffer = BitConverter.GetBytes(x); File.WriteAllBytes("test_little.bin", buffer); } } /* 위의 코드는 이렇게 명시적으로 바이트 순서를 지정하는 것과 동일 { byte upper = (byte)((x & 0xFF00) >> 8); byte lower = (byte)(x & 0xFF); byte[] buffer = new byte[] { <span style='color: blue; font-weight: bold'>lower, upper</span> }; File.WriteAllBytes("test_little.bin", buffer); } */ </pre> <br /> test_little.bin 파일에는 0x10, 0x60과 같이 저장되는 반면 동일한 숫자를 다음과 같이 저장하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > byte upper = (byte)((x & 0xFF00) >> 8); byte lower = (byte)(x & 0xFF); byte[] buffer = new byte[] { <span style='color: blue; font-weight: bold'>upper, lower</span> }; File.WriteAllBytes("test_big.bin", buffer); </pre> <br /> 0x60, 0x10 순으로 바이트가 배열됩니다. 데이터 저장 시의 엔디언 선택이 중요한 이유는, 그 데이터를 다시 로드할 때에도 순서를 맞춰야 하기 때문입니다. 만약, Little endian 방식으로 숫자 24592를 저장한 파일을 PowerPC 계열에서 로드한다면 엉뚱하게 4192로 읽히게 됩니다.<br /> <br /> 따라서 전혀 다른 아키텍처에서 사용되는 파일을 다룬다면 데이터 저장에서부터 엔디언 방식을 합의해야만 합니다. 참고로, C#의 경우 현재 실행 중인 환경의 엔디언 종류를 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.bitconverter.islittleendian'>BitConverter.IsLittleEndian</a>으로 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://dotnet.microsoft.com/en-us/download/dotnet/7.0'>닷넷의 지원 범위</a>가 x86/x64와 ARM32/64이기 때문에 대부분의 경우 True를 반환 Console.WriteLine(BitConverter.IsLittleEndian); </pre> <br /> 이렇게 CPU 아키텍처와 독립적으로 응용 프로그램 수준에서 엔디언을 정해야 하는 것은 당연할 수 있습니다. 가령 윈도우에서 실행하는 아래아 한글이 파일을 Little 엔디언으로 저장하면, 이후 PowerPC 아키텍처를 지원하는 운영체제에서 실행하는 아래아 한글 파일을 만들게 된다면 반드시 데이터를 Little 엔디언으로 읽어내야 합니다.<br /> <br /> 이러한 예로, <a target='tab' href='https://developer.arm.com/documentation/den0013/d/Porting/Endianness'>BMP나 GIF 파일은 little 엔디언을 따르지만 JPG 포맷은 big 엔디언</a>을 따릅니다.<br /> <br /> 그런데, 단순히 응용 프로그램 하나로 해결될 문제가 아닌 사례가 있습니다. 바로 네트워크 통신입니다.<br /> <br /> 일례로, TCP 헤더의 포트 번호는 2바이트 숫자인데, 이 값은 단순히 응용 프로그램에서만 쓰이지 않고 라우터 등의 네트워크 통신 장비에서도 인식을 해야 합니다. 따라서, 이에 대해서는 전체 산업계에서 합의를 봐야 하고 결국 Big Endian으로 직렬화하자고 정의를 한 것입니다.<br /> <br /> 또한, 이러한 합의는 단순히 네트워크 프로토콜의 헤더에만 국한하지 않고 TCP/IP 응용 프로그램 내에서의 데이터 송/수신도 Big Endian으로 하는 것이 관례처럼 되었습니다. 아마도 초창기 네트워크가 운영되던 시절에는 서버 급에서 Big Endian을 채택한 시스템이 많아 자연스럽게 Big Endian으로 합의했을 것입니다.<br /> <br /> 그렇다고 모든 네트워크 통신이 Big 엔디언은 아닙니다. TCP/IP와는 달리 USB나 <a target='tab' href='https://en.wikipedia.org/wiki/Peripheral_Component_Interconnect'>PCI</a> 통신은 Little 엔디언을 따릅니다.<br /> <br /> 물론, 이러한 산업 표준에서의 관례와는 별개로 응용 프로그램 데이터는 여러분들이 마음대로 서버 프로그램과 함께 정의하면 그만입니다. 즉, 서버와 합의만 할 수 있다면 소켓으로 송/수신하는 데이터만큼은 그냥 Little 엔디언으로 처리해도 무방합니다.<br /> <br /> 이 정도면 대충 설명이 되었을 것 같고, 그 외에도 Middle Endian 등의 용어들도 있지만 그냥 있다는 정도만 알아두셔도 될 듯합니다.<br /> <br /> 여기까지... 이제 위의 설명을 염두에 두고 "<a target='tab' href='https://www.sysnet.pe.kr/3/0/5759'>c# socket 통신할때 빅엔디언으로 바꿔줘야 하나요?</a>" 질문을 다시 볼까요? <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 1) 네트워크 통신에서 빅엔디언으로 약속된걸로 알고있는데요. 2) 인텔/amd 환경에서 데이터 보낼때 항상 빅엔디언으로 바꾸는 코드를 넣어줘야 하나요? 3) 아니면 내부적으로 알아서 빅엔디언으로 변환해서 보내고 4) 받을때에는 환경에 맞춰서 알아서 바이트 정렬을 해주나요? </pre> <br /> 이 글의 내용을 충분히 이해했다면 다음과 같은 답변으로 정리가 될 것입니다.<br /> <br /> 1) 응용 프로그램이 전송하는 데이터 자체가 언제나 100% 빅엔디언이라고 장담할 수는 없습니다.<br /> 2) 응용 프로그램의 데이터가 빅엔디언으로 합의되었다면 Intel/AMD 환경에서는 항상 엔디언 변환 코드를 넣어야 합니다.<br /> 3) 내부적으로 알아서 변환하지는 않습니다.<br /> 4) 응용 프로그램에서, (예를 들어 TCP의 경우) <a target='tab' href='https://learn.microsoft.com/ko-kr/dotnet/api/system.net.sockets.socket.receive'>Receive</a>로 수신한 바이트는 합의를 빅엔디언으로 했다면 자신의 환경에 맞게 바이트 정렬을 바꿔야 합니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
5610
(왼쪽의 숫자를 입력해야 합니다.)