虛擬網(wǎng)關

架構

基于boost::asio異步開源組件距误,實現(xiàn)了一個線程池世落。
異步服務器代碼架構可參考boost源碼里的樣例async_tcp_echo_server.cpp的實現(xiàn),如下鸯屿。

#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class session
  : public std::enable_shared_from_this<session>
{
public:
  session(tcp::socket socket)
    : socket_(std::move(socket))
  {
  }

  void start()
  {
    do_read();
  }

private:
  void do_read()
  {
    auto self(shared_from_this());
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
        [this, self](boost::system::error_code ec, std::size_t length)
        {
          if (!ec)
          {
            do_write(length);
          }
        });
  }

  void do_write(std::size_t length)
  {
    auto self(shared_from_this());
    boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
        [this, self](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec)
          {
            do_read();
          }
        });
  }

  tcp::socket socket_;
  enum { max_length = 1024 };
  char data_[max_length];
};

class server
{
public:
  server(boost::asio::io_context& io_context, short port)
    : acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
  {
    do_accept();
  }

private:
  void do_accept()
  {
    acceptor_.async_accept(
        [this](boost::system::error_code ec, tcp::socket socket)
        {
          if (!ec)
          {
            std::make_shared<session>(std::move(socket))->start();
          }

          do_accept();
        });
  }

  tcp::acceptor acceptor_;
};

int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: async_tcp_echo_server <port>\n";
      return 1;
    }

    boost::asio::io_context io_context;

    server s(io_context, std::atoi(argv[1]));

    io_context.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

線程池的實現(xiàn)

參考源碼樣例io_context_pool.cpp盏档,路徑libs\asio\example\cpp03\http\server2凶掰。
io_context_pool構造時傳入線程池大小pool_size,創(chuàng)建出pool_size個asio::io_context對象蜈亩。在run()接口中完成pool_size個thread的創(chuàng)建懦窘,并完成所有線程的join()。提供一個get_io_context()接口勺拣,獲取一個可以使用的asio::io_context對象奶赠。

整個過程

1、main函數(shù)中添加了一個額外的參數(shù)指定線程池大小药有,并在server類中聲明了一個線程池類成員io_service_pool io_service_pool_。線程池類的構造函數(shù)接收一個整型參數(shù)io_service_pool_size指定線程池大小。
2愤惰、在server類中創(chuàng)建connection實例時需要從線程池中獲取asio::io_context對象苇经,這時使用線程池類的io_service_pool_.get_io_context()獲取asio::io_context對象。
3宦言、根據(jù)asio的約定扇单,異步操作由哪個線程執(zhí)行與其相關io_context對象有關。調用io_service::run()的線程才能執(zhí)行相關異步操作奠旺。因此要想實現(xiàn)線程池蜘澜,只需要在線程池對象中創(chuàng)建多個io_service對象,并創(chuàng)建同樣多的線程對象响疚,在每個線程中都調用一個io_service對象的run()方法鄙信,這樣通過在線程池中均勻的獲取io_context對象,就可以實現(xiàn)將異步操作均勻的分配給多個線程來執(zhí)行了忿晕。


先看看server類創(chuàng)建connection的代碼:

void server::start_accept()
{
      new_connection_.reset(new connection(io_service_pool_.get_io_service(), request_handler_));
          acceptor_.async_accept(new_connection_->socket(),
          boost::bind(&server::handle_accept, this, boost::asio::placeholders::error));
}

connection構造函數(shù)需要一個io_service對象,這里是從線程池中獲取的,同時也就指定了這個connection類中的異步操作都由線程池中相應的線程來處理.
下面重點分析一下線程池類的定義和實現(xiàn).io_service_pool從noncopyable繼承,不能進行拷貝復制.
io_service_pool定義的數(shù)據(jù)成員:

    std::vector<io_service_ptr> io_services_;//存放io_service對象的容器.
    std::vector<work_ptr> work_;             //存放工作者對象的容器
    std::size_t next_io_service_;            //指定下一個將被分配的io_service
io_service_pool定義的函數(shù)成員:
1装诡、顯式構造函數(shù):

