Boost::asio範例分析 服務端

   main函數要求程序調用者傳遞3個參數:服務器IP地址,端口號和文檔根目錄.其中IP地址可以是IPv4或IPv6格式.接着創建server對象實例,將傳遞進來的IP地址,端口號,文檔根目錄作爲server對象的構造函數參數傳遞到處理程序中.最後調用server的run成員函數啓動服務端處理例程.
    http::server::server s(argv[1], argv[2], argv[3]);
  s.run();
  首先看看server類的定義和實現.爲防止server對象被拷貝複製,這個類從boost::noncopyable類私有繼承.這個類主要用來控制服務端核心代碼的運行,管理tcp鏈接請求,建立鏈接緩衝池等功能.
  Server類定義的數據成員:
1) boost::asio::io_service io_service_;這個對象很熟悉了,用來執行異步操作.
2) boost::asio::signal_set signals_;信號集合用來註冊程序結束信號,以便於退出時釋放資源.
3) boost::asio::ip::tcp::acceptor acceptor_;接收器用來偵聽到來的鏈接請求.
4) connection_manager connection_manager_;連接池用來管理所有活動的鏈接.
5) connection_ptr new_connection_;這是一個shared_ptr類型,用來存放新建的鏈接,是實現鏈接自動釋放的關鍵.
6) request_handler request_handler_;請求處理器,用來處理所有到來的請求.
  Server類的函數成員:
1) 構造函數:構造函數初始化了上述的數據成員,並向signals_信號集合中添加程序發生中斷和結束的信號,並在信號集合上設置異步等待,當集合中的信號受信後執行server::handle_stop函數,結束當前運行的鏈接對象.接着根據傳入的服務器地址參數得到端點,與接收器綁定開始偵聽客戶端的鏈接,並執行start_accept函數成員,處理到來的鏈接請求.
server::server(const std::string& address, const std::string& port,
    const std::string& doc_root)
  : io_service_(),
    signals_(io_service_),
    acceptor_(io_service_),
    connection_manager_(),
    new_connection_(),
    request_handler_(doc_root)
{
  signals_.add(SIGINT);
  signals_.add(SIGTERM);
#if defined(SIGQUIT)
  signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)
  signals_.async_wait(boost::bind(&server::handle_stop, this));

  boost::asio::ip::tcp::resolver resolver(io_service_);
  boost::asio::ip::tcp::resolver::query query(address, port);
  boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
  acceptor_.open(endpoint.protocol());
  acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
  acceptor_.bind(endpoint);
  acceptor_.listen();

  start_accept();
}
2) Server::run函數:
這個函數實現比較簡單,調用io_service_.run();函數啓動異步操作.
3) Server::start_accept函數:
這個函數首先創建一個新的鏈接對象,並使用new_connection_對象來保證自動釋放(引用計數減一).通過shared_ptr類的reset成員函數,使new_connection_釋放掉原來持有的鏈接對象,轉而管理新建的鏈接.接着啓動異步接收操作,收到異步鏈接後,執行server::handle_accept函數.注意connection類的構造函數接收一個鏈接池對象,創建好鏈接後自動將鏈接放入緩衝池進行管理.
void server::start_accept()
{
  new_connection_.reset(new connection(io_service_,
        connection_manager_, request_handler_));
  acceptor_.async_accept(new_connection_->socket(),
      boost::bind(&server::handle_accept, this,
        boost::asio::placeholders::error));
}
4) server::handle_accept函數:
這個函數啓動鏈接對象的處理流程,並調用start_accept繼續接收到來的客戶端鏈接.這個函數與start_accept函數構成了遞歸調用,異步接收到鏈接後進行處理並再次啓動異步鏈接.
void server::handle_accept(const boost::system::error_code& e)
{
  if (!acceptor_.is_open())
  {
    return;
  }
  if (!e)
  {
    connection_manager_.start(new_connection_);
  }
  start_accept();
}
5) server::handle_stop函數:
這個函數由信號集合受信後異步除法,用來停止接收器,停止當前鏈接處理例程.
void server::handle_stop()
{
  acceptor_.close();
  connection_manager_.stop_all();
}
接下來分析一下connection類.這個類從enable_shared_from_this<connection>類和noncopyable類繼承,前一個父類提供了shared_from_this()成員函數,返回持有this指針的shared_ptr對象,實現了自動內存回收.後一個基類防止對象被拷貝複製.這個類的實現與asio的echo服務端範例很相似,只增加了解析http協議包的對象成員和連接池成員.
Connection類的數據成員:
1) boost::asio::ip::tcp::socket socket_;鏈接的socket對象,用來進行與客戶端通信.
2) connection_manager& connection_manager_;連接池對象
3) request_handler& request_handler_;請求處理對象
4) boost::array<char, 8192> buffer_;接收客戶端數據的緩衝區
5) request request_;請求對象
6) request_parser request_parser_;請求解析對象
7) reply reply_;應答對象
   Connection類的函數成員:
