초안이라 버그가 남아있을테지만 나중에 버그수정후에도 여기 업데이트 할지는 모르겠네.
이건 bufrw.hpp 초안이라 그냥 헤더에 때려박았다. 원래 write<T> read<T> 식으로 템플릿으로 해볼까 했었는데 바이트오더 때문에 좀 귀찮아서 노가다질로. memcpy 쓰면 될부분에 포인터로 하나씩 복사한다거나.. 루프돌려야 할부분에 리커시브 돌린다거나(이건 뭐 최적화키면 전혀 문제 안되지만) 등등 막짠부분이 많으니 주의하자.
펼쳐두기..
// 바이너리 패킷을 다룰일이 생겨서 간단히 만들어봤다.
//
// writer 는 write_T 로 줄줄이 값을 쓰고 나중에 만들어진 버퍼를
// 꺼낼수있도록 했고
//
// reader 는 버퍼를 생성시 받아서 read_T 로 값을 적절히 꺼내고 나중에
// 남은 버퍼를 꺼낼수 있도록 만들었다.
//
// 대강 적어본 초안이라 테스트는 전혀 안되었고 나중에 버그나오면
// 수정해가면서 쓰면 되겠지.
#ifndef BUFRW_HPP
#define BUFRW_HPP
#include <winsock2.h>
#include <vector>
#include <stdexcept>
#include <string>
#include <memory>
#include <cstdio>
#if MSVC
// 음 아직 msvc 에선 안해봤다. 나중에 고치자.
typedef __int8 uint8_t;
typedef __int16 uint16_t;
typedef __int32 uint32_t;
#endif
// 뭔가를 읽으려고 하는데 버퍼가 부족한경우
struct insufficient_buffer_error : public std::runtime_error
{
insufficient_buffer_error(): std::runtime_error("insufficient_buffer_error") {}
};
// write_T 로 적절히 버퍼를 채워나가다가 작업이 끝나면 writed() 와
// writed_len() 으로 버퍼를 얻을수있다.
class buffer_writer
{
public:
buffer_writer()
{
}
inline const char* writed() const
{
return &(buf_.front());
}
inline const int writed_len() const
{
return buf_.size();
}
inline void write_uint8(uint8_t x)
{
buf_.push_back(x);
}
inline void write_uint16(uint16_t x)
{
x = htons(x);
buf_.push_back(((char*)&x)[0]);
buf_.push_back(((char*)&x)[1]);
}
inline void write_uint32(uint32_t x)
{
x = htonl(x);
buf_.push_back(((char*)&x)[0]);
buf_.push_back(((char*)&x)[1]);
buf_.push_back(((char*)&x)[2]);
buf_.push_back(((char*)&x)[3]);
}
inline void write_string(int n, const char* p)
{
if(n<=0) return;
buf_.push_back(*p);
write_string(n-1, p+1);
}
inline void write_string(const char* p)
{
write_string(strlen(p), p);
}
inline void write_string(const std::string& s)
{
write_string(s.size(), s.c_str());
}
private:
std::vector<char> buf_;
buffer_writer(const buffer_writer&);
void operator=(const buffer_writer&);
};
// 생성시 버퍼를 받아서 그 버퍼로부터 read_T 등의 함수로 값을 읽어내는
// 놈. 만약 버퍼길이가 부족한경우엔 insufficient_buffer_error 예외가
// 떨어지게 되어있고, 리드가 가능한지 확인하기 위한 함수로 redable_T
// 함수들이 제공된다.
//
// read_T 로 신나게 읽은후엔 남은 버퍼를 remains() 와 remains_len()
// 으로 얻을수있다.
class buffer_reader
{
public:
buffer_reader(const char* buf, int n) : buf_(buf), len_(n), idx_(0)
{
}
inline int consumed() const
{
return idx_;
}
inline const char* remains() const
{
return buf_ + idx_;
}
inline int remains_len() const
{
return len_ - idx_;
}
inline bool readable_uint8()
{
return readable(1);
}
inline uint8_t read_uint8()
{
if(!readable_uint8()) throw insufficient_buffer_error();
uint8_t x = buf_[idx_++];
return x;
}
inline bool readable_uint16()
{
return readable(2);
}
inline uint16_t read_uint16()
{
if(!readable_uint16()) throw insufficient_buffer_error();
uint16_t x;
((char*)&x)[0] = buf_[idx_++];
((char*)&x)[1] = buf_[idx_++];
return ntohs(x);
}
inline bool readable_uint32()
{
return readable(4);
}
inline uint32_t read_uint32()
{
if(!readable_uint32()) throw insufficient_buffer_error();
uint32_t x;
((char*)&x)[0] = buf_[idx_++];
((char*)&x)[1] = buf_[idx_++];
((char*)&x)[2] = buf_[idx_++];
((char*)&x)[3] = buf_[idx_++];
return ntohl(x);
}
inline bool readable_string(int n)
{
return readable(n);
}
inline void read_string(int n, char* buf)
{
if(!readable_string(n)) throw insufficient_buffer_error();
if(n <= 0) {
return;
}
buf[0] = buf_[idx_++];
read_string(n-1, buf+1);
}
inline std::string read_string(int n)
{
std::auto_ptr<char> buf(new char(n));
read_string(n, buf.get());
return std::string(buf.get());
}
private:
const char* buf_;
const int len_;
int idx_;
buffer_reader(const buffer_reader&);
void operator=(const buffer_reader&);
inline bool readable(int n)
{
return n <= remains_len();
}
};
#endif
이건 간단히 돌려본것
펼쳐두기..
#include "bufrw.hpp"
#include <cassert>
#include <stdio.h>
void test_simple()
{
buffer_writer w;
w.write_uint8(8);
w.write_uint16(3000);
w.write_uint32(8000);
buffer_reader r(w.writed(), w.writed_len());
assert(r.read_uint8() == 8);
assert(r.read_uint16() == 3000);
assert(r.read_uint32() == 8000);
assert(r.remains_len() == 0);
}
void test_pascal_string(const char* t)
{
// 요렇게.. 16비트로 길이 적고 문자열 적은 패킷은
buffer_writer w;
w.write_uint16(strlen(t));
w.write_string(t);
// 요렇게.. 길이 읽고 그만큼 문자열 읽으면 된다.
buffer_reader r(w.writed(), w.writed_len());
int l = r.read_uint16();
std::string t2 = r.read_string(l);
assert(t2 == t);
}
int main()
{
test_simple();
test_pascal_string("hahahahaha damamamamdm");
}
음.. 이건 비슷한 스타일의 파이썬 코드 사실 이걸 먼저 작성했는데 C++ 버전도 필요해진것.
펼쳐두기..
# -*- coding: utf-8 -*-
from struct import unpack_from
class reader:
""" 간단한 버퍼 리더
몇가지 타입별로
readable_foo
peek_foo
pop_foo
read_foo
의 함수들을 제공한다.
리딩이 끝난후 남은 버퍼를 얻기 위한 함수로
remains
를 제공한다.
TODO 사실 이런 클래스를 껴넣는게 좀 부담되는데 C/C++ 이라면
인라이닝이 잘 되니 이런 헬퍼 클래스가 어울리지만 파이썬은 인라인이
없는것으로 기억한다... 흠 나중에 좀 껄쩍지근 하면 패킷모양이 완성된
후에 좀더 타이트한 코드로 재작성을 하자.
"""
def __init__(self, buf):
self.buf = buf
self.i = 0
# peek, pop, readable 은 다른 함수들이 불러쓰는 유틸리티 함수
def peek(self,fmt):
return unpack_from(fmt, self.buf, self.i)[0]
def pop(self, x):
self.i += x
def readable(self, x):
return x <= len(self.buf) - self.i
# pop 하고 남은 버퍼
def remains(self):
return self.buf[self.i:]
# uint8
def readable_uint8(self):
return self.readable(1)
def peek_uint8(self):
return self.peek("B")
def pop_uint8(self):
self.pop(1)
def read_uint8(self):
x = self.peek_uint8()
self.pop_uint8()
return x
# uint16 (network endian)
def readable_uint16(self):
return self.readable(2)
def peek_uint16(self):
return self.peek("!H")
def pop_uint16(self):
self.pop(2)
def read_uint16(self):
x = self.peek_uint16()
self.pop_uint16()
return x
# uint32 (network endian)
def readable_uint32(self):
return self.readable(4)
def peek_uint32(self):
return self.peek("!I")
def pop_uint32(self):
self.pop(4)
def read_uint32(self):
x = self.peek_uint32()
self.pop_uint32()
return x
# uint64
# 음 이건 좀 애매해서 구현 안했다.
#
# struct 문서에 의하면 Q 가 unsigned long long 타입인데 이게 해당
# 플래폼 C 컴파일러의 long long 을 쓴다고 명시되어있다(윈도일경우엔
# __int64 라네) 어쨌건 C 표준은 long long 이 64비트 이상 이라고만
# 정의하고 있는것으로 아는데 그렇다면 엄밀히 말해서 Q 가
# 64비트이상을 읽는 경우도 생길 가능성이 있다는 소리.
#
# 따라서 일단 비워둔다.
# nbyte string
def readable_string(self, n):
return self.readable(n)
def peek_string(self, n):
return self.peek("%ds" % n)
def pop_string(self, n):
return self.pop(n)
def read_string(self, n):
x = self.peek_string(n)
self.pop_string(n)
return x
펼쳐두기..
# -*- coding: utf-8 -*-
from struct import pack
class writer:
""" 간단한 버퍼 라이터
몇가지 타입별로
write_foo
의 함수를 제공한다.
현재까지 쓴 버퍼 얻기위한 함수로
writed
함수를 제공한다.
TODO pack 을 쓰게 되면 매번 스트링 오브젝트를 생성하고 + 를 부르는
식으로 돌아가는데 성능상 별로겠지. 미리 적절히 버퍼를 만들고
pack_into 를 쓰게 되면 성능개선이 가능하다. 단 이경우 일반 스트링
타입을 사용할수 없으니 좀 성가시다.
http://coding.derkeiler.com/Archive/Python/comp.lang.python/2008-09/msg02039.html
를 참고.
"""
def __init__(self):
self.buf = ""
def writed(self):
return self.buf
# 유틸리티 함수
def write(self, fmt, x):
self.buf += pack(fmt, x)
# uint8
def write_uint8(self, x):
self.write("B", x)
# uint16
def write_uint16(self, x):
self.write("!H", x)
# uint32
def writer_uint32(self, x):
self.write("!I", x)
# n-byte string
def write_string(self, x):
self.write("%ds" % len(x), x)
댓글 없음:
댓글 쓰기