Why does ofstream not trigger IN_CREATE - c++

I'm writing tests for an inotify library and I'm currently working on capturing the IN_CREATE event. I'm trying to use boost::filesystem::ofstream, which is essentially std::basic_ofstream, but I'm not getting the IN_CREATE event.
There are two threads in execution, the main thread, and one I create that processes events like so:
std::thread thread([&]()
{
boost::asio::io_service::work(g_io_service);
g_io_service.run();
}
Whenever an event is received, the library calls this function in the context of the above thread:
[&](event_type event, std::string const & path)
{
std::lock_guard<std::mutex> lock(ready_mtx);
events.push_back(event);
condition.notify_all();
ready = true;
}
What the test looks like (main thread):
{
boost::filesystem::ofstream file(path);
file << "test";
}
{
std::lock_guard<std::mutex> lock(ready_mtx);
while (!ready)
condition.wait(lock);
REQUIRE(events[0] == event_type::FILE_CREATE);
}
I'm expecting an event for the creation and modification of the file, and the file is in fact being created when I look for it in the terminal, but I do not receive any event.
However, when I manually create the file with echo "test" > test.file I do receive the create and modify events.
Is there a reason for this behavior? Am I approaching this the wrong way?

I created the following without my library and it works just fine. I'm obviously doing something wrong in my own code. Thanks.
#include <atomic>
#include <condition_variable>
#include <fstream>
#include <functional>
#include <iostream>
#include <iterator>
#include <string>
#include <thread>
#include <linux/limits.h>
#include <sys/inotify.h>
#include <unistd.h>
std::mutex cout_mtx;
std::string path("/tmp/test-inotify");
std::string filename("test.file");
void print(std::string const & msg)
{
std::lock_guard<std::mutex> lock(cout_mtx);
std::cout << msg << std::endl;
}
void thread_fn(int fd, std::function<void(int, std::string const & name)> callback)
{
std::string buffer;
buffer.resize(64 * (sizeof(inotify_event) + NAME_MAX + 1));
while (true)
{
int bytes_read = ::read(fd, &buffer[0], buffer.size());
if (bytes_read == -1)
{
if (errno != EAGAIN)
{
print("Fatal error to call read");
break;
}
}
int offset = 0;
inotify_event const * event = nullptr;
for (; offset < bytes_read; offset += sizeof(inotify_event) + event->len)
{
event = reinterpret_cast<inotify_event const*>(&buffer[offset]);
if (event->mask & IN_IGNORED)
{
// rewatch
int wd = ::inotify_add_watch(fd, path.c_str(), IN_CREATE);
if (wd == -1)
{
print("Unable to rewatch directory");
break;
}
}
std::string name;
std::copy(&event->name[0], &event->name[event->len], std::back_inserter(name));
int event_value = event->mask & 0xffff;
callback(event_value, name);
}
}
}
int main()
{
int fd = ::inotify_init1(IN_NONBLOCK);
if (fd == -1)
{
print("inotifiy_init1 failed");
return errno;
}
int wd = ::inotify_add_watch(fd, path.c_str(), IN_CREATE);
if (wd == -1)
{
print("inotify_add_watch failed");
return errno;
}
std::atomic<bool> ready;
std::mutex ready_mtx;
std::condition_variable condition;
int first_event = -1;
std::thread thread([&]()
{
thread_fn(fd,
[&](int event, std::string const & name)
{
std::unique_lock<std::mutex> lock(ready_mtx);
print(std::to_string(event));
if (event == IN_CREATE)
{
first_event = event;
print(name + " was created");
}
condition.notify_all();
ready = true;
});
});
{
std::ofstream file(path + "/" + filename);
}
{
std::unique_lock<std::mutex> lock(ready_mtx);
while (!ready)
condition.wait(lock);
if (first_event == IN_CREATE)
print("success");
else
print("failure");
}
thread.join();
return 0;
}

Related

How to force terminate std::thread by TerminateThread()?

I called IMFSourceReader::ReadSample and I found it was stuck if it cannot read data.
So I tried to terminate the thread by TerminateThread() but it returned 0 as a fail.
How could I terminate the stuck thread?
This is my sample code:
#include <iostream>
#include <vector>
#include <codecvt>
#include <string>
#include <thread>
#include <mutex>
#include <chrono>
#include <condition_variable>
#include <Windows.h>
using namespace std::chrono_literals;
class MyObject
{
private:
...
std::thread *t;
std::mutex m;
std::condition_variable cv;
std::thread::native_handle_type handle;
int getsample(uint8_t* data)
{
// call a ReadSample
hr = pVideoReader->ReadSample(
MF_SOURCE_READER_ANY_STREAM, // Stream index.
0, // Flags.
&streamIndex, // Receives the actual stream index.
&flags, // Receives status flags.
&llTimeStamp, // Receives the time stamp.
&pSample // Receives the sample or NULL.
);
...
return 0;
}
int myfunc_wrapper(uint8_t* data)
{
int ret = 0;
BOOL bpass = 0;
if (t == nullptr) {
t = new std::thread([this, &data, &ret]()
{
ret = this->getsample(data);
this->cv.notify_one();
});
handle = t->native_handle();
t->detach();
}
{
std::unique_lock<std::mutex> l(this->m);
if (this->cv.wait_for(l, 2500ms) == std::cv_status::timeout) {
bpass = TerminateThread(handle, 0);
if (bpass == 0) {
std::cout << "TerminateThread Fail! " << GetLastError() << std::endl;
}
throw std::runtime_error("Timeout Fail 2500 ms");
}
}
delete t;
t = nullptr;
}
public:
int my_func(uint8_t* raw_data)
{
bool timedout = false;
try {
if (myfunc_wrapper(raw_data) != 0)
return -1;
}
catch (std::runtime_error& e) {
std::cout << e.what() << std::endl;
timedout = true;
}
if (timedout)
return -1;
return 0;
}
};
int main()
{
uint8_t data[512];
MyObject* obj = new MyObject();
while (true)
{
obj->my_func(data);
}
return 0;
}
Output:
TerminateThread Fail! 6
Timeout Fail 2500 ms
TerminateThread Fail! 6
Timeout Fail 2500 ms
...
I also tried to use pthread_cancel but it cannot be compiled because there is a type error.
no suitable constructor exists to convert from "std::thread::native_handle_type" to "__ptw32_handle_t"
handle = t->native_handle();
...
pthread_cancel(handle); // no suitable constructor exists to convert
The reason it failed to terminate is that the native handle is no longer valid after detaching, one way you could do this is to OpenThread using the thread id to get a new handle.
To get the thread id, you could use its handle before detaching like this:
DWORD nativeId = GetThreadId(t->native_handle());
t->detach();
After that, just open a new handle to the thread to terminate it:
HANDLE hThread = OpenThread(THREAD_TERMINATE, FALSE, nativeId);
if (hThread)
{
BOOL result = TerminateThread(hThread, 0);
CloseHandle(hThread);
}
But you should not do this, consider other ways to signal the thread to terminate on its own.

C++: Use sideeffects of a conditional_variable test

I wonder whether I can use the side effects of a conditional_variable test?
Is it guaranteed that the conditional_variable test is returning to execution if it returns true, or can there be the situation that the test returns
true, but it is called again or times out in between?
In the below example maybeCmd_locked() de-queues a cmd, however I want to
avoid that it is called 2 times for one exit of the conditional_variable wait:
if (cv.wait_until(lk, now + 100ms, [&cmd,this]{ return ((cmd = maybeCmd_locked()) != -1); }))
//g++ test.cpp -o test.exe -lstdc++ -lpthread
#include <stdlib.h>
#include <stdio.h>
#include <thread>
#include <queue>
#include <chrono>
#include <mutex>
#include <condition_variable>
using namespace std::literals::chrono_literals;
class eventLooper {
public:
eventLooper() : threadexit(false) {};
bool threadexit;
std::queue<int> cmds;
std::mutex m;
std::condition_variable cv;;
int maybeCmd_locked()
{
if (cmds.size() > 0) {
int cmd = cmds.front();
cmds.pop();
return cmd;
}
return -1;
}
int getNextCmd(void)
{
int cmd = -1;
std::unique_lock<std::mutex> lk(m);
auto now = std::chrono::system_clock::now();
if (cv.wait_until(lk, now + 100ms, [&cmd,this]{ return ((cmd = maybeCmd_locked()) != -1); }))
{
return cmd;
}
return -1;
}
int sendCmd(int cmd)
{
std::lock_guard<std::mutex> lock(m);
cmds.push(cmd);
cv.notify_one();
return 0;
}
void run(void)
{
int cmd;
printf("run\n");
std::this_thread::sleep_for(std::chrono::milliseconds(10));
while (!threadexit)
{
cmd = getNextCmd();
if (cmd == -1) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
} else {
printf("cmd received: %d\n", cmd);
}
}
}
};
eventLooper e;
int main(int argc, char **argv)
{
(void) argc;
(void) argv;
std::thread n(&eventLooper::run, &e);
for (int i = 0; i < 10; i++)
{
std::this_thread::sleep_for(1000ms);
e.sendCmd(i);
}
std::this_thread::sleep_for(1000ms);
e.threadexit = true;
n.join();
printf("exit\n");
return 0;
}
The predicate is always checked under the lock, and another wait isn't done if the predicate returns true. In a simplifed version of your code (which doesn't have time outs) is:
if (cv.wait(lk, [&cmd,this]{ return ((cmd = maybeCmd_locked()) != -1); }))
{
return cmd;
}
cv.wait(lock, pred) is defined to be equivalent of:
while(!pred())
{
wait(lock);
}
In this case you can see that your predicate cannot be called twice if it returns true the first time.
Adding the timeout to the question doesn't change how this work. cv.wait_until(...) is the equivalent of:
while (!pred()) {
if (wait_until(lock, timeout_time) == std::cv_status::timeout) {
return pred();
}
}
Again, its clear that what you're worried about cannot happen.

