2011年2月15日 星期二

聊天室 - based Command Tool

需求:

client:    傳送一個 message   ("filename","varname")
server:   將 filename 檔案內的 varname 的數值減一,減到零時回傳 YES, 否則回傳 NO

eq.  hello.txt
==================
fab = 100
Oxford = 99
==================

client 傳送 ("hello.txt","Oxford"),server便會將Oxford的值減一 => 98。

這樣的程式,可以用網路 http REST 來做,或是包成網路 API Service 來做(這我不會)
但這種簡單的需求,用 socket 就綽綽有餘了。

寫 socket 程式就需要自己定義 protocol,貪圖方便,我就拿了 boost/asio 的聊天室範例程式來修改 (chat):
http://www.boost.org/doc/libs/1_45_0/doc/html/boost_asio/examples.html
以下是其 protocol: |size|string|

class chat_message
{
public:
  enum { header_length = 4 };
  enum { max_body_length = 512 };

  chat_message()
    : body_length_(0)
  {
  }

  const char* data() const
  {
    return data_;
  }

  char* data()
  {
    return data_;
  }

  size_t length() const
  {
    return header_length + body_length_;
  }

  const char* body() const
  {
    return data_ + header_length;
  }

  char* body()
  {
    return data_ + header_length;
  }

  size_t body_length() const
  {
    return body_length_;
  }

  void body_length(size_t length)
  {
    body_length_ = length;
    if (body_length_ > max_body_length)
      body_length_ = max_body_length;
  }

  bool decode_header()
  {
    using namespace std; // For strncat and atoi.
    char header[header_length + 1] = "";
    strncat(header, data_, header_length);
    body_length_ = atoi(header);
    if (body_length_ > max_body_length)
    {
      body_length_ = 0;
      return false;
    }
    return true;
  }

  void encode_header()
  {
    using namespace std; // For sprintf and memcpy.
    char header[header_length + 1] = "";
    sprintf(header, "%4d", body_length_);
    memcpy(data_, header, header_length);
  }

private:
  char data_[header_length + max_body_length];
  size_t body_length_;
};

既然,protocol 都用此範例了,client & server 當然也用這個範例修改了。

比較麻煩的是,這個範例程式 client & server 都是非同步的方式,非同步在 server 是沒甚麼問題,但 client 使用非同步會很麻煩。需要再 while 迴圈前 Sleep 一秒左右,讓 ioservice 建立連線。
Note:
因為實際使用時,是把 while 中的使用者輸入 cin.getline 部分改成,用參數傳遞的 string,while 迴圈也要拿掉,因此若是非同步的方式,程式馬上就到 c.close(),修改這段程式花了我很大的時間除錯 = ="

boost::asio::io_service io_service;

    tcp::resolver resolver(io_service);
    tcp::resolver::query query(argv[1], argv[2]);
    tcp::resolver::iterator iterator = resolver.resolve(query);

    chat_client c(io_service, iterator);

    boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));

    char line[chat_message::max_body_length + 1];
    while (std::cin.getline(line, chat_message::max_body_length + 1))
    {
      using namespace std; // For strlen and memcpy.
      chat_message msg;
      msg.body_length(strlen(line));
      memcpy(msg.body(), line, msg.body_length());
      msg.encode_header();
      c.write(msg);
    }

    c.close();
    t.join();

因此我將 client 程式改成同步的方式來執行 (舒服)

boost::asio::io_service io_service;

        tcp::resolver resolver(io_service);
        tcp::resolver::query query(argv[1], argv[2]);
        tcp::resolver::iterator iterator = resolver.resolve(query);
        tcp::resolver::iterator end;

        tcp::socket s(io_service);
        boost::system::error_code error = boost::asio::error::host_not_found;
        while (error && iterator != end)
        {
            s.close();
            s.connect(*iterator++, error);
        }
        if (error)
            throw boost::system::system_error(error);

        char line[chat_message::max_body_length + 1];
        strcpy(line,"hello.txt Seg ");
        chat_message write_msgs_;
        write_msgs_.body_length(strlen(line));
        memcpy(write_msgs_.body(), line, write_msgs_.body_length());
        write_msgs_.encode_header();

        boost::asio::write(s, 
            boost::asio::buffer(write_msgs_.data(), write_msgs_.length()));

        chat_message read_msg_;
        //boost::asio::read(sread_msg_.body(), read_msg_.body_length()),
        
        size_t reply_length_header = boost::asio::read(s,
            boost::asio::buffer(read_msg_.data(), chat_message::header_length));
        std::cout << "reply_length_header = " << reply_length_header << std::endl;

        read_msg_.decode_header();

        size_t reply_length_body = boost::asio::read(s,
            boost::asio::buffer(read_msg_.body(), read_msg_.body_length()));

        std::cout << read_msg_.body() << std::endl;

        s.close();

等待時間的問題解決了,接下來要包成 library。

包成 static library 還有些 issue

1. boost library 的 BOOST_ALL_NO_LIB 問題:
我包成 foo.lib 給 test.exe 用但在編譯的時候 linker 竟然還會有問題
LINK : fatal error LNK1104: cannot open file 'libboost_thread-vc100-mt-1_45.lib'
參考下文:
http://stackoverflow.com/questions/4736877/how-to-link-boost-in-a-dependant-static-library

2. VS2010 的 Static Library 無法給 VC6 用


於是我就包成 DLL 的版本
修改下面的 code 包成的 (用 vs2010 建立的 dll wizard 我包失敗 = =)
http://www.codeguru.com/cpp/cpp/cpp_mfc/tutorials/article.php/c9855

於是我的情人節就給了這支程式了! e04!!

1 則留言:

  1. 補充幾篇參考的文章
    將 Lib 打包:
    http://stackoverflow.com/questions/2157629/linking-static-libraries-to-other-static-libraries

    Linking Error:
    http://dev.firnow.com/course/4_webprogram/asp.net/netjs/20090412/164875.html

    回覆刪除