-
캡쳐한 그림으로 binary parsing 을 해보자Python 2025. 2. 28. 23:31
socket 을 통해 데이터를 전송 받거나, 바이너리 형태의 로그 파일을 읽으면 바이너리 데이터가 나온다.
그 바이너리 데이터에서 필요한 정보를 획득할 수 있도록 파싱(parsing)을 해보자.
파이썬에는 struct 모듈을 이용해서 바이너리 데이터를 처리할 수 있다.
일단 다음과 같은 바이너리 포맷을 사용하는 GPS 장비가 있다고 하자. 해당 장비는 socket을 통해서 바이너리 데이터를 전송하고, 우리는 해당 데이터를 받아서 파싱해야 하는 상황이라 가정하자.
이런 바이너리 포맷을 사용하는 장비가 있다고 하자. 위와 같은 바이너리 데이터가 들어오면, 파이썬에서는 보통 struct 모듈을 사용해서 처리한다.
다만, 각 필드를 일일이 매칭하여 파싱하기가 살짝 귀찮다.
본 예제에서는 필드가 몇개 안되지만, 필드가 100개를 넘어가면 정말 헷갈리고 귀찮은 일이 된다.
그럴때는 쳇지피티에게 시켜보자.
저 위의 테이블을 그림으로 캡쳐해서 그대로 쳇지피티에게 입력으로 주고 물어보자.
일단 쳇지피티에게 물어보자. 오. struct 모듈을 사용하라고 한다. 일단 물어보면 아래와 같은 코드를 보여준다.
import struct # 바이너리 데이터 (예제 데이터, 실제 데이터에 맞게 변경 필요) binary_data = b'\x01\x02\xDE\xAD\xBE\xEF\xC3\xF5H@\x8D\x97\xEBl@\xCD\xCC\xCC\xCC\xCC\xCC\xE4@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x03\xAB\xCD' # 구조체 포맷 정의 struct_format = '<BBIffffffffffB H' # '<'는 little-endian을 의미 # 구조체 크기 확인 expected_size = struct.calcsize(struct_format) if len(binary_data) != expected_size: raise ValueError(f"데이터 크기가 일치하지 않습니다. (expected: {expected_size}, actual: {len(binary_data)})") # 데이터 언패킹 unpacked_data = struct.unpack(struct_format, binary_data) # 필드 매핑 parsed_data = { "헤더": unpacked_data[0], "메시지 ID": unpacked_data[1], "타임스탬프": unpacked_data[2], "위도": unpacked_data[3], "경도": unpacked_data[4], "고도": unpacked_data[5], "속도": unpacked_data[6], "방향": unpacked_data[7], "온도": unpacked_data[8], "습도": unpacked_data[9], "기압": unpacked_data[10], "조도": unpacked_data[11], "배터리 상태": unpacked_data[12], "체크섬": unpacked_data[13], } # 출력 for key, value in parsed_data.items(): print(f"{key}: {value}")
쳇지피티도 종종 실수를 하기 때문에 알려준 코드가 잘 동작하는지 한번 실행해보자.
그런데 역시...
이럴수가... 쳇지피티 너마저... 쳇지피티가 알려준 코드가 에러가 난다.
에러메시지를 한번 자세히 보자. '데이터 크기가 일치하지 않습니다' 라고 한다.
흠.. 뭔가 데이터 크기가 맞지 않아서 에러가 나는 것 같다.
한번 데이터 크기를 직접 확인해보자.
먼저 맨 위 그림의 포맷 테이블에 있던 데이터 크기를 모두 더해보면 45바이트이다.
그런데 에러메시지를 보면 expected: 49, actual:86 이라고 한다.
이거 뭐 맞는게 하나도 없네.
하나씩 확실하게 해보자.
먼저 맨 위의 바이너리 데이터를 45바이트로 만들어주자.
# 바이너리 데이터 (예제 데이터, 실제 데이터에 맞게 변경 필요) # binary_data = b'\x01\x02\xDE\xAD\xBE\xEF\xC3\xF5H@\x8D\x97\xEBl@\xCD\xCC\xCC\xCC\xCC\xCC\xE4@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x03\xAB\xCD' # print("binary_data len:", len(binary_data)) binary_data = b'\x00' * 45 print("binary_data len:", len(binary_data))
모두 0 으로 채웠지만 전체 바이너리 데이터 크기는 45바이트가 된다.
이제 struct_format 부분을 보자.
쳇지피티가 만들어준 struct_format 은 아래와 같다.
# 구조체 포맷 정의 struct_format = '<BBIffffffffffB H' # '<'는 little-endian을 의미
자세히 보면 B, I, f, H 등의 글자의 조합으로 되어 있는데, 이는 struct 모듈에서 사용하는 포맷문자로서 바이너리 데이터를 파싱할 때 어떤 데이터 포맷으로 파싱해야 하는지 알려주는 글자이다.
대략 다음과 같다.
B:unsigned int8, I:unsigned int32, f:float32, H:unsigned int16
좀 더 자세한 포맷문자에 대한 정보는 아래에서 확인 가능하다.
https://docs.python.org/ko/3.10/library/struct.html
struct — 패킹 된 바이너리 데이터로 바이트열을 해석 — Python 3.10.16 문서
struct — 패킹 된 바이너리 데이터로 바이트열을 해석 소스 코드: Lib/struct.py This module converts between Python values and C structs represented as Python bytes objects. Compact format strings describe the intended conversions to/fr
docs.python.org
다시 본론으로 돌아와서 포맷문자를 사용하는 것은 맞는데, 왜 사이즈가 다른지 찾아야 한다.
위의 표에서 직접 확인한 바이너리 데이터 사이즈는 45바이트인데, 쳇지피티가 만들어준 포맷문자열은 49바이트를 사용한다. 뭔가 이상하다.
쳇지피티가 만들어준 포맷 문자열을 한번 더 자세히 보자.
# 구조체 포맷 정의 struct_format = '<BBIffffffffffB H' # '<'는 little-endian을 의미
맨 위의 그림에 의하면 float 타입을 갖는 필드가 9개이다. 그러므로 포맷문자열에는 f 가 9개 나와야 한다.
즉, 전체 포맷 문자열이 '<BBI fffff ffff BH' 가 되어야 한다. (띄어쓰기는 무시)
그런데 쳇지피티가 만들어준 포맷 문자열에 f 가 10개로서, f가 하나 더 들어있었다.
이런.. 믿을 사람.. 아니 믿을 AI 하나 없는 세상..
암튼 원인을 찾았다. struct_format 에서 f 를 하나 빼주면 된다.
해당 부분을 수정한 후 프로그램을 다시 실행해보면 결과가 잘 나오는 것을 확인 할 수 있다.
f 하나 수정해줬더니 결과가 잘 나온다. struct.unpack() 함수를 통해 바이너리 데이터를 파싱하면, 파싱할때 사용한 포맷문자열에 맞게 리스트 형태로 파싱결과가 나온다. 그래서 위의 코드에서 처럼 unpacked_data[0] 과 같이 데이터를 가져와서 사용할 수 있게 된다.
오늘의 내용은 아래와 같다.
1. 쳇지피티에게 테이블형태의 그림파일만 가지고 parsing 프로그램을 만들어 달라고 하면, 실제로 parsing 프로그램을 잘 만들어준다.
2. 근데 오류가 있을 수 있다!!
3. 그 오류를 사람이 찾아서 고쳐주어야 한다.
4. 그러면 잘 동작한다.
언젠가는 쳇지피티가 오류없이 잘 동작하는 프로그램을 만들어주겠지..
오늘은 여기까지!!
'Python' 카테고리의 다른 글
Protobuf 를 쉽게 사용해보자 (1/2) (0) 2025.03.08 파이썬 리스트 깊은 복사(deep copy) vs 얕은 복사(shallow copy) (0) 2025.03.05 파이썬 리스트 복사(list copy) (0) 2025.03.04 간단한 설정파일을 직접 만들어서 사용해보자 (0) 2025.03.01 PySide6 에서 Tab 구현하기 (ChatGPT를 이용하여) (0) 2025.02.27