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: | 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 "" 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/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..ff2acc1 --- /dev/null +++ b/src/socket/simple_tcp/SimpleTCPClient.cpp @@ -0,0 +1,53 @@ +#include +#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; + } + std::cout << "connected to " << "host: " << client.getHost() + << " port:" << client.getPort() << "\n"; + std::string response = client.receive(); + 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; + } +} + +} // 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..c517989 --- /dev/null +++ b/src/socket/simple_tcp/TCPClient.cpp @@ -0,0 +1,85 @@ +#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; + } +} + +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 new file mode 100644 index 0000000..5ddd62c --- /dev/null +++ b/src/socket/simple_tcp/TCPClient.h @@ -0,0 +1,20 @@ +#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(); + + const std::string& getHost() const; + uint16_t getPort() const; + private: + std::string host_; + uint16_t port_; + int client_fd_; +}; \ 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