How can I take a value from thread without future?

I wrote the program to count all words in .log files in the different threads and output the result on the screen. First argument in command line is dir to find all .log files and then count words in this files. Second argument in command line is number of threads (by default = 4)
I used the ThreadPool for this program
ThreadPool.h
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <boost/thread/condition_variable.hpp>
#include <boost/thread.hpp>
#include <future> // I don't how to work with boost future
#include <queue>
#include <vector>
#include <functional>
class ThreadPool
{
public:
using Task = std::function<void()>; // Our task
explicit ThreadPool(int num_threads)
{
start(num_threads);
}
~ThreadPool()
{
stop();
}
template<class T>
auto enqueue(T task)->std::future<decltype(task())>
{
// packaged_task wraps any Callable target
auto wrapper = std::make_shared<std::packaged_task<decltype(task()) ()>>(std::move(task));
{
boost::unique_lock<boost::mutex> lock{ mutex_p };
tasks_p.emplace([=] {
(*wrapper)();
});
}
event_p.notify_one();
return wrapper->get_future();
}
//void enqueue(Task task)
//{
// {
// boost::unique_lock<boost::mutex> lock { mutex_p };
// tasks_p.emplace(std::move(task));
// event_p.notify_one();
// }
//}
private:
std::vector<boost::thread> threads_p; // num of threads
std::queue<Task> tasks_p; // Tasks to make
boost::condition_variable event_p;
boost::mutex mutex_p;
bool isStop = false;
void start(int num_threads)
{
for (int i = 0; i < num_threads; ++i)
{
// Add to the end our thread
threads_p.emplace_back([=] {
while (true)
{
// Task to do
Task task;
{
boost::unique_lock<boost::mutex> lock(mutex_p);
event_p.wait(lock, [=] { return isStop || !tasks_p.empty(); });
// If we make all tasks
if (isStop && tasks_p.empty())
break;
// Take new task from queue
task = std::move(tasks_p.front());
tasks_p.pop();
}
// Execute our task
task();
}
});
}
}
void stop() noexcept
{
{
boost::unique_lock<boost::mutex> lock(mutex_p);
isStop = true;
event_p.notify_all();
}
for (auto& thread : threads_p)
{
thread.join();
}
}
};
#endif
main.cpp
#include "ThreadPool.h"
#include <iostream>
#include <iomanip>
#include <Windows.h>
#include <vector>
#include <map>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
namespace bfs = boost::filesystem;
int count_words(const std::string& filename)
{
int counter = 0;
std::ifstream file(filename);
std::string buffer;
while (file >> buffer)
{
++counter;
}
return counter;
}
int main(int argc, const char* argv[])
{
bfs::path path = argv[1];
// If this path is exist and if this is dir
if (bfs::exists(path) && bfs::is_directory(path))
{
// Number of threads. Default = 4
int n = (argc == 3 ? atoi(argv[2]) : 4);
ThreadPool pool(n);
// Container to store all filenames and number of words inside them
std::map<bfs::path, int> all_files_and_sums;
// Iterate all files in dir
for (auto& p : bfs::directory_iterator(path)) {
// Takes only .txt files
if (p.path().extension() == ".log") {
// Future for taking value from here
auto fut = pool.enqueue([&p, &all_files_and_sums]() {
// In this lambda function I count all words in file and return this value
int result = count_words(p.path().string());
std::cout << "TID " << GetCurrentThreadId() << "\n";
return result;
});
// "filename = words in this .txt file"
all_files_and_sums[p.path()] = fut.get();
}
}
int result = 0;
for (auto& k : all_files_and_sums)
{
std::cout << k.first << "- " << k.second << "\n";
result += k.second;
}
std::cout << "Result: " << result << "\n";
}
else
std::perror("Dir is not exist");
}
And this solution works correctly. But if in the directory many files this solution works so slow. I think it's because of the futures. How can I take values from different threads without futures.
(P.S)
Sorry for my english

