first commit

This commit is contained in:
hukai 2025-07-30 09:38:43 +08:00
commit 3342020ecf
19 changed files with 886 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# Xmake cache
.xmake/
build/
# MacOS Cache
.DS_Store
.vscode/

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# Node Communication
## 准备
```shell
xmake f -m release -cv
```
## 运行
### TCP
1. 接收端执行 `xmake run ipc_interface tcp receive`
1. 发送端执行 `xmake run ipc_interface tcp send --data test_data`
### Share Memory
1. 接收端执行 `xmake run ipc_interface shm receive`
1. 发送端执行 `xmake run ipc_interface shm send --data test_data`
### Unix Domain
1. 接收端执行 `xmake run ipc_interface unix_domain receive`
1. 发送端执行 `xmake run ipc_interface unix_domain send --data test_data`

31
include/context.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#include "core.h"
#include <memory>
/**
* @brief
*
*/
class IpcRequestReplyContext {
private:
std::unique_ptr<IpcRequestReplyInterface> strategy;
public:
void setStrategy(std::unique_ptr<IpcRequestReplyInterface> newStrategy) {
strategy = std::move(newStrategy);
}
std::expected<void, std::string> send(const std::string& message) {
if (strategy) {
return strategy->send(message);
}
return std::unexpected("Not Found Strategy");
}
std::expected<std::string, std::string> receive() {
if (strategy) {
return strategy->receive();
}
return std::unexpected("Not Found Strategy");
}
};

33
include/core.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <expected>
#include <string>
/**
* @brief
*
*/
class IpcPubSubInterface {
public:
virtual ~IpcPubSubInterface() = default;
virtual std::expected<void, std::string> publish(
const std::string& topic, const std::string& message) = 0;
virtual std::expected<void, std::string> subscribe(
const std::string& topic) = 0;
};
/**
* @brief
*
*/
class IpcRequestReplyInterface {
public:
virtual ~IpcRequestReplyInterface() = default;
virtual std::expected<void, std::string> send(const std::string& message) = 0;
virtual std::expected<std::string, std::string> receive() = 0;
};
// 缓冲区大小
constexpr size_t BUFFER_SIZE = 4096;

36
include/shm.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include "core.h"
/**
* @brief
*
*/
class ShmRequestReply : public IpcRequestReplyInterface {
public:
ShmRequestReply();
~ShmRequestReply();
std::expected<std::string, std::string> receive() override;
std::expected<void, std::string> send(const std::string& message) override;
};
/**
* @brief
*
*/
class ShmPubSub : public IpcPubSubInterface {
public:
ShmPubSub();
~ShmPubSub();
std::expected<void, std::string> publish(const std::string& topic,
const std::string& message) override;
std::expected<void, std::string> subscribe(const std::string& topic) override;
};
/**
* @brief
*
*/
constexpr const char* SHM_REQUEST_REPLY_NAME = "/shm_req";

19
include/tcp.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include "core.h"
/**
* @brief TCP
*
*/
class TcpRequestReply : public IpcRequestReplyInterface {
public:
TcpRequestReply();
~TcpRequestReply();
std::expected<std::string, std::string> receive() override;
std::expected<void, std::string> send(const std::string& message) override;
};
constexpr int PORT = 12345;
constexpr const char* LOCALHOST = "127.0.0.1";

23
include/unix_domain.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include "core.h"
/**
* @brief unix-domain
*
*/
class UnixDomainRequestReply : public IpcRequestReplyInterface {
public:
UnixDomainRequestReply();
~UnixDomainRequestReply();
std::expected<std::string, std::string> receive() override;
std::expected<void, std::string> send(const std::string& message) override;
};
// unix-domain 地址
constexpr const char* SOCKET_PATH = "/tmp/unix_domain_socket";
// 默认重试次数
const int RETRY_COUNT = 10;
// 重试间隔(毫秒)
const int RETRY_DELAY_MS = 500;

19
include/zmq_ipc.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include "core.h"
#include <zmq.hpp>
class ZMQRequestReply : public IpcRequestReplyInterface {
public:
ZMQRequestReply();
~ZMQRequestReply();
std::expected<std::string, std::string> receive() override;
std::expected<void, std::string> send(const std::string& message) override;
private:
zmq::context_t ctx_;
zmq::socket_t receiver_;
zmq::socket_t sender_;
static constexpr const char* endpoint_ = "inproc://test";
};

0
src/core.cpp Normal file
View File

87
src/main.cpp Normal file
View File

