架構
基于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ù)給管理平面互拾。接收對應的應答結果歪今。