1) 構造函數:
這裏只是初始化了上述的數據成員.
connection::connection(boost::asio::io_service& io_service,
    connection_manager& manager, request_handler& handler)
  : socket_(io_service),
    connection_manager_(manager),
    request_handler_(handler)
{
}
2) connection::socket函數:
簡單的返回鏈接的socket對象.
3) Connection::start函數:
這個函數啓動異步接收客戶端數據操作.接收到數據存入數據成員buffer_中後調用handle_read函數進行處理.注意async_read_some接收數據後馬上返回,不會判斷接收到的數據大小.如果使用全局async_read函數則必須在緩衝區被填滿後才返回.
void connection::start()
{
  socket_.async_read_some(boost::asio::buffer(buffer_),
      boost::bind(&connection::handle_read, shared_from_this(),
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred));
}
4) Connection::stop函數:
函數簡單的調用socket_close()停止socket對象.
5) Connection::handle_read函數:
這個函數處理connection對象的核心邏輯,接收到數據後,處理http請求,解析成功則根據請求組織響應信息發送給客戶端.如果解析失敗則向客戶端發送錯誤信息.
void connection::handle_read(const boost::system::error_code& e,
    std::size_t bytes_transferred)
{
  if (!e)//接收時沒有發生錯誤
  {
boost::tribool result;//三狀態bool類型
// boost::tie構造一個類似pair的對象,用來接收函數的多個返回值
//這裏解析接收到http請求,並將解析結果存入request_結構體中.
    boost::tie(result, boost::tuples::ignore) = request_parser_.parse(
        request_, buffer_.data(), buffer_.data() + bytes_transferred);
    if (result)//如果解析成功
    {
      request_handler_.handle_request(request_, reply_);//處理請求,生成響應數據
      boost::asio::async_write(socket_, reply_.to_buffers(),//發送響應數據
          boost::bind(&connection::handle_write, shared_from_this(),
            boost::asio::placeholders::error));
    }
    else if (!result)//解析失敗,則想客戶端發送錯誤信息
    {
      reply_ = reply::stock_reply(reply::bad_request);
      boost::asio::async_write(socket_, reply_.to_buffers(),
          boost::bind(&connection::handle_write, shared_from_this(),
            boost::asio::placeholders::error));
    }
    else//解析結果爲第三狀態,則繼續接收
    {
      socket_.async_read_some(boost::asio::buffer(buffer_),
          boost::bind(&connection::handle_read, shared_from_this(),
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
  }
  else if (e != boost::asio::error::operation_aborted)
  {
    connection_manager_.stop(shared_from_this());
  }
}
6) Connection::handle_write函數:
向客戶端異步發送響應數據後,調用這個函數,關閉socket鏈接,結束一次HTTP請求.並從連接池中將這個鏈接清除掉,回收這個鏈接對象的內存空間.
void connection::handle_write(const boost::system::error_code& e)
{
  if (!e)//如果發送響應數據成功
  {
boost::system::error_code ignored_ec;
// shutdown停止socket發送接收函數,但不釋放socket對象
    socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
  }
  if (e != boost::asio::error::operation_aborted)
  {
    connection_manager_.stop(shared_from_this());
  }
}
  這裏說說connection的內存管理機制,接收到客戶端鏈接請求後,使用一個shared_ptr對象持有一個新建的鏈接對象,當這個shared_ptr轉而持有其他對象時,將對此鏈接對象的引用計數減一.而connection類中的異步處理函數中傳遞的this指針都是通過share_from_this獲取的,這個傳遞的this指針也是被shared_ptr對象進行管理的,處理完畢後引用計數自動減一.當與這個鏈接相關的所有操作都執行完畢後,鏈接對象的引用計數爲0,自動釋放.由此實現了每個客戶端鏈接創建一個鏈接對象,鏈接對象處理完請求後釋放自己.
  再看連接池類的實現. connection_manager類從noncopyable繼承,防止拷貝複製.
connection_manager的數據成員:
1) std::set<connection_ptr> connections_;定義一個存放鏈接對象的集合.
connection_manager的函數成員:
1) connection_manager::start函數:
這個函數有一個connection_ptr類型的參數,函數將這個鏈接存入connections_集合,並調用這個鏈接的start函數,啓動鏈接的處理操作.
2) connection_manager::stop函數:
這個函數有一個connection_ptr類型的參數,將鏈接從connections_集合中去除,並調用鏈接的stop函數停止請求處理.
3) connection_manager::stop_all函數:
對connections_集合中的每個鏈接都調用其stop方法,並清空集合.
void connection_manager::stop_all()
{
  //for_each表示對從起始迭代器至終止迭代器的每個對象都執行第三個參數指定的函數操作,
//_1 用於替換當前迭代器執行的對象,是一個佔位符
  std::for_each(connections_.begin(), connections_.end(),
      boost::bind(&connection::stop, _1));
  connections_.clear();
}
稍後研究請求解析類,請求處理類和響應類的代碼.

