[
  {
    "path": ".dockerignore",
    "content": "cmake-build\ncmake-build-debug\nbuild"
  },
  {
    "path": ".github/workflows/cmake.yml",
    "content": "name: CMake\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\nenv:\n  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)\n  BUILD_TYPE: Release\n\njobs:\n  build:\n    # The CMake configure and build commands are platform agnostic and should work equally\n    # well on Windows or Mac.  You can convert this to a matrix build if you need\n    # cross-platform coverage.\n    # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Configure CMake\n      # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.\n      # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type\n      run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}\n\n    - name: Build\n      # Build your program with the given configuration\n      run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}\n\n    - name: Test\n      working-directory: ${{github.workspace}}/build\n      # Execute tests defined by the CMake configuration.  \n      # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail\n      run: ctest -C ${{env.BUILD_TYPE}}\n      \n"
  },
  {
    "path": ".gitignore",
    "content": "/build/\n.idea/\ncmake-build-debug/\ncmake-build/\n"
  },
  {
    "path": ".ignore",
    "content": "cmake-build-debug/\n.idea/\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.8.1)\nproject(tcp_client_server)\n\nfind_package (Threads)\n\nset(CMAKE_CXX_STANDARD 11)\nset(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} \"-std=c++11\")\n\nadd_library(${PROJECT_NAME}\n        src/tcp_client.cpp\n        src/tcp_server.cpp\n        src/client.cpp\n        src/pipe_ret_t.cpp\n        src/common.cpp)\n\noption(SERVER_EXAMPLE \"Build SERVER\" ON)\n\nif(SERVER_EXAMPLE)\n\n    add_definitions(\n            -DSERVER_EXAMPLE\n    )\n\n    add_executable(tcp_server examples/server_example.cpp)\n\n    target_link_libraries (tcp_server ${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})\n\nendif()\n\noption(CLIENT_EXAMPLE \"Build CLIENT\" ON)\n\nif(CLIENT_EXAMPLE)\n\n    add_definitions(\n            -DCLIENT_EXAMPLE\n    )\n\n    add_executable(tcp_client examples/client_example.cpp)\n\n    target_link_libraries (tcp_client ${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})\n\nendif()\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM alpine:3.8\n\nRUN set -ex && \\\n    apk add --no-cache gcc musl-dev cmake cmake clang clang-dev make g++ libc-dev linux-headers\n\nWORKDIR /usr/src/tcp_server_client\n\nCOPY . .\n\nRUN chmod +x build.sh\nRUN ./build.sh\n\nEXPOSE 65123\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Elhay Rauper\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![cmake workflow](https://github.com/elhayra/tcp_server_client/actions/workflows/cmake.yml/badge.svg)\n\n# TCP server client\nA thin and simple C++ TCP client and server library with examples.\n\n### Platforms Support\nCurrently, both linux and mac are supported\n\n### Examples\nSimple tcp server-client examples. They are optimized for simplicity and ease of use/read but not for performance. However, I believe tuning this code to suite your needs should be easy in most cases.\nYou can find code examples of both server and client under the 'examples' directory. Both the library and the examples are well documented.\n\n### Server\nThe server is thread-safe, and can handle multiple clients at the same time, and remove dead clients resources automatically. \n\n## Quick start\nbuild the examples and static library file:\n```bash\n$ cd tcp_server_client\n$ ./build\n```\n\nrun the server and client examples:\nNavigate into the `build` folder and run in the terminal:\n```bash\n$ cd build\n```\nIn terminal #1:\n```bash\n$ ./tcp_server\n```\nIn terminal #2:\n```bash\n$ ./tcp_client\n```\n\n## Building \n\nThis project is set to use CMake to build both the *client example* and the *server example*. In addition, CMake builds a static *library file* to hold the common code to both server and client.\n\nIn order to build the project you can either use the `build.sh` script:\n```bash\n$ cd tcp_server_client\n$ ./build\n```\n\nor build it manually:\n```bash\n$ cd tcp_server_client\n$ mkdir build\n$ cmake ..\n$ make\n```\n\nThe build process generate three files: `libtcp_client_server.a`, `tcp_client` and `tcp_server`.\nThe last two are the executables of the examples which can be executed in the terminal. \n\n\n#### Building Only Server or Client\n\nBy default, the CMake configuration builds both server and client. However, you can use flags to build only one of the apps as follows:\n\n###### Disabling the server build\n\nTo build only the client, disable the server build by replace the `cmake ..` call by:\n\n```bash\ncmake -DSERVER_EXAMPLE=OFF ..\n```\nAnd then run the make command as usual.\n\n###### Disabling the client build\n\nTo build only the server, disable the client build by replace the `cmake ..` call by:\n\n```bash\ncmake -DCLIENT_EXAMPLE=OFF ..\n```\nAnd then run the make command as usual.\n\n## Run the Examples \nNavigate into the `build` folder and run in the terminal:\n```bash\n$ ./tcp_server\n```\n\nYou should see a menu output on the screen, we'll get back to that.\nIn a different terminal, run the client:\n````bash\n$ ./tcp_client\n````\n\nYou should see a similar menu for the client too. In addition, as mentioned, the client will try to connect to the server right away, so you should also see an output messages on both client and server terminals indicating that the connection succeeded.\nNow, feel free to play with each of the client/server menus. You can exchange messages b/w client and server, print active clients etc. You can also spawn more clients in other terminals to see how the server handles multiple clients.\n\n## Server-Client API\n\nAfter playing with the examples, go into the examples source code and have a look at the `main()` function to learn how the server and client interacts. The examples are heavily documented.\nIn addition, you can also look at the public functions in `tcp_client.h` and `tcp_server.h` to learn what APIs are available.\n\n### Server and Client Events \nBoth server and client are using the observer design pattern to register and handle events.\nWhen registering to an event with a callback, you should make sure that:\n- The callback is fast (not doing any heavy lifting tasks) because those callbacks are called from the context of the server or client. \n- No server / client function calls are made in those callbacks to avoid possible deadlock.\n"
  },
  {
    "path": "build.sh",
    "content": "#!/bin/sh\n\nmkdir -p build && \\\ncd build && \\\ncmake .. && make\n\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "\nservices:\n  tcp-server:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    command: sh -c \"cd build && ./tcp_server\"\n\n  tcp-client:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    command: sh -c \"cd build && ./tcp_client\"\n\n"
  },
  {
    "path": "examples/client_example.cpp",
    "content": "///////////////////////////////////////////////////////////\n/////////////////////CLIENT EXAMPLE////////////////////////\n///////////////////////////////////////////////////////////\n\n#ifdef CLIENT_EXAMPLE\n\n#include <iostream>\n#include <csignal>\n#include \"../include/tcp_client.h\"\n\nTcpClient client;\n\n// on sig_exit, close client\nvoid sig_exit(int s)\n{\n\tstd::cout << \"Closing client...\\n\";\n\tpipe_ret_t finishRet = client.close();\n\tif (finishRet.isSuccessful()) {\n\t\tstd::cout << \"Client closed.\\n\";\n\t} else {\n\t\tstd::cout << \"Failed to close client.\\n\";\n\t}\n\texit(0);\n}\n\n// observer callback. will be called for every new message received by the server\nvoid onIncomingMsg(const char * msg, size_t size) {\n\tstd::cout << \"Got msg from server: \" << msg << \"\\n\";\n}\n\n// observer callback. will be called when server disconnects\nvoid onDisconnection(const pipe_ret_t & ret) {\n\tstd::cout << \"Server disconnected: \" << ret.message() << \"\\n\";\n}\n\nvoid printMenu() {\n    std::cout << \"select one of the following options: \\n\" <<\n                 \"1. send message to server\\n\" <<\n                 \"2. close client and exit\\n\";\n}\n\nint getMenuSelection() {\n    int selection = 0;\n    std::cin >> selection;\n    if (!std::cin) {\n        throw std::runtime_error(\"invalid menu input. expected a number, but got something else\");\n    }\n    std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\\n');\n    return selection;\n}\n\nbool handleMenuSelection(int selection) {\n    static const int minSelection = 1;\n    static const int maxSelection = 2;\n    if (selection < minSelection || selection > maxSelection) {\n        std::cout << \"invalid selection: \" << selection <<\n                     \". selection must be b/w \" << minSelection << \" and \" << maxSelection << \"\\n\";\n        return false;\n    }\n    switch (selection) {\n        case 1: { // send message to server\n            std::cout << \"enter message to send:\\n\";\n            std::string message;\n            std::cin >> message;\n            pipe_ret_t sendRet = client.sendMsg(message.c_str(), message.size());\n            if (!sendRet.isSuccessful()) {\n                std::cout << \"Failed to send message: \" << sendRet.message() << \"\\n\";\n            } else {\n                std::cout << \"message was sent successfuly\\n\";\n            }\n            break;\n        }\n        case 2: { // close client\n            const pipe_ret_t closeResult = client.close();\n            if (!closeResult.isSuccessful()) {\n                std::cout << \"closing client failed: \" << closeResult.message() << \"\\n\";\n            } else {\n                std::cout << \"closed client successfully\\n\";\n            }\n            return true;\n        }\n        default: {\n            std::cout << \"invalid selection: \" << selection <<\n                      \". selection must be b/w \" << minSelection << \" and \" << maxSelection << \"\\n\";\n        }\n    }\n    return false;\n}\n\nint main() {\n    //register to SIGINT to close client when user press ctrl+c\n\tsignal(SIGINT, sig_exit);\n\n    // configure and register observer\n    client_observer_t observer;\n\tobserver.wantedIP = \"127.0.0.1\";\n\tobserver.incomingPacketHandler = onIncomingMsg;\n\tobserver.disconnectionHandler = onDisconnection;\n\tclient.subscribe(observer);\n\n\t// connect client to an open server\n\tbool connected = false;\n    while (!connected) {\n        pipe_ret_t connectRet = client.connectTo(\"127.0.0.1\", 65123);\n        connected = connectRet.isSuccessful();\n        if (connected) {\n            std::cout << \"Client connected successfully\\n\";\n        } else {\n            std::cout << \"Client failed to connect: \" << connectRet.message() << \"\\n\"\n                      << \"Make sure the server is open and listening\\n\\n\";\n            sleep(2);\n            std::cout << \"Retrying to connect...\\n\";\n        }\n\t};\n\n\t// send messages to server\n\tbool shouldTerminate = false;\n\twhile(!shouldTerminate)\n\t{\n        printMenu();\n        int selection = getMenuSelection();\n        shouldTerminate = handleMenuSelection(selection);\n\t}\n\n\treturn 0;\n}\n\n#endif"
  },
  {
    "path": "examples/server_example.cpp",
    "content": "///////////////////////////////////////////////////////////\n/////////////////////SERVER EXAMPLE////////////////////////\n///////////////////////////////////////////////////////////\n\n#ifdef SERVER_EXAMPLE\n\n#include <iostream>\n#include <csignal>\n\n#include \"../include/tcp_server.h\"\n\n\n// declare the server\nTcpServer server;\n\n// declare a server observer which will receive incomingPacketHandler messages.\n// the server supports multiple observers\nserver_observer_t observer1, observer2;\n\n// observer callback. will be called for every new message received by clients\n// with the requested IP address\nvoid onIncomingMsg1(const std::string &clientIP, const char * msg, size_t size) {\n    std::string msgStr = msg;\n    // print client message\n    std::cout << \"Observer1 got client msg: \" << msgStr << \"\\n\";\n}\n\n// observer callback. will be called for every new message received by clients\n// with the requested IP address\nvoid onIncomingMsg2(const std::string &clientIP, const char * msg, size_t size) {\n    std::string msgStr = msg;\n    // print client message\n    std::cout << \"Observer2 got client msg: \" << msgStr << \"\\n\";\n}\n\n// observer callback. will be called when client disconnects\nvoid onClientDisconnected(const std::string &ip, const std::string &msg) {\n    std::cout << \"Client: \" << ip << \" disconnected. Reason: \" << msg << \"\\n\";\n}\n\n// accept a single client.\n// if you wish to accept multiple clients, call this function in a loop\n// (you might want to use a thread to accept clients without blocking)\nvoid acceptClient() {\n    try {\n        std::cout << \"waiting for incoming client...\\n\";\n        std::string clientIP = server.acceptClient(0);\n        std::cout << \"accepted new client with IP: \" << clientIP << \"\\n\" <<\n                  \"== updated list of accepted clients ==\" << \"\\n\";\n        server.printClients();\n    } catch (const std::runtime_error &error) {\n        std::cout << \"Accepting client failed: \" << error.what() << \"\\n\";\n    }\n}\n\nvoid printMenu() {\n    std::cout << \"\\n\\nselect one of the following options: \\n\" <<\n              \"1. send all clients a message\\n\" <<\n              \"2. print list of accepted clients\\n\" <<\n              \"3. send message to a specific client\\n\" <<\n              \"4. close server and exit\\n\";\n}\n\nint getMenuSelection() {\n    int selection = 0;\n    std::cin >> selection;\n    if (!std::cin) {\n        throw std::runtime_error(\"invalid menu input. expected a number, but got something else\");\n    }\n    std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\\n');\n    return selection;\n}\n\n/**\n * handle menu selection and return true in case program should terminate\n * after handling selection\n */\nbool handleMenuSelection(int selection) {\n    static const int minSelection = 1;\n    static const int maxSelection = 4;\n    if (selection < minSelection || selection > maxSelection) {\n        std::cout << \"invalid selection: \" << selection <<\n                  \". selection must be b/w \" << minSelection << \" and \" << maxSelection << \"\\n\";\n        return false;\n    }\n    switch (selection) {\n        case 1: { // send all clients a message\n            std::string msg;\n            std::cout << \"type message to send to all connected clients:\\n\";\n            getline(std::cin, msg);\n            pipe_ret_t sendingResult = server.sendToAllClients(msg.c_str(), msg.size());\n            if (sendingResult.isSuccessful()) {\n                std::cout << \"sent message to all clients successfully\\n\";\n            } else {\n                std::cout << \"failed to sent message: \" << sendingResult.message() << \"\\n\";\n            }\n            break;\n        }\n        case 2: { // print list of accepted clients\n            server.printClients();\n            break;\n        }\n        case 3: { // send message to a specific client\n            std::cout << \"enter client IP:\\n\";\n            std::string clientIP;\n            std::cin >> clientIP;\n            std::cout << \"enter message to send:\\n\";\n            std::string message;\n            std::cin >> message;\n            pipe_ret_t result = server.sendToClient(clientIP, message.c_str(), message.size());\n            if (!result.isSuccessful()) {\n                std::cout << \"sending failed: \" << result.message() << \"\\n\";\n            } else {\n                std::cout << \"sending succeeded\\n\";\n            }\n            break;\n        };\n        case 4: { // close server\n            pipe_ret_t sendingResult = server.close();\n            if (sendingResult.isSuccessful()) {\n                std::cout << \"closed server successfully\\n\";\n            } else {\n                std::cout << \"failed to close server: \" << sendingResult.message() << \"\\n\";\n            }\n            return true;\n        }\n        default: {\n            std::cout << \"invalid selection: \" << selection <<\n                      \". selection must be b/w \" << minSelection << \" and \" << maxSelection << \"\\n\";\n        }\n    }\n    return false;\n}\n\nint main()\n{\n    // start server on port 65123\n    pipe_ret_t startRet = server.start(65123);\n    if (startRet.isSuccessful()) {\n        std::cout << \"Server setup succeeded\\n\";\n    } else {\n        std::cout << \"Server setup failed: \" << startRet.message() << \"\\n\";\n        return EXIT_FAILURE;\n    }\n\n    // configure and register observer1\n    observer1.incomingPacketHandler = onIncomingMsg1;\n    observer1.disconnectionHandler = onClientDisconnected;\n    observer1.wantedIP = \"127.0.0.1\";\n    server.subscribe(observer1);\n\n    // configure and register observer2\n    observer2.incomingPacketHandler = onIncomingMsg2;\n    observer2.disconnectionHandler = nullptr; // nullptr or not setting this means we don't care about disconnection event\n    observer2.wantedIP = \"10.88.0.11\"; // use empty string instead to receive messages from any IP address\n    server.subscribe(observer2);\n\n    acceptClient();\n\n    bool shouldTerminate = false;\n    while(!shouldTerminate) {\n        printMenu();\n        int selection = getMenuSelection();\n        shouldTerminate = handleMenuSelection(selection);\n    }\n\n    return 0;\n}\n\n#endif"
  },
  {
    "path": "include/client.h",
    "content": "#pragma once\n\n#include <string>\n#include <thread>\n#include <functional>\n#include <mutex>\n#include <atomic>\n\n#include \"pipe_ret_t.h\"\n#include \"client_event.h\"\n#include \"file_descriptor.h\"\n\n\nclass Client {\n\n    using client_event_handler_t = std::function<void(const Client&, ClientEvent, const std::string&)>;\n\nprivate:\n    FileDescriptor _sockfd;\n    std::string _ip = \"\";\n    std::atomic<bool> _isConnected;\n    std::thread * _receiveThread = nullptr;\n    client_event_handler_t _eventHandlerCallback;\n\n    void setConnected(bool flag) { _isConnected = flag; }\n\n    void receiveTask();\n\n    void terminateReceiveThread();\n\npublic:\n    Client(int);\n\n    bool operator ==(const Client & other) const ;\n\n    void setIp(const std::string & ip) { _ip = ip; }\n    std::string getIp() const { return _ip; }\n\n    void setEventsHandler(const client_event_handler_t & eventHandler) { _eventHandlerCallback = eventHandler; }\n    void publishEvent(ClientEvent clientEvent, const std::string &msg = \"\");\n\n\n    bool isConnected() const { return _isConnected; }\n\n    void startListen();\n\n    void send(const char * msg, size_t msgSize) const;\n\n    void close();\n\n    void print() const;\n\n};\n\n\n"
  },
  {
    "path": "include/client_event.h",
    "content": "#pragma once\n\nenum ClientEvent {\n    DISCONNECTED,\n    INCOMING_MSG\n};"
  },
  {
    "path": "include/client_observer.h",
    "content": "#pragma once\n\n#include <string>\n#include <functional>\n#include \"pipe_ret_t.h\"\n\nstruct client_observer_t {\n    std::string wantedIP = \"\";\n    std::function<void(const char * msg, size_t size)> incomingPacketHandler = nullptr;\n    std::function<void(const pipe_ret_t & ret)> disconnectionHandler = nullptr;\n};\n\n\n"
  },
  {
    "path": "include/common.h",
    "content": "#pragma once\n\n#include <cstdio>\n\n#define MAX_PACKET_SIZE 4096\n\nnamespace fd_wait {\n    enum Result {\n        FAILURE,\n        TIMEOUT,\n        SUCCESS\n    };\n\n    Result waitFor(const FileDescriptor &fileDescriptor, uint32_t timeoutSeconds = 1);\n};\n\n\n\n\n"
  },
  {
    "path": "include/file_descriptor.h",
    "content": "#pragma once\n\nclass FileDescriptor {\nprivate:\n    int _sockfd = 0;\n\npublic:\n    void set(int fd) { _sockfd = fd; }\n    int get() const { return _sockfd; }\n};"
  },
  {
    "path": "include/pipe_ret_t.h",
    "content": "#pragma once\n\nclass pipe_ret_t {\n\nprivate:\n\n    bool _successFlag = false;\n    std::string _msg = \"\";\n\npublic:\n\n    pipe_ret_t() = default;\n    pipe_ret_t(bool successFlag, const std::string &msg) :\n        _successFlag{successFlag},\n        _msg{msg}\n    {}\n\n    std::string message() const { return _msg; }\n    bool isSuccessful() const { return _successFlag; }\n\n    static pipe_ret_t failure(const std::string & msg);\n    static pipe_ret_t success(const std::string &msg = \"\");\n};\n\n"
  },
  {
    "path": "include/server_observer.h",
    "content": "#pragma once\n\n#include <string>\n#include <functional>\n#include \"client.h\"\n\nstruct server_observer_t {\n\tstd::string wantedIP = \"\";\n\tstd::function<void(const std::string &clientIP, const char * msg, size_t size)> incomingPacketHandler;\n\tstd::function<void(const std::string &ip, const std::string &msg)> disconnectionHandler;\n};\n\n"
  },
  {
    "path": "include/tcp_client.h",
    "content": "#pragma once\n\n#include <iostream>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n#include <netdb.h>\n#include <vector>\n#include <errno.h>\n#include <thread>\n#include <mutex>\n#include <atomic>\n#include \"client_observer.h\"\n#include \"pipe_ret_t.h\"\n#include \"file_descriptor.h\"\n\n\nclass TcpClient\n{\nprivate:\n    FileDescriptor _sockfd;\n    std::atomic<bool> _isConnected;\n    std::atomic<bool> _isClosed;\n    struct sockaddr_in _server;\n    std::vector<client_observer_t> _subscibers;\n    std::thread * _receiveTask = nullptr;\n    std::mutex _subscribersMtx;\n\n    void initializeSocket();\n    void startReceivingMessages();\n    void setAddress(const std::string& address, int port);\n    void publishServerMsg(const char * msg, size_t msgSize);\n    void publishServerDisconnected(const pipe_ret_t & ret);\n    void receiveTask();\n    void terminateReceiveThread();\n\npublic:\n    TcpClient();\n    ~TcpClient();\n    pipe_ret_t connectTo(const std::string & address, int port);\n    pipe_ret_t sendMsg(const char * msg, size_t size);\n\n    void subscribe(const client_observer_t & observer);\n    bool isConnected() const { return _isConnected; }\n    pipe_ret_t close();\n};\n\n"
  },
  {
    "path": "include/tcp_server.h",
    "content": "#pragma once\n\n#include <vector>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <thread>\n#include <functional>\n#include <cstring>\n#include <errno.h>\n#include <iostream>\n#include <mutex>\n#include \"client.h\"\n#include \"server_observer.h\"\n#include \"pipe_ret_t.h\"\n#include \"file_descriptor.h\"\n\nclass TcpServer {\n\nprivate:\n\n    FileDescriptor _sockfd;\n    struct sockaddr_in _serverAddress;\n    struct sockaddr_in _clientAddress;\n    fd_set _fds;\n    std::vector<Client*> _clients;\n    std::vector<server_observer_t> _subscribers;\n\n    std::mutex _subscribersMtx;\n    std::mutex _clientsMtx;\n\n    std::thread * _clientsRemoverThread = nullptr;\n    std::atomic<bool> _stopRemoveClientsTask;\n\n    void publishClientMsg(const Client & client, const char * msg, size_t msgSize);\n    void publishClientDisconnected(const std::string&, const std::string&);\n    pipe_ret_t waitForClient(uint32_t timeout);\n    void clientEventHandler(const Client&, ClientEvent, const std::string &msg);\n    void removeDeadClients();\n    void terminateDeadClientsRemover();\n    static pipe_ret_t sendToClient(const Client & client, const char * msg, size_t size);\n\npublic:\n    TcpServer();\n    ~TcpServer();\n    pipe_ret_t start(int port, int maxNumOfClients = 5, bool removeDeadClientsAutomatically = true);\n    void initializeSocket();\n    void bindAddress(int port);\n    void listenToClients(int maxNumOfClients);\n    std::string acceptClient(uint timeout);\n    void subscribe(const server_observer_t & observer);\n    pipe_ret_t sendToAllClients(const char * msg, size_t size);\n    pipe_ret_t sendToClient(const std::string & clientIP, const char * msg, size_t size);\n    pipe_ret_t close();\n    void printClients();\n};\n\n"
  },
  {
    "path": "src/client.cpp",
    "content": "#include <cstdio>\n#include <cstring>\n#include <cerrno>\n#include <unistd.h>\n#include <stdexcept>\n#include <sys/socket.h>\n#include <iostream>\n\n#include \"../include/client.h\"\n#include \"../include/common.h\"\n\nClient::Client(int fileDescriptor) {\n    _sockfd.set(fileDescriptor);\n    setConnected(false);\n}\n\nbool Client::operator==(const Client & other) const {\n    if ((this->_sockfd.get() == other._sockfd.get()) &&\n        (this->_ip == other._ip) ) {\n        return true;\n    }\n    return false;\n}\n\nvoid Client::startListen() {\n    setConnected(true);\n    _receiveThread = new std::thread(&Client::receiveTask, this);\n}\n\nvoid Client::send(const char *msg, size_t msgSize) const {\n    const size_t numBytesSent = ::send(_sockfd.get(), (char *)msg, msgSize, 0);\n\n    const bool sendFailed = (numBytesSent < 0);\n    if (sendFailed) {\n        throw std::runtime_error(strerror(errno));\n    }\n\n    const bool notAllBytesWereSent = (numBytesSent < msgSize);\n    if (notAllBytesWereSent) {\n        char errorMsg[100];\n        sprintf(errorMsg, \"Only %lu bytes out of %lu was sent to client\", numBytesSent, msgSize);\n        throw std::runtime_error(errorMsg);\n    }\n}\n\n/*\n * Receive client packets, and notify user\n */\nvoid Client::receiveTask() {\n    while(isConnected()) {\n        const fd_wait::Result waitResult = fd_wait::waitFor(_sockfd);\n\n        if (waitResult == fd_wait::Result::FAILURE) {\n            throw std::runtime_error(strerror(errno));\n        } else if (waitResult == fd_wait::Result::TIMEOUT) {\n            continue;\n        }\n\n        char receivedMessage[MAX_PACKET_SIZE];\n        const size_t numOfBytesReceived = recv(_sockfd.get(), receivedMessage, MAX_PACKET_SIZE, 0);\n\n        if(numOfBytesReceived < 1) {\n            const bool clientClosedConnection = (numOfBytesReceived == 0);\n            std::string disconnectionMessage;\n            if (clientClosedConnection) {\n                disconnectionMessage = \"Client closed connection\";\n            } else {\n                disconnectionMessage = strerror(errno);\n            }\n            setConnected(false);\n            publishEvent(ClientEvent::DISCONNECTED, disconnectionMessage);\n            return;\n        } else {\n            publishEvent(ClientEvent::INCOMING_MSG, receivedMessage);\n        }\n    }\n}\n\nvoid Client::publishEvent(ClientEvent clientEvent, const std::string &msg) {\n    _eventHandlerCallback(*this, clientEvent, msg);\n}\n\nvoid Client::print() const {\n    const std::string connected = isConnected() ? \"True\" : \"False\";\n    std::cout << \"-----------------\\n\" <<\n              \"IP address: \" << getIp() << std::endl <<\n              \"Connected?: \" << connected << std::endl <<\n              \"Socket FD: \" << _sockfd.get() << std::endl;\n}\n\nvoid Client::terminateReceiveThread() {\n    setConnected(false);\n    if (_receiveThread) {\n        _receiveThread->join();\n        delete _receiveThread;\n        _receiveThread = nullptr;\n    }\n}\n\nvoid Client::close() {\n    terminateReceiveThread();\n\n    const bool closeFailed = (::close(_sockfd.get()) == -1);\n    if (closeFailed) {\n        throw std::runtime_error(strerror(errno));\n    }\n}\n\n"
  },
  {
    "path": "src/common.cpp",
    "content": "#include <cstdint>\n#include \"../include/file_descriptor.h\"\n#include \"../include/common.h\"\n\n#include <sys/select.h>\n\n#define SELECT_FAILED -1\n#define SELECT_TIMEOUT 0\n\nnamespace fd_wait {\n    /**\n     * monitor file descriptor and wait for I/O operation\n     */\n    Result waitFor(const FileDescriptor &fileDescriptor, uint32_t timeoutSeconds) {\n        struct timeval tv;\n        tv.tv_sec = timeoutSeconds;\n        tv.tv_usec = 0;\n        fd_set fds;\n\n        FD_ZERO(&fds);\n        FD_SET(fileDescriptor.get(), &fds);\n        const int selectRet = select(fileDescriptor.get() + 1, &fds, nullptr, nullptr, &tv);\n\n        if (selectRet == SELECT_FAILED) {\n            return Result::FAILURE;\n        } else if (selectRet == SELECT_TIMEOUT) {\n            return Result::TIMEOUT;\n        }\n        return Result::SUCCESS;\n    }\n}\n\n"
  },
  {
    "path": "src/pipe_ret_t.cpp",
    "content": "#include <string>\n#include \"../include/pipe_ret_t.h\"\n\npipe_ret_t pipe_ret_t::failure(const std::string &msg) {\n    return pipe_ret_t(false, msg);\n}\n\npipe_ret_t pipe_ret_t::success(const std::string &msg) {\n    return pipe_ret_t(true, msg);\n}\n"
  },
  {
    "path": "src/tcp_client.cpp",
    "content": "\n#include \"../include/tcp_client.h\"\n#include \"../include/common.h\"\n\nTcpClient::TcpClient() {\n    _isConnected = false;\n    _isClosed = true;\n}\n\nTcpClient::~TcpClient() {\n    close();\n}\n\npipe_ret_t TcpClient::connectTo(const std::string & address, int port) {\n    try {\n        initializeSocket();\n        setAddress(address, port);\n    } catch (const std::runtime_error& error) {\n        return pipe_ret_t::failure(error.what());\n    }\n\n    const int connectResult = connect(_sockfd.get() , (struct sockaddr *)&_server , sizeof(_server));\n    const bool connectionFailed = (connectResult == -1);\n    if (connectionFailed) {\n        return pipe_ret_t::failure(strerror(errno));\n    }\n\n    startReceivingMessages();\n    _isConnected = true;\n    _isClosed = false;\n\n    return pipe_ret_t::success();\n}\n\nvoid TcpClient::startReceivingMessages() {\n    _receiveTask = new std::thread(&TcpClient::receiveTask, this);\n}\n\nvoid TcpClient::initializeSocket() {\n    pipe_ret_t ret;\n\n    _sockfd.set(socket(AF_INET , SOCK_STREAM , 0));\n    const bool socketFailed = (_sockfd.get() == -1);\n    if (socketFailed) {\n        throw std::runtime_error(strerror(errno));\n    }\n}\n\nvoid TcpClient::setAddress(const std::string& address, int port) {\n    const int inetSuccess = inet_aton(address.c_str(), &_server.sin_addr);\n\n    if(!inetSuccess) { // inet_addr failed to parse address\n        // if hostname is not in IP strings and dots format, try resolve it\n        struct hostent *host;\n        struct in_addr **addrList;\n        if ( (host = gethostbyname( address.c_str() ) ) == nullptr){\n            throw std::runtime_error(\"Failed to resolve hostname\");\n        }\n        addrList = (struct in_addr **) host->h_addr_list;\n        _server.sin_addr = *addrList[0];\n    }\n    _server.sin_family = AF_INET;\n    _server.sin_port = htons(port);\n}\n\n\npipe_ret_t TcpClient::sendMsg(const char * msg, size_t size) {\n    const size_t numBytesSent = send(_sockfd.get(), msg, size, 0);\n\n    if (numBytesSent < 0 ) { // send failed\n        return pipe_ret_t::failure(strerror(errno));\n    }\n    if (numBytesSent < size) { // not all bytes were sent\n        char errorMsg[100];\n        sprintf(errorMsg, \"Only %lu bytes out of %lu was sent to client\", numBytesSent, size);\n        return pipe_ret_t::failure(errorMsg);\n    }\n    return pipe_ret_t::success();\n}\n\nvoid TcpClient::subscribe(const client_observer_t & observer) {\n    std::lock_guard<std::mutex> lock(_subscribersMtx);\n    _subscibers.push_back(observer);\n}\n\n/*\n * Publish incomingPacketHandler client message to observer.\n * Observers get only messages that originated\n * from clients with IP address identical to\n * the specific observer requested IP\n */\nvoid TcpClient::publishServerMsg(const char * msg, size_t msgSize) {\n    std::lock_guard<std::mutex> lock(_subscribersMtx);\n    for (const auto &subscriber : _subscibers) {\n        if (subscriber.incomingPacketHandler) {\n            subscriber.incomingPacketHandler(msg, msgSize);\n        }\n    }\n}\n\n/*\n * Publish client disconnection to observer.\n * Observers get only notify about clients\n * with IP address identical to the specific\n * observer requested IP\n */\nvoid TcpClient::publishServerDisconnected(const pipe_ret_t & ret) {\n    std::lock_guard<std::mutex> lock(_subscribersMtx);\n    for (const auto &subscriber : _subscibers) {\n        if (subscriber.disconnectionHandler) {\n            subscriber.disconnectionHandler(ret);\n        }\n    }\n}\n\n/*\n * Receive server packets, and notify user\n */\nvoid TcpClient::receiveTask() {\n    while(_isConnected) {\n        const fd_wait::Result waitResult = fd_wait::waitFor(_sockfd);\n\n        if (waitResult == fd_wait::Result::FAILURE) {\n            throw std::runtime_error(strerror(errno));\n        } else if (waitResult == fd_wait::Result::TIMEOUT) {\n            continue;\n        }\n\n        char msg[MAX_PACKET_SIZE];\n        const size_t numOfBytesReceived = recv(_sockfd.get(), msg, MAX_PACKET_SIZE, 0);\n\n        if(numOfBytesReceived < 1) {\n            std::string errorMsg;\n            if (numOfBytesReceived == 0) { //server closed connection\n                errorMsg = \"Server closed connection\";\n            } else {\n                errorMsg = strerror(errno);\n            }\n            _isConnected = false;\n            publishServerDisconnected(pipe_ret_t::failure(errorMsg));\n            return;\n        } else {\n            publishServerMsg(msg, numOfBytesReceived);\n        }\n    }\n}\n\nvoid TcpClient::terminateReceiveThread() {\n    _isConnected = false;\n\n    if (_receiveTask) {\n        _receiveTask->join();\n        delete _receiveTask;\n        _receiveTask = nullptr;\n    }\n}\n\npipe_ret_t TcpClient::close(){\n    if (_isClosed) {\n        return pipe_ret_t::failure(\"client is already closed\");\n    }\n    terminateReceiveThread();\n\n    const bool closeFailed = (::close(_sockfd.get()) == -1);\n    if (closeFailed) {\n        return pipe_ret_t::failure(strerror(errno));\n    }\n    _isClosed = true;\n    return pipe_ret_t::success();\n}\n\n\n"
  },
  {
    "path": "src/tcp_server.cpp",
    "content": "\n#include <functional>\n#include <thread>\n#include <algorithm>\n#include \"../include/tcp_server.h\"\n#include \"../include/common.h\"\n\n\nTcpServer::TcpServer() {\n    _subscribers.reserve(10);\n    _clients.reserve(10);\n    _stopRemoveClientsTask = false;\n}\n\nTcpServer::~TcpServer() {\n    close();\n}\n\nvoid TcpServer::subscribe(const server_observer_t & observer) {\n    std::lock_guard<std::mutex> lock(_subscribersMtx);\n    _subscribers.push_back(observer);\n}\n\nvoid TcpServer::printClients() {\n    std::lock_guard<std::mutex> lock(_clientsMtx);\n    if (_clients.empty()) {\n        std::cout << \"no connected clients\\n\";\n    }\n    for (const Client *client : _clients) {\n        client->print();\n    }\n}\n\n/**\n * Remove dead clients (disconnected) from clients vector periodically\n */\nvoid TcpServer::removeDeadClients() {\n    std::vector<Client*>::const_iterator clientToRemove;\n    while (!_stopRemoveClientsTask) {\n        {\n            std::lock_guard<std::mutex> lock(_clientsMtx);\n            do {\n                clientToRemove = std::find_if(_clients.begin(), _clients.end(),\n                                              [](Client *client) { return !client->isConnected(); });\n\n                if (clientToRemove != _clients.end()) {\n                    (*clientToRemove)->close();\n                    delete *clientToRemove;\n                    _clients.erase(clientToRemove);\n                }\n            } while (clientToRemove != _clients.end());\n        }\n\n        sleep(2);\n    }\n}\n\nvoid TcpServer::terminateDeadClientsRemover() {\n    if (_clientsRemoverThread) {\n        _stopRemoveClientsTask = true;\n        _clientsRemoverThread->join();\n        delete _clientsRemoverThread;\n        _clientsRemoverThread = nullptr;\n    }\n}\n\n/**\n * Handle different client events. Subscriber callbacks should be short and fast, and must not\n * call other server functions to avoid deadlock\n */\nvoid TcpServer::clientEventHandler(const Client &client, ClientEvent event, const std::string &msg) {\n    switch (event) {\n        case ClientEvent::DISCONNECTED: {\n            publishClientDisconnected(client.getIp(), msg);\n            break;\n        }\n        case ClientEvent::INCOMING_MSG: {\n            publishClientMsg(client, msg.c_str(), msg.size());\n            break;\n        }\n    }\n}\n\n/*\n * Publish incomingPacketHandler client message to observer.\n * Observers get only messages that originated\n * from clients with IP address identical to\n * the specific observer requested IP\n */\nvoid TcpServer::publishClientMsg(const Client & client, const char * msg, size_t msgSize) {\n    std::lock_guard<std::mutex> lock(_subscribersMtx);\n\n    for (const server_observer_t& subscriber : _subscribers) {\n        if (subscriber.wantedIP == client.getIp() || subscriber.wantedIP.empty()) {\n            if (subscriber.incomingPacketHandler) {\n                subscriber.incomingPacketHandler(client.getIp(), msg, msgSize);\n            }\n        }\n    }\n}\n\n/*\n * Publish client disconnection to observer.\n * Observers get only notify about clients\n * with IP address identical to the specific\n * observer requested IP\n */\nvoid TcpServer::publishClientDisconnected(const std::string &clientIP, const std::string &clientMsg) {\n    std::lock_guard<std::mutex> lock(_subscribersMtx);\n\n    for (const server_observer_t& subscriber : _subscribers) {\n        if (subscriber.wantedIP == clientIP) {\n            if (subscriber.disconnectionHandler) {\n                subscriber.disconnectionHandler(clientIP, clientMsg);\n            }\n        }\n    }\n}\n\n/*\n * Bind port and start listening\n * Return tcp_ret_t\n */\npipe_ret_t TcpServer::start(int port, int maxNumOfClients, bool removeDeadClientsAutomatically) {\n    if (removeDeadClientsAutomatically) {\n        _clientsRemoverThread = new std::thread(&TcpServer::removeDeadClients, this);\n    }\n    try {\n        initializeSocket();\n        bindAddress(port);\n        listenToClients(maxNumOfClients);\n    } catch (const std::runtime_error &error) {\n        return pipe_ret_t::failure(error.what());\n    }\n    return pipe_ret_t::success();\n}\n\nvoid TcpServer::initializeSocket() {\n    _sockfd.set(socket(AF_INET, SOCK_STREAM, 0));\n    const bool socketFailed = (_sockfd.get() == -1);\n    if (socketFailed) {\n        throw std::runtime_error(strerror(errno));\n    }\n\n    // set socket for reuse (otherwise might have to wait 4 minutes every time socket is closed)\n    const int option = 1;\n    setsockopt(_sockfd.get(), SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));\n}\n\nvoid TcpServer::bindAddress(int port) {\n    memset(&_serverAddress, 0, sizeof(_serverAddress));\n    _serverAddress.sin_family = AF_INET;\n    _serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);\n    _serverAddress.sin_port = htons(port);\n\n    const int bindResult = bind(_sockfd.get(), (struct sockaddr *)&_serverAddress, sizeof(_serverAddress));\n    const bool bindFailed = (bindResult == -1);\n    if (bindFailed) {\n        throw std::runtime_error(strerror(errno));\n    }\n}\n\nvoid TcpServer::listenToClients(int maxNumOfClients) {\n    const int clientsQueueSize = maxNumOfClients;\n    const bool listenFailed = (listen(_sockfd.get(), clientsQueueSize) == -1);\n    if (listenFailed) {\n        throw std::runtime_error(strerror(errno));\n    }\n}\n\n/*\n * Accept and handle new client socket. To handle multiple clients, user must\n * call this function in a loop to enable the acceptance of more than one.\n * If timeout argument equal 0, this function is executed in blocking mode.\n * If timeout argument is > 0 then this function is executed in non-blocking\n * mode (async) and will quit after timeout seconds if no client tried to connect.\n * Return accepted client IP, or throw error if failed\n */\nstd::string TcpServer::acceptClient(uint timeout) {\n    const pipe_ret_t waitingForClient = waitForClient(timeout);\n    if (!waitingForClient.isSuccessful()) {\n        throw std::runtime_error(waitingForClient.message());\n    }\n\n    socklen_t socketSize  = sizeof(_clientAddress);\n    const int fileDescriptor = accept(_sockfd.get(), (struct sockaddr*)&_clientAddress, &socketSize);\n\n    const bool acceptFailed = (fileDescriptor == -1);\n    if (acceptFailed) {\n        throw std::runtime_error(strerror(errno));\n    }\n\n    auto newClient = new Client(fileDescriptor);\n    newClient->setIp(inet_ntoa(_clientAddress.sin_addr));\n    using namespace std::placeholders;\n    newClient->setEventsHandler(std::bind(&TcpServer::clientEventHandler, this, _1, _2, _3));\n    newClient->startListen();\n\n    std::lock_guard<std::mutex> lock(_clientsMtx);\n    _clients.push_back(newClient);\n\n    return newClient->getIp();\n}\n\npipe_ret_t TcpServer::waitForClient(uint32_t timeout) {\n    if (timeout > 0) {\n        const fd_wait::Result waitResult = fd_wait::waitFor(_sockfd, timeout);\n        const bool noIncomingClient = (!FD_ISSET(_sockfd.get(), &_fds));\n\n        if (waitResult == fd_wait::Result::FAILURE) {\n            return pipe_ret_t::failure(strerror(errno));\n        } else if (waitResult == fd_wait::Result::TIMEOUT) {\n            return pipe_ret_t::failure(\"Timeout waiting for client\");\n        } else if (noIncomingClient) {\n            return pipe_ret_t::failure(\"File descriptor is not set\");\n        }\n    }\n\n    return pipe_ret_t::success();\n}\n\n/*\n * Send message to all connected clients.\n * Return true if message was sent successfully to all clients\n */\npipe_ret_t TcpServer::sendToAllClients(const char * msg, size_t size) {\n    std::lock_guard<std::mutex> lock(_clientsMtx);\n\n    for (const Client *client : _clients) {\n        pipe_ret_t sendingResult = sendToClient(*client, msg, size);\n        if (!sendingResult.isSuccessful()) {\n            return sendingResult;\n        }\n    }\n\n    return pipe_ret_t::success();\n}\n\n/*\n * Send message to specific client (determined by client IP address).\n * Return true if message was sent successfully\n */\npipe_ret_t TcpServer::sendToClient(const Client & client, const char * msg, size_t size){\n    try{\n        client.send(msg, size);\n    } catch (const std::runtime_error &error) {\n        return pipe_ret_t::failure(error.what());\n    }\n\n    return pipe_ret_t::success();\n}\n\npipe_ret_t TcpServer::sendToClient(const std::string & clientIP, const char * msg, size_t size) {\n    std::lock_guard<std::mutex> lock(_clientsMtx);\n\n    const auto clientIter = std::find_if(_clients.begin(), _clients.end(),\n         [&clientIP](Client *client) { return client->getIp() == clientIP; });\n\n    if (clientIter == _clients.end()) {\n        return pipe_ret_t::failure(\"client not found\");\n    }\n\n    const Client &client = *(*clientIter);\n    return sendToClient(client, msg, size);\n}\n\n/*\n * Close server and clients resources.\n * Return true is successFlag, false otherwise\n */\npipe_ret_t TcpServer::close() {\n    terminateDeadClientsRemover();\n    { // close clients\n        std::lock_guard<std::mutex> lock(_clientsMtx);\n\n        for (Client * client : _clients) {\n            try {\n                client->close();\n            } catch (const std::runtime_error& error) {\n                return pipe_ret_t::failure(error.what());\n            }\n        }\n        _clients.clear();\n    }\n\n    { // close server\n        const int closeServerResult = ::close(_sockfd.get());\n        const bool closeServerFailed = (closeServerResult == -1);\n        if (closeServerFailed) {\n            return pipe_ret_t::failure(strerror(errno));\n        }\n    }\n\n    return pipe_ret_t::success();\n}\n"
  }
]