Skip to content

Commit 2f17502

Browse files
Documentation
1 parent 7f72c3a commit 2f17502

6 files changed

Lines changed: 412 additions & 0 deletions

File tree

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ int main() {
6161
- [Build from source](#build-from-source)
6262
- [How to Use DBC API Clients](#how-to-use-dbc-api-clients)
6363
- [Common Clients' Interface](#common-clients-interface)
64+
- [Thread-Safe Parallel Usage](#thread-safe-parallel-usage)
6465
- [Available Methods](#available-methods)
6566
- [Credentials & Configuration](#credentials--configuration)
6667
- [Quick Setup](#quick-setup)
@@ -165,6 +166,37 @@ dbc::HttpClient client(username, password);
165166
166167
Both clients share the same interface. Below is a summary of every available method.
167168
169+
<a id="thread-safe-parallel-usage"></a>
170+
### 🧵 Thread-Safe Parallel Usage
171+
172+
Recommended pattern for parallel solving: **one client instance per thread**.
173+
174+
- Do not share a single client instance between workers if your goal is maximum throughput.
175+
- Create one `dbc::HttpClient` or `dbc::SocketClient` per worker thread.
176+
- Each worker can independently call `decode()`, `upload()`, and `get_captcha()`.
177+
178+
Ready-to-run samples:
179+
180+
- HTTP (one instance per thread): [`examples/example.ThreadSafe_Http.cpp`](examples/example.ThreadSafe_Http.cpp)
181+
- Socket (one instance per thread): [`examples/example.ThreadSafe_Socket.cpp`](examples/example.ThreadSafe_Socket.cpp)
182+
183+
Build and run:
184+
185+
```bash
186+
cmake --preset examples
187+
cmake --build --preset examples
188+
189+
# Basic mode (no DBC_IMAGE_PATH): each thread does get_balance()
190+
export DBC_USERNAME="your_username"
191+
export DBC_PASSWORD="your_password"
192+
export DBC_THREADS=2
193+
194+
./build/examples/examples/ex_threadsafe_http
195+
./build/examples/examples/ex_threadsafe_socket
196+
```
197+
198+
If `DBC_IMAGE_PATH` is set, workers use `decode()` with that image path.
199+
168200
<a id="available-methods"></a>
169201

170202
| Method | Signature | Returns | Description |

examples/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ dbc_example(ex_mtcaptcha example.Mtcaptcha.cpp)
3232
dbc_example(ex_siara example.Siara.cpp)
3333
dbc_example(ex_tencent example.Tencent.cpp)
3434
dbc_example(ex_textcaptcha example.Textcaptcha.cpp)
35+
dbc_example(ex_threadsafe_http example.ThreadSafe_Http.cpp)
36+
dbc_example(ex_threadsafe_socket example.ThreadSafe_Socket.cpp)
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// example.ThreadSafe_Http.cpp — one HttpClient instance per thread
2+
#include <cstdlib>
3+
#include <iostream>
4+
#include <string>
5+
#include <thread>
6+
#include <vector>
7+
8+
#include "deathbycaptcha/deathbycaptcha.hpp"
9+
10+
namespace {
11+
std::string getenv_or_empty(const char* key) {
12+
const char* v = std::getenv(key);
13+
return v ? std::string(v) : std::string{};
14+
}
15+
16+
void run_http_worker(const std::string& username,
17+
const std::string& password,
18+
const std::string& image_path,
19+
bool decode_mode,
20+
int worker_id) {
21+
// Each worker owns its own client instance.
22+
dbc::HttpClient client(username, password);
23+
client.is_verbose = false;
24+
25+
try {
26+
if (!decode_mode) {
27+
std::cout << "[HTTP worker " << worker_id
28+
<< "] balance: " << client.get_balance() << " US cents\n";
29+
} else {
30+
auto result = client.decode(image_path, 120);
31+
if (result && result->text.has_value()) {
32+
std::cout << "[HTTP worker " << worker_id << "] solved captcha "
33+
<< result->captcha << ": " << *result->text << '\n';
34+
} else {
35+
std::cout << "[HTTP worker " << worker_id
36+
<< "] timeout/no result\n";
37+
}
38+
}
39+
} catch (const std::exception& e) {
40+
std::cerr << "[HTTP worker " << worker_id << "] error: " << e.what()
41+
<< '\n';
42+
}
43+
}
44+
} // namespace
45+
46+
int main() {
47+
const std::string username = getenv_or_empty("DBC_USERNAME");
48+
const std::string password = getenv_or_empty("DBC_PASSWORD");
49+
const std::string image_path = getenv_or_empty("DBC_IMAGE_PATH");
50+
const std::string threads_env = getenv_or_empty("DBC_THREADS");
51+
52+
if (username.empty() || password.empty()) {
53+
std::cerr << "Set DBC_USERNAME and DBC_PASSWORD before running.\n";
54+
return 1;
55+
}
56+
57+
int threads_count = 2;
58+
if (!threads_env.empty()) {
59+
try {
60+
threads_count = std::stoi(threads_env);
61+
} catch (...) {
62+
std::cerr << "Invalid DBC_THREADS value. Use a positive integer.\n";
63+
return 1;
64+
}
65+
}
66+
if (threads_count <= 0) {
67+
std::cerr << "DBC_THREADS must be greater than zero.\n";
68+
return 1;
69+
}
70+
71+
const bool decode_mode = !image_path.empty();
72+
73+
// Main thread sanity check in normal usage.
74+
try {
75+
dbc::HttpClient sanity_client(username, password);
76+
sanity_client.is_verbose = false;
77+
std::cout << "[HTTP main] initial balance: "
78+
<< sanity_client.get_balance() << " US cents\n";
79+
} catch (const std::exception& e) {
80+
std::cerr << "[HTTP main] error: " << e.what() << '\n';
81+
return 1;
82+
}
83+
84+
std::vector<std::thread> workers;
85+
workers.reserve(static_cast<std::size_t>(threads_count));
86+
87+
for (int i = 0; i < threads_count; ++i) {
88+
workers.emplace_back(run_http_worker,
89+
username,
90+
password,
91+
image_path,
92+
decode_mode,
93+
i + 1);
94+
}
95+
96+
for (auto& t : workers) t.join();
97+
return 0;
98+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// example.ThreadSafe_Socket.cpp — one SocketClient instance per thread
2+
#include <cstdlib>
3+
#include <iostream>
4+
#include <string>
5+
#include <thread>
6+
#include <vector>
7+
8+
#include "deathbycaptcha/deathbycaptcha.hpp"
9+
10+
namespace {
11+
std::string getenv_or_empty(const char* key) {
12+
const char* v = std::getenv(key);
13+
return v ? std::string(v) : std::string{};
14+
}
15+
16+
void run_socket_worker(const std::string& username,
17+
const std::string& password,
18+
const std::string& image_path,
19+
bool decode_mode,
20+
int worker_id) {
21+
// Each worker owns its own client instance.
22+
dbc::SocketClient client(username, password);
23+
client.is_verbose = false;
24+
25+
try {
26+
if (!decode_mode) {
27+
std::cout << "[SOCKET worker " << worker_id
28+
<< "] balance: " << client.get_balance() << " US cents\n";
29+
} else {
30+
auto result = client.decode(image_path, 120);
31+
if (result && result->text.has_value()) {
32+
std::cout << "[SOCKET worker " << worker_id << "] solved captcha "
33+
<< result->captcha << ": " << *result->text << '\n';
34+
} else {
35+
std::cout << "[SOCKET worker " << worker_id
36+
<< "] timeout/no result\n";
37+
}
38+
}
39+
} catch (const std::exception& e) {
40+
std::cerr << "[SOCKET worker " << worker_id << "] error: " << e.what()
41+
<< '\n';
42+
}
43+
}
44+
} // namespace
45+
46+
int main() {
47+
const std::string username = getenv_or_empty("DBC_USERNAME");
48+
const std::string password = getenv_or_empty("DBC_PASSWORD");
49+
const std::string image_path = getenv_or_empty("DBC_IMAGE_PATH");
50+
const std::string threads_env = getenv_or_empty("DBC_THREADS");
51+
52+
if (username.empty() || password.empty()) {
53+
std::cerr << "Set DBC_USERNAME and DBC_PASSWORD before running.\n";
54+
return 1;
55+
}
56+
57+
int threads_count = 2;
58+
if (!threads_env.empty()) {
59+
try {
60+
threads_count = std::stoi(threads_env);
61+
} catch (...) {
62+
std::cerr << "Invalid DBC_THREADS value. Use a positive integer.\n";
63+
return 1;
64+
}
65+
}
66+
if (threads_count <= 0) {
67+
std::cerr << "DBC_THREADS must be greater than zero.\n";
68+
return 1;
69+
}
70+
71+
const bool decode_mode = !image_path.empty();
72+
73+
// Main thread sanity check in normal usage.
74+
try {
75+
dbc::SocketClient sanity_client(username, password);
76+
sanity_client.is_verbose = false;
77+
std::cout << "[SOCKET main] initial balance: "
78+
<< sanity_client.get_balance() << " US cents\n";
79+
} catch (const std::exception& e) {
80+
std::cerr << "[SOCKET main] error: " << e.what() << '\n';
81+
return 1;
82+
}
83+
84+
std::vector<std::thread> workers;
85+
workers.reserve(static_cast<std::size_t>(threads_count));
86+
87+
for (int i = 0; i < threads_count; ++i) {
88+
workers.emplace_back(run_socket_worker,
89+
username,
90+
password,
91+
image_path,
92+
decode_mode,
93+
i + 1);
94+
}
95+
96+
for (auto& t : workers) t.join();
97+
return 0;
98+
}

tests/unit/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,12 @@ target_include_directories(test_socket_transport PRIVATE
7171
${CMAKE_SOURCE_DIR}/include
7272
${CMAKE_SOURCE_DIR}/src)
7373
add_test(NAME socket_transport COMMAND test_socket_transport)
74+
75+
# ── test_thread_safe_usage ────────────────────────────────────────────────────
76+
add_executable(test_thread_safe_usage test_thread_safe_usage.cpp)
77+
target_link_libraries(test_thread_safe_usage PRIVATE ${DBC_TEST_LIB}
78+
GTest::gtest_main)
79+
target_include_directories(test_thread_safe_usage PRIVATE
80+
${CMAKE_SOURCE_DIR}/include
81+
${CMAKE_SOURCE_DIR}/src)
82+
add_test(NAME thread_safe_usage COMMAND test_thread_safe_usage)

0 commit comments

Comments
 (0)