This commit is contained in:
2026-07-25 23:51:39 -05:00
parent 815c77decb
commit da484bd853
4 changed files with 176 additions and 105 deletions

View File

@@ -3,18 +3,21 @@
#include <arpa/inet.h>
TcpClient::TcpClient(ConfigService* config, LoggerService* logger) : TcpBase(logger) {
TcpClient::TcpClient(ConfigService* config, LoggerService* logger, QObject* parent) : QObject(parent), logger_(logger) {
// load config
if(!(config->getConfig<ClientConfig>("TcpClient", "main", &configuration_))) {
logger_->log("TcpClient", LogFlag::Error, "Failed to get configuration");
return;
}
if(init() == ErrorCode::Success) {
// connect sockets
connect(&socket_, &QTcpSocket::connected, this, &TcpClient::onConnected());
connect(&socket_, &QTcpSocket::disconnected, this, &TcpClient::onDisconnected());
connect(&socket_, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead());
connect(&socket_, &QTcpSocket::errorOccured, this, [this](QAbstractSocket::SocketError) { emit errorOccured(socket_.errorString()); }); // lazy
logger_->log("TcpClient", LogFlag::Info, "TcpClient initialized.");
} else {
logger_->log("TcpClient", LogFlag::Error, "TcpClient failed to initialize.");
}
}
@@ -22,102 +25,156 @@ TcpClient::~TcpClient() {
// close port
}
ErrorCode TcpClient::init() {
logger_->log("TcpClient", LogFlag::Debug, "Initializing TcpClient...");
// keeping everything in here for now
int receiveBytes;
char buffer[1024];
struct hostent* host; // houses host network info
struct sockaddr_in serverAddress; // ip address of the server socket
// parse the hostname string into uint8s
host = gethostbyname(configuration_.hostname.c_str());
if(host == NULL) {
logger_->log("TcpClient", LogFlag::Error, "Unable to parse hostname.");
close(socketFd_); // TODO: probably on a destructor
return ErrorCode::Error;
ErrorCode TcpClient::connect() {
socket_.connectToHost(configuration_.hostname, configuration_.port);
}
// create tcp socket
socketFd_ = socket(AF_INET, SOCK_STREAM, 0);
if(socketFd_ < 0) {
logger_->log("TcpClient", LogFlag::Error, "Unable to open client socket.");
close(socketFd_);
return ErrorCode::Error;
ssize_t send(const char* data, size_t numBytes) {
socket_.write(data, numBytes);
}
// describe the server address from the hostname and port
memset(&serverAddress.sin_zero, 0, sizeof(serverAddress.sin_zero));
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(configuration_.port);
serverAddress.sin_addr = *((struct in_addr*)host->h_addr);
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, host->h_addr_list[0], ip, sizeof(ip));
logger_->log("TcpClient", LogFlag::Error, "Connecting to {}:{}", ip, configuration_.port);
// connect to server
if(connect(socketFd_, (struct sockaddr*)&serverAddress, sizeof(struct sockaddr)) < 0) {
logger_->log("TcpClient", LogFlag::Error, "Unable to connect to server.");
close(socketFd_);
return ErrorCode::Error;
void TcpClient::onConnected() {
logger_->log("TcpClient", LogFlag::Info, "TcpClient connected to server.");
}
// start sending thread (only needed for cli because std::cin is blocking. a regular message box doesn't need to block for input)
pthread_t messengerThread;
if(pthread_create(&messengerThread, NULL, TcpClient::sendMessage, static_cast<TcpClient*>(this)) != 0) {
logger_->log("TcpClient", LogFlag::Debug, "Unable to spawn messenger thread.");
close(socketFd_);
return ErrorCode::Error;
void TcpClient::onDisconnected() {
logger_->log("TcpClient", LogFlag::Info, "TcpClient disconnected from server.");
}
void TcpClient::onReadyRead() {
receiveBuffer_.append(socket_.readAll());
handleMessage();
}
void TcpClient::handleMessage() {
while(true) {
// listen for receive from the server
memset(buffer, 0x00, sizeof(buffer));
receiveBytes = tcpRead(socketFd_, buffer, sizeof(buffer)); // TODO: this currently blocks forever, add timeout
// TODO: separate thread
// we expect a response based on the server design, something went wrong otherwise
if(receiveBytes < 0) {
logger_->log("TcpClient", LogFlag::Error, "Unable to receive from server.");
return ErrorCode::Error;
} else if(receiveBytes == 0) {
logger_->log("TcpClient", LogFlag::Error, "Server closed the connection during receive.");
close(socketFd_);
return ErrorCode::Error;
if(receiveBuffer_.size() < kHeaderSize) {
logger_->log("TcpClient", LogFlag::Error, "Received message size less than header size.");
return;
}
std::string receivedMessage(buffer, receiveBytes);
logger_->log("TcpClient", LogFlag::Debug, "Received {} bytes back from the server: {}", receiveBytes, receivedMessage);
// TODO: do this with a struct
uint32_t msgSize;
uint32_t msgType;
memcpy(&msgSize, receiveBuffer_.constData(), sizeof(msgSize));
memcpy(&msgType, receiveBuffer_.constData() + sizeof(msgSize), sizeof(msgType));
// don't know if there's a way to generalize this part
msgSize = ntohl(msgSize);
msgType = ntohl(msgType);
if(receiveBuffer_.size() != kHeaderSize + msgSize) {
logger_->log("TcpClient", LogFlag::Error, "Received message size ({}), does not match specified ({}).", msgSize, receiveBuffer_.size() - kHeaderSize);
return;
}
close(socketFd_);
pthread_join(messengerThread, NULL);
QByteArray message = receiveBuffer_.mid(kHeaderSize, msgSize);
receiveBuffer_.remove(0, msgSize + kHeaderSize);
emit messageRecieved(message);
return ErrorCode::Success;
}
void* TcpClient::sendMessage(void* arg) {
int sendBytes;
std::string message = "placeholder";
TcpClient* obj = static_cast<TcpClient*>(arg);
while(true) {
obj->logger_->log("TcpClient", LogFlag::Debug, "Enter message...");
std::getline(std::cin, message);
// send data buffer to server
sendBytes = obj->tcpSend(obj->socketFd_, message.c_str(), message.size());
if(sendBytes < 0) {
obj->logger_->log("TcpClient", LogFlag::Error, "Unable to send to server.");
close(obj->socketFd_);
return nullptr;
}
obj->logger_->log("TcpClient", LogFlag::Debug, "Sent {} bytes to the server.", sendBytes);
}
}
// ErrorCode TcpClient::init() {
// logger_->log("TcpClient", LogFlag::Debug, "Initializing TcpClient...");
// // keeping everything in here for now
// int receiveBytes;
// char buffer[1024];
// struct hostent* host; // houses host network info
// struct sockaddr_in serverAddress; // ip address of the server socket
// // parse the hostname string into uint8s
// host = gethostbyname(configuration_.hostname.c_str());
// if(host == NULL) {
// logger_->log("TcpClient", LogFlag::Error, "Unable to parse hostname.");
// close(socketFd_); // TODO: probably on a destructor
// return ErrorCode::Error;
// }
// // create tcp socket
// socketFd_ = socket(AF_INET, SOCK_STREAM, 0);
// if(socketFd_ < 0) {
// logger_->log("TcpClient", LogFlag::Error, "Unable to open client socket.");
// close(socketFd_);
// return ErrorCode::Error;
// }
// // describe the server address from the hostname and port
// memset(&serverAddress.sin_zero, 0, sizeof(serverAddress.sin_zero));
// serverAddress.sin_family = AF_INET;
// serverAddress.sin_port = htons(configuration_.port);
// serverAddress.sin_addr = *((struct in_addr*)host->h_addr);
// char ip[INET_ADDRSTRLEN];
// inet_ntop(AF_INET, host->h_addr_list[0], ip, sizeof(ip));
// logger_->log("TcpClient", LogFlag::Error, "Connecting to {}:{}", ip, configuration_.port);
// // connect to server
// if(connect(socketFd_, (struct sockaddr*)&serverAddress, sizeof(struct sockaddr)) < 0) {
// logger_->log("TcpClient", LogFlag::Error, "Unable to connect to server.");
// close(socketFd_);
// return ErrorCode::Error;
// }
// // start sending thread (only needed for cli because std::cin is blocking. a regular message box doesn't need to block for input)
// pthread_t messengerThread;
// if(pthread_create(&messengerThread, NULL, TcpClient::sendMessage, static_cast<TcpClient*>(this)) != 0) {
// logger_->log("TcpClient", LogFlag::Debug, "Unable to spawn messenger thread.");
// close(socketFd_);
// return ErrorCode::Error;
// }
// while(true) {
// // listen for receive from the server
// memset(buffer, 0x00, sizeof(buffer));
// receiveBytes = tcpRead(socketFd_, buffer, sizeof(buffer)); // TODO: this currently blocks forever, add timeout
// // TODO: separate thread
// // we expect a response based on the server design, something went wrong otherwise
// if(receiveBytes < 0) {
// logger_->log("TcpClient", LogFlag::Error, "Unable to receive from server.");
// return ErrorCode::Error;
// } else if(receiveBytes == 0) {
// logger_->log("TcpClient", LogFlag::Error, "Server closed the connection during receive.");
// close(socketFd_);
// return ErrorCode::Error;
// }
// std::string receivedMessage(buffer, receiveBytes);
// logger_->log("TcpClient", LogFlag::Debug, "Received {} bytes back from the server: {}", receiveBytes, receivedMessage);
// }
// close(socketFd_);
// pthread_join(messengerThread, NULL);
// return ErrorCode::Success;
// }
// void* TcpClient::sendMessage(void* arg) {
// int sendBytes;
// std::string message = "placeholder";
// TcpClient* obj = static_cast<TcpClient*>(arg);
// while(true) {
// obj->logger_->log("TcpClient", LogFlag::Debug, "Enter message...");
// std::getline(std::cin, message);
// // send data buffer to server
// sendBytes = obj->tcpSend(obj->socketFd_, message.c_str(), message.size());
// if(sendBytes < 0) {
// obj->logger_->log("TcpClient", LogFlag::Error, "Unable to send to server.");
// close(obj->socketFd_);
// return nullptr;
// }
// obj->logger_->log("TcpClient", LogFlag::Debug, "Sent {} bytes to the server.", sendBytes);
// }
// }

View File

@@ -22,14 +22,26 @@ public:
TcpClient(ConfigService* config, LoggerService* logger);
~TcpClient();
void connect(); // asynchronous so no errorcode return
ssize_t send(const char* data, size_t numBytes);
signals:
void connected();
void disconnected();
void messageReceived(QByteArray message);
void error(QString error);
private slots:
void onConnected();
void onDisconnected();
void onReadyRead();
private:
ErrorCode init();
// handler for sending messages since std::cin is blocking
static void* sendMessage(void* arg);
void handleMessage();
ClientParams configuration_;
int socketFd_; // file descriptor for the client socket (socket proper is a function)
QTcpSocket socket_;
QByteArray receiveBuffer_[1024];
};

View File

@@ -7,7 +7,7 @@ struct ClientParams {
std::string id;
std::string hostname;
uint32_t port;
uint16_t port;
};
class ClientConfig : public IConfig<ClientParams> {

View File

@@ -6,11 +6,13 @@
// TcpBase contains common tcp I/O operations for the tcp client and tcp server
// Abstracts away the networking methods into an accordion common protocol (ACP)
class TcpBase {
class TcpBase : public QObject{
Q_OBJECT
public:
TcpBase(LoggerService* logger);
TcpBase(LoggerService* logger, QObject* parent = nullptr);
~TcpBase();
// read from a tcp socket, blocking