@ -0,0 +1,87 @@
#include <context.h>
#include <shm.h>
#include <tcp.h>
#include <unix_domain.h>
#include <zmq_ipc.h>
#include <argparse/argparse.hpp>
#include <iostream>
#include <optional>
#include <thread>
int parse_cmd_args(int argc, char** argv, std::string& communication,
std::string& method, std::string& data) {
argparse::ArgumentParser program("IPC_Interface");
program.add_argument("communication")
.help("zmq or shm or unix_domain")
.store_into(communication);
program.add_argument("method").help("send or receive").store_into(method);
program.add_argument("--data").default_value(std::string("")).nargs(1);
try {
program.parse_args(argc, argv);
} catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
}
if (method == "send") {
std::cout << method << std::endl;
}
data = program.get<std::string>("--data");
return 0;
}
int run_main(int argc, char** argv) {
std::string communication;
std::string method;
std::string data;
if (auto res = parse_cmd_args(argc, argv, communication, method, data);
res != 0) {
return res;
}
IpcRequestReplyContext context;
// 使用共享内存策略
if (communication == "zmq") {
context.setStrategy(std::make_unique<ZMQRequestReply>());
} else if (communication == "shm") {
context.setStrategy(std::make_unique<ShmRequestReply>());
} else if (communication == "unix_domain") {
context.setStrategy(std::make_unique<UnixDomainRequestReply>());
} else if (communication == "tcp") {
context.setStrategy(std::make_unique<TcpRequestReply>());
}
if (method == "send") {
if (!data.empty()) {
auto res = context.send(data);
if (!res.has_value()) {
std::cerr << "send error: " << res.error() << std::endl;
return -1;
}
std::cout << "send success" << std::endl;
}
} else if (method == "receive") {
auto read_msg = context.receive();
if (!read_msg.has_value()) {
std::cerr << "receive error: " << read_msg.error() << std::endl;
return -1;
}
std::cout << "Received: " << *read_msg << std::endl;
}
return 0;
}
int main(int argc, char** argv) {
return run_main(argc, argv);
// return 0;
}

161
src/shm.cpp Normal file
View File

