nesting function calls in c++ thread - c++

I've got a threading problem that I've been trying to find an answer to on SO and Google, but with no luck.
Simply put I would want to create a thread that can execute a function that is using another function as a parameter (with its own arguments - see below).
I've followed these guides (https://www.justsoftwaresolutions.co.uk/threading/multithreading-in-c++0x-part-2-function-objects-and-arguments.html) on threading that work absolutely flawlessly, except for the above mentioned use case.
This is the specific line where I attempt to create the thread:
std::thread t(executeQuery, buildQuery(*_mVecHistIter, symbol));
And these are the nested functions definitions:
std::wstring database_con::buildQuery(vector<std::wstring> input, string symbol)
void database_con::executeQuery(wstring query)
And the error:
no instance of constructor "std::thread::thread" matches the argument
list
I could simply rebuild the functions so that buildQuery starts executeQuery. But I would want to separate the calls and ensure that as much as possible is done from the class "body" for readability and maintainability down the line.
Any kind of advice, pointers or ideas of where I can find more material on the subject would be appreciated! :)
I'm using MSVC 2015.
Edit:
#include "stdafx.h"
#include "database_con.h"
////////////////////////////////////////////////////////////////////////
// Show errors from the SQLHANDLE
void database_con::show_error(unsigned int handletype, const SQLHANDLE& handle)
{
SQLWCHAR sqlstate[1024];
SQLWCHAR message[1024];
if (SQL_SUCCESS == SQLGetDiagRec(handletype, handle, 1, sqlstate, NULL, message, 1024, NULL))
wcout << "Message: " << message << "\nSQLSTATE: " << sqlstate << endl;
}
std::wstring database_con::StringToWString(const std::string& s)
{
std::wstring temp(s.length(), L' ');
std::copy(s.begin(), s.end(), temp.begin());
return temp;
}
////////////////////////////////////////////////////////////////////////
// Builds the stored procedure query.
std::wstring database_con::buildQuery(vector<std::wstring> input, string symbol)
{
std::wstringstream builder;
builder << L"EXEC sp_addHistorical " << "#Symbol='" << L"" << StringToWString(symbol) << "'," <<
"#Date='" << (wstring)L"" << input.at(0) << "'," <<
"#Open=" << (wstring)L"" << input.at(1) << "," <<
"#Close=" << (wstring)L"" << input.at(2) << "," <<
"#MaxPrice=" << (wstring)L"" << input.at(3) << "," <<
"#MinPrice=" << (wstring)L"" << input.at(4) << "," <<
"#Volume=" << (wstring)L"" << input.at(5) << ";";
return builder.str();
}
void database_con::executeQuery(wstring query) {
if (SQL_SUCCESS != SQLExecDirectW(stmt, const_cast<SQLWCHAR*>(query.c_str()), SQL_NTS)) {
std::cout << "Execute error " << std::endl;
show_error(SQL_HANDLE_STMT, stmt);
std::wcout << L"Unsuccessful Query: " << query << std::endl;
}
// Close Cursor before next iteration starts:
SQLRETURN closeCursRet = SQLFreeStmt(stmt, SQL_CLOSE);
if (!SQL_SUCCEEDED(closeCursRet))
{
show_error(SQL_HANDLE_STMT, stmt);
// maybe add some handling for the case that closing failed.
}
}
////////////////////////////////////////////////////////////////////////
// Constructs a database connector object with the historical data and its symbol
database_con::database_con(std::vector<std::vector<std::wstring>> historical, string symbol){
/*
Set up the handlers
*/
/* Allocate an environment handle */
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
/* We want ODBC 3 support */
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
/* Allocate a connection handle */
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
/* Connect to the DSN */
SQLDriverConnectW(dbc, NULL, L"DRIVER={SQL Server};SERVER=ERA-PC-STUART\\JBK_DB;DATABASE=master;UID=geo;PWD=kalle123;", SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
/* Check for success */
if (SQL_SUCCESS != SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt))
{
show_error(SQL_HANDLE_DBC, dbc);
std::cout << "Failed to connect";
}
std::cout << "Building and executing the query" << std::endl;
for (_mVecHistIter = historical.begin();
_mVecHistIter != historical.end();
_mVecHistIter+5) {
std::thread t(&database_con::executeQuery, *this, buildQuery(*_mVecHistIter, symbol));
std::thread t2(&database_con::executeQuery, *this, buildQuery(*_mVecHistIter, symbol));
std::thread t3(&database_con::executeQuery, *this, buildQuery(*_mVecHistIter, symbol));
std::thread t4(&database_con::executeQuery, *this, buildQuery(*_mVecHistIter, symbol));
std::thread t5(&database_con::executeQuery, *this, buildQuery(*_mVecHistIter, symbol));
t.join();
t2.join();
t3.join();
t4.join();
t5.join();
//executeQuery(buildQuery(*_mVecHistIter, symbol));
}
/*_mSymbol = symbol;
std::wstringstream stream(StringToWString(historical));
std::wstring line;
int row = 0;
while (std::getline(stream, line)) {
if (row > 0) {
vector<wstring> vHistorical = parseData(L"" + line, ',');
std::wstring SQL = buildQuery(vHistorical, _mSymbol);
if (SQL_SUCCESS != SQLExecDirectW(stmt, const_cast<SQLWCHAR*>(SQL.c_str()), SQL_NTS)) {
std::cout << "Execute error " << std::endl;
show_error(SQL_HANDLE_STMT, stmt);
std::wcout << L"Unsuccessful Query: " << SQL << std::endl;
}
// Close Cursor before next iteration starts:
SQLRETURN closeCursRet = SQLFreeStmt(stmt, SQL_CLOSE);
if (!SQL_SUCCEEDED(closeCursRet))
{
show_error(SQL_HANDLE_STMT, stmt);
// maybe add some handling for the case that closing failed.
}
}
row++;
}*/
std::cout << "Query " << _mSymbol << " ready" << std::endl;
}
database_con::~database_con() {
std::cout << "The database object has been deleted" << std::endl;
}