初始化next_io_service_為0,創(chuàng)建指定線程池大小個數(shù)的io_service對象和工作者對象,并分別存放在io_service_容器和work_容器中。

  for (std::size_t i = 0; i < pool_size; ++i)//創(chuàng)建多個io_service和多個工作者對象
  {
    io_service_ptr io_service(new boost::asio::io_service);
    work_ptr work(new boost::asio::io_service::work(*io_service));
    io_services_.push_back(io_service);
    work_.push_back(work);
  }
2践盼、run函數(shù):

根據(jù)指定線程池大小創(chuàng)建多個線程,這些線程的執(zhí)行函數(shù)為相應io_service對象的run方法.并讓主線程阻塞等待所有線程執(zhí)行完畢鸦采。

  for (std::size_t i = 0; i < io_services_.size(); ++i)
  {
    boost::shared_ptr<boost::thread> thread(new boost::thread(
          boost::bind(&boost::asio::io_service::run, io_services_[i])));
    threads.push_back(thread);
  }
  //主線程一直阻塞等待 直到所有線程結束
  for (std::size_t i = 0; i < threads.size(); ++i)
    threads[i]->join();
3、stop函數(shù):

調用所有io_service對象的stop方法,停止處理異步通信.

4咕幻、get_io_service函數(shù):

這個函數(shù)根據(jù)next_io_service_數(shù)據(jù)成員獲取在容器中一個io_service對象,并將next_io_service_加1,如果達到容器的最大個數(shù),則置為0.實現(xiàn)循環(huán)獲取io_service對象的目的渔伯。

boost::asio::io_service& io_service_pool::get_io_service()
{
  //循環(huán)獲取io_service對象 異步操作由創(chuàng)建io_service的線程執(zhí)行,從而實現(xiàn)為線程池中的線程均勻分配任務
  boost::asio::io_service& io_service = *io_services_[next_io_service_];
  ++next_io_service_;
  if (next_io_service_ == io_services_.size())
    next_io_service_ = 0;
  return io_service;
}

最后注意存放在容器中的線程,io_service對象,工作者對象,線程對象都是使用shared_ptr指針保護的,保證了這些對象的自動釋放.
這里創(chuàng)建了多個io_service對象,也可以只創(chuàng)建一個io_service對象,多個線程都執(zhí)行io_service::run()函數(shù),則異步操作可在這里線程中進行隨機分配.請看server3范例。

openssl與gmssl共存——隱藏符號表

openssl與gmssl需要共存肄程,但他們兩者對外拋出的接口基本上一樣咱旱,這樣會到值接口沖突。
解決方法:
將gmssl編譯成靜態(tài)庫绷耍,再封裝一層gmsslwarp的動態(tài)庫對外使用吐限,其中gmsslwarp拋出的接口增加gm前綴,以避免和openssl中的接口沖突褂始。那么其中gmssl靜態(tài)庫诸典、封裝的gmsslwarp動態(tài)庫在編譯時需要加上一個gcc編譯參數(shù)-fvisibility=hidden,以隱藏大部分未使用的接口崎苗。而同時在gmsslwarp動態(tài)庫的實現(xiàn)的函數(shù)頭文件前需要加上:__attribute__((visibility("default")))狐粱,以保證對外可見。

fvisibility=hidden說明

-fvisibility=[default|internal|hidden|protected]
fvisibility=hidden將函數(shù)名隱藏胆数,需要暴露給用戶的函數(shù)接口可以單獨通過 __attribute__((visibility ("default")))聲明避免被隱藏肌蜻。

__attribute__((visibility ("default"))) int func1()
{
    return 1;
}

斷連問題定位

現(xiàn)象

網(wǎng)關出現(xiàn)斷連,日志中出現(xiàn)打開文件失敗必尼。

分析過程

1蒋搜、復現(xiàn)
初步分析原因為文件句柄數(shù)使用超過了篡撵,可能存在某處文件描述符未關閉的資源泄露,使用ulimit –a/c豆挽,查看和修改文件描述符最大限制育谬。測試復現(xiàn),發(fā)現(xiàn)多次連接斷開后帮哈,總有幾個SOCK未關閉膛檀。通過ps –eaf | grep -i vncgate,查的進程娘侍。cd /proc/pid/fd獲取進程打開的文件描述符信息咖刃。ls –l | wc –l 該命令實時多執(zhí)行幾次,實時查看當前進程正打開的文件描述符個數(shù)憾筏。