How to use the libuv to accept the tcp connection with multi-thread?

I write a C++ dome of tcp server with the libuv. When I check the cpu performance, I found the dome is a single thread running, how can I implement it with multi-thread?
Currently, the dome can hanlde 100,000+ tcp request per second, it can only eat 1 CPU.
Code:
#include <iostream>
#include <atomic>
#include "uv.h"
#include <thread>
#include <mutex>
#include <map>
using namespace std;
auto loop = uv_default_loop();
struct sockaddr_in addr;
typedef struct {
uv_write_t req;
uv_buf_t buf;
} write_req_t;
typedef struct {
uv_stream_t* client;
uv_alloc_cb alloc_cb;
uv_read_cb read_cb;
} begin_read_req;
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
buf->base = (char*)malloc(suggested_size);
buf->len = suggested_size;
}
void free_write_req(uv_write_t *req) {
write_req_t *wr = (write_req_t*)req;
free(wr->buf.base);
free(wr);
}
void echo_write(uv_write_t *req, int status) {
if (status) {
fprintf(stderr, "Write error %s\n", uv_strerror(status));
}
free_write_req(req);
}
void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) {
if (nread > 0) {
auto req = (write_req_t*)malloc(sizeof(write_req_t));
auto *aaa = (char*)malloc(5);
aaa[0] = '+';
aaa[1] = 'O';
aaa[2] = 'K';
aaa[3] = '\r';
aaa[4] = '\n';
req->buf = uv_buf_init(aaa, 5);
uv_write((uv_write_t*)req, client, &req->buf, 1, echo_write);
}
if (nread < 0) {
if (nread != UV_EOF)
fprintf(stderr, "Read error %s\n", uv_err_name(static_cast<unsigned int>(nread)));
uv_close((uv_handle_t*)client, nullptr);
}
free(buf->base);
}
void acceptClientRead(uv_work_t *req) {
begin_read_req *data = (begin_read_req *)req->data;
uv_read_start(data->client, data->alloc_cb, data->read_cb);
}
void on_new_connection(uv_stream_t *server, int status) {
if (status < 0) {
cout << "New connection error:" << uv_strerror(status);
return;
}
uv_tcp_t *client = (uv_tcp_t *)malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, client);
uv_work_t *req = (uv_work_t *)malloc(sizeof(uv_work_t));
begin_read_req *read_req = (begin_read_req *)malloc(sizeof(begin_read_req));
read_req->client = (uv_stream_t *)client;
read_req->read_cb = echo_read;
read_req->alloc_cb = alloc_buffer;
req->data = read_req;
if (uv_accept(server, (uv_stream_t *)client) == 0) {
uv_read_start((uv_stream_t *)client, alloc_buffer, echo_read);
// uv_queue_work(workloop[0], req, acceptClientRead, nullptr);
}
else {
uv_close((uv_handle_t *)client, nullptr);
}
}
void timer_callback(uv_timer_t* handle) {
cout << std::this_thread::get_id() << "---------" << "hello" << endl;
}
int main() {
uv_tcp_t server{};
uv_tcp_init(loop, &server);
uv_ip4_addr("0.0.0.0", 8790, &addr);
uv_tcp_bind(&server, (const struct sockaddr *) &addr, 0);
uv_listen((uv_stream_t *)&server, 511, on_new_connection);
uv_run(loop, UV_RUN_DEFAULT);
return 0;
}
Of course, I can make the write step asynchronous in the method "echo_read", but I didn't do anything before the write, can I make the demo multi-thread in another way to improve the throughput?

