http://www.boost.org/doc/libs/1_42_0/doc/html/boost_asio/examples.html
http://www.boost.org/doc/libs/1_42_0/doc/html/boost_asio/example/http/server4/coroutine.hpp
일단 링크만 적어둔다.
추가.
http://www.crystalclearsoftware.com/soc/coroutine/
헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤헤
2010년 1월 21일 목요일
boost asio 를 이용 serialport 로 테스트용 바이트시퀀스를 줄줄이 싸봤다.
보드쪽에서 시리얼 읽는게 좀 이상하길래 만들어본 테스트 코드.
처음엔 haskell 로 짰는데( serialport 라고, haskell 에서 win32 시리얼 포트를 지원하는 라이브러리가 나온게 본기억이 나서 ) ㅋㅋㅋ 잘 안되더라. send 가 묘하게 느리던데.. 업무상 필요한 작업이라 삽질 그만두고 바로 C++ 로 짜버렸다.
테스트한 시나리오가.. C++ 로 0x00 부터 0xFF 를 줄기차게 날리고 보드쪽에서 매번 한바이트읽을때마다 기존 값하고 1 차이나는지 비교한.. 단순히 한방향 통신만 하는 코드라서 시리얼포트에서 읽거나 하는 코드는 들어있지 않다.
뭐 사실 그냥 asio 사용하고 다를거하나 없고 set_option 의 사용법 정도나 봐두면 되겠다.
처음엔 haskell 로 짰는데( serialport 라고, haskell 에서 win32 시리얼 포트를 지원하는 라이브러리가 나온게 본기억이 나서 ) ㅋㅋㅋ 잘 안되더라. send 가 묘하게 느리던데.. 업무상 필요한 작업이라 삽질 그만두고 바로 C++ 로 짜버렸다.
테스트한 시나리오가.. C++ 로 0x00 부터 0xFF 를 줄기차게 날리고 보드쪽에서 매번 한바이트읽을때마다 기존 값하고 1 차이나는지 비교한.. 단순히 한방향 통신만 하는 코드라서 시리얼포트에서 읽거나 하는 코드는 들어있지 않다.
뭐 사실 그냥 asio 사용하고 다를거하나 없고 set_option 의 사용법 정도나 봐두면 되겠다.
#include <boost/asio.hpp>
#include <exception>
#include <iostream>
using namespace std;
int main()
{
try
{
boost::asio::io_service io_service;
// 시리얼포트 열고
boost::asio::serial_port s(io_service, "COM4");
boost::asio::serial_port::baud_rate baud_rate(9600);
s.set_option(baud_rate);
boost::asio::deadline_timer t(io_service);
unsigned char buf[1] = {0};
while(true)
{
// 한바이트씩 보내자
s.write_some(boost::asio::buffer(buf, sizeof(buf)));
cout << static_cast<unsigned int>(buf[0]) << endl;
// 0x00 부터 0xFF 까지 보내면 되겠지.
buf[0]++;
// 잠시 쉬자.
t.expires_from_now(boost::posix_time::milliseconds(5));
t.wait();
}
io_service.run();
}
catch(exception& e)
{
cerr << e.what() << endl;
}
}
2009년 4월 21일 화요일
unspecified_bool_type, safe bool idiom, C++ 에서 어떤 클래스를 bool 값으로 캐스팅하는 좀더 안전한 방법
boost 코드에 unspecified_bool_type 라는게 자주 나오는데 이게 뭔지 찾을수 있도록 실마리가 될 링크를 적어둔다.
The Safe Bool Idiom
나는 항상 명시적인 캐스팅을 하는 주의라 .valid(), .good() 등의 멤버함수를 두는 편인데 그렇지 않은 코드들도 자주 보이니 읽어두자. 이거 거꾸로 코드만 보고서 왜 이런 짓을 했는지 역으로 알아보는것은.. 내 능력으론 불가능 했겠지? C++ 도 오래된 언어라 여러가지 트릭이 많은편인데 그중 몇가지는 idiom 이란 거창한 이름으로 포장되어 있으니 가끔씩 둘러봐야 한다.
The Safe Bool Idiom
나는 항상 명시적인 캐스팅을 하는 주의라 .valid(), .good() 등의 멤버함수를 두는 편인데 그렇지 않은 코드들도 자주 보이니 읽어두자. 이거 거꾸로 코드만 보고서 왜 이런 짓을 했는지 역으로 알아보는것은.. 내 능력으론 불가능 했겠지? C++ 도 오래된 언어라 여러가지 트릭이 많은편인데 그중 몇가지는 idiom 이란 거창한 이름으로 포장되어 있으니 가끔씩 둘러봐야 한다.
2009년 4월 14일 화요일
boost::asio 로 만들어본 간단한 콘솔 서버 ( async_read_until 사용예 )
서버를 짜다보면 간단한 제어 인터페이스를 노출할 경우가 많은데 보통 웹을 붙이거나 shell 을 흉내낸다. 이하는 asio 로 간단히 shell 흉내를 내는 코드를 만들어본것.
그냥 시간이 남아 간단히 만들어본건데 async_read_until 때문에 조금 고생했다.
이놈이 받는 버퍼는 다른 async 류 함수들과 달리 streambuf 더라.
나는 평소 C++ 을 쓰더라도 stream 쪽은 전혀 쓰질 않아서 이쪽은 하나도 모르는데.. 그래서 좀 애먹었다. 급한대로 streambuf 로부터 한글자씩 꺼내서 처리해봤는데 이건 좀 아닌거 같고 좀더 나은 방법을 뒤져봐야겠다.
이하는 main 을 담고있는 코드.
한줄을 받아서 한줄을 내놓는 최소한의 로직만 담고있는 클래스 echo 를 정의하고 이놈으로 console<echo> 인스턴스를 만들어 io_service 에 붙였다. 당연히 그냥 asio 공부하려고 만들어본 코드니 제구실하기엔 부족한 코드.
console<T> 는 그냥 acceptor 라고 보면 된다. 특정 포트로 tcp 받아서 session<T> 를 까주는 놈. session<T> 는.. 결국 session<echo> 로 인스턴스가 만들어지는데 echo 가 가진 함수를 부르기 위한 소켓관련 작업들을 해주는 놈이다.
좀더 뒤져서 asio::streambuf 에서 값을 꺼내는 방법을 하나 더 찾았다.
asio::const_buffer data = buf_.data();
const char* b = asio::buffer_cast<const char*>(data);
const char* e = b + readed;
뭐 대략 이런식인데 이것도 통쾌한 방법은 아닌거 같다. 문서상 그리고 메일링에서도 istream 을 통해 꺼내오는것을 권장하는거 같은데.. iostream 쪽 코드는 영 손대기 그렇구만..
그리고 이런식으로 뽑아쓸경우 consume 으로 버퍼 비워주는것도 잊지말자.
추가.
ReadHandler 같은경우 error 와 readed 를 인자로 받는데 나는 error 나면 readed 무시하고 바로 에러를 내도록 했지만 경우에 따라 readed 를 먼저 처리하고 error 확인해야 할때도 있을거 같은데.. 이건 나중에 asio 를 정말 쓰게되면 다시 만날 문제겠지.
그냥 시간이 남아 간단히 만들어본건데 async_read_until 때문에 조금 고생했다.
이놈이 받는 버퍼는 다른 async 류 함수들과 달리 streambuf 더라.
나는 평소 C++ 을 쓰더라도 stream 쪽은 전혀 쓰질 않아서 이쪽은 하나도 모르는데.. 그래서 좀 애먹었다. 급한대로 streambuf 로부터 한글자씩 꺼내서 처리해봤는데 이건 좀 아닌거 같고 좀더 나은 방법을 뒤져봐야겠다.
이하는 main 을 담고있는 코드.
한줄을 받아서 한줄을 내놓는 최소한의 로직만 담고있는 클래스 echo 를 정의하고 이놈으로 console<echo> 인스턴스를 만들어 io_service 에 붙였다. 당연히 그냥 asio 공부하려고 만들어본 코드니 제구실하기엔 부족한 코드.
아래는 console, session 을 담고있는 코드main.cpp..
#include "console.hpp"
// 걍 간단한 예제니까 operator() 하나만 요구하도록 했다.
// 좀더 써먹으려면 프롬프트제어나.. 등등 많은 기능이 이쪽으로 들어와야겠지?
struct echo
{
string operator()(const string& input)
{
// 간단히 만들다 보니 그만 끝내라는 제어를 예외로 하게됐다.
if(input == "quit") throw runtime_error("damn shit");
// 여기서 적절한 처리를 하면 되겠지.
return input;
}
};
int main()
{
try
{
asio::io_service io_service;
console<echo> con(io_service, tcp::endpoint(tcp::v4(), 4321), "sucks> ");
io_service.run();
}
catch(exception& e)
{
cerr << e.what() << endl;
}
}
console<T> 는 그냥 acceptor 라고 보면 된다. 특정 포트로 tcp 받아서 session<T> 를 까주는 놈. session<T> 는.. 결국 session<echo> 로 인스턴스가 만들어지는데 echo 가 가진 함수를 부르기 위한 소켓관련 작업들을 해주는 놈이다.
이건 include 모음집. 네임스페이스 처리가 짱나서 하나에 몰아두고 쓰는게 좋지.console.hpp..
// 몇줄 안되는 코드지만 async_read_until 때문에 고생했다. 우선 이놈은
// 버퍼로 받는 타입이 다른 어싱크 함수들과는 다르더라. basic_streambuf
// 라.. 문서상에는 std::streambuf 확장이라고 되어있던데.. 헌데 난 C++
// 을 쓰더라도 가능하면 stream 라이브러리쪽을 쓰지 않는 주의(C 쪽
// FILE* 또는 win32 api 선호)라 streambuf 를 다뤄보는건 처음이었다.
//
// 결론적으로 streambuf 는 잘 모른다는 소리. 예제에서는 streambuf 에서
// istream 만들어서 이 스트림을 가지고 버퍼를 읽던데.. 이 무슨
// 삽질인가? 버퍼면 그냥 꺼낼수 있어야지. 그런데 적절히 둘러봐도 멋지게
// 꺼낼 메서드를 못찾아서(몰라서) 결국 한글자씩 뽑아내는 무식한짓을
// 했다... 헐....
//
// 여유나면 C++ stream 쪽도 공부를 좀 해두자.
//
#ifndef CONSOLE_HPP
#define CONSOLE_HPP
#include "boostasio.hpp"
template<class T>
class session
{
public:
// 최초에 프롬프트 찍어주고
session(socket_ptr sock, const string& pr)
: sock_(sock),
prompt_(pr)
{
prompt();
}
private:
socket_ptr sock_;
T proc_;
asio::streambuf buf_;
string prompt_;
// 프롬프트 날려준다.
void prompt()
{
asio::async_write(*sock_,
asio::buffer(prompt_),
bind(&session<T>::handle_prompt,
this,
asio::placeholders::error));
}
// 프롬프트 날아갔으면 한줄 읽을 차례
void handle_prompt(const error_code& error)
{
if(error) return die(error);
readline();
}
// 한줄 읽는다.
void readline()
{
asio::async_read_until(*sock_,
buf_,
"\r\n",
bind(&session<T>::handle_readline,
this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
// 한줄 읽었으면 처리할 차례. 단 빈 문자열이면 프롬프트나 날린다.
void handle_readline(const error_code& error, size_t readed)
{
if(error) return die(error);
// 아래 istream 이 문서상에 나온 사용법. 그런데 이런식의
// 사용법은 좀 거시기하네.
// istream i(&buf_);
// string line;
// getline(i, line);
// 으음. streambuf 로부터 직접 값을 꺼냈다. 이런식의 접근도
// 정상은 아닌거 같은데.. 어쨌거나 마지막에 \r\n 이 들어있으니
// 적절히 처리를 해줘야 했는데 코드를 깔끔하게 하려고 모두
// 읽은후 트리밍해줬다. 릴리즈할 코드였다면 readed-2 만큼
// 읽었겠지. 아니 애초에 한글자씩 꺼내는 이짓을 하지 않고 다른
// 방법을 찾았겠지.
string line;
for(int i = 0; i < readed; ++i)
line.push_back(buf_.sbumpc());
boost::trim(line);
// 빈입력이 들어오면 바로 다시 프롬프트찍도록 했다. 이거 빠지면
// 엔터 마구칠때 보기 흉해진다.
if(line.empty()) return prompt();
proc(line);
}
// 처리.. 처리하는 코드를 템플릿 인자로 받도록 했다. 별의미는 없고 그냥..
void proc(const string& input)
{
try
{
string output(proc_(input));
writeline(output+"\r\n");
}
catch(exception& e)
{
die(e);
}
}
// 처리결과 쏴주고
void writeline(const string& output)
{
asio::async_write(*sock_,
asio::buffer(output),
bind(&session<T>::handle_writeline,
this,
asio::placeholders::error));
}
// 다시 프롬프트 쏠 차례
void handle_writeline(const error_code& error)
{
if(error) return die(error);
prompt();
}
// 에러나면 걍 delete this
void die(const error_code& error)
{
cerr << error.message() << endl;
delete this;
}
void die(exception& e)
{
cerr << e.what() << endl;
delete this;
}
};
// 템플릿 인자로 받은건 괜히 쑈한것. 그나저나 이 T 로 인스턴스를 직접
// 만드는게 아니고 session 에 전달만 하네.. 이거 좀 모양이 별로군.
template <class T>
class console
{
public:
console(asio::io_service& io_service, const tcp::endpoint& endpoint, const string& prompt)
: acceptor_(io_service, endpoint),
prompt_(prompt)
{
start_accept();
}
private:
tcp::acceptor acceptor_;
string prompt_;
void start_accept()
{
socket_ptr childsocket(new tcp::socket(acceptor_.io_service()));
acceptor_.async_accept(*childsocket,
bind(&console<T>::handle_accept,
this,
childsocket,
asio::placeholders::error));
}
void handle_accept(socket_ptr childsocket, const error_code& error)
{
if(error)
{
cerr << error.message() << endl;
return;
}
new session<T>(childsocket, prompt_);
start_accept();
}
};
#endif
음.boostasio.hpp..
#ifndef BOOSTASIO_HPP
#define BOOSTASIO_HPP
#include <iostream>
#include <boost/algorithm/string.hpp>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
using namespace std;
namespace asio = boost::asio;
using boost::asio::ip::tcp;
using boost::lexical_cast;
using boost::array;
using boost::bind;
using boost::shared_ptr;
using boost::system::error_code;
typedef shared_ptr<tcp::socket> socket_ptr;
#endif
좀더 뒤져서 asio::streambuf 에서 값을 꺼내는 방법을 하나 더 찾았다.
asio::const_buffer data = buf_.data();
const char* b = asio::buffer_cast<const char*>(data);
const char* e = b + readed;
뭐 대략 이런식인데 이것도 통쾌한 방법은 아닌거 같다. 문서상 그리고 메일링에서도 istream 을 통해 꺼내오는것을 권장하는거 같은데.. iostream 쪽 코드는 영 손대기 그렇구만..
그리고 이런식으로 뽑아쓸경우 consume 으로 버퍼 비워주는것도 잊지말자.
추가.
ReadHandler 같은경우 error 와 readed 를 인자로 받는데 나는 error 나면 readed 무시하고 바로 에러를 내도록 했지만 경우에 따라 readed 를 먼저 처리하고 error 확인해야 할때도 있을거 같은데.. 이건 나중에 asio 를 정말 쓰게되면 다시 만날 문제겠지.
2009년 4월 13일 월요일
BCCL(boost concept check library) 첫인상
http://groups.google.com/group/comp.lang.c++/browse_thread/thread/ec94a39f632a6c05
할짓없으니 쓰지도 않을 다른 잡다한 언어들을 공부를 했었는데 밥벌이 도구 C++ 도 조금씩 따라가야겠군. 뭐 어차피 다음 표준은 멀었고 나온다고 해도 몇년간은 건드리지 못하겠지만.. 뭐 어쨌건 컨셉(이라기보단 BCCL)에 대해서 약간.
먼저 결론부터 말하면 병신같다.
용어부터 몇개 적어두자. 참고로 현재 컨셉에 대해 적는것은 BCCL (부스트 컨셉 체크 라이브러리) 쪽 문서를 대강 읽어버고 적는것으로 C++ 다음 표준에 대한 문서를 읽어본것은 아니다. 그건 나중에 구현이 나오면 다시 읽어보든가 하게 되겠고 일단 BCCL 쪽에서 말하는 용어부터.
문제는 haskell 같은경우 애초에 이런식(메타프로래밍)으로 만들어진 언어라서 언어적인 지원이 좋다는거. 어떤 타입이 어떤 타입클래스의 인스턴스여야 하는데 그게 누락되었다면 친절하게 이해갈수있는 에러메시지를 띄워준다.
그런데 C++ 은 그렇지 못하다.. 템플릿이 가장 짱나는 점이 에러가 나면 대책없다는거.( 사실 빌드 느린게 더 짜증난다... )
당장 stl 만 보더라도 쓰다가 에러가 나면 에러메시지를 읽느니 내가 뭘 잘못했는지 코드를 한번더 읽어보는게 디버깅이 더 빠르다. 이게 stl 같이 익숙한 표준 라이브러리니까 가능한거지 생소한 템플릿 라이브러리를 쓰다가 에러가 나면 욕밖에 안나온다.
그래서 나온게 컨셉 체크 라이브러리 인데.. 글쎄.. 이게 공부의 의욕을 부르는 좋은 라이브러리는 아니다. 드러운 템플릿/매크로 매직...
템플릿 관련 공부를 하다보면 항상 결론은 같다..
정말 믿을수있는 라이브러리라면 잘 공부한후에 쓰고(안쓰기가 힘들다. 템플릿만으로 가능한게 많으니)
내가 만드는 라이브러리는 절대로 템플릿 쓰지 말것.
타이핑 더 하더라도 유지보수 어려운 쓰레기를 만드는것보단 나을게다.
음
진짜 결론은 템플릿 공부하기 싫다는거 ㅎㅎㅎ
그래도 틈틈히 공부는 해둬야겠지..
다음엔 실제로 조금 코딩을 해봐야겠다.
http://www.boost.org/doc/libs/1_38_0/libs/concept_check/concept_check.htm
http://www.boost.org/community/generic_programming.html#concept
추가.
C++ 다음 확장에서 컨셉을 언어레벨에서 지원하는구만.
그럼 굳이 이런 매크로질을 공부할 의미는 없지.
이거 완존 뻘짓했네.
http://www.generic-programming.org/languages/conceptcpp/specification/
참고
할짓없으니 쓰지도 않을 다른 잡다한 언어들을 공부를 했었는데 밥벌이 도구 C++ 도 조금씩 따라가야겠군. 뭐 어차피 다음 표준은 멀었고 나온다고 해도 몇년간은 건드리지 못하겠지만.. 뭐 어쨌건 컨셉(이라기보단 BCCL)에 대해서 약간.
먼저 결론부터 말하면 병신같다.
용어부터 몇개 적어두자. 참고로 현재 컨셉에 대해 적는것은 BCCL (부스트 컨셉 체크 라이브러리) 쪽 문서를 대강 읽어버고 적는것으로 C++ 다음 표준에 대한 문서를 읽어본것은 아니다. 그건 나중에 구현이 나오면 다시 읽어보든가 하게 되겠고 일단 BCCL 쪽에서 말하는 용어부터.
- concept - 타입이 지켜야 할 것들
- model - concept 을 지킨 타입
- refinement - 컨셉의 확장이라는데 잘 모르겠다. 다른 컨셉을 포함하는 컨셉이란걸까?
- valid expression - 컴파일 되야 한다는거. 뭐 정의안된 함수를 부르거나 하면 안된다는 뻔한소리겠지.
- associcate types - 글쎄 뭔지 모르겠네. 어떤 타입 T 가 어떤 컨셉의 모델이면 부수적으로 다른 타입들고 유효해야 하는데 그 성질을 말하는거 같다.
- invariants - 이건 전혀 상상이 안간다. 불변의 런타임 특성이라.. 정적 특성도 아니고..
- complexity guarantees - 이건 정말 이해불가능이었다.. 어떻게 언어차원에서 이걸 정의할수가 있는거지. 결국 이 단어때문에 낚여서 BCCL 문서를 보다가 때려치게 됐다. BCCL 은 이 네가지중 위 두가지.. 즉 신택스 쪽만 체크해준다. 뭐 당연한거면 당연한건데 낚였다는 느낌을 지울수가 없네.
문제는 haskell 같은경우 애초에 이런식(메타프로래밍)으로 만들어진 언어라서 언어적인 지원이 좋다는거. 어떤 타입이 어떤 타입클래스의 인스턴스여야 하는데 그게 누락되었다면 친절하게 이해갈수있는 에러메시지를 띄워준다.
그런데 C++ 은 그렇지 못하다.. 템플릿이 가장 짱나는 점이 에러가 나면 대책없다는거.( 사실 빌드 느린게 더 짜증난다... )
당장 stl 만 보더라도 쓰다가 에러가 나면 에러메시지를 읽느니 내가 뭘 잘못했는지 코드를 한번더 읽어보는게 디버깅이 더 빠르다. 이게 stl 같이 익숙한 표준 라이브러리니까 가능한거지 생소한 템플릿 라이브러리를 쓰다가 에러가 나면 욕밖에 안나온다.
그래서 나온게 컨셉 체크 라이브러리 인데.. 글쎄.. 이게 공부의 의욕을 부르는 좋은 라이브러리는 아니다. 드러운 템플릿/매크로 매직...
템플릿 관련 공부를 하다보면 항상 결론은 같다..
정말 믿을수있는 라이브러리라면 잘 공부한후에 쓰고(안쓰기가 힘들다. 템플릿만으로 가능한게 많으니)
내가 만드는 라이브러리는 절대로 템플릿 쓰지 말것.
타이핑 더 하더라도 유지보수 어려운 쓰레기를 만드는것보단 나을게다.
음
진짜 결론은 템플릿 공부하기 싫다는거 ㅎㅎㅎ
그래도 틈틈히 공부는 해둬야겠지..
다음엔 실제로 조금 코딩을 해봐야겠다.
http://www.boost.org/doc/libs/1_38_0/libs/concept_check/concept_check.htm
http://www.boost.org/community/generic_programming.html#concept
추가.
C++ 다음 확장에서 컨셉을 언어레벨에서 지원하는구만.
그럼 굳이 이런 매크로질을 공부할 의미는 없지.
이거 완존 뻘짓했네.
http://www.generic-programming.org/languages/conceptcpp/specification/
참고
2009년 4월 9일 목요일
boost::asio 를 이용한 tcp 연결 테스터 -- boost::bind 와 boost::function 을 이용해서 두 클래스의 의존성 줄이기
음 제목짓기가 애매하네.
어떤 서버가 tcp 연결을 몇개까지 받는지 테스트해야 했는데 어쩌다 보니 C++ 로 짜게 됐다.(ghc 의 소켓제한 때문에..)
워낙 간단한 코드라 평소 안하던짓을 한번 해보고 코드를 적어둔다.
소켓을 다룰때 많은 세션을 다루려면 이 세션을 다루는 매니저 클래스가 있을법 한데 보통 이경우 세션 클래스가 매니저 클래스를 알아야만 한다(커플링). 이걸 bind 와 function 으로 좀 오버해서 만들어봤다.
물론 이외에도 방법은 많고.. 실무에 써야 한다면 아마도 당연히 그냥 의존성을 그냥 두고 가거나 아니면 signal 류 라이브러리를 썼겠지. 아래 코드는 재미삼아 만들어본것에 불과하다.
어떤 서버가 tcp 연결을 몇개까지 받는지 테스트해야 했는데 어쩌다 보니 C++ 로 짜게 됐다.(ghc 의 소켓제한 때문에..)
워낙 간단한 코드라 평소 안하던짓을 한번 해보고 코드를 적어둔다.
소켓을 다룰때 많은 세션을 다루려면 이 세션을 다루는 매니저 클래스가 있을법 한데 보통 이경우 세션 클래스가 매니저 클래스를 알아야만 한다(커플링). 이걸 bind 와 function 으로 좀 오버해서 만들어봤다.
물론 이외에도 방법은 많고.. 실무에 써야 한다면 아마도 당연히 그냥 의존성을 그냥 두고 가거나 아니면 signal 류 라이브러리를 썼겠지. 아래 코드는 재미삼아 만들어본것에 불과하다.
펼쳐두기..
// 테스트용도로 프로그램을 작성해야 했는데 boost 의 asio, function,
// bind 를 함 써봤다. asio 는 뭐 워낙 문서 잘되있고 쓰기 쉬운놈이라
// 그냥 문서 따라하면 되고..
//
// 잠깐 재미삼아 드러운 코드를 만들어봤는데 대강 적어둔다. killer 란
// 이름은 좀 장난스러운 건데 manager 정도로 지을걸 그랬구먼.
//
// killer 는 일종의 매니저 클래스로 필요에 따라 session 을 생성하면서
// sessions 타입으로 적절히 관리(걍 들고만 있는다)를 하며 session
// 클래스는 소켓이 끊어지면 killer 의 어떤 메서드를 불러서 자신이
// 종료된다는것을 알리고 delete this 로 죽는다.
//
// 평소대로라면 session 에 killer 의 레퍼런스를 넘겨서 들고있다가
// 필요할때 이 레퍼런스를 통해서 killer 의 멤버를 불렀겠지...
//
// 그런데 이번에는 그냥 재미삼아서 session 클래스와 killer 클래스의
// 커플링을 제거해보려고 했다..
//
// OOP 식으로 접근하자면 매니저클래스의 추상클래스를 하나 만들어서
// killer 가 이를 상속받고 session 에는 추상클래스 포인터를 넘겨서
// session 가 구체클래스인 killer 가 아니라 추상클래스를 보도록
// 하겠지...
//
// 그건 별 재미가 없고 이번에는 killer 가 session 이 부를 자신의
// 메서드를 하나의 함수객체로 포장해서 session 에 넘겨버리고 session 은
// 이놈만 부르면 되도록 해봤다. killer 입장에서는 session 을 만들때
// session 이 상황에 따라 부를 함수들을 지정할수 있는 장점이 있고
// session 입장에서는 함수객체 라는 아주 추상화된 타입만 바라보면 되니
// 쓸데없이 클래스 끼고 상속받는것보다는 좀더 깔끔하다 하겠다.
//
// 1. killer::insert_session 에서 새로운 session 인스턴스를 만들어야
// 겠지. 이때 생성자 안에 필요한 모든 정보를 때려박으려
// 했었다. 그런데 이 정보중에는 session 인스턴스의 포인터가
// 포함되어있고, 이는 생성자가 끝나야만 유효한 값이니 결국 생성자
// 말고 또 다른 함수 session::set_finalizer 를 만들수밖에 없었다.
//
// 2. 필요한 작업은 하나인데 함수가 둘(new, set_finalizer) 로
// 쪼개졌으니 이걸 하나로 묶어야 했다. session 의 생성자를 private
// 로 감춰버리고 make 라는 스태틱 멤버를 추가해서 이놈이
// 생성자역할을 대신 하도록 했다. 물론 set_finalizer 로 받을
// 함수객체도 이놈이 받아야했다.
//
// 3. 이거 좀 억지스러운 코드지만.. session::make 에
// killer::remove_session 를 넘기기 위해 바인딩을 할때 마찬가지로
// session 의 포인터가 필요한 문제는 여전히 남아있다. 다행이 잘난
// boost::bind 가 콤포징을 지원하기 때문에 간단히 해결. _1 를 일단
// 바인딩 해서 넘기고 make 안에서 포인터를 마저 바인딩 하도록
// 했다. 당연히 이런 식의 코드는 공유코드를 만들때는 주의하도록
// 해야겠지.
//
// 4. session::make 에서는 먼저 session 을 생성하고 그 포인터에 인자로
// 받은 반쪽함수를 콤포징 해서 하나의 완전한 함수객체를 만들어
// 기억해두고 소멸될때 부르도록 했다.
//
// 중요.
//
// session 을 shared_ptr 로 들고다니지 않은 이유는 session 을 내가
// 강제적으로 delete 하는 구조라 그런것. 이런식의 구조라면
// shared_ptr 가 객체가 이미 delete 된걸 모르게 되니 당연히 죽는다.
//
// delete this 를 버리고 소멸을 shared_ptr 에 의존하는 식으로
// 수정하거나 weak_ptr 을 쓰면 해결이 가능하다.
//
// 물론.. 위에서 언급한 모든 작업들은 그다지 복잡한 작업이 아니므로
// 이렇게 boost 질을 한다는것 자체가 오버헤드. asio 는 libevent 를
// 쓰는것에 비해 월등한 장점이 있으니 잘 활용을 해야겠지만
// bind,function 등의 과도한 사용은 피하도록 하자.
#include <iostream>
#include <string>
#include <list>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/function.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
using namespace std;
namespace asio = boost::asio;
namespace posix_time = boost::posix_time;
using boost::array;
using boost::asio::ip::tcp;
using boost::bind;
using boost::function;
using boost::lexical_cast;
using boost::shared_ptr;
using boost::system::error_code;
typedef shared_ptr<tcp::socket> socket_ptr;
// 연결된 소켓을 받아서 그냥 들고만 있는다. 좀더 정확히 말하면 소켓
// 끊어지는것을 감시해야 하니 async_read_some 을 걸어두고 읽히는
// 데이타는 그냥 무시한다. 만약 소켓이 끊어지면 killer 의 특정 메서드를
// 불러야 하는데 이때 killer 와 의존성을 완전히 없애기 위해서 function
// 을 써봤다.
class session
{
public:
static session* make(socket_ptr& sock, function<void(session*)> fun)
{
session* s = new session(sock);
s->set_finalizer(bind(fun,s));
return s;
}
~session()
{
finalizer_();
}
private:
function<void()> finalizer_;
socket_ptr sock_;
array<char, 8> buf_;
session(socket_ptr& sock) : sock_(sock)
{
readmore();
}
void set_finalizer(const function<void()>& fun)
{
// 이걸 원래 생성자에 넣었어야 모양이 더 좋은데 생성자가 끝나야
// 이놈의 포인터가 떨어지기 때문에 포인터까지 포함해서 바인딩을
// 할수가 없었다. 그렇다고 function<void(session)> 타입을
// 받아서 후에 finalizer_(this)) 뭐 이딴식으로 부르는건 별
// 재미없는 코드라... 별수없이 생성자와는 별도로 함수가
// 추가되었다.
//
// 물론 생성자와 set_finalizer 는 항상 붙어다녀야 하는 놈이므로
// 생성자에 해당하는 함수 make 를 만들어주고 생성자는 private
// 으로 빼돌렸다.
finalizer_ = fun;
}
void readmore()
{
sock_->async_read_some(asio::buffer(buf_),
bind(&session::handle_read_some,
this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
void handle_read_some(const error_code& error, size_t readed)
{
if(error)
{
// 소켓이 끊어지면 delete this
cerr << "handle_read_some: " << error.message() << endl;
delete this;
return;
}
cout << "(unexpected) incoming data" << endl;
readmore();
}
};
// `host:port` 에 `interval` 간격으로 tcp 연결을 최대 `count` 개까지
// 하는놈
class killer
{
typedef list<session*> sessions;
public:
killer(asio::io_service& io_service,
const string& host,
int port,
int count,
int interval) : timer_(io_service),
target_(asio::ip::address::from_string(host), port),
count_(count),
interval_(interval)
{
set_timer();
}
private:
asio::deadline_timer timer_;
tcp::endpoint target_;
int count_;
int interval_;
sessions sessions_;
// 타이머 세팅
void set_timer()
{
timer_.expires_from_now(posix_time::milliseconds(interval_));
timer_.async_wait(bind(&killer::handle_timer, this, asio::placeholders::error));
}
// 타이머핸들러. 새 연결을 시도한다.
void handle_timer(const error_code& error)
{
if(error)
{
cerr << "handle_timer: " << error.message() << endl;
return;
}
socket_ptr sock(new tcp::socket(timer_.io_service()));
sock->async_connect(target_,
bind(&killer::handle_connect,
this,
sock,
asio::placeholders::error));
}
// 컨넥션 핸들러. 떨어진 소켓으로 새 세션을 만들고 필요하다면 다시 타이머를 세팅한다
void handle_connect(socket_ptr& sock, const error_code& error)
{
if(error)
{
cerr << "handle_connect: " << error.message() << endl;
return;
}
insert_session(sock);
if(sessions_.size() < count_)
set_timer();
}
// 소켓으로 새 세션을 만들어 기억해둔다.
void insert_session(socket_ptr& sock)
{
session* ses = session::make(sock,
bind(&killer::remove_session,
this,
_1));
sessions_.push_back(ses);
dump_session_counts();
}
// 세션을 삭제한다.
void remove_session(session* ses)
{
sessions_.remove(ses);
dump_session_counts();
}
// `현재세션`/`최대세션` 출력
void dump_session_counts()
{
cout << "sessions: " << sessions_.size() << "/" << count_ << endl;
}
};
int main(int argc, char* argv[])
{
try
{
if(argc != 5)
{
cout << "Usage: foo <host> <port> <count> <interval>" << endl;
return 1;
}
string host(argv[1]);
int port(lexical_cast<int>(argv[2]));
int count(lexical_cast<int>(argv[3]));
int interval(lexical_cast<int>(argv[4]));
asio::io_service io_service;
killer k(io_service, host, port, count, interval);
io_service.run();
}
catch(exception& e)
{
cerr << e.what() << endl;
}
}
추가.
어쩌다보니 연결 받는놈도 필요해서 만들었다. 걍 같이 적어둔다.펼쳐두기..
// tcpkiller 로 찔러볼수있는 놈. 즉 여러개의 소켓을 accept 만 해보는
// 간단한 프로그램. java 로 만들어진 서버가 컨넥션을 몇개 못받길래 OS
// 환경상의 문제가 아닌가 해서 만들어본것.
#include <iostream>
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
using namespace std;
namespace asio = boost::asio;
using boost::asio::ip::tcp;
using boost::lexical_cast;
using boost::array;
using boost::bind;
using boost::shared_ptr;
using boost::system::error_code;
typedef shared_ptr<tcp::socket> socket_ptr;
// idle 세션이 몇개나 떴는지는 다른 클래스에 일일히 보고하지 않고 그냥
// 스태틱 멤버를 둬서 생성 소멸시 카운팅 하도록 했다. asio 에 thread 를
// 하나만 썼기때문에 따로 락을 걸지는 않았다.
class idle
{
public:
idle(socket_ptr sock) : sock_(sock)
{
++instances_;
dump_idle_counts();
readmore();
}
~idle()
{
--instances_;
dump_idle_counts();
}
private:
socket_ptr sock_;
array<char, 8> buf_;
static size_t instances_;
void readmore()
{
sock_->async_read_some(asio::buffer(buf_),
bind(&idle::handle_read_some,
this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
void handle_read_some(const error_code& error, size_t readed)
{
if(error)
{
cerr << error.message() << endl;
delete this;
return;
}
readmore();
}
void dump_idle_counts()
{
cout << "idle: " << instances_ << endl;
}
};
size_t idle::instances_ = 0;
template<class T>
class async_acceptorT
{
public:
async_acceptorT(asio::io_service& io_service, const tcp::endpoint& endpoint)
: acceptor_(io_service, endpoint)
{
start_accept();
}
private:
tcp::acceptor acceptor_;
void start_accept()
{
socket_ptr childsocket(new tcp::socket(acceptor_.io_service()));
acceptor_.async_accept(*childsocket,
bind(&async_acceptorT::handle_accept,
this,
childsocket,
asio::placeholders::error));
}
void handle_accept(socket_ptr childsocket, const error_code& error)
{
if(error)
{
cerr << error.message() << endl;
return;
}
new T(childsocket);
start_accept();
}
};
int main(int argc, char* argv[])
{
try
{
int port = argc == 2 ? lexical_cast<int>(argv[1]) : 9413;
cout << "listen: " << port << endl;
asio::io_service io_service;
async_acceptorT<idle> target(io_service, tcp::endpoint(tcp::v4(), port));
io_service.run();
}
catch(exception& e)
{
cerr << e.what() << endl;
}
}
피드 구독하기:
글 (Atom)