通過lsof命令查詢文件描述符命令:lsof -p pid
其中NODE一列表示了文件描述的inode號
計算個數(shù):lsof -p pid | wc -l
2嚎杨、代碼跟蹤
發(fā)現(xiàn)是sock句柄未關,則在代碼中將所有相關連的sock句柄創(chuàng)建和銷毀的地方增加日志打印踩叭,再運行服務磕潮,復現(xiàn)問題。打印信息包括:上下行容贝、文件描述符自脯、狀態(tài)、inode號斤富。最關鍵是的inode號膏潮,識別唯一的資源。使用如下接口從文件描述符獲取對于的inode號满力。Linux的文件描述符FD與Inode

int getinode(int fd)
{
    struct stat fileStat;
    if (fstat(fd, &fileStat) < 0) {
        return -1;
    }
    return fileStat.st_ino;
}

3焕参、復現(xiàn)問題,分析日志
分析日志發(fā)現(xiàn)油额,確實有幾次創(chuàng)建的sock句柄叠纷,并未走入關閉流程。
4潦嘶、解決
分析代碼涩嚣,增加一個超時關閉機制。
5掂僵、卡死根本原因
vag關閉鏈接時只調用了shutdown航厚,該接口是同步阻塞接口,此時需要客戶端回饋一個消息(close_notify)锰蓬,而客戶端代碼里直接調用底層close方法關閉了ssl連接幔睬,未在之前調用shutdown方法,故close_notify消息一直不會被vag收到芹扭,故那個sock文件描述符一直不會被正常關閉麻顶。
解決辦法在vag處增加一個超時關閉機制赦抖,即直接調用底層close方法關閉。防止因為網(wǎng)絡原因澈蚌,客戶端的close_notify消息收不到的情況摹芙。

shutdown close區(qū)別

shutdown()函數(shù)可以選擇關閉全雙工連接的讀通道或者寫通道灼狰,如果兩個通道同時關閉宛瞄,則這個連接不能再繼續(xù)通信。
close()函數(shù)會同時關閉全雙工連接的讀寫通道交胚,除了關閉連接外份汗,還會釋放套接字占用的文件描述符。而shutdown()只會關閉連接蝴簇,但是不會釋放占用的文件描述符杯活。

GET/POST

采用curl實現(xiàn)了一套以REST為標準的GET/POST方法。
post:上報心跳熬词、網(wǎng)關內存旁钧、CPU使用情況,連接個數(shù)等數(shù)據(jù)給管理平面互拾。接收對應的應答結果歪今。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颜矿,隨后出現(xiàn)的幾起案子寄猩,更是在濱河造成了極大的恐慌,老刑警劉巖骑疆,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件田篇,死亡現(xiàn)場離奇詭異,居然都是意外死亡箍铭,警方通過查閱死者的電腦和手機泊柬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诈火,“玉大人兽赁,你說我怎么就攤上這事”澹” “怎么了闸氮?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長教沾。 經(jīng)常有香客問我蒲跨,道長,這世上最難降的妖魔是什么授翻? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任或悲,我火速辦了婚禮孙咪,結果婚禮上,老公的妹妹穿的比我還像新娘巡语。我一直安慰自己翎蹈,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布男公。 她就那樣靜靜地躺著荤堪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枢赔。 梳的紋絲不亂的頭發(fā)上澄阳,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音踏拜,去河邊找鬼碎赢。 笑死,一個胖子當著我的面吹牛速梗,可吹牛的內容都是我干的肮塞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼姻锁,長吁一口氣:“原來是場噩夢啊……” “哼枕赵!你這毒婦竟也來了?” 一聲冷哼從身側響起屋摔,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烁设,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后钓试,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體装黑,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年弓熏,在試婚紗的時候發(fā)現(xiàn)自己被綠了恋谭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡挽鞠,死狀恐怖疚颊,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情信认,我是刑警寧澤材义,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站嫁赏,受9級特大地震影響其掂,放射性物質發(fā)生泄漏。R本人自食惡果不足惜潦蝇,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一款熬、第九天 我趴在偏房一處隱蔽的房頂上張望深寥。 院中可真熱鬧,春花似錦贤牛、人聲如沸惋鹅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闰集。三九已至,卻和暖如春喂链,著一層夾襖步出監(jiān)牢的瞬間返十,已是汗流浹背妥泉。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工椭微, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盲链。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓蝇率,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刽沾。 傳聞我的和親對象是個殘疾皇子本慕,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容