前言
最近的一些开发,需要用到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
待补充…
耕人扶耒语林丘,花外时时落一鸥。
欲验春来多少雨,野塘漫水可回舟。
– 宋代·周邦彦 《春雨》