@ -0,0 +1,161 @@
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstring>
#include <expected>
#include <iostream>
#include "shm.h"
/**
* @brief Construct a new Shm Request Reply:: Shm Request Reply object
*
*/
ShmRequestReply::ShmRequestReply() {
// 初始化共享内存(创建端或者连接端)
// 此处省略具体实现,可用 shm_open + mmap 等
}
/**
* @brief Destroy the Shm Request Reply:: Shm Request Reply object
*
*/
ShmRequestReply::~ShmRequestReply() {
// 清理共享内存资源
}
/**
* @brief
*
* @return std::expected<std::string, std::string>
*/
std::expected<std::string, std::string> ShmRequestReply::receive() {
// 以只读方式打开请求共享内存区域
int fd = shm_open(SHM_REQUEST_REPLY_NAME, O_RDONLY, 0666);
if (fd == -1)
return std::unexpected("shm_open failed: " + std::string(strerror(errno)));
// 将共享内存映射到进程地址空间
void* ptr = mmap(nullptr, BUFFER_SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) return std::unexpected("mmap failed");
// 将共享内存内容转换为字符串
std::string data(static_cast<char*>(ptr));
// 清理资源:取消映射、关闭文件描述符
munmap(ptr, BUFFER_SIZE);
close(fd);
// 返回读取到的数据
return data;
}
/**
* @brief
*
* @param message
* @return std::expected<void, std::string>
*/
std::expected<void, std::string> ShmRequestReply::send(
const std::string& message) {
// 创建或打开响应共享内存区域(可读写)
int fd = shm_open(SHM_REQUEST_REPLY_NAME, O_CREAT | O_RDWR, 0666);
if (fd == -1)
return std::unexpected("shm_open failed: " + std::string(strerror(errno)));
// 调整共享内存大小
auto res = ftruncate(fd, BUFFER_SIZE);
if (res != 0) {
return std::unexpected("ftruncate failed: " + std::string(strerror(errno)));
}
// 将共享内存映射到进程地址空间(可写)
void* ptr = mmap(nullptr, BUFFER_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) return std::unexpected("mmap failed");
// 清空共享内存区域
std::memset(ptr, 0, BUFFER_SIZE);
// 将消息内容拷贝到共享内存(确保不超过大小限制)
std::memcpy(ptr, message.data(), std::min(message.size(), BUFFER_SIZE - 1));
// 清理资源:取消映射、关闭文件描述符
munmap(ptr, BUFFER_SIZE);
close(fd);
return {};
}
/**
* @brief topic
*
* @param topic
* @return std::string
*/
std::string make_topic_name(const std::string& topic) {
return "/shm_topic_" + topic;
}
/**
* @brief Construct a new Shm Pub Sub:: Shm Pub Sub object
*
*/
ShmPubSub::ShmPubSub() {
// 初始化
}
/**
* @brief Destroy the Shm Pub Sub:: Shm Pub Sub object
*
*/
ShmPubSub::~ShmPubSub() {
// 清理
}
/**
* @brief
*
* @param topic
* @param message
* @return std::expected<void, std::string>
*/
std::expected<void, std::string> ShmPubSub::publish(
const std::string& topic, const std::string& message) {
auto shm_name = make_topic_name(topic);
// 创建或打开响应共享内存区域(可读写)
int fd = shm_open(shm_name.c_str(), O_CREAT | O_RDWR, 0666);
if (fd == -1)
return std::unexpected("shm_open failed: " + std::string(strerror(errno)));
// 调整共享内存大小
auto res = ftruncate(fd, BUFFER_SIZE);
if (res != 0) {
return std::unexpected("ftruncate failed: " + std::string(strerror(errno)));
}
// 将共享内存映射到进程地址空间(可写)
void* ptr = mmap(nullptr, BUFFER_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) return std::unexpected("mmap failed");
// 清空共享内存区域
std::memset(ptr, 0, BUFFER_SIZE);
std::memcpy(ptr, message.data(), std::min(message.size(), BUFFER_SIZE - 1));
// 清理资源:取消映射、关闭文件描述符
munmap(ptr, BUFFER_SIZE);
close(fd);
return {};
}
/**
* @brief
*
* @param topic
* @return std::expected<void, std::string>
*/
std::expected<void, std::string> ShmPubSub::subscribe(
const std::string& topic) {
auto shm_name = make_topic_name(topic);
// 以只读方式打开请求共享内存区域
int fd = shm_open(shm_name.c_str(), O_RDONLY, 0666);
if (fd == -1)
return std::unexpected("shm_open failed: " + std::string(strerror(errno)));
// 将共享内存映射到进程地址空间
void* ptr = mmap(nullptr, BUFFER_SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) return std::unexpected("mmap failed");
// 将共享内存内容转换为字符串
std::string data(static_cast<char*>(ptr));
std::cout << "Received [" << topic << "]: " << data << "\n";
// 清理资源:取消映射、关闭文件描述符
munmap(ptr, BUFFER_SIZE);
close(fd);
return {};
}

91
src/tcp.cpp Normal file
View File

@ -0,0 +1,91 @@
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstring>
#include <expected>
#include <iostream>
#include "tcp.h"
TcpRequestReply::TcpRequestReply() {}
TcpRequestReply::~TcpRequestReply() {}
std::expected<std::string, std::string> TcpRequestReply::receive() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
return std::unexpected("socket() failed");
}
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(server_fd, reinterpret_cast<sockaddr*>(&server_addr),
sizeof(server_addr)) < 0) {
close(server_fd);
return std::unexpected("bind() failed");
}
if (listen(server_fd, 1) < 0) {
close(server_fd);
return std::unexpected("listen() failed");
}
int client_fd = accept(server_fd, nullptr, nullptr);
if (client_fd < 0) {
close(server_fd);
return std::unexpected("accept() failed");
}
char buffer[BUFFER_SIZE] = {0};
ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE);
if (bytes_read < 0) {
close(client_fd);
close(server_fd);
return std::unexpected("read() failed");
}
close(client_fd);
close(server_fd);
return std::string(buffer, static_cast<size_t>(bytes_read));
}
std::expected<void, std::string> TcpRequestReply::send(
const std::string& message) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
return std::unexpected("socket() failed");
}
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, LOCALHOST, &server_addr.sin_addr) <= 0) {
close(sock);
return std::unexpected("inet_pton() failed");
}
if (connect(sock, reinterpret_cast<sockaddr*>(&server_addr),
sizeof(server_addr)) < 0) {
close(sock);
return std::unexpected("connect() failed");
}
if (write(sock, message.c_str(), message.size()) < 0) {
close(sock);
return std::unexpected("write() failed");
}
close(sock);
return {};
}

139
src/unix_domain.cpp Normal file
View File