OK, I probably misunderstood the question. You don't want create a thread using a function, having one of its arguments another function. But rather create a thread using a function and having its parameter computed by another function. There is nothing particular about that. You probably just forget to add & operator to take an address of the function.
std::thread t(&executeQuery, buildQuery(*_mVecHistIter, symbol));
^-- note the &
EDIT
So, you forget to mention that database_con is a class. In order to use class member function as param for function acception pointer to function as parameter (in your case std::thread::thread) you have to use fully qualified name with operand & (&database_con::executeQuery). However, since that is non static member function, it will require its first argument pointer to instance of such object (google std::thread non-static member function).
std::thread t(&database_con::executeQuery, this, buildQuery(*_mVecHistIter, symbol));
Previous answer
Did you considered using std::function?
#include <functional>
#include <thread>
int foo(int arg1, int arg2)
{
return arg1 + arg2;
}
void bar(std::function<int(int, int)> fnc, int arg1, int arg2)
{
fnc(arg1, arg2);
}
int main()
{
std::thread thr{&bar, &foo, 1, 2};
}

Related

Finding SQL server deadlock cause

I have a simple c++ program running on MSVC using an ODBC connection to execute a stored procedure.
When running it on the main thread it works fine, but when I attempt to multithread the same class I am getting this error:
(Process ID XX) was deadlocked on lock resource....
As mentioned above I can run it fine on the main thread and also on two threads with no issues.
This is how I spawn, use and join the threads:
historical apple("AAPL");
historical google("GOOG");
historical yahoo("YHOO");
historical samsung("005930.KS");
historical ibm("IBM");
auto t1 = thread([&] {apple.go(); });
auto t2 = thread([&] {google.go(); });
auto t3 = thread([&] {yahoo.go(); });
auto t4 = thread([&] {samsung.go(); });
auto t5 = thread([&] {ibm.go(); });
//writer(his.getHistorical()); // this writes to the file test.csv
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
I know it looks like shit, but I'm basically trying to see how hard I can push the software before making it look nice and more formatted.
This is how I call the stored procedure:
#include "stdafx.h"
#include "database_con.h"
////////////////////////////////////////////////////////////////////////
// Show errors from the SQLHANDLE
void database_con::show_error(unsigned int handletype, const SQLHANDLE& handle)
{
SQLWCHAR sqlstate[1024];
SQLWCHAR message[1024];
if (SQL_SUCCESS == SQLGetDiagRec(handletype, handle, 1, sqlstate, NULL, message, 1024, NULL))
wcout << "Message: " << message << "\nSQLSTATE: " << sqlstate << endl;
}
////////////////////////////////////////////////////////////////////////
// Builds the stored procedure query.
std::wstring database_con::buildQuery(vector<std::wstring> input, string symbol)
{
std::wstringstream builder;
builder << L"EXEC sp_addHistorical " << "#Symbol='" << L"" << StringToWString(symbol) << "'," <<
"#Date='" << (wstring)L"" << input.at(0) << "'," <<
"#Open=" << (wstring)L"" << input.at(1) << "," <<
"#Close=" << (wstring)L"" << input.at(2) << "," <<
"#MaxPrice=" << (wstring)L"" << input.at(3) << "," <<
"#MinPrice=" << (wstring)L"" << input.at(4) << "," <<
"#Volume=" << (wstring)L"" << input.at(5) << ";";
return builder.str();
}
////////////////////////////////////////////////////////////////////////
// Adds a substring of the string before the delimiter to a vector<wstring> that is returned.
std::vector<wstring> database_con::parseData(wstring line, char delim) {
size_t pos = 0;
std::vector<std::wstring> vOut;
while ((pos = line.find(delim)) != std::string::npos) {
vOut.push_back(line.substr(0, pos));
line.erase(0, pos + 1);
}
vOut.push_back(line.substr(0, pos));
return vOut;
}
////////////////////////////////////////////////////////////////////////
// Converts a std::string to a std::wstring
std::wstring database_con::StringToWString(const std::string& s)
{
std::wstring temp(s.length(), L' ');
std::copy(s.begin(), s.end(), temp.begin());
return temp;
}
void database_con::historicalLooper(string historical) {
}
////////////////////////////////////////////////////////////////////////
// Constructs a database connector object with the historical data and its symbol
database_con::database_con(std::string historical, string symbol){
/*
Set up the handlers
*/
/* Allocate an environment handle */
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
/* We want ODBC 3 support */
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
/* Allocate a connection handle */
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
/* Connect to the DSN */
SQLDriverConnectW(dbc, NULL, L"DRIVER={SQL Server};SERVER=ERA-PC-STUART\\JBK_DB;DATABASE=master;UID=geo;PWD=kalle123;", SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
/* Check for success */
if (SQL_SUCCESS != SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt))
{
show_error(SQL_HANDLE_DBC, dbc);
std::cout << "Failed to connect";
}
_mSymbol = symbol;
std::wstringstream stream(StringToWString(historical));
std::wstring line;
int row = 0;
while (std::getline(stream, line)) {
if (row > 0) {
vector<wstring> vHistorical = parseData(L"" + line, ',');
std::wstring SQL = buildQuery(vHistorical, _mSymbol);
if (SQL_SUCCESS != SQLExecDirectW(stmt, const_cast<SQLWCHAR*>(SQL.c_str()), SQL_NTS)) {
std::cout << "Execute error " << std::endl;
show_error(SQL_HANDLE_STMT, stmt);
std::wcout << L"Unsuccessful Query: " << SQL << std::endl;
}
// Close Cursor before next iteration starts:
SQLRETURN closeCursRet = SQLFreeStmt(stmt, SQL_CLOSE);
if (!SQL_SUCCEEDED(closeCursRet))
{
show_error(SQL_HANDLE_STMT, stmt);
// maybe add some handling for the case that closing failed.
}
}
row++;
}
std::cout << "Query " << _mSymbol << " ready" << std::endl;
}
database_con::~database_con() {
}
And finally this is the stored procedure:
GO
CREATE PROCEDURE sp_addHistorical
#Symbol nchar(10),#Date datetime,
#Open decimal(12,2),#Close decimal(12,2),#MinPrice decimal(12,2),
#MaxPrice decimal(12,2),#Volume int
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
MERGE HistoricalStock WITH (UPDLOCK) AS myTarget
USING (SELECT #Symbol AS Symbol,
#Date AS Date, #Open AS [Open], #Close AS [Close],
#MinPrice AS MinPrice, #MaxPrice AS MaxPrice,#Volume AS Volume) AS mySource
ON mySource.Symbol = myTarget.Symbol AND mySource.Date = myTarget.Date
WHEN MATCHED
THEN UPDATE
SET [Open] = mySource.[Open], [Close] = mySource.[Close],
MinPrice = mySource.MinPrice, MaxPrice = mySource.MaxPrice, Volume = mySource.Volume
WHEN NOT MATCHED
THEN
INSERT(Symbol,Date,[Open],[Close],MinPrice,MaxPrice,Volume)
VALUES(#Symbol,#Date,#Open,#Close,#MinPrice,#MaxPrice,#Volume);
COMMIT
GO
Any sort of help or advice on either how it's all structured or pointers to good examples regarding the issue I'm facing would be greatly appreciated.
I'm somewhat new to SQL server (more importantly to stored procedures) and can't really figure out what is wrong, even though I understand what a deadlock is.
Thanks!

SQLSTATE 24000 - Invalid cursor state ODBC VS c++

I've got a fairly simple c++ application that uses ODBC to connect to a SQL Server instance that uses a stored procedure to populate.
I use the lines of a wstring object to build a query that is then passed through to the stored procedure. Everything works fine if I run it a single time - however, I want to be able to loop through a quite extensive amount of code (3000+ excel rows) and as I do it the cursor error mentioned in the title occurs.
This is the stored procedure:
GO
CREATE PROCEDURE s_addHistorical
#Symbol nchar(10),#Date datetime,
#Open decimal(8,2),#Close decimal(8,2),#MinPrice decimal(8,2),
#MaxPrice decimal(8,2),#Volume int
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
MERGE HistoricalStock WITH (UPDLOCK) AS myTarget
USING (SELECT #Symbol AS Symbol,
#Date AS Date, #Open AS [Open], #Close AS [Close],
#MinPrice AS MinPrice, #MaxPrice AS MaxPrice,#Volume AS Volume) AS mySource
ON mySource.Symbol = myTarget.Symbol AND mySource.Date = myTarget.Date
WHEN MATCHED
THEN UPDATE
SET [Open] = mySource.[Open], [Close] = mySource.[Close],
MinPrice = mySource.MinPrice, MaxPrice = mySource.MaxPrice, Volume = mySource.Volume
WHEN NOT MATCHED
THEN
INSERT(Symbol,Date,[Open],[Close],MinPrice,MaxPrice,Volume)
VALUES(#Symbol,#Date,#Open,#Close,#MinPrice,#MaxPrice,#Volume);
COMMIT
GO
And this is the connector:
#include "stdafx.h"
#include "database_con.h"
////////////////////////////////////////////////////////////////////////
// Show errors from the SQLHANDLE
void database_con::show_error(unsigned int handletype, const SQLHANDLE& handle)
{
SQLWCHAR sqlstate[1024];
SQLWCHAR message[1024];
if (SQL_SUCCESS == SQLGetDiagRec(handletype, handle, 1, sqlstate, NULL, message, 1024, NULL))
wcout << "Message: " << message << "\nSQLSTATE: " << sqlstate << endl;
}
////////////////////////////////////////////////////////////////////////
// Builds the stored procedure query.
std::wstring database_con::buildQuery(vector<std::wstring> input)
{
std::cout << "Building the query" << std::endl;
std::wstringstream builder;
builder << L"EXEC sp_addHistorical " << "#Symbol='" << L"" << input.at(0) << "'," <<
"#Date=" << (wstring)L"" << input.at(1) << "," <<
"#Open=" << (wstring)L"" << input.at(2) << "," <<
"#Close=" << (wstring)L"" << input.at(3) << "," <<
"#MaxPrice=" << (wstring)L"" << input.at(4) << "," <<
"#MinPrice=" << (wstring)L"" << input.at(5) << "," <<
"#Volume=" << (wstring)L"" << input.at(6) << ";";
return builder.str();
}
////////////////////////////////////////////////////////////////////////
// Adds a substring of the string before the delimiter to a vector<wstring> that is returned.
std::vector<wstring> database_con::parseData(wstring line, char delim) {
size_t pos = 0;
std::vector<std::wstring> vOut;
while ((pos = line.find(delim)) != std::string::npos) {
vOut.push_back(line.substr(0, pos));
line.erase(0, pos + 2);
}
vOut.push_back(line.substr(0, pos));
return vOut;
}
std::wstring database_con::StringToWString(const std::string& s)
{
std::wstring temp(s.length(), L' ');
std::copy(s.begin(), s.end(), temp.begin());
return temp;
}
database_con::database_con(std::string historical){
/*
Set up the handlers
*/
/* Allocate an environment handle */
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
/* We want ODBC 3 support */
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
/* Allocate a connection handle */
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
/* Connect to the DSN */
SQLDriverConnectW(dbc, NULL, L"DRIVER={SQL Server};SERVER=ERA-PC-STUART\\JBK_DB;DATABASE=master;UID=geo;PWD=kalle123;", SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
/* Check for success */
if (SQL_SUCCESS != SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt))
{
show_error(SQL_HANDLE_DBC, dbc);
std::cout << "Failed to connect";
}
std::wstringstream stream(StringToWString(historical));
std::wstring line;
while (std::getline(stream, line)) {
vector<wstring> vHistorical = parseData(L"" + line, ',');
std::wstring SQL = buildQuery(vHistorical);
if (SQL_SUCCESS != SQLExecDirectW(stmt, const_cast<SQLWCHAR*>(SQL.c_str()), SQL_NTS)) {
show_error(SQL_HANDLE_STMT, stmt);
}
}
}
database_con::~database_con() {
}
I've been looking around at google and on SO, but I can't seem to find any questions that are usable in my current question. Most of them seem to be revolving around stored procedures sending back some sort of extracts, whereas my SP is simply inserting / updating.
Any sort of help would be greatly appriciated. :)
Anyone?
You need to call SQLCloseCursor to release the Cursor.
Change your code to:
while (std::getline(stream, line)) {
vector<wstring> vHistorical = parseData(L"" + line, ',');
std::wstring SQL = buildQuery(vHistorical);
if (SQL_SUCCESS != SQLExecDirectW(stmt, const_cast<SQLWCHAR*>(SQL.c_str()), SQL_NTS)) {
show_error(SQL_HANDLE_STMT, stmt);
}
// Close Cursor before next iteration starts:
SQLRETURN closeCursRet = SQLCLoseCursor(stmt);
if(!SQL_SUCCEEDED(closeCursRet))
{
// maybe add some handling for the case that closing failed.
}
}
See: https://msdn.microsoft.com/en-us/library/ms709301%28v=vs.85%29.aspx

SQLite and BOOST_SCOPE_EXIT_ALL

Why does the SQLite C interface requires to pass the pointer by the reference when using BOOST_SCOPE_EXIT_ALL, for example?
Suppose that I have the following code:
#include <boost/scope_exit.hpp>
#include <sqlite3.h>
#include <cstdlib>
#include <iostream>
int main()
{
sqlite3* db;
BOOST_SCOPE_EXIT_ALL(db)
{
if (sqlite3_close(db) != SQLITE_OK)
{
std::cerr << sqlite3_errmsg(db) << '\n';
}
};
if (sqlite3_open(":memory:", &db) != SQLITE_OK)
{
std::cerr << sqlite3_errmsg(db) << '\n';
return EXIT_FAILURE;
}
sqlite3_stmt* prepared_stmt = NULL;
BOOST_SCOPE_EXIT_ALL(db, prepared_stmt)
{
if (sqlite3_finalize(prepared_stmt) != SQLITE_OK)
{
std::cerr << sqlite3_errmsg(db) << '\n';
}
};
if (sqlite3_prepare_v2(db, "CREATE TABLE IF NOT EXISTS foo(bar TEXT, baz TEXT)", -1, &prepared_stmt, 0) != SQLITE_OK)
{
std::cerr << sqlite3_errmsg(db) << '\n';
return EXIT_FAILURE;
}
if (sqlite3_step(prepared_stmt) != SQLITE_DONE)
{
std::cerr << sqlite3_errmsg(db) << '\n';
return EXIT_FAILURE;
}
}
It crashes when trying to call sqlite3_close function and doesn't print anything to the stderr. However, if I change
BOOST_SCOPE_EXIT_ALL(db)
to
BOOST_SCOPE_EXIT_ALL(&db)
it states that
unable to close due to unfinalized statements or unfinished backups
when trying to call sqlite3_close function.
If I pass the prepared_stmt by the reference too, it works as expected:
BOOST_SCOPE_EXIT_ALL(db, &prepared_stmt)
Why? I thought that the SQLite library only complains about the addresses that db and prepared_stmt points to.
Thanks in advance.
The first line in main()
sqlite3* db;
is probably why sqlite3_close() is crashing. Your compiler may be reusing a memory location for that declared pointer and may not necessarily be using a location that is null or making it null. Therefore, the call to sqlite3_close() is receiving a bogus pointer.
The docs at https://www.sqlite.org/c3ref/close.html state:
The C parameter to sqlite3_close(C) and sqlite3_close_v2(C) must be
either a NULL pointer or an sqlite3 object pointer obtained from
sqlite3_open(), sqlite3_open16(), or sqlite3_open_v2(), and not
previously closed. Calling sqlite3_close() or sqlite3_close_v2() with
a NULL pointer argument is a harmless no-op.
So, according to the above statement, the following code should remove the crash:
sqlite3* db = nullptr;
Of course, all of this would be moot if you just removed this block of unecessary code entirely:
BOOST_SCOPE_EXIT_ALL(db)
{
if (sqlite3_close(db) != SQLITE_OK)
{
std::cerr << sqlite3_errmsg(db) << '\n';
}
};
There is no reason to close a db that has never been opened.
EDIT: Here is a version of your function as I would try it. I'm really out of time right now so I can't test it, but I think it will work. I removed the boost macros since I'm unfamiliar with them, but you could add those back in. Hopefully this helps!
int main()
{
sqlite3* db = nullptr;
if (sqlite3_open(":memory:", &db) != SQLITE_OK)
goto ERROR_OCCURRED;
sqlite3_stmt* prepared_stmt = nullptr;
if (sqlite3_finalize(prepared_stmt) != SQLITE_OK)
std::cerr << sqlite3_errmsg(db) << '\n';
if (sqlite3_prepare_v2(db, "CREATE TABLE IF NOT EXISTS foo(bar TEXT, baz TEXT)", -1, &prepared_stmt, 0) != SQLITE_OK)
goto ERROR_OCCURRED;
if (sqlite3_step(prepared_stmt) != SQLITE_DONE)
goto ERROR_OCCURRED;
if (sqlite3_close(db) != SQLITE_OK)
{
std::cerr << sqlite3_errmsg(db) << '\n';
sqlite3_close(db);
return EXIT_FAILURE;
}
return 0;
ERROR_OCCURRED:
std::cerr << sqlite3_errmsg(db) << '\n';
sqlite3_close(db);
return EXIT_FAILURE;
}
Unfortunately, I'm out-of-time to check your call to sqlite3_finalize() but I also think that it's probably too early in the function. I don't see a reason to finalize a null pointer.

Code working inline, but when in class

I'm writing program using Boost::Asio, I want to implement simple chat. Currently I'm struggling with problem that when I put some code inline of class function it's working, but when same code is provided by another class object is not. I think it could be connected to Boost::Asio, but I'm not sure:
void Connection::session(socket_ptr sock)
{
try{
for(;;) {
char mesg[1024];
boost::system::error_code error;
size_t length = sock->read_some(boost::asio::buffer(mesg), error);
if (error == boost::asio::error::eof){
disconnected(sock);
break;
}
else if (error)
throw boost::system::system_error(error);
message msg(mesg,length);
char *data;
data = msg.getData();
std::cout << "In session " << strlen(data) << " " << data;
/*This is working
string s_msg,s_data;
s_msg = mesg;
s_data = s_msg.substr(2,length);
std::vector<char> writable(s_data.size() + 1);
std::copy(s_data.begin(), s_data.end(), writable.begin());
std::cout << "In session " << strlen(&writable[0]) << " " << &writable[0];
send_all(sock,&writable[0],strlen(&writable[0]));
*/
send_all(sock,data,strlen(data));
}
}
catch (std::exception& e){
std::cerr << "Exception in thread: " << e.what() << "\n";
}
}
Class message that is only parsing data
message::message(char *c_msg, size_t length)
{
msg = c_msg;
id = msg.at(0);
data = msg.substr(2,length);
}
char* message::getData()
{
std::vector<char> writable(data.size() + 1);
std::copy(data.begin(), data.end(), writable.begin());
std::cout << data;
std::cout << &writable[0];
return &writable[0];
}
So when using class message this line:
std::cout << "In session " << strlen(data) << " " << data;
I get:
st String //this is cout from message getData
st String //this is cout from message getData
In session 0
With inline version:
In session 11 st String
So, in session function string is empty although message cout shows something opposite.
I don't know if it's important, but this function is invoked as new thread from main.
Regards,
Piotr
You're returning the address of a temporary:
char* message::getData()
{
std::vector<char> writable(data.size() + 1);
//...
return &writable[0];
}
This is undefined behaviour.
I'm assuming that data is just a std::string. You could do this instead:
const char* message::getData() const
{
return data.c_str();
}

HOW-TO: Client connection manager for Boost::asio?

I created a server using boost:asio. When a client connects it sends a file_size, file_name and the file_data. The server stores this in a file on disk. This works perfectly! Though now I'm running both client application and server application in the main thread of their application (so I've got a server and client app) which blocks the rest of the application(s) from executing.
So in abstract I want to create something like this:
server app
have one thread to receive and handle all incoming file transfers
have another thread in which the rest of the application can do the things it want to
client app
when I press the space bar, or whenever i want, I want to send a file to the server in a separate thread from the main one so my application can continue doing other stuff it needs to do.
My question: how do I create a manager for my client file transfers?
File transfer server accepts new file transfer client connections
#include "ofxFileTransferServer.h"
ofxFileTransferServer::ofxFileTransferServer(unsigned short nPort)
:acceptor(
io_service
,boost::asio::ip::tcp::endpoint(
boost::asio::ip::tcp::v4()
,nPort
)
,true
)
,port(nPort)
{
}
// test
void ofxFileTransferServer::startThread() {
boost::thread t(boost::bind(
&ofxFileTransferServer::accept
,this
));
}
void ofxFileTransferServer::accept() {
ofxFileTransferConnection::pointer new_connection(new ofxFileTransferConnection(io_service));
acceptor.async_accept(
new_connection->socket()
,boost::bind(
&ofxFileTransferServer::handleAccept
,this
,new_connection
,boost::asio::placeholders::error
)
);
std::cout << __FUNCTION__ << " start accepting " << std::endl;
io_service.run();
}
void ofxFileTransferServer::handleAccept(
ofxFileTransferConnection::pointer pConnection
,const boost::system::error_code& rErr
)
{
std::cout << __FUNCTION__ << " " << rErr << ", " << rErr.message() << std::endl;
if(!rErr) {
pConnection->start();
ofxFileTransferConnection::pointer new_connection(new ofxFileTransferConnection(io_service));
acceptor.async_accept(
new_connection->socket()
,boost::bind(
&ofxFileTransferServer::handleAccept
,this
,new_connection
,boost::asio::placeholders::error
)
);
}
}
File transfer client
#include "ofxFileTransferClient.h"
#include "ofMain.h"
using boost::asio::ip::tcp;
ofxFileTransferClient::ofxFileTransferClient(
boost::asio::io_service &rIOService
,const std::string sServer
,const std::string nPort
,const std::string sFilePath
):resolver_(rIOService)
,socket_(rIOService)
,file_path_(sFilePath)
,server_(sServer)
,port_(nPort)
{
}
ofxFileTransferClient::~ofxFileTransferClient() {
std::cout << "~~~~ ofxFileTransferClient" << std::endl;
}
void ofxFileTransferClient::start() {
// open file / get size
source_file_stream_.open(
ofToDataPath(file_path_).c_str()
,std::ios_base::binary | std::ios_base::ate
);
if(!source_file_stream_) {
std::cout << ">> failed to open:" << file_path_ << std::endl;
return;
}
size_t file_size = source_file_stream_.tellg();
source_file_stream_.seekg(0);
// send file size and name to server.
std::ostream request_stream(&request_);
request_stream << file_path_ << "\n"
<< file_size << "\n\n";
std::cout << ">> request_size:" << request_.size()
<< " file_path: " << file_path_
<< " file_size: "<< file_size
<< std::endl;
// resolve ofxFileTransferServer
tcp::resolver::query query(server_, port_);
resolver_.async_resolve(
query
,boost::bind(
&ofxFileTransferClient::handleResolve
,shared_from_this()
,boost::asio::placeholders::error
,boost::asio::placeholders::iterator
)
);
}
void ofxFileTransferClient::handleResolve(
const boost::system::error_code& rErr
,tcp::resolver::iterator oEndPointIt
)
{
if(!rErr) {
tcp::endpoint endpoint = *oEndPointIt;
socket_.async_connect(
endpoint
,boost::bind(
&ofxFileTransferClient::handleConnect
,shared_from_this()
,boost::asio::placeholders::error
,++oEndPointIt
)
);
}
else {
std::cout << ">> error: " << rErr.message() << std::endl;
}
}
void ofxFileTransferClient::handleConnect(
const boost::system::error_code& rErr
,tcp::resolver::iterator oEndPointIt
)
{
if(!rErr) {
cout << ">> connected!" << std::endl;
boost::asio::async_write(
socket_
,request_
,boost::bind(
&ofxFileTransferClient::handleFileWrite
,shared_from_this()
,boost::asio::placeholders::error
)
);
}
else if (oEndPointIt != tcp::resolver::iterator()) {
// connection failed, try next endpoint in list
socket_.close();
tcp::endpoint endpoint = *oEndPointIt;
socket_.async_connect(
endpoint
,boost::bind(
&ofxFileTransferClient::handleConnect
,shared_from_this()
,boost::asio::placeholders::error
,++oEndPointIt
)
);
}
else {
std::cout << ">> error: " << rErr.message() << std::endl;
}
}
void ofxFileTransferClient::handleFileWrite(
const boost::system::error_code& rErr
)
{
if(!rErr) {
if(source_file_stream_.eof() == false) {
source_file_stream_.read(buf_.c_array(), buf_.size());
if(source_file_stream_.gcount() <= 0) {
std::cout << ">> read file error." << std::endl;
return;
}
std::cout << ">> send: " << source_file_stream_.gcount() << " bytes, total: " << source_file_stream_.tellg() << " bytes\n";
boost::asio::async_write(
socket_
,boost::asio::buffer(buf_.c_array(), source_file_stream_.gcount())
,boost::bind(
&ofxFileTransferClient::handleFileWrite
,this
,boost::asio::placeholders::error
)
);
if(rErr) {
std::cout <<">> send error: " << rErr << std::endl; // not sure bout this one..
}
}
else {
return; // eof()
}
}
else {
std::cout << ">> error:" << rErr.message() << std::endl;
}
}
And a tiny manager to manager client transfers (which is used in the client app)
Again the threading code is only for testing purposes and isnt used.
#include "ofxFileTransferManager.h"
ofxFileTransferManager::ofxFileTransferManager() {
}
void ofxFileTransferManager::transferFile(
const std::string sServer
,const std::string nPort
,const std::string sFile
)
{
ofxFileTransferClient::pointer client(new ofxFileTransferClient(
io_service_
,sServer
,nPort
,sFile
));
client->start();
io_service_.run();
}
void ofxFileTransferManager::startThread() {
boost::thread t(boost::bind(
&ofxFileTransferManager::run
,this
));
}
void ofxFileTransferManager::run() {
cout << "starting filemanager" << std::endl;
while(true) {
io_service_.run();
boost::this_thread::sleep(boost::posix_time::milliseconds(250));
cout << ".";
}
cout << "ready filemanager" << std::endl;
}
It would be awesome if someone can help me out here. The example of boost all use a "one-time" client connection which doesn't really help me further.
roxlu
Great! I just figured it out. I had to wrap my io_service around a boost::asio::io_service::work object! (and forgot a shared_from_this()) somewhere. I've uploaded my code here: http://github.com/roxlu/ofxFileTransfer
For convenience here is the manager code:
#include "ofxFileTransferManager.h"
ofxFileTransferManager::ofxFileTransferManager()
:work_(io_service_)
{
}
void ofxFileTransferManager::transferFile(
const std::string sServer
,const std::string nPort
,const std::string sFile
,const std::string sRemoteFile
)
{
ofxFileTransferClient::pointer client(new ofxFileTransferClient(
io_service_
,sServer
,nPort
,sFile
,sRemoteFile
));
client->start();
}
void ofxFileTransferManager::startThread() {
boost::thread t(boost::bind(
&ofxFileTransferManager::run
,this
));
}
void ofxFileTransferManager::run() {
io_service_.run();
}
From what I can tell, all you really need is to create a new thread and put in its main loop io_service.run();.
Obviously, you would have to take care of protecting classes and variables in mutexes that are shared between the appss main thread and asio's thread.
Edit: Something like this?
static sem_t __semSendFile;
static void* asioThread(void*)
{
while( true )
{
sem_wait( &__semSendFile );
io_service.run();
}
return NULL;
}
void ofxFileTransferManager::transferFile(
const std::string sServer
,const std::string nPort
,const std::string sFile
)
{
ofxFileTransferClient::pointer client(new ofxFileTransferClient(
io_service_
,sServer
,nPort
,sFile
));
client->start();
sem_post( &__semSendFile );
}
int main(int argc, char **argv)
{
if ( sem_init( &__semSendFile, 0, 0 ) != 0 )
{
std::cerr << strerror( errno ) << std::endl;
return -1;
}
pthread_t thread;
if ( pthread_create( &thread, NULL, asioThread, NULL ) != 0 )
{
std::cerr << strerror( errno ) << std::endl;
return -1;
}
[...]