From 2a05ad9f5a1a018ec9e749fd764ab697689e1312 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Sat, 7 Mar 2026 16:42:19 +0700 Subject: [PATCH 1/6] Remove TODO (done) --- .github/workflows/cpp-build-test-coverage.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/cpp-build-test-coverage.yml b/.github/workflows/cpp-build-test-coverage.yml index efe759c..0495483 100644 --- a/.github/workflows/cpp-build-test-coverage.yml +++ b/.github/workflows/cpp-build-test-coverage.yml @@ -71,7 +71,6 @@ jobs: # ------------------------------------------------------- # Step 4: Run unit tests - # TODO: need to update, I think we should not hard-code the executable name here # ------------------------------------------------------- - name: Run tests run: | From 72aa7eb1b46a68578f37a81d58564d07127dc2c6 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Sat, 7 Mar 2026 17:08:27 +0700 Subject: [PATCH 2/6] Update CMake Update TCPEcho Server --- src/CMakeLists.txt | 2 ++ src/controller/CMakeLists.txt | 4 +-- src/patterns/CMakeLists.txt | 44 +++++++++++------------ src/socket/simple_tcp/TCPServer.cpp | 56 ++++++++++++++++++----------- src/socket/simple_tcp/TCPServer.h | 2 ++ 5 files changed, 64 insertions(+), 44 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7701cd4..4e4fc7c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,8 @@ add_executable(${PROJECT_NAME} main.cpp ${CORE_SOURCES} ${SOCKET_SOURCES} + ${CONTROLLER_SOURCES} + ${DS_SOURCES} ) # Add header include paths diff --git a/src/controller/CMakeLists.txt b/src/controller/CMakeLists.txt index f6e453b..1e44de8 100644 --- a/src/controller/CMakeLists.txt +++ b/src/controller/CMakeLists.txt @@ -1,8 +1,8 @@ set(CONTROLLER_SOURCES # PID - pid/pid.cpp - pid/PIDSim.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pid/pid.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pid/PIDSim.cpp ) set(CONTROLLER_SOURCES ${CONTROLLER_SOURCES} PARENT_SCOPE) \ No newline at end of file diff --git a/src/patterns/CMakeLists.txt b/src/patterns/CMakeLists.txt index 86e9a7c..314e52e 100644 --- a/src/patterns/CMakeLists.txt +++ b/src/patterns/CMakeLists.txt @@ -1,32 +1,32 @@ set(DS_SOURCES # Structural Patterns - structural/Adapter.cpp - structural/Bridge.cpp - structural/Proxy.cpp - structural/Composite.cpp - structural/Flyweight.cpp - structural/Facade.cpp - structural/Decorator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/structural/Adapter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/structural/Bridge.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/structural/Proxy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/structural/Composite.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/structural/Flyweight.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/structural/Facade.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/structural/Decorator.cpp # Behavioral Patterns - behavioral/ChainOfCommand.cpp - behavioral/Command.cpp - behavioral/Iterator.cpp - behavioral/Mediator.cpp - behavioral/Memento.cpp - behavioral/Visitor.cpp - behavioral/TemplateMethod.cpp - behavioral/Strategy.cpp - behavioral/State.cpp - behavioral/Observer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/ChainOfCommand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/Command.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/Iterator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/Mediator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/Memento.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/Visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/TemplateMethod.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/Strategy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/State.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/behavioral/Observer.cpp # Creational Patterns - creational/Singleton.cpp - creational/FactoryMethod.cpp - creational/AbstractFactory.cpp - creational/Builder.cpp - creational/Prototype.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/creational/Singleton.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/creational/FactoryMethod.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/creational/AbstractFactory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/creational/Builder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/creational/Prototype.cpp ) set(DS_SOURCES ${DS_SOURCES} PARENT_SCOPE) \ No newline at end of file diff --git a/src/socket/simple_tcp/TCPServer.cpp b/src/socket/simple_tcp/TCPServer.cpp index 7be6bf5..1aa5e0b 100644 --- a/src/socket/simple_tcp/TCPServer.cpp +++ b/src/socket/simple_tcp/TCPServer.cpp @@ -88,47 +88,63 @@ void TCPServer::acceptLoop() { handleClient(client_fd); } catch (const std::exception& e) { std::cerr << "[TCPServer] client error: " << e.what() << "\n"; + close(client_fd); } + } +} - // close connection after the client session end - close(client_fd); +void TCPServer::sendAll(int fd, const char* data, size_t len) { + size_t total = 0; + while (total < len) { + ssize_t sent = send(fd, data + total, len - total, 0); + if (sent <= 0) { + throw std::runtime_error("send failed"); + } + total += sent; } } void TCPServer::handleClient(int client_fd) { + char buffer[1024]; + // send a notify to the client - std::string welcome = - "Connected to the TCP Server on port" + std::to_string(port_) + "\n"; - if (send(client_fd, welcome.c_str(), welcome.size(), 0) < 0) { - throw std::runtime_error("send failed"); - } + std::string welcome = "Connected to the TCP Server on port " + + std::to_string(port_) + + "\nType 'Q' to stop the connection.\n"; - while (true) { - char buffer[1024]; - std::string prompt = - "Type 'Q' to stop the connection. Enter your messages: "; - if (send(client_fd, prompt.c_str(), prompt.size(), 0) < 0) { - throw std::runtime_error("send failed"); - } + sendAll(client_fd, welcome.c_str(), welcome.size()); + while (true) { // receive data from the client int bytes = recv(client_fd, buffer, sizeof(buffer) - 1, 0); - if (bytes <= 0) { - // bytes == 0 : client closed connection - // bytes < 0 : error + + if (bytes == 0) { // : client closed connection + std::cout << "[TCPServer] client closed connection\n"; + break; + } + + if (bytes < 0) { // : error throw std::runtime_error("recv failed"); } buffer[bytes] = '\0'; - std::cout << "[TCPServer] Received: " << buffer << "\n"; + // print out + std::cout << "client: " << buffer; + + sendAll(client_fd, buffer, bytes); // stop connection if client sends 'Q' - if (buffer[0] == 'Q') { - std::cout << "[TCPServer] Client disconnected\n"; + std::string line(buffer, bytes); + line.erase(line.find_last_not_of("\r\n") + 1); // remove \r\n + if (line == "Q") { + std::cout << "[TCPServer] client disconnected\n"; break; } } + + // close connection after the client session end + close(client_fd); } void TCPServer::start() noexcept(false) { diff --git a/src/socket/simple_tcp/TCPServer.h b/src/socket/simple_tcp/TCPServer.h index 019c5cb..fff399f 100644 --- a/src/socket/simple_tcp/TCPServer.h +++ b/src/socket/simple_tcp/TCPServer.h @@ -43,6 +43,8 @@ class TCPServer { */ void handleClient(int client_fd); + static void sendAll(int fd, const char* data, size_t len); + private: uint16_t port_; // TCP ports are in 0 - 65535 int server_fd_{-1}; // socket descriptor From 5a9d6ef3c8e1f8bb41cc706b522fdc48f790faba Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Sun, 8 Mar 2026 00:19:29 +0700 Subject: [PATCH 3/6] Add simple TCP CLient --- src/socket/CMakeLists.txt | 2 + src/socket/simple_tcp/SimpleTCPClient.cpp | 41 ++++++++++++ src/socket/simple_tcp/TCPClient.cpp | 77 +++++++++++++++++++++++ src/socket/simple_tcp/TCPClient.h | 18 ++++++ 4 files changed, 138 insertions(+) create mode 100644 src/socket/simple_tcp/SimpleTCPClient.cpp create mode 100644 src/socket/simple_tcp/TCPClient.cpp create mode 100644 src/socket/simple_tcp/TCPClient.h diff --git a/src/socket/CMakeLists.txt b/src/socket/CMakeLists.txt index 50b400a..be4f185 100644 --- a/src/socket/CMakeLists.txt +++ b/src/socket/CMakeLists.txt @@ -3,6 +3,8 @@ set(SOCKET_SOURCES # Socket ${CMAKE_CURRENT_SOURCE_DIR}/simple_tcp/TCPServer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/simple_tcp/SimpleTCPServer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/simple_tcp/TCPClient.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/simple_tcp/SimpleTCPClient.cpp ) set(SOCKET_SOURCES ${SOCKET_SOURCES} PARENT_SCOPE) \ No newline at end of file diff --git a/src/socket/simple_tcp/SimpleTCPClient.cpp b/src/socket/simple_tcp/SimpleTCPClient.cpp new file mode 100644 index 0000000..5bfc62e --- /dev/null +++ b/src/socket/simple_tcp/SimpleTCPClient.cpp @@ -0,0 +1,41 @@ +#include +#include "ExampleRegistry.h" +#include "TCPClient.h" + +namespace { + +void run() { + try { + TCPClient client("127.0.0.1", 8080); + + if (!client.connect()) { + std::cout << "connect failed\n"; + return; + } + + client.send("Hello server\n"); + + std::string response = client.receive(); + + std::cout << "server: " << response << std::endl; + + client.close(); + } catch (const std::exception& e) { + std::cout << "error: " << e.what() << std::endl; + } +} + +} // namespace + +class SimpleTCPClient : public IExample { + public: + std::string group() const override { return "socket/tcp"; } + + std::string name() const override { return "SimpleTCPClient"; } + + std::string description() const override { return "Simple TCP Client"; } + + void execute() override { run(); } +}; + +REGISTER_EXAMPLE(SimpleTCPClient, "socket/tcp", "SimpleTCPClient"); \ No newline at end of file diff --git a/src/socket/simple_tcp/TCPClient.cpp b/src/socket/simple_tcp/TCPClient.cpp new file mode 100644 index 0000000..86cc5d0 --- /dev/null +++ b/src/socket/simple_tcp/TCPClient.cpp @@ -0,0 +1,77 @@ +#include "TCPClient.h" + +#include +#include + +#include +#include +#include + +TCPClient::TCPClient(const std::string& host, uint16_t port) + : host_{host}, port_{port}, client_fd_{-1} {} + +bool TCPClient::connect() { + client_fd_ = socket(AF_INET, SOCK_STREAM, 0); + if (client_fd_ < 0) { + throw std::runtime_error("socket failed."); + } + + // specifying the address + sockaddr_in serverAddress{}; + serverAddress.sin_family = AF_INET; + serverAddress.sin_port = htons(port_); + + // serverAddress.sin_addr.s_addr = INADDR_ANY; + if (::inet_pton(AF_INET, host_.c_str(), &serverAddress.sin_addr) <= 0) { + throw std::runtime_error("invalid address"); + } + + // sending connection request + return ( + ::connect(client_fd_, + reinterpret_cast(&serverAddress), // global syscall + sizeof(serverAddress)) == 0); +} + +void TCPClient::send(const std::string& msg) { + if (client_fd_ < 0) { + throw std::runtime_error("socket not connected"); + } + + const char* data = msg.c_str(); + size_t total = 0; + size_t len = msg.size(); + while (total < len) { + ssize_t sent = ::send(client_fd_, data + total, len - total, 0); + if (sent <= 0) { + throw std::runtime_error("send failed"); + } + total += sent; + } +} + +std::string TCPClient::receive() { + if (client_fd_ < 0) { + throw std::runtime_error("socket not connected"); + } + + char buffer[1024]; + + ssize_t bytes = recv(client_fd_, buffer, sizeof(buffer), 0); + if (bytes < 0) { + throw std::runtime_error("recv failed"); + } + + if (bytes == 0) { + throw std::runtime_error("connection closed"); + } + + return std::string(buffer, bytes); +} + +void TCPClient::close() { + if (client_fd_ >= 0) { + ::close(client_fd_); + client_fd_ = -1; + } +} \ No newline at end of file diff --git a/src/socket/simple_tcp/TCPClient.h b/src/socket/simple_tcp/TCPClient.h new file mode 100644 index 0000000..73f416a --- /dev/null +++ b/src/socket/simple_tcp/TCPClient.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include + +class TCPClient { + public: + TCPClient(const std::string& host, uint16_t port); + + bool connect(); + void close(); + void send(const std::string& msg); + std::string receive(); + + private: + std::string host_; + uint16_t port_; + int client_fd_; +}; \ No newline at end of file From 497aacac8312e67ea4c157cedcb0057b3fe2ed66 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Mon, 9 Mar 2026 13:40:54 +0700 Subject: [PATCH 4/6] Update for TCPClient --- src/socket/simple_tcp/TCPClient.cpp | 8 ++++++++ src/socket/simple_tcp/TCPClient.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/socket/simple_tcp/TCPClient.cpp b/src/socket/simple_tcp/TCPClient.cpp index 86cc5d0..c517989 100644 --- a/src/socket/simple_tcp/TCPClient.cpp +++ b/src/socket/simple_tcp/TCPClient.cpp @@ -74,4 +74,12 @@ void TCPClient::close() { ::close(client_fd_); client_fd_ = -1; } +} + +const std::string& TCPClient::getHost() const { + return host_; +} + +uint16_t TCPClient::getPort() const { + return port_; } \ No newline at end of file diff --git a/src/socket/simple_tcp/TCPClient.h b/src/socket/simple_tcp/TCPClient.h index 73f416a..5ddd62c 100644 --- a/src/socket/simple_tcp/TCPClient.h +++ b/src/socket/simple_tcp/TCPClient.h @@ -11,6 +11,8 @@ class TCPClient { void send(const std::string& msg); std::string receive(); + const std::string& getHost() const; + uint16_t getPort() const; private: std::string host_; uint16_t port_; From d07a1e1cb18b211d9b7af515a58729edd6f22f46 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Mon, 9 Mar 2026 13:41:19 +0700 Subject: [PATCH 5/6] Update run script. set Unix Makefiles as default --- run.sh | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/run.sh b/run.sh index 235e75c..3f264b6 100755 --- a/run.sh +++ b/run.sh @@ -9,19 +9,19 @@ set -e # Exit immediately if a command fails PROJECT_EXEC="./build/bin/cpp_lab_project" BUILD_DIR="./build" -if [ ! -d "build" ]; then -echo "Build directory not found. Creating build directory..." +# if [ ! -d "build" ]; then +# echo "Build directory not found. Creating build directory..." -rm -rf build -mkdir build -cd build || exit +# rm -rf build +# mkdir build +# cd build || exit -cmake .. -cd .. +# cmake .. +# cd .. -else -echo "Build directory already exists." -fi +# else +# echo "Build directory already exists." +# fi clear echo "==============================" @@ -38,6 +38,7 @@ done echo "" echo "===========>> Building project..." +cmake -G "Unix Makefiles" -B "$BUILD_DIR" cmake --build "$BUILD_DIR" echo "" From 5c684e15201b5374dc996a024f80e81b904b9f92 Mon Sep 17 00:00:00 2001 From: Phong Nguyen Date: Mon, 9 Mar 2026 13:44:28 +0700 Subject: [PATCH 6/6] Quick update for TCPClient --- src/socket/simple_tcp/SimpleTCPClient.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/socket/simple_tcp/SimpleTCPClient.cpp b/src/socket/simple_tcp/SimpleTCPClient.cpp index 5bfc62e..ff2acc1 100644 --- a/src/socket/simple_tcp/SimpleTCPClient.cpp +++ b/src/socket/simple_tcp/SimpleTCPClient.cpp @@ -1,4 +1,5 @@ #include +#include #include "ExampleRegistry.h" #include "TCPClient.h" @@ -12,15 +13,26 @@ void run() { std::cout << "connect failed\n"; return; } - - client.send("Hello server\n"); - + std::cout << "connected to " << "host: " << client.getHost() + << " port:" << client.getPort() << "\n"; std::string response = client.receive(); - - std::cout << "server: " << response << std::endl; + std::cout << response << std::endl; + std::string msg; + while (true) { + if (!std::getline(std::cin, msg)) { + std::cout << "get line failed" << std::endl; + break; + }; + + // TODO: why we need \n here + client.send(msg + "\n"); + response = client.receive(); + std::cout << response; + } client.close(); } catch (const std::exception& e) { + // Connection closed by foreign host std::cout << "error: " << e.what() << std::endl; } }