@ -0,0 +1,139 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <chrono>
#include <cstring>
#include <expected>
#include <iostream>
#include <thread>
#include "unix_domain.h"
/**
* @brief Construct a new Unix Domain Request Reply:: Unix Domain Request Reply
* object
*
*/
UnixDomainRequestReply::UnixDomainRequestReply() {}
/**
* @brief Destroy the Unix Domain Request Reply:: Unix Domain Request Reply
* object
*
*/
UnixDomainRequestReply::~UnixDomainRequestReply() {
unlink(SOCKET_PATH); // 清理 socket 文件
}
/**
* @brief
*
* @return std::expected<std::string, std::string>
*/
std::expected<std::string, std::string> UnixDomainRequestReply::receive() {
// 1. 创建UNIX域流式socket
int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd < 0) {
return std::unexpected("创建socket失败: " + std::string(strerror(errno)));
}
// 2. 绑定socket到指定路径
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
// 确保之前的socket文件被删除
unlink(SOCKET_PATH);
if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) < 0) {
close(server_fd);
return std::unexpected("绑定socket失败: " + std::string(strerror(errno)));
}
// 3. 开始监听连接队列长度为1
if (listen(server_fd, 1) < 0) {
close(server_fd);
return std::unexpected("监听失败: " + std::string(strerror(errno)));
}
// 4. 接受客户端连接
int client_fd = accept(server_fd, nullptr, nullptr);
if (client_fd < 0) {
close(server_fd);
return std::unexpected("接受连接失败: " + std::string(strerror(errno)));
}
// 5. 读取客户端数据
char buffer[BUFFER_SIZE] = {0};
ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer));
// 6. 关闭连接使用RAII会更好
close(client_fd);
close(server_fd);
if (bytes_read < 0) {
return std::unexpected("读取数据失败: " + std::string(strerror(errno)));
}
return std::string(buffer, bytes_read);
}
/**
* @brief
*
* @param message
* @return std::expected<void, std::string>
*/
std::expected<void, std::string> UnixDomainRequestReply::send(
const std::string& message) {
int sock = -1;
// 1. 创建socket放在循环外避免重复创建
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
return std::unexpected("创建socket失败: " + std::string(strerror(errno)));
}
// 2. 准备地址结构
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
// 3. 带重试的连接逻辑
bool connected = false;
for (int attempt = 0; attempt < RETRY_COUNT; ++attempt) {
if (connect(sock, (sockaddr*)&addr, sizeof(addr)) == 0) {
connected = true;
break;
}
// 最后一次尝试不等待
if (attempt < RETRY_COUNT - 1) {
std::this_thread::sleep_for(std::chrono::milliseconds(RETRY_DELAY_MS));
}
}
if (!connected) {
close(sock);
return std::unexpected("连接服务端失败: " + std::string(strerror(errno)));
}
// 4. 设置发送超时
timeval timeout{.tv_sec = 5, .tv_usec = 0};
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout))) {
close(sock);
return std::unexpected("设置超时失败: " + std::string(strerror(errno)));
}
// 3. 发送数据
ssize_t bytes_sent = write(sock, message.c_str(), message.size());
if (bytes_sent < 0) {
close(sock);
return std::unexpected("发送数据失败: " + std::string(strerror(errno)));
}
// 4. 关闭连接
close(sock);
return {};
}

27
src/zmq_ipc.cpp Normal file
View File

@ -0,0 +1,27 @@
#include "zmq_ipc.h"
ZMQRequestReply::ZMQRequestReply()
: ctx_(1), // 1个IO线程
receiver_(ctx_, zmq::socket_type::pull),
sender_(ctx_, zmq::socket_type::push) {
// 绑定接收端
receiver_.bind(endpoint_);
// 连接发送端
sender_.connect(endpoint_);
}
ZMQRequestReply::~ZMQRequestReply() {}
std::expected<std::string, std::string> ZMQRequestReply::receive() {
zmq::message_t msg;
auto res = receiver_.recv(msg, zmq::recv_flags::none);
return std::string(msg.to_string());
}
std::expected<void, std::string> ZMQRequestReply::send(
const std::string& message) {
sender_.send(zmq::buffer(message), zmq::send_flags::none);
return {};
}

12
tests/test_shm.cpp Normal file
View File

@ -0,0 +1,12 @@
#include <gtest/gtest.h>
#include <utils.hpp>
#include <shm.h>
INSTANTIATE_TYPED_TEST_SUITE_P(ShmIPCTest, IPCTest, ShmRequestReply);
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -0,0 +1,11 @@
#include <gtest/gtest.h>
#include <unix_domain.h>
#include <utils.hpp>
INSTANTIATE_TYPED_TEST_SUITE_P(UnixDomainTest, IPCTest, UnixDomainRequestReply);
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