下面看看請求解析器request_parser.這個類主要的作用就是解析http請求,從中提取出http請求中方法,uri,指定的http版本號,請求頭等信息.這個類的成員函數parse定義如下:

  template <typename InputIterator>
  boost::tuple<boost::tribool, InputIterator> parse(request& req,
      InputIterator begin,  InputIterator end)
  {
    while (begin != end)
    {
      boost::tribool result = consume(req, *begin++);
      if (result || !result)
        return boost::make_tuple(result, begin);
    }
    boost::tribool result = boost::indeterminate;
    return boost::make_tuple(result, begin);
  }

三個參數分別爲返回分析出來http請求信息的request結構體,和起始迭代器及截止迭代器.函數對迭代器對指定的每個字符調用consume方法,如果返回true或false則結束循環,否則繼續.consume函數分析傳入的char參數,根據商定的協議和當前狀態state_解析這個char字符,將char字符加入到request結構體的成員中或調整當前狀態state_.如果一個request成員沒有解析完則返回boost::indeterminate,指示繼續傳遞下一個字符.解析出錯和完成返回true或false.這種協議解析方式很清晰,很多地方可以借鑑.當然如果協議調整了,對應的解析代碼也需要相應變動.

request_handler類負責處理http請求.這個類從noncopyable類繼承,防止拷貝複製,構造函數接受一個string參數指定根目錄.在解析完http請求後,將得到的request結構體傳遞給這個類的handle_request成員函數,解碼request中指定的url地址(即請求文件名稱),如果解碼出錯項客戶端發送錯誤信息.如果只指定了文件的目錄,則從指定目錄中尋找index.html文件.接着使用ifstream對象打開指定的文件,將文件內容讀入到數組中,並添加到響應結構體reply的content中(str::string類型).最後生成響應結構體的headers信息,指定響應的長度和類型(文件後綴).在connection類的handle_read成員函數中獲取到響應信息後,異步向客戶端發送響應數據.

  char buf[512];
  while (is.read(buf, sizeof(buf)).gcount() > 0)//gcount返回讀取到的字節數量
    rep.content.append(buf, is.gcount());
  rep.headers.resize(2);
  rep.headers[0].name = "Content-Length";
  rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
  rep.headers[1].name = "Content-Type";
  rep.headers[1].value = mime_types::extension_to_type(extension);

這個服務端範例沒有提供多線程支持,在http的server2範例中提供了線程池支持,以後有機會繼續分析.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章