前言
最近的一些開發,需要用到http服務,大致是兩種,一種是我們算法端起http服務,等到後端發送消息給算法,然後算法去解析消息,得到我們要的圖像數據;第二種是,我們算法端處理完圖像之後,需要將結果發送給後端,那麼如果是後端發送給我們的,其實可以將處理完的結果返回即可;但也有一種情況是,需要我們算法去給後端發消息的。
所以這其中就涉及兩種情形:第一種是算法起服務,第二種是算法發送http請求。
如果算法使用的是Python,那麼起算法服務可以使用Flask這個庫,如果是算法要發送http請求,那麼可以使用requests這個庫,也可以很方便的實現。但是如果算法是C++來寫的,那麼就可以考慮將算法封裝成庫,然後給後端調用,如果後端是python,那麼可以將C++編寫的算法封裝成so或者dll庫,然後python通過ctypes導入C++的動態庫來調用,Java則是通過JNI調C++,C#也是通過調C++的動態庫實現調C++的。但是,也可以類似Python一樣,起算法服務,來接收http請求。這個時候就需要C++來寫web服務了。這方面,其實C++也有很多web庫的,但是可能用起來就沒有python的庫那麼容易了。
一、crow
crow是一個github上找到的開源的C++web框架,這個框架是受python下的Flask啓發的,github上的介紹是:
Crow is C++ microframework for web. (inspired by Python Flask)
可見其估計功能和用法應該是和Python的Flask應該是類似的哈。該工程的結構其實也挺簡單的,核心的代碼都寫在include中的頭文件中,因此下載下來也不需要編譯成庫,直接include頭文件即可使用,當然後期我覺得還是可以將代碼的聲明和定義分離,封裝成庫,方便調用,這樣每次編譯新的應用的時候都需要重新編譯還是比較繁瑣的。
接下來就根據crow提供的例子來寫一下,這個框架是怎麼使用的。首先是要起服務,然後去監聽某個端口的消息。用crow實現如下:
#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include "crow.h" //crow的外部頭文件
#include "base64.h" //用來編碼數據的base64的庫
int main()
{
crow::SimpleApp app;
app.port(8888).multithreaded().run();
return 0;
}
這樣我的web服務就起來了,但是也看到這樣是沒有消息處理的,只是監聽消息,而不對任何消息做處理,那麼後端給發消息的時候就會出錯。接下來就記錄下個各種消息的處理。
1.1 無參數消息
這種常見的是應該是一些我們不需要發送什麼參數,只是需要從服務端獲取一些信息的情況,比如服務的證據狀態等。這種多數時候是一個默認的服務,常見get一個ip加端口,或者有不同的信息就給不同的字段。比如我要其一個服務,然後後端需要監控我的服務是否還健在,那麼我就可以給一個默認服務或者給一個health的服務,當後端get這個地址,如果我的服務還在,就給後端返回ok等信息。實現代碼也比較簡單,如下:
int main()
{
crow::SimpleApp app;
CROW_ROUTE(app, "/")([](){
return "Hello world!";
});
CROW_ROUTE(app, "/health")([](){
return "OK";
});
app.port(8888).multithreaded().run();
return 0;
}
Python端的發送消息的代碼如下:
import requests
def send_health():
url_get = "http://localhost:8888/"
r = requests.get(url_get)
print(r.text)
if __name__ == '__main__':
send_health()
這樣如果C++服務端收到消息,就會返回對應的信息給Python端。
1.2 返回json格式消息
有時候,我們需要返回的消息會比較多,那麼我們可能會指定那個字段上會有什麼消息,這樣得到返回的消息後,就會去解析消息,取到所需要的字段上的信息即可,不是需要的字段則可以不去管它。C++服務端的實現代碼如下:
CROW_ROUTE(app, "/json")
([]{
crow::json::wvalue x;
x["msg"] = "Hello, World!";
return x;
});
Python端的發送消息的代碼如下:
def send_get_json():
url_get = "http://localhost:8888/json"
r = requests.get(url_get)
print(r.text)
str_json = json.loads(r.text)
print(str_json["msg"])
1.3 接收json格式消息
這種情況與1.2的類似,只不過這一次反過來,我們服務端可能會收到很多參數,但是我們只解析我們需要的字段的消息即可,所以就要接收json格式的數據,同樣C++端實現如下:
CROW_ROUTE(app, "/add_json")
.methods("POST"_method)
([](const crow::request& req){
auto x = crow::json::load(req.body);
if (!x)
return crow::response(400);
int sum = x["a"].i()+x["b"].i();
std::ostringstream os;
os << sum;
return crow::response{os.str()};
});
這裏可以看到,返回的消息應該是要設置爲string或者crow支持的json等格式,Python端的發送消息的代碼如下:
def send_num():
url_get = "http://localhost:8888/add_json"
data = {"a":1, "b":2}
r = requests.post(url_get, data=json.dumps(data))
print(r.text)
1.4 一個綜合的例子
在這個例子裏:我將用Python讀取一副圖像,然後將圖像數據通過base64編碼,再把編碼後的數據通過json格式數據發送給C++端,C++端接收到消息之後,會對base64數據進行解碼得到圖像數據,然後對圖像進行一些處理,將處理後的圖像通過base64進行編碼,並將編碼的結果返回去,Python端將返回的base64數據進行解碼得到處理過後的圖像。
C++端會起算法服務,並且需要對base64數據進行編解碼,然後還需要做一些圖像處理的操作:
CROW_ROUTE(app, "/img")
.methods("POST"_method)
([](const crow::request& req){
auto x = crow::json::load(req.body);
if (!x)
{
return crow::response(400);
}
std::string dst_code = x["img"].s();
std::string dec_jpg = urlsafe_base64_decode(dst_code);
std::vector<uchar> data(dec_jpg.begin(), dec_jpg.end());
cv::Mat img = cv::imdecode(cv::Mat(data), 1);
cv::Mat img_ = 255 - img;
std::vector<uchar> buf;
cv::imencode(".jpg", img_, buf);
auto *enc_msg = reinterpret_cast<unsigned char*>(buf.data());
std::string encoded = base64_encode(enc_msg, buf.size());
return crow::response{encoded};
});
Python需要讀取圖像,並把圖像進行base64的編碼,然後發送,對接受到的數據進行解碼,並顯示圖像,Python端的發送消息的代碼如下:
def image_to_base64(image_np):
image = cv2.imencode('.jpg',image_np)[1]
image_code = str(base64.urlsafe_b64encode(image))[2:-1]
# image_code = str(base64.b64encode(image))[2:-1]
return image_code
def base64_to_image(base64_code):
# base64解碼
# img_data = base64.b64decode(base64_code)
img_data = base64.urlsafe_b64decode(base64_code)
# 轉換爲np數組
img_array = np.fromstring(img_data, np.uint8)
# 轉換成opencv可用格式
img = cv2.imdecode(img_array, cv2.COLOR_RGB2BGR)
def send_img():
url_get = "http://localhost:8888/img"
mat = cv2.imread("./cat.jpg")
data_base64 = image_to_base64(mat)
data = {"img": data_base64}
r = requests.post(url_get, data=json.dumps(data))
# print(r.text)
img = base64_to_image(r.text)
cv2.imshow("img", img)
cv2.waitKey(0)
以上是我目前需要用到的功能,後期如果需要用到更多的功能,還會繼續開發這個庫,不過目前基本滿足了我的需要了。
cpp-httplib
待補充…
耕人扶耒語林丘,花外時時落一鷗。
欲驗春來多少雨,野塘漫水可回舟。
– 宋代·周邦彥 《春雨》