How to use boost.Asio with MJPEG?

I want to broadcast OpenCV images (coming from a camera) to a distant computer in real time, it has to be done via Ethernet. The images are continuously received in standard OpenCV Mat objects. The final code has to be integrated into a C++ (Qt) application.
I have found this Python script that does the job very well.
Now I'm trying to get a C++ equivalent of that code, I managed to create an HTTP server using Boost Asio and the Simple-Web-Server project. I am able to display a static blue image/webcam camera image (not refreshed).
I have written a code but it's not working. My guess is that the data are sent only at the return of the function (which never returns).
How can I force sending the data after each iteration of the while loop?
#include "server_http.hpp"
#include <thread>
#include <boost/chrono.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
#include <opencv2/opencv.hpp>
//#include <opencv/cv.h>
using namespace boost::posix_time;
typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
cv::Mat image;
cv::VideoCapture cap;
int main()
{
cap.open(0);
if (!cap.isOpened ())
{
std::cerr << "Could not initialize capturing" << std::endl;
return (-1);
}
cap >> image;
HttpServer server(8080, 2);
// Image resource is requested
server.resource["^/cam.mjpg"]["GET"] =
[=](HttpServer::Response& response, std::shared_ptr<HttpServer::Request> request)
{
time_facet *facet = new time_facet("%d-%b-%Y %H:%M:%S");
std::cout.imbue(std::locale(std::cout.getloc(), facet));
std::cout << second_clock::local_time() << " | " << "Camera image requested!" << std::endl;
response <<
"HTTP/1.1 200 OK\r\n"
"Content-type: multipart/x-mixed-replace; boundary=--jpgboundary";
//TODO: Send header
while (1) // TODO: Allow exiting this
{
std::cout << "Send image" << std::endl;
cap >> image;
// Encode mat to jpg and copy it to content
std::vector<uchar> buf;
cv::imencode(".jpg", image, buf, std::vector<int>());
std::string img_content(buf.begin(), buf.end());
response << "--jpgboundary\r\n" << // Signal we start a new image
"Content-type: image/jpeg" <<
"Content-Length: " << img_content.length() << "\r\n" <<
"\r\n" << img_content << "\r\n";
std::this_thread::sleep_for(std::chrono::milliseconds(400));
}
};
// Anything else is requested
server.default_resource["GET"] = [](HttpServer::Response& response, std::shared_ptr<HttpServer::Request> request)
{
time_facet *facet = new time_facet("%d-%b-%Y %H:%M:%S");
std::cout.imbue(std::locale(std::cout.getloc(), facet));
std::cout << second_clock::local_time() << " | " << request->path << std::endl;
std::string content =
"<html><head></head><body>"
"<img src=\"cam.mjpg\"/>"
"</body></html>";
response <<
"HTTP/1.1 200 OK\r\n"
"Content-Length: " << content.length() << "\r\n"
"\r\n" << content;
};
std::thread server_thread([&server]()
{
server.start();
});
std::this_thread::sleep_for(std::chrono::seconds(1));
server_thread.join();
return 0;
}
EDIT 1
Based on Technik Empire comment I went back to boost examples;
In the HTTP server example the response in sent when the callback returns so I modified the callback to allow do_write() operations on the socket.
The HTML page is correctly displayed but the image is not displayed (the broken image icon is shown instead), I tried to see what happens with Wireshark but I don't know what is wrong.
Here is my handle_request function: (request_handler.cpp):
void request_handler::handle_request(const request& req, reply& rep, connection &con)
{
// Decode url to path.
std::string request_path;
if (!url_decode(req.uri, request_path))
{
rep = reply::stock_reply(reply::bad_request);
return;
}
// Request path must be absolute and not contain "..".
if (request_path.empty() || request_path[0] != '/'
|| request_path.find("..") != std::string::npos)
{
rep = reply::stock_reply(reply::bad_request);
return;
}
// Determine the file extension.
std::size_t last_slash_pos = request_path.find_last_of("/");
std::string filename;
if (last_slash_pos != std::string::npos)
filename = request_path.substr(last_slash_pos + 1);
if (filename == "cam.mjpg") // Image is requested
{
rep.status = reply::ok;
rep.headers.resize(1);
rep.headers[0].name = "Content-Type";
rep.headers[0].value = "multipart/x-mixed-replace; boundary=--jpgboundary";
rep.content.empty();
con.do_write();
rep.status = reply::none;
while (true) // FIXME: How do I handle disconnection from the client?
{
cv::Mat image(200, 300, CV_8UC3);
int random = rand() % 255 + 1;
image = cv::Scalar(random, 0, 0); // Fill image with blue
std::vector<uchar> buf;
cv::imencode(".jpg", image, buf, std::vector<int>());
std::string img_content(buf.begin(), buf.end());
rep.headers.clear();
rep.content.clear();
rep.content.append("--jpgboundary\r\n");
con.do_write();
rep.content.clear();
rep.headers.resize(2);
rep.headers[0].name = "Content-Type";
rep.headers[0].value = mime_types::extension_to_type("jpg");
rep.headers[1].name = "Content-length";
rep.headers[1].value = img_content.size();
rep.content.append(img_content);
rep.content.append("\r\n");
con.do_write();
boost::this_thread::sleep(boost::posix_time::milliseconds(500));
}
}
else // Anything but the image is requested
{
std::string content =
"<html><head></head><body>"
"Hello :)<br>"
"<img src=\"cam.mjpg\"/>"
"</body></html>";
rep.status = reply::ok;
rep.headers.resize(2);
rep.headers[0].name = "Content-Length";
rep.headers[0].value = content.length();
rep.headers[1].name = "Content-Type";
rep.headers[1].value = mime_types::extension_to_type("html");
rep.content.append(content);
con.do_write();
return;
}
}
I got it working by analyzing the packets thanks to the Firefox network analyzer, I replicated the Python headers/content answer and it works fine:
I added a reply::none reply type and made sure that if this reply was provided, no HTTP status is being sent. So in the reply::to_buffers() function I added this:
if (status != none) // Don't add status to buffer if status is "none"
buffers.push_back(status_strings::to_buffer(status));
The request_handler.cpp code looks like this:
if (filename == "cam.mjpg") // Image is requested
{
rep.status = reply::ok;
rep.headers.resize(1);
rep.headers[0].name = "Content-Type";
rep.headers[0].value = "multipart/x-mixed-replace; boundary=--jpgboundary\r\n";
con.do_write();
while (true) // FIXME: How do I handle disconnection from the client?
{
cv::Mat image(200, 300, CV_8UC3);
int random = rand() % 255 + 1;
image = cv::Scalar(random, 0, 0); // Fill image with blue
std::vector<uchar> buf;
cv::imencode(".jpg", image, buf, std::vector<int>());
std::string img_content(buf.begin(), buf.end());
rep.status = reply::none;
rep.headers.resize(0);
rep.content.clear();
rep.content.append("--jpgboundary");
rep.content.append("\r\n");
rep.content.append("Content-Type: image/jpeg");
rep.content.append("\r\n");
rep.content.append("Content-length: "+boost::lexical_cast<std::string>(img_content.length()));
rep.content.append("\r\n");
rep.content.append("\r\n");
rep.content.append(img_content);
rep.content.append("\r\n");
con.do_write();
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
}
}
else // Anything but the image is requested
{
std::string content =
"<html><head></head><body>"
"<img src=\"cam.mjpg\"/>"
"</body></html>";
rep.status = reply::ok;
rep.headers.resize(2);
rep.headers[0].name = "Content-Length";
rep.headers[0].value = content.length();
rep.headers[1].name = "Content-Type";
rep.headers[1].value = mime_types::extension_to_type("html");
rep.content.append(content);
con.do_write();
return;
}
Improvements suggestions are welcome in the comments. I don't know how to handle a disconnection from the client (exiting the while loop) so at the moment the server will only work with 1 request; after that it's stucked in the while loop.
Http_server.hpp
#ifndef HTTPSERVER_HPP_INCLUDED
#define HTTPSERVER_HPP_INCLUDED
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <opencv2/core/core.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
typedef boost::shared_ptr<tcp::socket> socket_ptr;
class HttpServer
{
public:
std::map<std::string, std::string> winnames;
std::map<std::string,int> requestcounts;
std::map<std::string,std::vector<unsigned char> > jpegbuffers;
short port;
HttpServer();
void run(int portno);
boost::shared_mutex mut;
boost::condition_variable_any cond;
int httpdelay;
void IMSHOW(std::string win, cv::Mat mat);
int compression;
bool is_debug;
private:
int it;
void server(int port);
void session(socket_ptr sock);
void handleinfo(socket_ptr sock);
void handlewindows(socket_ptr sock);
void handlemjpeg(socket_ptr sock,std::string winname);
void handlejpg(socket_ptr sock,std::string winname);
void handle404(socket_ptr sock);
};
#endif
Http_server.cpp
#include "http_server.hpp"
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>
#include <opencv2/opencv.hpp>
#include <boost/lexical_cast.hpp>
namespace bfs= boost::filesystem;
using namespace std;
using boost::lexical_cast;
// Helper functions
#if defined(unix) || defined(__unix) || defined(__unix__) \
|| defined(linux) || defined(__linux) || defined(__linux__) \
|| defined(sun) || defined(__sun) \
|| defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \
|| defined(__FreeBSD__) || defined __DragonFly__ \
|| defined(sgi) || defined(__sgi) \
|| defined(__MACOSX__) || defined(__APPLE__) \
|| defined(__CYGWIN__)
#define is_nix
#endif
#if defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \
|| defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
#define is_win
#endif
#ifdef is_win
#include <windows.h>
#define SLEEP(ms) Sleep(ms)
#endif
#ifdef is_nix
#define SLEEP(ms) usleep(ms*1000)
#endif
std::vector<std::string> &dssplit(const std::string &s, char delim, std::vector<std::string> &elems) {
std::stringstream ss(s);
std::string item;
while (getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}
std::vector<std::string> dssplit(const std::string &s, char delim) {
std::vector<std::string> elems;
return dssplit(s, delim, elems);
}
void removeEmptyStrings(std::vector<std::string>& strings)
{
std::vector<std::string>::iterator it = remove_if(strings.begin(), strings.end(), mem_fun_ref(&std::string::empty));
strings.erase(it, strings.end());
}
bool hasEnding(std::string const &fullString, std::string const &ending)
{
if (fullString.length() >= ending.length()) {
return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), ending));
}
else {
return false;
}
}
bool startswith(std::string const &src, std::string const &start)
{
if (src.compare(0, start.length(), start) == 0)
{
return true;
}
return false;
}
std::string urldecode(std::string &src) {
std::string ret;
char ch;
int ii;
for (size_t i = 0; i<src.length(); i++) {
if (int(src[i]) == 37) {
sscanf(src.substr(i + 1, 2).c_str(), "%x", &ii);
ch = static_cast<char>(ii);
ret += ch;
i = i + 2;
}
else {
ret += src[i];
}
}
return (ret);
}
// Server implementation
HttpServer::HttpServer() :compression(70), is_debug(true), httpdelay(100)
{
}
void HttpServer::IMSHOW(std::string win, cv::Mat mat)
{
winnames[win] = lexical_cast<string>(mat.cols) + "," + lexical_cast<string>(mat.rows);
if (is_debug)
{
cv::imshow(win, mat);
}
else
{
//cvDestroyWindow(win.c_str());
}
if (requestcounts[win] > 0)
{
cv::Mat towrite;
if (mat.type() == CV_8UC1)
{
cvtColor(mat, towrite, CV_GRAY2BGR);
}
else if (mat.type() == CV_32FC3)
{
double minVal, maxVal;
minMaxLoc(mat, &minVal, &maxVal);
mat.convertTo(towrite, CV_8U, 255.0 / (maxVal - minVal), -minVal * 255.0 / (maxVal - minVal));
}
else{
towrite = mat;
}
std::vector<uchar> buffer;
std::vector<int> param(2);
param[0] = CV_IMWRITE_JPEG_QUALITY;
param[1] = compression;
imencode(".jpg", towrite, buffer, param);
jpegbuffers[win].swap(buffer);
}
}
void HttpServer::run(int portno)
{
port=portno;
boost::thread t(boost::bind(&HttpServer::server,this,port));
}
void HttpServer::server(int port)
{
try
{
boost::asio::io_service io_service;
io_service.run();
tcp::acceptor a(io_service, tcp::endpoint(tcp::v4(), port));
for (;;)
{
socket_ptr sock(new tcp::socket(io_service));
a.accept(*sock);
boost::thread t(boost::bind(&HttpServer::session, this, sock));
}
}
catch (boost::exception & e)
{
std::cout << "OMG!" << boost::diagnostic_information(e)<<endl;
}
}
void HttpServer::session(socket_ptr sock)
{
try
{
boost::system::error_code ec;
boost::asio::streambuf sbuffer;
boost::asio::read_until(* sock, sbuffer, "\0", ec );
const char* header=boost::asio::buffer_cast<const char*>(sbuffer.data());
std::string reqStr(header,header+sbuffer.size());
sbuffer.consume(sbuffer.size());
std::vector<std::string> strs;
strs = dssplit(reqStr,' ');
if(strs.size()>1)
{
std::string requesturl = urldecode(strs[1]);
std::vector<std::string> splited=dssplit(requesturl,'/');
removeEmptyStrings(splited);
if(splited.size()==1)
{
if(startswith(splited[0],"windows"))
{
handlewindows(sock);
}else if(startswith(splited[0],"info"))
{
handleinfo(sock);
}else if(hasEnding(splited[0],".mjpg"))
{
handlemjpeg(sock,splited[0].substr(0,splited[0].size()-5));
}else if(hasEnding(splited[0],".jpg") || splited[0].find(".jpg?")!=string::npos)
{
handlejpg(sock,splited[0]);
}else
{
handle404(sock);
}
}else
{
handle404(sock);
}
sock->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
}
}catch(const std::exception& ex)
{
boost::system::error_code ec;
boost::asio::ip::tcp::endpoint endpoint = sock->remote_endpoint(ec);
if(!ec)
{
sock->shutdown(boost::asio::ip::tcp::socket::shutdown_both);
}
//DPRINTERR(ex.what());
}catch(const std::string& ex)
{
boost::system::error_code ec;
boost::asio::ip::tcp::endpoint endpoint = sock->remote_endpoint(ec);
if(!ec)
{
sock->shutdown(boost::asio::ip::tcp::socket::shutdown_both);
}
}
}
void HttpServer::handleinfo(socket_ptr sock)
{
boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);
string retstr;
for (std::map<std::string,std::string>::iterator it=winnames.begin(); it!=winnames.end(); ++it)
{
string wname =it->first;
int rcnt = 0;
if(requestcounts.find(wname)!=requestcounts.end())
{
rcnt=requestcounts[wname];
}
retstr+=boost::str(boost::format("{"
"\"name\":\"%s\","
"\"reqCnt\":%d,"
"\"size\":\"%s\""
"},"
)
%wname
%rcnt
%it->second
);
}
if(retstr.size()>0) retstr.resize(retstr.size()-1);
retstr=boost::str(boost::format("{"
"\"windows\":[%s],"
"\"version\":\"%s\","
"\"fps\":%s"
"}"
)
%retstr
%"0.0"
% to_string(0.)
);
response_stream << "HTTP/1.1 200 OK\r\n";
response_stream << "Access-Control-Allow-Origin: *\r\n";
response_stream << "Content-Type: text/plain\r\n\r\n";
response_stream << retstr << "\r\n\r\n";
boost::asio::write(*sock, sbuffer);
}
void HttpServer::handlewindows(socket_ptr sock)
{
boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);
string retstr;
for (std::map<std::string,std::string>::iterator it=winnames.begin(); it!=winnames.end(); ++it)
{
string wname =it->first;
int rcnt = 0;
if(requestcounts.find(wname)!=requestcounts.end())
{
rcnt=requestcounts[wname];
}
retstr+=boost::str(boost::format("{"
"\"name\":\"%s\","
"\"reqCnt\":%d,"
"\"size\":\"%s\""
"},"
)
%wname
%rcnt
%it->second
);
}
if(retstr.size()>0) retstr.resize(retstr.size()-1);
retstr="{\"windows\":["+retstr+"]}";
response_stream<<"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n\r\n"<<
retstr<<"\r\n\r\n";
boost::asio::write(*sock, sbuffer);
}
void HttpServer::handlemjpeg(socket_ptr sock,std::string winname)
{
if(requestcounts.find(winname)==requestcounts.end())
{
handle404(sock);
return;
}
std::string frame=winname;
//boost::shared_lock<boost::shared_mutex> lock(mut);
//lock.lock();
requestcounts[frame]++;
//lock.unlock();
boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);
response_stream<<"HTTP/1.1 200 OK\r\n";
response_stream<<"Content-Type: multipart/mixed;boundary=b\r\n";
response_stream<<"Cache-Control: no-store\r\n";
response_stream<<"Pragma: no-cache\r\n";
response_stream<<"Audio Mode : None\r\n";
response_stream<<"Connection: close\r\n";
response_stream<<"\r\n";
boost::asio::write(*sock, sbuffer);
for(;;)
{
try
{
if( (jpegbuffers.count(frame)<0 ||
jpegbuffers[frame].size()<4) ||
(jpegbuffers[frame][0]!=0xff && jpegbuffers[frame][1]!=0xd8 &&
jpegbuffers[frame][jpegbuffers[frame].size()-2]!=0xff && jpegbuffers[frame][jpegbuffers[frame]. size()-1]!=0xd9))
{
SLEEP(10);
continue;
}
//boost::shared_lock<boost::shared_mutex> lock(mut);
response_stream<<"--b\r\n";
response_stream<<"Content-Type: image/jpeg\r\n";
response_stream<<"Content-length: "<<jpegbuffers[frame].size()<<"\r\n";
response_stream<<"\r\n";
boost::asio::write(*sock, sbuffer);
boost::asio::write(*sock,boost::asio::buffer(jpegbuffers[frame], jpegbuffers[frame].size()));
//lock.unlock();
SLEEP(httpdelay);
}
catch (std::exception& e)
{
SLEEP(50);
//lock.lock();
requestcounts[frame]--;
//lock.unlock();
return;
}
}
//lock.lock();
requestcounts[frame]--;
//lock.unlock();
}
void HttpServer::handlejpg(socket_ptr sock,std::string winname)
{
if(winname.find("?")!=string::npos)
{
winname = winname.substr(0,winname.find("?"));
}
winname =winname.substr(0,winname.size()-4);
std::string frame=winname;
requestcounts[frame]++;
boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);
jpegbuffers[frame].clear();
for(;;)
{
try
{
if( (jpegbuffers.count(frame)<0 ||
jpegbuffers[frame].size()<4) ||
(jpegbuffers[frame][0]!=0xff && jpegbuffers[frame][1]!=0xd8 &&
jpegbuffers[frame][jpegbuffers[frame].size()-2]!=0xff && jpegbuffers[frame][jpegbuffers[frame]. size()-1]!=0xd9))
{
SLEEP(10);
continue;
}
response_stream<<"HTTP/1.1 200 OK\r\n";
response_stream<<"Content-Type: image/jpeg\r\n";
response_stream<<"Cache-Control: no-store\r\n";
response_stream<<"Access-Control-Allow-Origin: *\r\n";
response_stream<<"Pragma: no-cache\r\n";
response_stream<<"Content-length: "<<jpegbuffers[frame].size()<<"\r\n";
response_stream<<"Connection: close\r\n";
response_stream<<"\r\n";
boost::asio::write(*sock, sbuffer);
boost::asio::write(*sock,boost::asio::buffer(jpegbuffers[frame], jpegbuffers[frame].size()));
break;
}
catch (std::exception& e)
{
//DPRINTERR( "net exceptoin:"+std::string(e.what()));
SLEEP(50);
requestcounts[frame]--;
return;
}
}
requestcounts[frame]--;
}
void HttpServer::handle404(socket_ptr sock)
{
boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);
response_stream<<"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"Content-Length: 132\r\n\r\n"
"<html>\r\n"
"<head><title>404 Not Found</title></head>\r\n"
"<body bgcolor=\"white\">\r\n"
"<center><h1>404 Not Found</h1></center>\r\n"
"</body>\r\n"
"</html>\r\n";
boost::asio::write(*sock, sbuffer);
}
Main.cpp
#include <opencv2/opencv.hpp>
#include "http_server.hpp"
#include <iostream>
#include <fstream>
using namespace cv;
#define MJPGFILE_BUFFER_SIZE 10240
class MjpgFileCapture{
public:
static double lastframeseen;
MjpgFileCapture() {};
MjpgFileCapture(std::string filepath)
{
filepath_ = filepath;
is_inited_ = false;
skip_ = true;
imgready_ = false;
ff_ = false;
readbytes_ = -2;
i_ = 0;
};
void init();
MjpgFileCapture& operator >>(cv::Mat& out);
private:
std::string filepath_;
bool is_inited_;
std::ifstream ifstream_;
std::vector<char> data_;
bool skip_;
bool imgready_;
bool ff_;//have we seen ff byte?
long long readbytes_;
char ca_[MJPGFILE_BUFFER_SIZE];
int i_;//loop index
};
void MjpgFileCapture::init()
{
is_inited_ = true;
ifstream_ = std::ifstream(filepath_.c_str(), std::ios::binary);
}
MjpgFileCapture& MjpgFileCapture::operator >> (cv::Mat& out)
{
out = Mat();
if (!is_inited_)
{
init();
}
while (1)
{
uchar c;
if (readbytes_ != 0 && readbytes_ != -1)
{
if (i_ >= readbytes_)
{
ifstream_.read(ca_, MJPGFILE_BUFFER_SIZE);
readbytes_ = ifstream_.gcount();
i_ = 0;
}
for (; i_ < readbytes_; i_++)
{
c = ca_[i_];
if (ff_ && c == 0xd8)
{
skip_ = false;
data_.push_back((uchar)0xff);
}
if (ff_ && c == 0xd9)
{
imgready_ = true;
data_.push_back((uchar)0xd9);
skip_ = true;
}
ff_ = c == 0xff;
if (!skip_)
{
data_.push_back(c);
}
if (imgready_)
{
if (data_.size() != 0)
{
cv::Mat data_mat(data_);
cv::Mat frame(imdecode(data_mat, 1));
out = frame;
}
else
{
printf("warning:image is ready and data is empty. Likely bug.");
}
imgready_ = false;
skip_ = true;
data_.clear();
return *this;
}
}
}
else
{
//own exception class
throw std::string("zero byte read:probably end of file.");
}
}
return *this;
}
HttpServer* server = 0;
void file_loop()
{
MjpgFileCapture cap("C:/v/frame.mjpg");
while (true)
{
Mat im;
cap >> im;
server->IMSHOW("im", im);
imshow("im", im);
if (waitKey(1) == 27)
exit(0);
}
}
int main(int argc, char** argv)
{
server = new HttpServer;
//server->port = 8080;
server->run(8080);
while (true)
{
try{
file_loop();
}
catch (...)
{
}
}
return 0;
}
Usage
server->IMSHOW("im",mat);//register mat as window name im in server, accessible from browser as http://localhost:8080/im.jpg or http://localhost:8080/im.mjpg
http://localhost:8080/windows (show all registered windows)