├── .dockerignore ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── .ignore ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── build.sh ├── docker-compose.yml ├── examples ├── client_example.cpp └── server_example.cpp ├── include ├── client.h ├── client_event.h ├── client_observer.h ├── common.h ├── file_descriptor.h ├── pipe_ret_t.h ├── server_observer.h ├── tcp_client.h └── tcp_server.h └── src ├── client.cpp ├── common.cpp ├── pipe_ret_t.cpp ├── tcp_client.cpp └── tcp_server.cpp /.dockerignore: -------------------------------------------------------------------------------- 1 | cmake-build 2 | cmake-build-debug 3 | build -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally 16 | # well on Windows or Mac. You can convert this to a matrix build if you need 17 | # cross-platform coverage. 18 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Configure CMake 25 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 26 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 27 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 28 | 29 | - name: Build 30 | # Build your program with the given configuration 31 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 32 | 33 | - name: Test 34 | working-directory: ${{github.workspace}}/build 35 | # Execute tests defined by the CMake configuration. 36 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 37 | run: ctest -C ${{env.BUILD_TYPE}} 38 | 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | .idea/ 3 | cmake-build-debug/ 4 | cmake-build/ 5 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | cmake-build-debug/ 2 | .idea/ 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8.1) 2 | project(tcp_client_server) 3 | 4 | find_package (Threads) 5 | 6 | set(CMAKE_CXX_STANDARD 11) 7 | set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-std=c++11") 8 | 9 | add_library(${PROJECT_NAME} 10 | src/tcp_client.cpp 11 | src/tcp_server.cpp 12 | src/client.cpp 13 | src/pipe_ret_t.cpp 14 | src/common.cpp) 15 | 16 | option(SERVER_EXAMPLE "Build SERVER" ON) 17 | 18 | if(SERVER_EXAMPLE) 19 | 20 | add_definitions( 21 | -DSERVER_EXAMPLE 22 | ) 23 | 24 | add_executable(tcp_server examples/server_example.cpp) 25 | 26 | target_link_libraries (tcp_server ${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) 27 | 28 | endif() 29 | 30 | option(CLIENT_EXAMPLE "Build CLIENT" ON) 31 | 32 | if(CLIENT_EXAMPLE) 33 | 34 | add_definitions( 35 | -DCLIENT_EXAMPLE 36 | ) 37 | 38 | add_executable(tcp_client examples/client_example.cpp) 39 | 40 | target_link_libraries (tcp_client ${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) 41 | 42 | endif() 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.8 2 | 3 | RUN set -ex && \ 4 | apk add --no-cache gcc musl-dev cmake cmake clang clang-dev make g++ libc-dev linux-headers 5 | 6 | WORKDIR /usr/src/tcp_server_client 7 | 8 | COPY . . 9 | 10 | RUN chmod +x build.sh 11 | RUN ./build.sh 12 | 13 | EXPOSE 65123 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Elhay Rauper 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![cmake workflow](https://github.com/elhayra/tcp_server_client/actions/workflows/cmake.yml/badge.svg) 2 | 3 | # TCP server client 4 | A thin and simple C++ TCP client and server library with examples. 5 | 6 | ### Platforms Support 7 | Currently, both linux and mac are supported 8 | 9 | ### Examples 10 | Simple 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. 11 | You can find code examples of both server and client under the 'examples' directory. Both the library and the examples are well documented. 12 | 13 | ### Server 14 | The server is thread-safe, and can handle multiple clients at the same time, and remove dead clients resources automatically. 15 | 16 | ## Quick start 17 | build the examples and static library file: 18 | ```bash 19 | $ cd tcp_server_client 20 | $ ./build 21 | ``` 22 | 23 | run the server and client examples: 24 | Navigate into the `build` folder and run in the terminal: 25 | ```bash 26 | $ cd build 27 | ``` 28 | In terminal #1: 29 | ```bash 30 | $ ./tcp_server 31 | ``` 32 | In terminal #2: 33 | ```bash 34 | $ ./tcp_client 35 | ``` 36 | 37 | ## Building 38 | 39 | This 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. 40 | 41 | In order to build the project you can either use the `build.sh` script: 42 | ```bash 43 | $ cd tcp_server_client 44 | $ ./build 45 | ``` 46 | 47 | or build it manually: 48 | ```bash 49 | $ cd tcp_server_client 50 | $ mkdir build 51 | $ cmake .. 52 | $ make 53 | ``` 54 | 55 | The build process generate three files: `libtcp_client_server.a`, `tcp_client` and `tcp_server`. 56 | The last two are the executables of the examples which can be executed in the terminal. 57 | 58 | 59 | #### Building Only Server or Client 60 | 61 | By default, the CMake configuration builds both server and client. However, you can use flags to build only one of the apps as follows: 62 | 63 | ###### Disabling the server build 64 | 65 | To build only the client, disable the server build by replace the `cmake ..` call by: 66 | 67 | ```bash 68 | cmake -DSERVER_EXAMPLE=OFF .. 69 | ``` 70 | And then run the make command as usual. 71 | 72 | ###### Disabling the client build 73 | 74 | To build only the server, disable the client build by replace the `cmake ..` call by: 75 | 76 | ```bash 77 | cmake -DCLIENT_EXAMPLE=OFF .. 78 | ``` 79 | And then run the make command as usual. 80 | 81 | ## Run the Examples 82 | Navigate into the `build` folder and run in the terminal: 83 | ```bash 84 | $ ./tcp_server 85 | ``` 86 | 87 | You should see a menu output on the screen, we'll get back to that. 88 | In a different terminal, run the client: 89 | ````bash 90 | $ ./tcp_client 91 | ```` 92 | 93 | You 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. 94 | Now, 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. 95 | 96 | ## Server-Client API 97 | 98 | After 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. 99 | In addition, you can also look at the public functions in `tcp_client.h` and `tcp_server.h` to learn what APIs are available. 100 | 101 | ### Server and Client Events 102 | Both server and client are using the observer design pattern to register and handle events. 103 | When registering to an event with a callback, you should make sure that: 104 | - The callback is fast (not doing any heavy lifting tasks) because those callbacks are called from the context of the server or client. 105 | - No server / client function calls are made in those callbacks to avoid possible deadlock. 106 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p build && \ 4 | cd build && \ 5 | cmake .. && make 6 | 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | services: 3 | tcp-server: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | command: sh -c "cd build && ./tcp_server" 8 | 9 | tcp-client: 10 | build: 11 | context: . 12 | dockerfile: Dockerfile 13 | command: sh -c "cd build && ./tcp_client" 14 | 15 | -------------------------------------------------------------------------------- /examples/client_example.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////// 2 | /////////////////////CLIENT EXAMPLE//////////////////////// 3 | /////////////////////////////////////////////////////////// 4 | 5 | #ifdef CLIENT_EXAMPLE 6 | 7 | #include 8 | #include 9 | #include "../include/tcp_client.h" 10 | 11 | TcpClient client; 12 | 13 | // on sig_exit, close client 14 | void sig_exit(int s) 15 | { 16 | std::cout << "Closing client...\n"; 17 | pipe_ret_t finishRet = client.close(); 18 | if (finishRet.isSuccessful()) { 19 | std::cout << "Client closed.\n"; 20 | } else { 21 | std::cout << "Failed to close client.\n"; 22 | } 23 | exit(0); 24 | } 25 | 26 | // observer callback. will be called for every new message received by the server 27 | void onIncomingMsg(const char * msg, size_t size) { 28 | std::cout << "Got msg from server: " << msg << "\n"; 29 | } 30 | 31 | // observer callback. will be called when server disconnects 32 | void onDisconnection(const pipe_ret_t & ret) { 33 | std::cout << "Server disconnected: " << ret.message() << "\n"; 34 | } 35 | 36 | void printMenu() { 37 | std::cout << "select one of the following options: \n" << 38 | "1. send message to server\n" << 39 | "2. close client and exit\n"; 40 | } 41 | 42 | int getMenuSelection() { 43 | int selection = 0; 44 | std::cin >> selection; 45 | if (!std::cin) { 46 | throw std::runtime_error("invalid menu input. expected a number, but got something else"); 47 | } 48 | std::cin.ignore (std::numeric_limits::max(), '\n'); 49 | return selection; 50 | } 51 | 52 | bool handleMenuSelection(int selection) { 53 | static const int minSelection = 1; 54 | static const int maxSelection = 2; 55 | if (selection < minSelection || selection > maxSelection) { 56 | std::cout << "invalid selection: " << selection << 57 | ". selection must be b/w " << minSelection << " and " << maxSelection << "\n"; 58 | return false; 59 | } 60 | switch (selection) { 61 | case 1: { // send message to server 62 | std::cout << "enter message to send:\n"; 63 | std::string message; 64 | std::cin >> message; 65 | pipe_ret_t sendRet = client.sendMsg(message.c_str(), message.size()); 66 | if (!sendRet.isSuccessful()) { 67 | std::cout << "Failed to send message: " << sendRet.message() << "\n"; 68 | } else { 69 | std::cout << "message was sent successfuly\n"; 70 | } 71 | break; 72 | } 73 | case 2: { // close client 74 | const pipe_ret_t closeResult = client.close(); 75 | if (!closeResult.isSuccessful()) { 76 | std::cout << "closing client failed: " << closeResult.message() << "\n"; 77 | } else { 78 | std::cout << "closed client successfully\n"; 79 | } 80 | return true; 81 | } 82 | default: { 83 | std::cout << "invalid selection: " << selection << 84 | ". selection must be b/w " << minSelection << " and " << maxSelection << "\n"; 85 | } 86 | } 87 | return false; 88 | } 89 | 90 | int main() { 91 | //register to SIGINT to close client when user press ctrl+c 92 | signal(SIGINT, sig_exit); 93 | 94 | // configure and register observer 95 | client_observer_t observer; 96 | observer.wantedIP = "127.0.0.1"; 97 | observer.incomingPacketHandler = onIncomingMsg; 98 | observer.disconnectionHandler = onDisconnection; 99 | client.subscribe(observer); 100 | 101 | // connect client to an open server 102 | bool connected = false; 103 | while (!connected) { 104 | pipe_ret_t connectRet = client.connectTo("127.0.0.1", 65123); 105 | connected = connectRet.isSuccessful(); 106 | if (connected) { 107 | std::cout << "Client connected successfully\n"; 108 | } else { 109 | std::cout << "Client failed to connect: " << connectRet.message() << "\n" 110 | << "Make sure the server is open and listening\n\n"; 111 | sleep(2); 112 | std::cout << "Retrying to connect...\n"; 113 | } 114 | }; 115 | 116 | // send messages to server 117 | bool shouldTerminate = false; 118 | while(!shouldTerminate) 119 | { 120 | printMenu(); 121 | int selection = getMenuSelection(); 122 | shouldTerminate = handleMenuSelection(selection); 123 | } 124 | 125 | return 0; 126 | } 127 | 128 | #endif -------------------------------------------------------------------------------- /examples/server_example.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////// 2 | /////////////////////SERVER EXAMPLE//////////////////////// 3 | /////////////////////////////////////////////////////////// 4 | 5 | #ifdef SERVER_EXAMPLE 6 | 7 | #include 8 | #include 9 | 10 | #include "../include/tcp_server.h" 11 | 12 | 13 | // declare the server 14 | TcpServer server; 15 | 16 | // declare a server observer which will receive incomingPacketHandler messages. 17 | // the server supports multiple observers 18 | server_observer_t observer1, observer2; 19 | 20 | // observer callback. will be called for every new message received by clients 21 | // with the requested IP address 22 | void onIncomingMsg1(const std::string &clientIP, const char * msg, size_t size) { 23 | std::string msgStr = msg; 24 | // print client message 25 | std::cout << "Observer1 got client msg: " << msgStr << "\n"; 26 | } 27 | 28 | // observer callback. will be called for every new message received by clients 29 | // with the requested IP address 30 | void onIncomingMsg2(const std::string &clientIP, const char * msg, size_t size) { 31 | std::string msgStr = msg; 32 | // print client message 33 | std::cout << "Observer2 got client msg: " << msgStr << "\n"; 34 | } 35 | 36 | // observer callback. will be called when client disconnects 37 | void onClientDisconnected(const std::string &ip, const std::string &msg) { 38 | std::cout << "Client: " << ip << " disconnected. Reason: " << msg << "\n"; 39 | } 40 | 41 | // accept a single client. 42 | // if you wish to accept multiple clients, call this function in a loop 43 | // (you might want to use a thread to accept clients without blocking) 44 | void acceptClient() { 45 | try { 46 | std::cout << "waiting for incoming client...\n"; 47 | std::string clientIP = server.acceptClient(0); 48 | std::cout << "accepted new client with IP: " << clientIP << "\n" << 49 | "== updated list of accepted clients ==" << "\n"; 50 | server.printClients(); 51 | } catch (const std::runtime_error &error) { 52 | std::cout << "Accepting client failed: " << error.what() << "\n"; 53 | } 54 | } 55 | 56 | void printMenu() { 57 | std::cout << "\n\nselect one of the following options: \n" << 58 | "1. send all clients a message\n" << 59 | "2. print list of accepted clients\n" << 60 | "3. send message to a specific client\n" << 61 | "4. close server and exit\n"; 62 | } 63 | 64 | int getMenuSelection() { 65 | int selection = 0; 66 | std::cin >> selection; 67 | if (!std::cin) { 68 | throw std::runtime_error("invalid menu input. expected a number, but got something else"); 69 | } 70 | std::cin.ignore (std::numeric_limits::max(), '\n'); 71 | return selection; 72 | } 73 | 74 | /** 75 | * handle menu selection and return true in case program should terminate 76 | * after handling selection 77 | */ 78 | bool handleMenuSelection(int selection) { 79 | static const int minSelection = 1; 80 | static const int maxSelection = 4; 81 | if (selection < minSelection || selection > maxSelection) { 82 | std::cout << "invalid selection: " << selection << 83 | ". selection must be b/w " << minSelection << " and " << maxSelection << "\n"; 84 | return false; 85 | } 86 | switch (selection) { 87 | case 1: { // send all clients a message 88 | std::string msg; 89 | std::cout << "type message to send to all connected clients:\n"; 90 | getline(std::cin, msg); 91 | pipe_ret_t sendingResult = server.sendToAllClients(msg.c_str(), msg.size()); 92 | if (sendingResult.isSuccessful()) { 93 | std::cout << "sent message to all clients successfully\n"; 94 | } else { 95 | std::cout << "failed to sent message: " << sendingResult.message() << "\n"; 96 | } 97 | break; 98 | } 99 | case 2: { // print list of accepted clients 100 | server.printClients(); 101 | break; 102 | } 103 | case 3: { // send message to a specific client 104 | std::cout << "enter client IP:\n"; 105 | std::string clientIP; 106 | std::cin >> clientIP; 107 | std::cout << "enter message to send:\n"; 108 | std::string message; 109 | std::cin >> message; 110 | pipe_ret_t result = server.sendToClient(clientIP, message.c_str(), message.size()); 111 | if (!result.isSuccessful()) { 112 | std::cout << "sending failed: " << result.message() << "\n"; 113 | } else { 114 | std::cout << "sending succeeded\n"; 115 | } 116 | break; 117 | }; 118 | case 4: { // close server 119 | pipe_ret_t sendingResult = server.close(); 120 | if (sendingResult.isSuccessful()) { 121 | std::cout << "closed server successfully\n"; 122 | } else { 123 | std::cout << "failed to close server: " << sendingResult.message() << "\n"; 124 | } 125 | return true; 126 | } 127 | default: { 128 | std::cout << "invalid selection: " << selection << 129 | ". selection must be b/w " << minSelection << " and " << maxSelection << "\n"; 130 | } 131 | } 132 | return false; 133 | } 134 | 135 | int main() 136 | { 137 | // start server on port 65123 138 | pipe_ret_t startRet = server.start(65123); 139 | if (startRet.isSuccessful()) { 140 | std::cout << "Server setup succeeded\n"; 141 | } else { 142 | std::cout << "Server setup failed: " << startRet.message() << "\n"; 143 | return EXIT_FAILURE; 144 | } 145 | 146 | // configure and register observer1 147 | observer1.incomingPacketHandler = onIncomingMsg1; 148 | observer1.disconnectionHandler = onClientDisconnected; 149 | observer1.wantedIP = "127.0.0.1"; 150 | server.subscribe(observer1); 151 | 152 | // configure and register observer2 153 | observer2.incomingPacketHandler = onIncomingMsg2; 154 | observer2.disconnectionHandler = nullptr; // nullptr or not setting this means we don't care about disconnection event 155 | observer2.wantedIP = "10.88.0.11"; // use empty string instead to receive messages from any IP address 156 | server.subscribe(observer2); 157 | 158 | acceptClient(); 159 | 160 | bool shouldTerminate = false; 161 | while(!shouldTerminate) { 162 | printMenu(); 163 | int selection = getMenuSelection(); 164 | shouldTerminate = handleMenuSelection(selection); 165 | } 166 | 167 | return 0; 168 | } 169 | 170 | #endif -------------------------------------------------------------------------------- /include/client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "pipe_ret_t.h" 10 | #include "client_event.h" 11 | #include "file_descriptor.h" 12 | 13 | 14 | class Client { 15 | 16 | using client_event_handler_t = std::function; 17 | 18 | private: 19 | FileDescriptor _sockfd; 20 | std::string _ip = ""; 21 | std::atomic _isConnected; 22 | std::thread * _receiveThread = nullptr; 23 | client_event_handler_t _eventHandlerCallback; 24 | 25 | void setConnected(bool flag) { _isConnected = flag; } 26 | 27 | void receiveTask(); 28 | 29 | void terminateReceiveThread(); 30 | 31 | public: 32 | Client(int); 33 | 34 | bool operator ==(const Client & other) const ; 35 | 36 | void setIp(const std::string & ip) { _ip = ip; } 37 | std::string getIp() const { return _ip; } 38 | 39 | void setEventsHandler(const client_event_handler_t & eventHandler) { _eventHandlerCallback = eventHandler; } 40 | void publishEvent(ClientEvent clientEvent, const std::string &msg = ""); 41 | 42 | 43 | bool isConnected() const { return _isConnected; } 44 | 45 | void startListen(); 46 | 47 | void send(const char * msg, size_t msgSize) const; 48 | 49 | void close(); 50 | 51 | void print() const; 52 | 53 | }; 54 | 55 | 56 | -------------------------------------------------------------------------------- /include/client_event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum ClientEvent { 4 | DISCONNECTED, 5 | INCOMING_MSG 6 | }; -------------------------------------------------------------------------------- /include/client_observer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "pipe_ret_t.h" 6 | 7 | struct client_observer_t { 8 | std::string wantedIP = ""; 9 | std::function incomingPacketHandler = nullptr; 10 | std::function disconnectionHandler = nullptr; 11 | }; 12 | 13 | 14 | -------------------------------------------------------------------------------- /include/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define MAX_PACKET_SIZE 4096 6 | 7 | namespace fd_wait { 8 | enum Result { 9 | FAILURE, 10 | TIMEOUT, 11 | SUCCESS 12 | }; 13 | 14 | Result waitFor(const FileDescriptor &fileDescriptor, uint32_t timeoutSeconds = 1); 15 | }; 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /include/file_descriptor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class FileDescriptor { 4 | private: 5 | int _sockfd = 0; 6 | 7 | public: 8 | void set(int fd) { _sockfd = fd; } 9 | int get() const { return _sockfd; } 10 | }; -------------------------------------------------------------------------------- /include/pipe_ret_t.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class pipe_ret_t { 4 | 5 | private: 6 | 7 | bool _successFlag = false; 8 | std::string _msg = ""; 9 | 10 | public: 11 | 12 | pipe_ret_t() = default; 13 | pipe_ret_t(bool successFlag, const std::string &msg) : 14 | _successFlag{successFlag}, 15 | _msg{msg} 16 | {} 17 | 18 | std::string message() const { return _msg; } 19 | bool isSuccessful() const { return _successFlag; } 20 | 21 | static pipe_ret_t failure(const std::string & msg); 22 | static pipe_ret_t success(const std::string &msg = ""); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /include/server_observer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "client.h" 6 | 7 | struct server_observer_t { 8 | std::string wantedIP = ""; 9 | std::function incomingPacketHandler; 10 | std::function disconnectionHandler; 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /include/tcp_client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "client_observer.h" 20 | #include "pipe_ret_t.h" 21 | #include "file_descriptor.h" 22 | 23 | 24 | class TcpClient 25 | { 26 | private: 27 | FileDescriptor _sockfd; 28 | std::atomic _isConnected; 29 | std::atomic _isClosed; 30 | struct sockaddr_in _server; 31 | std::vector _subscibers; 32 | std::thread * _receiveTask = nullptr; 33 | std::mutex _subscribersMtx; 34 | 35 | void initializeSocket(); 36 | void startReceivingMessages(); 37 | void setAddress(const std::string& address, int port); 38 | void publishServerMsg(const char * msg, size_t msgSize); 39 | void publishServerDisconnected(const pipe_ret_t & ret); 40 | void receiveTask(); 41 | void terminateReceiveThread(); 42 | 43 | public: 44 | TcpClient(); 45 | ~TcpClient(); 46 | pipe_ret_t connectTo(const std::string & address, int port); 47 | pipe_ret_t sendMsg(const char * msg, size_t size); 48 | 49 | void subscribe(const client_observer_t & observer); 50 | bool isConnected() const { return _isConnected; } 51 | pipe_ret_t close(); 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /include/tcp_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "client.h" 18 | #include "server_observer.h" 19 | #include "pipe_ret_t.h" 20 | #include "file_descriptor.h" 21 | 22 | class TcpServer { 23 | 24 | private: 25 | 26 | FileDescriptor _sockfd; 27 | struct sockaddr_in _serverAddress; 28 | struct sockaddr_in _clientAddress; 29 | fd_set _fds; 30 | std::vector _clients; 31 | std::vector _subscribers; 32 | 33 | std::mutex _subscribersMtx; 34 | std::mutex _clientsMtx; 35 | 36 | std::thread * _clientsRemoverThread = nullptr; 37 | std::atomic _stopRemoveClientsTask; 38 | 39 | void publishClientMsg(const Client & client, const char * msg, size_t msgSize); 40 | void publishClientDisconnected(const std::string&, const std::string&); 41 | pipe_ret_t waitForClient(uint32_t timeout); 42 | void clientEventHandler(const Client&, ClientEvent, const std::string &msg); 43 | void removeDeadClients(); 44 | void terminateDeadClientsRemover(); 45 | static pipe_ret_t sendToClient(const Client & client, const char * msg, size_t size); 46 | 47 | public: 48 | TcpServer(); 49 | ~TcpServer(); 50 | pipe_ret_t start(int port, int maxNumOfClients = 5, bool removeDeadClientsAutomatically = true); 51 | void initializeSocket(); 52 | void bindAddress(int port); 53 | void listenToClients(int maxNumOfClients); 54 | std::string acceptClient(uint timeout); 55 | void subscribe(const server_observer_t & observer); 56 | pipe_ret_t sendToAllClients(const char * msg, size_t size); 57 | pipe_ret_t sendToClient(const std::string & clientIP, const char * msg, size_t size); 58 | pipe_ret_t close(); 59 | void printClients(); 60 | }; 61 | 62 | -------------------------------------------------------------------------------- /src/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../include/client.h" 10 | #include "../include/common.h" 11 | 12 | Client::Client(int fileDescriptor) { 13 | _sockfd.set(fileDescriptor); 14 | setConnected(false); 15 | } 16 | 17 | bool Client::operator==(const Client & other) const { 18 | if ((this->_sockfd.get() == other._sockfd.get()) && 19 | (this->_ip == other._ip) ) { 20 | return true; 21 | } 22 | return false; 23 | } 24 | 25 | void Client::startListen() { 26 | setConnected(true); 27 | _receiveThread = new std::thread(&Client::receiveTask, this); 28 | } 29 | 30 | void Client::send(const char *msg, size_t msgSize) const { 31 | const size_t numBytesSent = ::send(_sockfd.get(), (char *)msg, msgSize, 0); 32 | 33 | const bool sendFailed = (numBytesSent < 0); 34 | if (sendFailed) { 35 | throw std::runtime_error(strerror(errno)); 36 | } 37 | 38 | const bool notAllBytesWereSent = (numBytesSent < msgSize); 39 | if (notAllBytesWereSent) { 40 | char errorMsg[100]; 41 | sprintf(errorMsg, "Only %lu bytes out of %lu was sent to client", numBytesSent, msgSize); 42 | throw std::runtime_error(errorMsg); 43 | } 44 | } 45 | 46 | /* 47 | * Receive client packets, and notify user 48 | */ 49 | void Client::receiveTask() { 50 | while(isConnected()) { 51 | const fd_wait::Result waitResult = fd_wait::waitFor(_sockfd); 52 | 53 | if (waitResult == fd_wait::Result::FAILURE) { 54 | throw std::runtime_error(strerror(errno)); 55 | } else if (waitResult == fd_wait::Result::TIMEOUT) { 56 | continue; 57 | } 58 | 59 | char receivedMessage[MAX_PACKET_SIZE]; 60 | const size_t numOfBytesReceived = recv(_sockfd.get(), receivedMessage, MAX_PACKET_SIZE, 0); 61 | 62 | if(numOfBytesReceived < 1) { 63 | const bool clientClosedConnection = (numOfBytesReceived == 0); 64 | std::string disconnectionMessage; 65 | if (clientClosedConnection) { 66 | disconnectionMessage = "Client closed connection"; 67 | } else { 68 | disconnectionMessage = strerror(errno); 69 | } 70 | setConnected(false); 71 | publishEvent(ClientEvent::DISCONNECTED, disconnectionMessage); 72 | return; 73 | } else { 74 | publishEvent(ClientEvent::INCOMING_MSG, receivedMessage); 75 | } 76 | } 77 | } 78 | 79 | void Client::publishEvent(ClientEvent clientEvent, const std::string &msg) { 80 | _eventHandlerCallback(*this, clientEvent, msg); 81 | } 82 | 83 | void Client::print() const { 84 | const std::string connected = isConnected() ? "True" : "False"; 85 | std::cout << "-----------------\n" << 86 | "IP address: " << getIp() << std::endl << 87 | "Connected?: " << connected << std::endl << 88 | "Socket FD: " << _sockfd.get() << std::endl; 89 | } 90 | 91 | void Client::terminateReceiveThread() { 92 | setConnected(false); 93 | if (_receiveThread) { 94 | _receiveThread->join(); 95 | delete _receiveThread; 96 | _receiveThread = nullptr; 97 | } 98 | } 99 | 100 | void Client::close() { 101 | terminateReceiveThread(); 102 | 103 | const bool closeFailed = (::close(_sockfd.get()) == -1); 104 | if (closeFailed) { 105 | throw std::runtime_error(strerror(errno)); 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /src/common.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../include/file_descriptor.h" 3 | #include "../include/common.h" 4 | 5 | #include 6 | 7 | #define SELECT_FAILED -1 8 | #define SELECT_TIMEOUT 0 9 | 10 | namespace fd_wait { 11 | /** 12 | * monitor file descriptor and wait for I/O operation 13 | */ 14 | Result waitFor(const FileDescriptor &fileDescriptor, uint32_t timeoutSeconds) { 15 | struct timeval tv; 16 | tv.tv_sec = timeoutSeconds; 17 | tv.tv_usec = 0; 18 | fd_set fds; 19 | 20 | FD_ZERO(&fds); 21 | FD_SET(fileDescriptor.get(), &fds); 22 | const int selectRet = select(fileDescriptor.get() + 1, &fds, nullptr, nullptr, &tv); 23 | 24 | if (selectRet == SELECT_FAILED) { 25 | return Result::FAILURE; 26 | } else if (selectRet == SELECT_TIMEOUT) { 27 | return Result::TIMEOUT; 28 | } 29 | return Result::SUCCESS; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/pipe_ret_t.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../include/pipe_ret_t.h" 3 | 4 | pipe_ret_t pipe_ret_t::failure(const std::string &msg) { 5 | return pipe_ret_t(false, msg); 6 | } 7 | 8 | pipe_ret_t pipe_ret_t::success(const std::string &msg) { 9 | return pipe_ret_t(true, msg); 10 | } 11 | -------------------------------------------------------------------------------- /src/tcp_client.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "../include/tcp_client.h" 3 | #include "../include/common.h" 4 | 5 | TcpClient::TcpClient() { 6 | _isConnected = false; 7 | _isClosed = true; 8 | } 9 | 10 | TcpClient::~TcpClient() { 11 | close(); 12 | } 13 | 14 | pipe_ret_t TcpClient::connectTo(const std::string & address, int port) { 15 | try { 16 | initializeSocket(); 17 | setAddress(address, port); 18 | } catch (const std::runtime_error& error) { 19 | return pipe_ret_t::failure(error.what()); 20 | } 21 | 22 | const int connectResult = connect(_sockfd.get() , (struct sockaddr *)&_server , sizeof(_server)); 23 | const bool connectionFailed = (connectResult == -1); 24 | if (connectionFailed) { 25 | return pipe_ret_t::failure(strerror(errno)); 26 | } 27 | 28 | startReceivingMessages(); 29 | _isConnected = true; 30 | _isClosed = false; 31 | 32 | return pipe_ret_t::success(); 33 | } 34 | 35 | void TcpClient::startReceivingMessages() { 36 | _receiveTask = new std::thread(&TcpClient::receiveTask, this); 37 | } 38 | 39 | void TcpClient::initializeSocket() { 40 | pipe_ret_t ret; 41 | 42 | _sockfd.set(socket(AF_INET , SOCK_STREAM , 0)); 43 | const bool socketFailed = (_sockfd.get() == -1); 44 | if (socketFailed) { 45 | throw std::runtime_error(strerror(errno)); 46 | } 47 | } 48 | 49 | void TcpClient::setAddress(const std::string& address, int port) { 50 | const int inetSuccess = inet_aton(address.c_str(), &_server.sin_addr); 51 | 52 | if(!inetSuccess) { // inet_addr failed to parse address 53 | // if hostname is not in IP strings and dots format, try resolve it 54 | struct hostent *host; 55 | struct in_addr **addrList; 56 | if ( (host = gethostbyname( address.c_str() ) ) == nullptr){ 57 | throw std::runtime_error("Failed to resolve hostname"); 58 | } 59 | addrList = (struct in_addr **) host->h_addr_list; 60 | _server.sin_addr = *addrList[0]; 61 | } 62 | _server.sin_family = AF_INET; 63 | _server.sin_port = htons(port); 64 | } 65 | 66 | 67 | pipe_ret_t TcpClient::sendMsg(const char * msg, size_t size) { 68 | const size_t numBytesSent = send(_sockfd.get(), msg, size, 0); 69 | 70 | if (numBytesSent < 0 ) { // send failed 71 | return pipe_ret_t::failure(strerror(errno)); 72 | } 73 | if (numBytesSent < size) { // not all bytes were sent 74 | char errorMsg[100]; 75 | sprintf(errorMsg, "Only %lu bytes out of %lu was sent to client", numBytesSent, size); 76 | return pipe_ret_t::failure(errorMsg); 77 | } 78 | return pipe_ret_t::success(); 79 | } 80 | 81 | void TcpClient::subscribe(const client_observer_t & observer) { 82 | std::lock_guard lock(_subscribersMtx); 83 | _subscibers.push_back(observer); 84 | } 85 | 86 | /* 87 | * Publish incomingPacketHandler client message to observer. 88 | * Observers get only messages that originated 89 | * from clients with IP address identical to 90 | * the specific observer requested IP 91 | */ 92 | void TcpClient::publishServerMsg(const char * msg, size_t msgSize) { 93 | std::lock_guard lock(_subscribersMtx); 94 | for (const auto &subscriber : _subscibers) { 95 | if (subscriber.incomingPacketHandler) { 96 | subscriber.incomingPacketHandler(msg, msgSize); 97 | } 98 | } 99 | } 100 | 101 | /* 102 | * Publish client disconnection to observer. 103 | * Observers get only notify about clients 104 | * with IP address identical to the specific 105 | * observer requested IP 106 | */ 107 | void TcpClient::publishServerDisconnected(const pipe_ret_t & ret) { 108 | std::lock_guard lock(_subscribersMtx); 109 | for (const auto &subscriber : _subscibers) { 110 | if (subscriber.disconnectionHandler) { 111 | subscriber.disconnectionHandler(ret); 112 | } 113 | } 114 | } 115 | 116 | /* 117 | * Receive server packets, and notify user 118 | */ 119 | void TcpClient::receiveTask() { 120 | while(_isConnected) { 121 | const fd_wait::Result waitResult = fd_wait::waitFor(_sockfd); 122 | 123 | if (waitResult == fd_wait::Result::FAILURE) { 124 | throw std::runtime_error(strerror(errno)); 125 | } else if (waitResult == fd_wait::Result::TIMEOUT) { 126 | continue; 127 | } 128 | 129 | char msg[MAX_PACKET_SIZE]; 130 | const size_t numOfBytesReceived = recv(_sockfd.get(), msg, MAX_PACKET_SIZE, 0); 131 | 132 | if(numOfBytesReceived < 1) { 133 | std::string errorMsg; 134 | if (numOfBytesReceived == 0) { //server closed connection 135 | errorMsg = "Server closed connection"; 136 | } else { 137 | errorMsg = strerror(errno); 138 | } 139 | _isConnected = false; 140 | publishServerDisconnected(pipe_ret_t::failure(errorMsg)); 141 | return; 142 | } else { 143 | publishServerMsg(msg, numOfBytesReceived); 144 | } 145 | } 146 | } 147 | 148 | void TcpClient::terminateReceiveThread() { 149 | _isConnected = false; 150 | 151 | if (_receiveTask) { 152 | _receiveTask->join(); 153 | delete _receiveTask; 154 | _receiveTask = nullptr; 155 | } 156 | } 157 | 158 | pipe_ret_t TcpClient::close(){ 159 | if (_isClosed) { 160 | return pipe_ret_t::failure("client is already closed"); 161 | } 162 | terminateReceiveThread(); 163 | 164 | const bool closeFailed = (::close(_sockfd.get()) == -1); 165 | if (closeFailed) { 166 | return pipe_ret_t::failure(strerror(errno)); 167 | } 168 | _isClosed = true; 169 | return pipe_ret_t::success(); 170 | } 171 | 172 | 173 | -------------------------------------------------------------------------------- /src/tcp_server.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "../include/tcp_server.h" 6 | #include "../include/common.h" 7 | 8 | 9 | TcpServer::TcpServer() { 10 | _subscribers.reserve(10); 11 | _clients.reserve(10); 12 | _stopRemoveClientsTask = false; 13 | } 14 | 15 | TcpServer::~TcpServer() { 16 | close(); 17 | } 18 | 19 | void TcpServer::subscribe(const server_observer_t & observer) { 20 | std::lock_guard lock(_subscribersMtx); 21 | _subscribers.push_back(observer); 22 | } 23 | 24 | void TcpServer::printClients() { 25 | std::lock_guard lock(_clientsMtx); 26 | if (_clients.empty()) { 27 | std::cout << "no connected clients\n"; 28 | } 29 | for (const Client *client : _clients) { 30 | client->print(); 31 | } 32 | } 33 | 34 | /** 35 | * Remove dead clients (disconnected) from clients vector periodically 36 | */ 37 | void TcpServer::removeDeadClients() { 38 | std::vector::const_iterator clientToRemove; 39 | while (!_stopRemoveClientsTask) { 40 | { 41 | std::lock_guard lock(_clientsMtx); 42 | do { 43 | clientToRemove = std::find_if(_clients.begin(), _clients.end(), 44 | [](Client *client) { return !client->isConnected(); }); 45 | 46 | if (clientToRemove != _clients.end()) { 47 | (*clientToRemove)->close(); 48 | delete *clientToRemove; 49 | _clients.erase(clientToRemove); 50 | } 51 | } while (clientToRemove != _clients.end()); 52 | } 53 | 54 | sleep(2); 55 | } 56 | } 57 | 58 | void TcpServer::terminateDeadClientsRemover() { 59 | if (_clientsRemoverThread) { 60 | _stopRemoveClientsTask = true; 61 | _clientsRemoverThread->join(); 62 | delete _clientsRemoverThread; 63 | _clientsRemoverThread = nullptr; 64 | } 65 | } 66 | 67 | /** 68 | * Handle different client events. Subscriber callbacks should be short and fast, and must not 69 | * call other server functions to avoid deadlock 70 | */ 71 | void TcpServer::clientEventHandler(const Client &client, ClientEvent event, const std::string &msg) { 72 | switch (event) { 73 | case ClientEvent::DISCONNECTED: { 74 | publishClientDisconnected(client.getIp(), msg); 75 | break; 76 | } 77 | case ClientEvent::INCOMING_MSG: { 78 | publishClientMsg(client, msg.c_str(), msg.size()); 79 | break; 80 | } 81 | } 82 | } 83 | 84 | /* 85 | * Publish incomingPacketHandler client message to observer. 86 | * Observers get only messages that originated 87 | * from clients with IP address identical to 88 | * the specific observer requested IP 89 | */ 90 | void TcpServer::publishClientMsg(const Client & client, const char * msg, size_t msgSize) { 91 | std::lock_guard lock(_subscribersMtx); 92 | 93 | for (const server_observer_t& subscriber : _subscribers) { 94 | if (subscriber.wantedIP == client.getIp() || subscriber.wantedIP.empty()) { 95 | if (subscriber.incomingPacketHandler) { 96 | subscriber.incomingPacketHandler(client.getIp(), msg, msgSize); 97 | } 98 | } 99 | } 100 | } 101 | 102 | /* 103 | * Publish client disconnection to observer. 104 | * Observers get only notify about clients 105 | * with IP address identical to the specific 106 | * observer requested IP 107 | */ 108 | void TcpServer::publishClientDisconnected(const std::string &clientIP, const std::string &clientMsg) { 109 | std::lock_guard lock(_subscribersMtx); 110 | 111 | for (const server_observer_t& subscriber : _subscribers) { 112 | if (subscriber.wantedIP == clientIP) { 113 | if (subscriber.disconnectionHandler) { 114 | subscriber.disconnectionHandler(clientIP, clientMsg); 115 | } 116 | } 117 | } 118 | } 119 | 120 | /* 121 | * Bind port and start listening 122 | * Return tcp_ret_t 123 | */ 124 | pipe_ret_t TcpServer::start(int port, int maxNumOfClients, bool removeDeadClientsAutomatically) { 125 | if (removeDeadClientsAutomatically) { 126 | _clientsRemoverThread = new std::thread(&TcpServer::removeDeadClients, this); 127 | } 128 | try { 129 | initializeSocket(); 130 | bindAddress(port); 131 | listenToClients(maxNumOfClients); 132 | } catch (const std::runtime_error &error) { 133 | return pipe_ret_t::failure(error.what()); 134 | } 135 | return pipe_ret_t::success(); 136 | } 137 | 138 | void TcpServer::initializeSocket() { 139 | _sockfd.set(socket(AF_INET, SOCK_STREAM, 0)); 140 | const bool socketFailed = (_sockfd.get() == -1); 141 | if (socketFailed) { 142 | throw std::runtime_error(strerror(errno)); 143 | } 144 | 145 | // set socket for reuse (otherwise might have to wait 4 minutes every time socket is closed) 146 | const int option = 1; 147 | setsockopt(_sockfd.get(), SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)); 148 | } 149 | 150 | void TcpServer::bindAddress(int port) { 151 | memset(&_serverAddress, 0, sizeof(_serverAddress)); 152 | _serverAddress.sin_family = AF_INET; 153 | _serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); 154 | _serverAddress.sin_port = htons(port); 155 | 156 | const int bindResult = bind(_sockfd.get(), (struct sockaddr *)&_serverAddress, sizeof(_serverAddress)); 157 | const bool bindFailed = (bindResult == -1); 158 | if (bindFailed) { 159 | throw std::runtime_error(strerror(errno)); 160 | } 161 | } 162 | 163 | void TcpServer::listenToClients(int maxNumOfClients) { 164 | const int clientsQueueSize = maxNumOfClients; 165 | const bool listenFailed = (listen(_sockfd.get(), clientsQueueSize) == -1); 166 | if (listenFailed) { 167 | throw std::runtime_error(strerror(errno)); 168 | } 169 | } 170 | 171 | /* 172 | * Accept and handle new client socket. To handle multiple clients, user must 173 | * call this function in a loop to enable the acceptance of more than one. 174 | * If timeout argument equal 0, this function is executed in blocking mode. 175 | * If timeout argument is > 0 then this function is executed in non-blocking 176 | * mode (async) and will quit after timeout seconds if no client tried to connect. 177 | * Return accepted client IP, or throw error if failed 178 | */ 179 | std::string TcpServer::acceptClient(uint timeout) { 180 | const pipe_ret_t waitingForClient = waitForClient(timeout); 181 | if (!waitingForClient.isSuccessful()) { 182 | throw std::runtime_error(waitingForClient.message()); 183 | } 184 | 185 | socklen_t socketSize = sizeof(_clientAddress); 186 | const int fileDescriptor = accept(_sockfd.get(), (struct sockaddr*)&_clientAddress, &socketSize); 187 | 188 | const bool acceptFailed = (fileDescriptor == -1); 189 | if (acceptFailed) { 190 | throw std::runtime_error(strerror(errno)); 191 | } 192 | 193 | auto newClient = new Client(fileDescriptor); 194 | newClient->setIp(inet_ntoa(_clientAddress.sin_addr)); 195 | using namespace std::placeholders; 196 | newClient->setEventsHandler(std::bind(&TcpServer::clientEventHandler, this, _1, _2, _3)); 197 | newClient->startListen(); 198 | 199 | std::lock_guard lock(_clientsMtx); 200 | _clients.push_back(newClient); 201 | 202 | return newClient->getIp(); 203 | } 204 | 205 | pipe_ret_t TcpServer::waitForClient(uint32_t timeout) { 206 | if (timeout > 0) { 207 | const fd_wait::Result waitResult = fd_wait::waitFor(_sockfd, timeout); 208 | const bool noIncomingClient = (!FD_ISSET(_sockfd.get(), &_fds)); 209 | 210 | if (waitResult == fd_wait::Result::FAILURE) { 211 | return pipe_ret_t::failure(strerror(errno)); 212 | } else if (waitResult == fd_wait::Result::TIMEOUT) { 213 | return pipe_ret_t::failure("Timeout waiting for client"); 214 | } else if (noIncomingClient) { 215 | return pipe_ret_t::failure("File descriptor is not set"); 216 | } 217 | } 218 | 219 | return pipe_ret_t::success(); 220 | } 221 | 222 | /* 223 | * Send message to all connected clients. 224 | * Return true if message was sent successfully to all clients 225 | */ 226 | pipe_ret_t TcpServer::sendToAllClients(const char * msg, size_t size) { 227 | std::lock_guard lock(_clientsMtx); 228 | 229 | for (const Client *client : _clients) { 230 | pipe_ret_t sendingResult = sendToClient(*client, msg, size); 231 | if (!sendingResult.isSuccessful()) { 232 | return sendingResult; 233 | } 234 | } 235 | 236 | return pipe_ret_t::success(); 237 | } 238 | 239 | /* 240 | * Send message to specific client (determined by client IP address). 241 | * Return true if message was sent successfully 242 | */ 243 | pipe_ret_t TcpServer::sendToClient(const Client & client, const char * msg, size_t size){ 244 | try{ 245 | client.send(msg, size); 246 | } catch (const std::runtime_error &error) { 247 | return pipe_ret_t::failure(error.what()); 248 | } 249 | 250 | return pipe_ret_t::success(); 251 | } 252 | 253 | pipe_ret_t TcpServer::sendToClient(const std::string & clientIP, const char * msg, size_t size) { 254 | std::lock_guard lock(_clientsMtx); 255 | 256 | const auto clientIter = std::find_if(_clients.begin(), _clients.end(), 257 | [&clientIP](Client *client) { return client->getIp() == clientIP; }); 258 | 259 | if (clientIter == _clients.end()) { 260 | return pipe_ret_t::failure("client not found"); 261 | } 262 | 263 | const Client &client = *(*clientIter); 264 | return sendToClient(client, msg, size); 265 | } 266 | 267 | /* 268 | * Close server and clients resources. 269 | * Return true is successFlag, false otherwise 270 | */ 271 | pipe_ret_t TcpServer::close() { 272 | terminateDeadClientsRemover(); 273 | { // close clients 274 | std::lock_guard lock(_clientsMtx); 275 | 276 | for (Client * client : _clients) { 277 | try { 278 | client->close(); 279 | } catch (const std::runtime_error& error) { 280 | return pipe_ret_t::failure(error.what()); 281 | } 282 | } 283 | _clients.clear(); 284 | } 285 | 286 | { // close server 287 | const int closeServerResult = ::close(_sockfd.get()); 288 | const bool closeServerFailed = (closeServerResult == -1); 289 | if (closeServerFailed) { 290 | return pipe_ret_t::failure(strerror(errno)); 291 | } 292 | } 293 | 294 | return pipe_ret_t::success(); 295 | } 296 | --------------------------------------------------------------------------------