12
tests/test_zmq.cpp Normal file
View File

@ -0,0 +1,12 @@
#include <gtest/gtest.h>
#include <utils.hpp>
#include <zmq_ipc.h>
INSTANTIATE_TYPED_TEST_SUITE_P(ZMQIPCTest, IPCTest,ZMQRequestReply);
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

48
tests/utils.hpp Normal file
View File

@ -0,0 +1,48 @@
#include <context.h>
#include <gtest/gtest.h>
#include <shm.h>
#include <thread>
template <class T>
class IPCTest : public testing::Test {
public:
void send(auto msg) {
IpcRequestReplyContext ctx;
ctx.setStrategy(std::make_unique<T>());
auto res = ctx.send(msg);
if (!res) FAIL() << "send failed!" << res.error();
}
void receive(auto msg) {
IpcRequestReplyContext ctx;
ctx.setStrategy(std::make_unique<T>());
auto res = ctx.receive();
if (!res) FAIL() << "receive failed!" << res.error();
EXPECT_EQ(res, msg);
}
};
// +++++++++++++++++++++++++++++++++ Test +++++++++++++++++++++++++++++++++
TYPED_TEST_SUITE_P(IPCTest);
// 单线程发送接收
TYPED_TEST_P(IPCTest, SendAndReceiveTest) {
auto msg = "Hello IPC";
this->send(msg);
this->receive(msg);
}
// 多线程发送接收
TYPED_TEST_P(IPCTest, MutliThreadSendAndReceiveTest) {
auto msg = "Hello IPC";
std::thread send_t([this, msg]() { this->send(msg); });
std::thread receive_t([this, msg]() { this->receive(msg); });
send_t.join();
receive_t.join();
}
REGISTER_TYPED_TEST_SUITE_P(IPCTest, SendAndReceiveTest,
MutliThreadSendAndReceiveTest);

104
xmake.lua Normal file
View File

@ -0,0 +1,104 @@
add_rules("mode.debug", "mode.release")
-- c++ 23 标准
add_languages("c++23")
-- 头文件目录
add_includedirs("include")
-- GoogleTest
add_requires("gtest")
-- cpp-httplib
add_requires("cpp-httplib")
-- zmq
add_requires("cppzmq")
-- argparse
add_requires("argparse")
target("ipc_interface")
set_kind("binary")
add_files("src/*.cpp")
add_packages("cpp-httplib")
add_packages("cppzmq")
add_packages("argparse")
for _, file in ipairs(os.files("tests/test_*.cpp")) do
local name = path.basename(file)
target(name)
set_kind("binary")
add_packages("gtest")
add_packages("cpp-httplib")
add_packages("cppzmq")
set_default(false)
add_includedirs("tests")
add_files(file,"src/*.cpp|main.cpp")
add_tests("default")
end
--
-- If you want to known more usage about xmake, please see https://xmake.io
--
-- ## FAQ
--
-- You can enter the project directory firstly before building project.
--
-- $ cd projectdir
--
-- 1. How to build project?
--
-- $ xmake
--
-- 2. How to configure project?
--
-- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release]
--
-- 3. Where is the build output directory?
--
-- The default output directory is `./build` and you can configure the output directory.
--
-- $ xmake f -o outputdir
-- $ xmake
--
-- 4. How to run and debug target after building project?
--
-- $ xmake run [targetname]
-- $ xmake run -d [targetname]
--
-- 5. How to install target to the system directory or other output directory?
--
-- $ xmake install
-- $ xmake install -o installdir
--
-- 6. Add some frequently-used compilation flags in xmake.lua
--
-- @code
-- -- add debug and release modes
-- add_rules("mode.debug", "mode.release")
--
-- -- add macro definition
-- add_defines("NDEBUG", "_GNU_SOURCE=1")
--
-- -- set warning all as error
-- set_warnings("all", "error")
--
-- -- set language: c99, c++11
-- set_languages("c99", "c++11")
--
-- -- set optimization: none, faster, fastest, smallest
-- set_optimize("fastest")
--
-- -- add include search directories
-- add_includedirs("/usr/include", "/usr/local/include")
--
-- -- add link libraries and search directories
-- add_links("tbox")
-- add_linkdirs("/usr/local/lib", "/usr/lib")
--
-- -- add system link libraries
-- add_syslinks("z", "pthread")
--
-- -- add compilation and link flags
-- add_cxflags("-stdnolib", "-fno-strict-aliasing")
-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true})
--
-- @endcode
--