Repository: elhayra/tcp_server_client
Branch: master
Commit: 34c8c5e1e391
Files: 26
Total size: 41.3 KB
Directory structure:
gitextract_ndbhzy7z/
├── .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
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
cmake-build
cmake-build-debug
build
================================================
FILE: .github/workflows/cmake.yml
================================================
name: CMake
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally
# well on Windows or Mac. You can convert this to a matrix build if you need
# cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Test
working-directory: ${{github.workspace}}/build
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C ${{env.BUILD_TYPE}}
================================================
FILE: .gitignore
================================================
/build/
.idea/
cmake-build-debug/
cmake-build/
================================================
FILE: .ignore
================================================
cmake-build-debug/
.idea/
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.8.1)
project(tcp_client_server)
find_package (Threads)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-std=c++11")
add_library(${PROJECT_NAME}
src/tcp_client.cpp
src/tcp_server.cpp
src/client.cpp
src/pipe_ret_t.cpp
src/common.cpp)
option(SERVER_EXAMPLE "Build SERVER" ON)
if(SERVER_EXAMPLE)
add_definitions(
-DSERVER_EXAMPLE
)
add_executable(tcp_server examples/server_example.cpp)
target_link_libraries (tcp_server ${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})
endif()
option(CLIENT_EXAMPLE "Build CLIENT" ON)
if(CLIENT_EXAMPLE)
add_definitions(
-DCLIENT_EXAMPLE
)
add_executable(tcp_client examples/client_example.cpp)
target_link_libraries (tcp_client ${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})
endif()
================================================
FILE: Dockerfile
================================================
FROM alpine:3.8
RUN set -ex && \
apk add --no-cache gcc musl-dev cmake cmake clang clang-dev make g++ libc-dev linux-headers
WORKDIR /usr/src/tcp_server_client
COPY . .
RUN chmod +x build.sh
RUN ./build.sh
EXPOSE 65123
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Elhay Rauper
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================

# TCP server client
A thin and simple C++ TCP client and server library with examples.
### Platforms Support
Currently, both linux and mac are supported
### Examples
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.
You can find code examples of both server and client under the 'examples' directory. Both the library and the examples are well documented.
### Server
The server is thread-safe, and can handle multiple clients at the same time, and remove dead clients resources automatically.
## Quick start
build the examples and static library file:
```bash
$ cd tcp_server_client
$ ./build
```
run the server and client examples:
Navigate into the `build` folder and run in the terminal:
```bash
$ cd build
```
In terminal #1:
```bash
$ ./tcp_server
```
In terminal #2:
```bash
$ ./tcp_client
```
## Building
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.
In order to build the project you can either use the `build.sh` script:
```bash
$ cd tcp_server_client
$ ./build
```
or build it manually:
```bash
$ cd tcp_server_client
$ mkdir build
$ cmake ..
$ make
```
The build process generate three files: `libtcp_client_server.a`, `tcp_client` and `tcp_server`.
The last two are the executables of the examples which can be executed in the terminal.
#### Building Only Server or Client
By default, the CMake configuration builds both server and client. However, you can use flags to build only one of the apps as follows:
###### Disabling the server build
To build only the client, disable the server build by replace the `cmake ..` call by:
```bash
cmake -DSERVER_EXAMPLE=OFF ..
```
And then run the make command as usual.
###### Disabling the client build
To build only the server, disable the client build by replace the `cmake ..` call by:
```bash
cmake -DCLIENT_EXAMPLE=OFF ..
```
And then run the make command as usual.
## Run the Examples
Navigate into the `build` folder and run in the terminal:
```bash
$ ./tcp_server
```
You should see a menu output on the screen, we'll get back to that.
In a different terminal, run the client:
````bash
$ ./tcp_client
````
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.
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.
## Server-Client API
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.
In addition, you can also look at the public functions in `tcp_client.h` and `tcp_server.h` to learn what APIs are available.
### Server and Client Events
Both server and client are using the observer design pattern to register and handle events.
When registering to an event with a callback, you should make sure that:
- The callback is fast (not doing any heavy lifting tasks) because those callbacks are called from the context of the server or client.
- No server / client function calls are made in those callbacks to avoid possible deadlock.
================================================
FILE: build.sh
================================================
#!/bin/sh
mkdir -p build && \
cd build && \
cmake .. && make
================================================
FILE: docker-compose.yml
================================================
services:
tcp-server:
build:
context: .
dockerfile: Dockerfile
command: sh -c "cd build && ./tcp_server"
tcp-client:
build:
context: .
dockerfile: Dockerfile
command: sh -c "cd build && ./tcp_client"
================================================
FILE: examples/client_example.cpp
================================================
///////////////////////////////////////////////////////////
/////////////////////CLIENT EXAMPLE////////////////////////
///////////////////////////////////////////////////////////
#ifdef CLIENT_EXAMPLE
#include <iostream>
#include <csignal>
#include "../include/tcp_client.h"
TcpClient client;
// on sig_exit, close client
void sig_exit(int s)
{
std::cout << "Closing client...\n";
pipe_ret_t finishRet = client.close();
if (finishRet.isSuccessful()) {
std::cout << "Client closed.\n";
} else {
std::cout << "Failed to close client.\n";
}
exit(0);
}
// observer callback. will be called for every new message received by the server
void onIncomingMsg(const char * msg, size_t size) {
std::cout << "Got msg from server: " << msg << "\n";
}
// observer callback. will be called when server disconnects
void onDisconnection(const pipe_ret_t & ret) {
std::cout << "Server disconnected: " << ret.message() << "\n";
}
void printMenu() {
std::cout << "select one of the following options: \n" <<
"1. send message to server\n" <<
"2. close client and exit\n";
}
int getMenuSelection() {
int selection = 0;
std::cin >> selection;
if (!std::cin) {
throw std::runtime_error("invalid menu input. expected a number, but got something else");
}
std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
return selection;
}
bool handleMenuSelection(int selection) {
static const int minSelection = 1;
static const int maxSelection = 2;
if (selection < minSelection || selection > maxSelection) {
std::cout << "invalid selection: " << selection <<
". selection must be b/w " << minSelection << " and " << maxSelection << "\n";
return false;
}
switch (selection) {
case 1: { // send message to server
std::cout << "enter message to send:\n";
std::string message;
std::cin >> message;
pipe_ret_t sendRet = client.sendMsg(message.c_str(), message.size());
if (!sendRet.isSuccessful()) {
std::cout << "Failed to send message: " << sendRet.message() << "\n";
} else {
std::cout << "message was sent successfuly\n";
}
break;
}
case 2: { // close client
const pipe_ret_t closeResult = client.close();
if (!closeResult.isSuccessful()) {
std::cout << "closing client failed: " << closeResult.message() << "\n";
} else {
std::cout << "closed client successfully\n";
}
return true;
}
default: {
std::cout << "invalid selection: " << selection <<
". selection must be b/w " << minSelection << " and " << maxSelection << "\n";
}
}
return false;
}
int main() {
//register to SIGINT to close client when user press ctrl+c
signal(SIGINT, sig_exit);
// configure and register observer
client_observer_t observer;
observer.wantedIP = "127.0.0.1";
observer.incomingPacketHandler = onIncomingMsg;
observer.disconnectionHandler = onDisconnection;
client.subscribe(observer);
// connect client to an open server
bool connected = false;
while (!connected) {
pipe_ret_t connectRet = client.connectTo("127.0.0.1", 65123);
connected = connectRet.isSuccessful();
if (connected) {
std::cout << "Client connected successfully\n";
} else {
std::cout << "Client failed to connect: " << connectRet.message() << "\n"
<< "Make sure the server is open and listening\n\n";
sleep(2);
std::cout << "Retrying to connect...\n";
}
};
// send messages to server
bool shouldTerminate = false;
while(!shouldTerminate)
{
printMenu();
int selection = getMenuSelection();
shouldTerminate = handleMenuSelection(selection);
}
return 0;
}
#endif
================================================
FILE: examples/server_example.cpp
================================================
///////////////////////////////////////////////////////////
/////////////////////SERVER EXAMPLE////////////////////////
///////////////////////////////////////////////////////////
#ifdef SERVER_EXAMPLE
#include <iostream>
#include <csignal>
#include "../include/tcp_server.h"
// declare the server
TcpServer server;
// declare a server observer which will receive incomingPacketHandler messages.
// the server supports multiple observers
server_observer_t observer1, observer2;
// observer callback. will be called for every new message received by clients
// with the requested IP address
void onIncomingMsg1(const std::string &clientIP, const char * msg, size_t size) {
std::string msgStr = msg;
// print client message
std::cout << "Observer1 got client msg: " << msgStr << "\n";
}
// observer callback. will be called for every new message received by clients
// with the requested IP address
void onIncomingMsg2(const std::string &clientIP, const char * msg, size_t size) {
std::string msgStr = msg;
// print client message
std::cout << "Observer2 got client msg: " << msgStr << "\n";
}
// observer callback. will be called when client disconnects
void onClientDisconnected(const std::string &ip, const std::string &msg) {
std::cout << "Client: " << ip << " disconnected. Reason: " << msg << "\n";
}
// accept a single client.
// if you wish to accept multiple clients, call this function in a loop
// (you might want to use a thread to accept clients without blocking)
void acceptClient() {
try {
std::cout << "waiting for incoming client...\n";
std::string clientIP = server.acceptClient(0);
std::cout << "accepted new client with IP: " << clientIP << "\n" <<
"== updated list of accepted clients ==" << "\n";
server.printClients();
} catch (const std::runtime_error &error) {
std::cout << "Accepting client failed: " << error.what() << "\n";
}
}
void printMenu() {
std::cout << "\n\nselect one of the following options: \n" <<
"1. send all clients a message\n" <<
"2. print list of accepted clients\n" <<
"3. send message to a specific client\n" <<
"4. close server and exit\n";
}
int getMenuSelection() {
int selection = 0;
std::cin >> selection;
if (!std::cin) {
throw std::runtime_error("invalid menu input. expected a number, but got something else");
}
std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
return selection;
}
/**
* handle menu selection and return true in case program should terminate
* after handling selection
*/
bool handleMenuSelection(int selection) {
static const int minSelection = 1;
static const int maxSelection = 4;
if (selection < minSelection || selection > maxSelection) {
std::cout << "invalid selection: " << selection <<
". selection must be b/w " << minSelection << " and " << maxSelection << "\n";
return false;
}
switch (selection) {
case 1: { // send all clients a message
std::string msg;
std::cout << "type message to send to all connected clients:\n";
getline(std::cin, msg);
pipe_ret_t sendingResult = server.sendToAllClients(msg.c_str(), msg.size());
if (sendingResult.isSuccessful()) {
std::cout << "sent message to all clients successfully\n";
} else {
std::cout << "failed to sent message: " << sendingResult.message() << "\n";
}
break;
}
case 2: { // print list of accepted clients
server.printClients();
break;
}
case 3: { // send message to a specific client
std::cout << "enter client IP:\n";
std::string clientIP;
std::cin >> clientIP;
std::cout << "enter message to send:\n";
std::string message;
std::cin >> message;
pipe_ret_t result = server.sendToClient(clientIP, message.c_str(), message.size());
if (!result.isSuccessful()) {
std::cout << "sending failed: " << result.message() << "\n";
} else {
std::cout << "sending succeeded\n";
}
break;
};
case 4: { // close server
pipe_ret_t sendingResult = server.close();
if (sendingResult.isSuccessful()) {
std::cout << "closed server successfully\n";
} else {
std::cout << "failed to close server: " << sendingResult.message() << "\n";
}
return true;
}
default: {
std::cout << "invalid selection: " << selection <<
". selection must be b/w " << minSelection << " and " << maxSelection << "\n";
}
}
return false;
}
int main()
{
// start server on port 65123
pipe_ret_t startRet = server.start(65123);
if (startRet.isSuccessful()) {
std::cout << "Server setup succeeded\n";
} else {
std::cout << "Server setup failed: " << startRet.message() << "\n";
return EXIT_FAILURE;
}
// configure and register observer1
observer1.incomingPacketHandler = onIncomingMsg1;
observer1.disconnectionHandler = onClientDisconnected;
observer1.wantedIP = "127.0.0.1";
server.subscribe(observer1);
// configure and register observer2
observer2.incomingPacketHandler = onIncomingMsg2;
observer2.disconnectionHandler = nullptr; // nullptr or not setting this means we don't care about disconnection event
observer2.wantedIP = "10.88.0.11"; // use empty string instead to receive messages from any IP address
server.subscribe(observer2);
acceptClient();
bool shouldTerminate = false;
while(!shouldTerminate) {
printMenu();
int selection = getMenuSelection();
shouldTerminate = handleMenuSelection(selection);
}
return 0;
}
#endif
================================================
FILE: include/client.h
================================================
#pragma once
#include <string>
#include <thread>
#include <functional>
#include <mutex>
#include <atomic>
#include "pipe_ret_t.h"
#include "client_event.h"
#include "file_descriptor.h"
class Client {
using client_event_handler_t = std::function<void(const Client&, ClientEvent, const std::string&)>;
private:
FileDescriptor _sockfd;
std::string _ip = "";
std::atomic<bool> _isConnected;
std::thread * _receiveThread = nullptr;
client_event_handler_t _eventHandlerCallback;
void setConnected(bool flag) { _isConnected = flag; }
void receiveTask();
void terminateReceiveThread();
public:
Client(int);
bool operator ==(const Client & other) const ;
void setIp(const std::string & ip) { _ip = ip; }
std::string getIp() const { return _ip; }
void setEventsHandler(const client_event_handler_t & eventHandler) { _eventHandlerCallback = eventHandler; }
void publishEvent(ClientEvent clientEvent, const std::string &msg = "");
bool isConnected() const { return _isConnected; }
void startListen();
void send(const char * msg, size_t msgSize) const;
void close();
void print() const;
};
================================================
FILE: include/client_event.h
================================================
#pragma once
enum ClientEvent {
DISCONNECTED,
INCOMING_MSG
};
================================================
FILE: include/client_observer.h
================================================
#pragma once
#include <string>
#include <functional>
#include "pipe_ret_t.h"
struct client_observer_t {
std::string wantedIP = "";
std::function<void(const char * msg, size_t size)> incomingPacketHandler = nullptr;
std::function<void(const pipe_ret_t & ret)> disconnectionHandler = nullptr;
};
================================================
FILE: include/common.h
================================================
#pragma once
#include <cstdio>
#define MAX_PACKET_SIZE 4096
namespace fd_wait {
enum Result {
FAILURE,
TIMEOUT,
SUCCESS
};
Result waitFor(const FileDescriptor &fileDescriptor, uint32_t timeoutSeconds = 1);
};
================================================
FILE: include/file_descriptor.h
================================================
#pragma once
class FileDescriptor {
private:
int _sockfd = 0;
public:
void set(int fd) { _sockfd = fd; }
int get() const { return _sockfd; }
};
================================================
FILE: include/pipe_ret_t.h
================================================
#pragma once
class pipe_ret_t {
private:
bool _successFlag = false;
std::string _msg = "";
public:
pipe_ret_t() = default;
pipe_ret_t(bool successFlag, const std::string &msg) :
_successFlag{successFlag},
_msg{msg}
{}
std::string message() const { return _msg; }
bool isSuccessful() const { return _successFlag; }
static pipe_ret_t failure(const std::string & msg);
static pipe_ret_t success(const std::string &msg = "");
};
================================================
FILE: include/server_observer.h
================================================
#pragma once
#include <string>
#include <functional>
#include "client.h"
struct server_observer_t {
std::string wantedIP = "";
std::function<void(const std::string &clientIP, const char * msg, size_t size)> incomingPacketHandler;
std::function<void(const std::string &ip, const std::string &msg)> disconnectionHandler;
};
================================================
FILE: include/tcp_client.h
================================================
#pragma once
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netdb.h>
#include <vector>
#include <errno.h>
#include <thread>
#include <mutex>
#include <atomic>
#include "client_observer.h"
#include "pipe_ret_t.h"
#include "file_descriptor.h"
class TcpClient
{
private:
FileDescriptor _sockfd;
std::atomic<bool> _isConnected;
std::atomic<bool> _isClosed;
struct sockaddr_in _server;
std::vector<client_observer_t> _subscibers;
std::thread * _receiveTask = nullptr;
std::mutex _subscribersMtx;
void initializeSocket();
void startReceivingMessages();
void setAddress(const std::string& address, int port);
void publishServerMsg(const char * msg, size_t msgSize);
void publishServerDisconnected(const pipe_ret_t & ret);
void receiveTask();
void terminateReceiveThread();
public:
TcpClient();
~TcpClient();
pipe_ret_t connectTo(const std::string & address, int port);
pipe_ret_t sendMsg(const char * msg, size_t size);
void subscribe(const client_observer_t & observer);
bool isConnected() const { return _isConnected; }
pipe_ret_t close();
};
================================================
FILE: include/tcp_server.h
================================================
#pragma once
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <thread>
#include <functional>
#include <cstring>
#include <errno.h>
#include <iostream>
#include <mutex>
#include "client.h"
#include "server_observer.h"
#include "pipe_ret_t.h"
#include "file_descriptor.h"
class TcpServer {
private:
FileDescriptor _sockfd;
struct sockaddr_in _serverAddress;
struct sockaddr_in _clientAddress;
fd_set _fds;
std::vector<Client*> _clients;
std::vector<server_observer_t> _subscribers;
std::mutex _subscribersMtx;
std::mutex _clientsMtx;
std::thread * _clientsRemoverThread = nullptr;
std::atomic<bool> _stopRemoveClientsTask;
void publishClientMsg(const Client & client, const char * msg, size_t msgSize);
void publishClientDisconnected(const std::string&, const std::string&);
pipe_ret_t waitForClient(uint32_t timeout);
void clientEventHandler(const Client&, ClientEvent, const std::string &msg);
void removeDeadClients();
void terminateDeadClientsRemover();
static pipe_ret_t sendToClient(const Client & client, const char * msg, size_t size);
public:
TcpServer();
~TcpServer();
pipe_ret_t start(int port, int maxNumOfClients = 5, bool removeDeadClientsAutomatically = true);
void initializeSocket();
void bindAddress(int port);
void listenToClients(int maxNumOfClients);
std::string acceptClient(uint timeout);
void subscribe(const server_observer_t & observer);
pipe_ret_t sendToAllClients(const char * msg, size_t size);
pipe_ret_t sendToClient(const std::string & clientIP, const char * msg, size_t size);
pipe_ret_t close();
void printClients();
};
================================================
FILE: src/client.cpp
================================================
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <stdexcept>
#include <sys/socket.h>
#include <iostream>
#include "../include/client.h"
#include "../include/common.h"
Client::Client(int fileDescriptor) {
_sockfd.set(fileDescriptor);
setConnected(false);
}
bool Client::operator==(const Client & other) const {
if ((this->_sockfd.get() == other._sockfd.get()) &&
(this->_ip == other._ip) ) {
return true;
}
return false;
}
void Client::startListen() {
setConnected(true);
_receiveThread = new std::thread(&Client::receiveTask, this);
}
void Client::send(const char *msg, size_t msgSize) const {
const size_t numBytesSent = ::send(_sockfd.get(), (char *)msg, msgSize, 0);
const bool sendFailed = (numBytesSent < 0);
if (sendFailed) {
throw std::runtime_error(strerror(errno));
}
const bool notAllBytesWereSent = (numBytesSent < msgSize);
if (notAllBytesWereSent) {
char errorMsg[100];
sprintf(errorMsg, "Only %lu bytes out of %lu was sent to client", numBytesSent, msgSize);
throw std::runtime_error(errorMsg);
}
}
/*
* Receive client packets, and notify user
*/
void Client::receiveTask() {
while(isConnected()) {
const fd_wait::Result waitResult = fd_wait::waitFor(_sockfd);
if (waitResult == fd_wait::Result::FAILURE) {
throw std::runtime_error(strerror(errno));
} else if (waitResult == fd_wait::Result::TIMEOUT) {
continue;
}
char receivedMessage[MAX_PACKET_SIZE];
const size_t numOfBytesReceived = recv(_sockfd.get(), receivedMessage, MAX_PACKET_SIZE, 0);
if(numOfBytesReceived < 1) {
const bool clientClosedConnection = (numOfBytesReceived == 0);
std::string disconnectionMessage;
if (clientClosedConnection) {
disconnectionMessage = "Client closed connection";
} else {
disconnectionMessage = strerror(errno);
}
setConnected(false);
publishEvent(ClientEvent::DISCONNECTED, disconnectionMessage);
return;
} else {
publishEvent(ClientEvent::INCOMING_MSG, receivedMessage);
}
}
}
void Client::publishEvent(ClientEvent clientEvent, const std::string &msg) {
_eventHandlerCallback(*this, clientEvent, msg);
}
void Client::print() const {
const std::string connected = isConnected() ? "True" : "False";
std::cout << "-----------------\n" <<
"IP address: " << getIp() << std::endl <<
"Connected?: " << connected << std::endl <<
"Socket FD: " << _sockfd.get() << std::endl;
}
void Client::terminateReceiveThread() {
setConnected(false);
if (_receiveThread) {
_receiveThread->join();
delete _receiveThread;
_receiveThread = nullptr;
}
}
void Client::close() {
terminateReceiveThread();
const bool closeFailed = (::close(_sockfd.get()) == -1);
if (closeFailed) {
throw std::runtime_error(strerror(errno));
}
}
================================================
FILE: src/common.cpp
================================================
#include <cstdint>
#include "../include/file_descriptor.h"
#include "../include/common.h"
#include <sys/select.h>
#define SELECT_FAILED -1
#define SELECT_TIMEOUT 0
namespace fd_wait {
/**
* monitor file descriptor and wait for I/O operation
*/
Result waitFor(const FileDescriptor &fileDescriptor, uint32_t timeoutSeconds) {
struct timeval tv;
tv.tv_sec = timeoutSeconds;
tv.tv_usec = 0;
fd_set fds;
FD_ZERO(&fds);
FD_SET(fileDescriptor.get(), &fds);
const int selectRet = select(fileDescriptor.get() + 1, &fds, nullptr, nullptr, &tv);
if (selectRet == SELECT_FAILED) {
return Result::FAILURE;
} else if (selectRet == SELECT_TIMEOUT) {
return Result::TIMEOUT;
}
return Result::SUCCESS;
}
}
================================================
FILE: src/pipe_ret_t.cpp
================================================
#include <string>
#include "../include/pipe_ret_t.h"
pipe_ret_t pipe_ret_t::failure(const std::string &msg) {
return pipe_ret_t(false, msg);
}
pipe_ret_t pipe_ret_t::success(const std::string &msg) {
return pipe_ret_t(true, msg);
}
================================================
FILE: src/tcp_client.cpp
================================================
#include "../include/tcp_client.h"
#include "../include/common.h"
TcpClient::TcpClient() {
_isConnected = false;
_isClosed = true;
}
TcpClient::~TcpClient() {
close();
}
pipe_ret_t TcpClient::connectTo(const std::string & address, int port) {
try {
initializeSocket();
setAddress(address, port);
} catch (const std::runtime_error& error) {
return pipe_ret_t::failure(error.what());
}
const int connectResult = connect(_sockfd.get() , (struct sockaddr *)&_server , sizeof(_server));
const bool connectionFailed = (connectResult == -1);
if (connectionFailed) {
return pipe_ret_t::failure(strerror(errno));
}
startReceivingMessages();
_isConnected = true;
_isClosed = false;
return pipe_ret_t::success();
}
void TcpClient::startReceivingMessages() {
_receiveTask = new std::thread(&TcpClient::receiveTask, this);
}
void TcpClient::initializeSocket() {
pipe_ret_t ret;
_sockfd.set(socket(AF_INET , SOCK_STREAM , 0));
const bool socketFailed = (_sockfd.get() == -1);
if (socketFailed) {
throw std::runtime_error(strerror(errno));
}
}
void TcpClient::setAddress(const std::string& address, int port) {
const int inetSuccess = inet_aton(address.c_str(), &_server.sin_addr);
if(!inetSuccess) { // inet_addr failed to parse address
// if hostname is not in IP strings and dots format, try resolve it
struct hostent *host;
struct in_addr **addrList;
if ( (host = gethostbyname( address.c_str() ) ) == nullptr){
throw std::runtime_error("Failed to resolve hostname");
}
addrList = (struct in_addr **) host->h_addr_list;
_server.sin_addr = *addrList[0];
}
_server.sin_family = AF_INET;
_server.sin_port = htons(port);
}
pipe_ret_t TcpClient::sendMsg(const char * msg, size_t size) {
const size_t numBytesSent = send(_sockfd.get(), msg, size, 0);
if (numBytesSent < 0 ) { // send failed
return pipe_ret_t::failure(strerror(errno));
}
if (numBytesSent < size) { // not all bytes were sent
char errorMsg[100];
sprintf(errorMsg, "Only %lu bytes out of %lu was sent to client", numBytesSent, size);
return pipe_ret_t::failure(errorMsg);
}
return pipe_ret_t::success();
}
void TcpClient::subscribe(const client_observer_t & observer) {
std::lock_guard<std::mutex> lock(_subscribersMtx);
_subscibers.push_back(observer);
}
/*
* Publish incomingPacketHandler client message to observer.
* Observers get only messages that originated
* from clients with IP address identical to
* the specific observer requested IP
*/
void TcpClient::publishServerMsg(const char * msg, size_t msgSize) {
std::lock_guard<std::mutex> lock(_subscribersMtx);
for (const auto &subscriber : _subscibers) {
if (subscriber.incomingPacketHandler) {
subscriber.incomingPacketHandler(msg, msgSize);
}
}
}
/*
* Publish client disconnection to observer.
* Observers get only notify about clients
* with IP address identical to the specific
* observer requested IP
*/
void TcpClient::publishServerDisconnected(const pipe_ret_t & ret) {
std::lock_guard<std::mutex> lock(_subscribersMtx);
for (const auto &subscriber : _subscibers) {
if (subscriber.disconnectionHandler) {
subscriber.disconnectionHandler(ret);
}
}
}
/*
* Receive server packets, and notify user
*/
void TcpClient::receiveTask() {
while(_isConnected) {
const fd_wait::Result waitResult = fd_wait::waitFor(_sockfd);
if (waitResult == fd_wait::Result::FAILURE) {
throw std::runtime_error(strerror(errno));
} else if (waitResult == fd_wait::Result::TIMEOUT) {
continue;
}
char msg[MAX_PACKET_SIZE];
const size_t numOfBytesReceived = recv(_sockfd.get(), msg, MAX_PACKET_SIZE, 0);
if(numOfBytesReceived < 1) {
std::string errorMsg;
if (numOfBytesReceived == 0) { //server closed connection
errorMsg = "Server closed connection";
} else {
errorMsg = strerror(errno);
}
_isConnected = false;
publishServerDisconnected(pipe_ret_t::failure(errorMsg));
return;
} else {
publishServerMsg(msg, numOfBytesReceived);
}
}
}
void TcpClient::terminateReceiveThread() {
_isConnected = false;
if (_receiveTask) {
_receiveTask->join();
delete _receiveTask;
_receiveTask = nullptr;
}
}
pipe_ret_t TcpClient::close(){
if (_isClosed) {
return pipe_ret_t::failure("client is already closed");
}
terminateReceiveThread();
const bool closeFailed = (::close(_sockfd.get()) == -1);
if (closeFailed) {
return pipe_ret_t::failure(strerror(errno));
}
_isClosed = true;
return pipe_ret_t::success();
}
================================================
FILE: src/tcp_server.cpp
================================================
#include <functional>
#include <thread>
#include <algorithm>
#include "../include/tcp_server.h"
#include "../include/common.h"
TcpServer::TcpServer() {
_subscribers.reserve(10);
_clients.reserve(10);
_stopRemoveClientsTask = false;
}
TcpServer::~TcpServer() {
close();
}
void TcpServer::subscribe(const server_observer_t & observer) {
std::lock_guard<std::mutex> lock(_subscribersMtx);
_subscribers.push_back(observer);
}
void TcpServer::printClients() {
std::lock_guard<std::mutex> lock(_clientsMtx);
if (_clients.empty()) {
std::cout << "no connected clients\n";
}
for (const Client *client : _clients) {
client->print();
}
}
/**
* Remove dead clients (disconnected) from clients vector periodically
*/
void TcpServer::removeDeadClients() {
std::vector<Client*>::const_iterator clientToRemove;
while (!_stopRemoveClientsTask) {
{
std::lock_guard<std::mutex> lock(_clientsMtx);
do {
clientToRemove = std::find_if(_clients.begin(), _clients.end(),
[](Client *client) { return !client->isConnected(); });
if (clientToRemove != _clients.end()) {
(*clientToRemove)->close();
delete *clientToRemove;
_clients.erase(clientToRemove);
}
} while (clientToRemove != _clients.end());
}
sleep(2);
}
}
void TcpServer::terminateDeadClientsRemover() {
if (_clientsRemoverThread) {
_stopRemoveClientsTask = true;
_clientsRemoverThread->join();
delete _clientsRemoverThread;
_clientsRemoverThread = nullptr;
}
}
/**
* Handle different client events. Subscriber callbacks should be short and fast, and must not
* call other server functions to avoid deadlock
*/
void TcpServer::clientEventHandler(const Client &client, ClientEvent event, const std::string &msg) {
switch (event) {
case ClientEvent::DISCONNECTED: {
publishClientDisconnected(client.getIp(), msg);
break;
}
case ClientEvent::INCOMING_MSG: {
publishClientMsg(client, msg.c_str(), msg.size());
break;
}
}
}
/*
* Publish incomingPacketHandler client message to observer.
* Observers get only messages that originated
* from clients with IP address identical to
* the specific observer requested IP
*/
void TcpServer::publishClientMsg(const Client & client, const char * msg, size_t msgSize) {
std::lock_guard<std::mutex> lock(_subscribersMtx);
for (const server_observer_t& subscriber : _subscribers) {
if (subscriber.wantedIP == client.getIp() || subscriber.wantedIP.empty()) {
if (subscriber.incomingPacketHandler) {
subscriber.incomingPacketHandler(client.getIp(), msg, msgSize);
}
}
}
}
/*
* Publish client disconnection to observer.
* Observers get only notify about clients
* with IP address identical to the specific
* observer requested IP
*/
void TcpServer::publishClientDisconnected(const std::string &clientIP, const std::string &clientMsg) {
std::lock_guard<std::mutex> lock(_subscribersMtx);
for (const server_observer_t& subscriber : _subscribers) {
if (subscriber.wantedIP == clientIP) {
if (subscriber.disconnectionHandler) {
subscriber.disconnectionHandler(clientIP, clientMsg);
}
}
}
}
/*
* Bind port and start listening
* Return tcp_ret_t
*/
pipe_ret_t TcpServer::start(int port, int maxNumOfClients, bool removeDeadClientsAutomatically) {
if (removeDeadClientsAutomatically) {
_clientsRemoverThread = new std::thread(&TcpServer::removeDeadClients, this);
}
try {
initializeSocket();
bindAddress(port);
listenToClients(maxNumOfClients);
} catch (const std::runtime_error &error) {
return pipe_ret_t::failure(error.what());
}
return pipe_ret_t::success();
}
void TcpServer::initializeSocket() {
_sockfd.set(socket(AF_INET, SOCK_STREAM, 0));
const bool socketFailed = (_sockfd.get() == -1);
if (socketFailed) {
throw std::runtime_error(strerror(errno));
}
// set socket for reuse (otherwise might have to wait 4 minutes every time socket is closed)
const int option = 1;
setsockopt(_sockfd.get(), SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
}
void TcpServer::bindAddress(int port) {
memset(&_serverAddress, 0, sizeof(_serverAddress));
_serverAddress.sin_family = AF_INET;
_serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
_serverAddress.sin_port = htons(port);
const int bindResult = bind(_sockfd.get(), (struct sockaddr *)&_serverAddress, sizeof(_serverAddress));
const bool bindFailed = (bindResult == -1);
if (bindFailed) {
throw std::runtime_error(strerror(errno));
}
}
void TcpServer::listenToClients(int maxNumOfClients) {
const int clientsQueueSize = maxNumOfClients;
const bool listenFailed = (listen(_sockfd.get(), clientsQueueSize) == -1);
if (listenFailed) {
throw std::runtime_error(strerror(errno));
}
}
/*
* Accept and handle new client socket. To handle multiple clients, user must
* call this function in a loop to enable the acceptance of more than one.
* If timeout argument equal 0, this function is executed in blocking mode.
* If timeout argument is > 0 then this function is executed in non-blocking
* mode (async) and will quit after timeout seconds if no client tried to connect.
* Return accepted client IP, or throw error if failed
*/
std::string TcpServer::acceptClient(uint timeout) {
const pipe_ret_t waitingForClient = waitForClient(timeout);
if (!waitingForClient.isSuccessful()) {
throw std::runtime_error(waitingForClient.message());
}
socklen_t socketSize = sizeof(_clientAddress);
const int fileDescriptor = accept(_sockfd.get(), (struct sockaddr*)&_clientAddress, &socketSize);
const bool acceptFailed = (fileDescriptor == -1);
if (acceptFailed) {
throw std::runtime_error(strerror(errno));
}
auto newClient = new Client(fileDescriptor);
newClient->setIp(inet_ntoa(_clientAddress.sin_addr));
using namespace std::placeholders;
newClient->setEventsHandler(std::bind(&TcpServer::clientEventHandler, this, _1, _2, _3));
newClient->startListen();
std::lock_guard<std::mutex> lock(_clientsMtx);
_clients.push_back(newClient);
return newClient->getIp();
}
pipe_ret_t TcpServer::waitForClient(uint32_t timeout) {
if (timeout > 0) {
const fd_wait::Result waitResult = fd_wait::waitFor(_sockfd, timeout);
const bool noIncomingClient = (!FD_ISSET(_sockfd.get(), &_fds));
if (waitResult == fd_wait::Result::FAILURE) {
return pipe_ret_t::failure(strerror(errno));
} else if (waitResult == fd_wait::Result::TIMEOUT) {
return pipe_ret_t::failure("Timeout waiting for client");
} else if (noIncomingClient) {
return pipe_ret_t::failure("File descriptor is not set");
}
}
return pipe_ret_t::success();
}
/*
* Send message to all connected clients.
* Return true if message was sent successfully to all clients
*/
pipe_ret_t TcpServer::sendToAllClients(const char * msg, size_t size) {
std::lock_guard<std::mutex> lock(_clientsMtx);
for (const Client *client : _clients) {
pipe_ret_t sendingResult = sendToClient(*client, msg, size);
if (!sendingResult.isSuccessful()) {
return sendingResult;
}
}
return pipe_ret_t::success();
}
/*
* Send message to specific client (determined by client IP address).
* Return true if message was sent successfully
*/
pipe_ret_t TcpServer::sendToClient(const Client & client, const char * msg, size_t size){
try{
client.send(msg, size);
} catch (const std::runtime_error &error) {
return pipe_ret_t::failure(error.what());
}
return pipe_ret_t::success();
}
pipe_ret_t TcpServer::sendToClient(const std::string & clientIP, const char * msg, size_t size) {
std::lock_guard<std::mutex> lock(_clientsMtx);
const auto clientIter = std::find_if(_clients.begin(), _clients.end(),
[&clientIP](Client *client) { return client->getIp() == clientIP; });
if (clientIter == _clients.end()) {
return pipe_ret_t::failure("client not found");
}
const Client &client = *(*clientIter);
return sendToClient(client, msg, size);
}
/*
* Close server and clients resources.
* Return true is successFlag, false otherwise
*/
pipe_ret_t TcpServer::close() {
terminateDeadClientsRemover();
{ // close clients
std::lock_guard<std::mutex> lock(_clientsMtx);
for (Client * client : _clients) {
try {
client->close();
} catch (const std::runtime_error& error) {
return pipe_ret_t::failure(error.what());
}
}
_clients.clear();
}
{ // close server
const int closeServerResult = ::close(_sockfd.get());
const bool closeServerFailed = (closeServerResult == -1);
if (closeServerFailed) {
return pipe_ret_t::failure(strerror(errno));
}
}
return pipe_ret_t::success();
}
gitextract_ndbhzy7z/
├── .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
SYMBOL INDEX (42 symbols across 15 files)
FILE: examples/client_example.cpp
function sig_exit (line 14) | void sig_exit(int s)
function onIncomingMsg (line 27) | void onIncomingMsg(const char * msg, size_t size) {
function onDisconnection (line 32) | void onDisconnection(const pipe_ret_t & ret) {
function printMenu (line 36) | void printMenu() {
function getMenuSelection (line 42) | int getMenuSelection() {
function handleMenuSelection (line 52) | bool handleMenuSelection(int selection) {
function main (line 90) | int main() {
FILE: examples/server_example.cpp
function onIncomingMsg1 (line 22) | void onIncomingMsg1(const std::string &clientIP, const char * msg, size_...
function onIncomingMsg2 (line 30) | void onIncomingMsg2(const std::string &clientIP, const char * msg, size_...
function onClientDisconnected (line 37) | void onClientDisconnected(const std::string &ip, const std::string &msg) {
function acceptClient (line 44) | void acceptClient() {
function printMenu (line 56) | void printMenu() {
function getMenuSelection (line 64) | int getMenuSelection() {
function handleMenuSelection (line 78) | bool handleMenuSelection(int selection) {
function main (line 135) | int main()
FILE: include/client.h
function class (line 14) | class Client {
FILE: include/client_event.h
type ClientEvent (line 3) | enum ClientEvent {
FILE: include/client_observer.h
type client_observer_t (line 7) | struct client_observer_t {
FILE: include/common.h
function namespace (line 7) | namespace fd_wait {
FILE: include/file_descriptor.h
function class (line 3) | class FileDescriptor {
FILE: include/pipe_ret_t.h
function class (line 3) | class pipe_ret_t {
FILE: include/server_observer.h
type server_observer_t (line 7) | struct server_observer_t {
FILE: include/tcp_client.h
function class (line 24) | class TcpClient
FILE: include/tcp_server.h
function class (line 22) | class TcpServer {
FILE: src/common.cpp
type fd_wait (line 10) | namespace fd_wait {
function Result (line 14) | Result waitFor(const FileDescriptor &fileDescriptor, uint32_t timeoutS...
FILE: src/pipe_ret_t.cpp
function pipe_ret_t (line 4) | pipe_ret_t pipe_ret_t::failure(const std::string &msg) {
function pipe_ret_t (line 8) | pipe_ret_t pipe_ret_t::success(const std::string &msg) {
FILE: src/tcp_client.cpp
function pipe_ret_t (line 14) | pipe_ret_t TcpClient::connectTo(const std::string & address, int port) {
type hostent (line 54) | struct hostent
type in_addr (line 55) | struct in_addr
type in_addr (line 59) | struct in_addr
function pipe_ret_t (line 67) | pipe_ret_t TcpClient::sendMsg(const char * msg, size_t size) {
function pipe_ret_t (line 158) | pipe_ret_t TcpClient::close(){
FILE: src/tcp_server.cpp
function pipe_ret_t (line 124) | pipe_ret_t TcpServer::start(int port, int maxNumOfClients, bool removeDe...
type sockaddr (line 156) | struct sockaddr
type sockaddr (line 186) | struct sockaddr
function pipe_ret_t (line 205) | pipe_ret_t TcpServer::waitForClient(uint32_t timeout) {
function pipe_ret_t (line 226) | pipe_ret_t TcpServer::sendToAllClients(const char * msg, size_t size) {
function pipe_ret_t (line 243) | pipe_ret_t TcpServer::sendToClient(const Client & client, const char * m...
function pipe_ret_t (line 253) | pipe_ret_t TcpServer::sendToClient(const std::string & clientIP, const c...
function pipe_ret_t (line 271) | pipe_ret_t TcpServer::close() {
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (45K chars).
[
{
"path": ".dockerignore",
"chars": 35,
"preview": "cmake-build\ncmake-build-debug\nbuild"
},
{
"path": ".github/workflows/cmake.yml",
"chars": 1417,
"preview": "name: CMake\n\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n\nenv:\n # Customize the CMake"
},
{
"path": ".gitignore",
"chars": 47,
"preview": "/build/\n.idea/\ncmake-build-debug/\ncmake-build/\n"
},
{
"path": ".ignore",
"chars": 26,
"preview": "cmake-build-debug/\n.idea/\n"
},
{
"path": "CMakeLists.txt",
"chars": 867,
"preview": "cmake_minimum_required(VERSION 3.8.1)\nproject(tcp_client_server)\n\nfind_package (Threads)\n\nset(CMAKE_CXX_STANDARD 11)\nset"
},
{
"path": "Dockerfile",
"chars": 229,
"preview": "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 linu"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2019 Elhay Rauper\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 3750,
"preview": "\n\n# TCP server clie"
},
{
"path": "build.sh",
"chars": 63,
"preview": "#!/bin/sh\n\nmkdir -p build && \\\ncd build && \\\ncmake .. && make\n\n"
},
{
"path": "docker-compose.yml",
"chars": 247,
"preview": "\nservices:\n tcp-server:\n build:\n context: .\n dockerfile: Dockerfile\n command: sh -c \"cd build && ./tcp_"
},
{
"path": "examples/client_example.cpp",
"chars": 4018,
"preview": "///////////////////////////////////////////////////////////\n/////////////////////CLIENT EXAMPLE////////////////////////\n"
},
{
"path": "examples/server_example.cpp",
"chars": 6060,
"preview": "///////////////////////////////////////////////////////////\n/////////////////////SERVER EXAMPLE////////////////////////\n"
},
{
"path": "include/client.h",
"chars": 1179,
"preview": "#pragma once\n\n#include <string>\n#include <thread>\n#include <functional>\n#include <mutex>\n#include <atomic>\n\n#include \"pi"
},
{
"path": "include/client_event.h",
"chars": 70,
"preview": "#pragma once\n\nenum ClientEvent {\n DISCONNECTED,\n INCOMING_MSG\n};"
},
{
"path": "include/client_observer.h",
"chars": 310,
"preview": "#pragma once\n\n#include <string>\n#include <functional>\n#include \"pipe_ret_t.h\"\n\nstruct client_observer_t {\n std::strin"
},
{
"path": "include/common.h",
"chars": 253,
"preview": "#pragma once\n\n#include <cstdio>\n\n#define MAX_PACKET_SIZE 4096\n\nnamespace fd_wait {\n enum Result {\n FAILURE,\n "
},
{
"path": "include/file_descriptor.h",
"chars": 157,
"preview": "#pragma once\n\nclass FileDescriptor {\nprivate:\n int _sockfd = 0;\n\npublic:\n void set(int fd) { _sockfd = fd; }\n i"
},
{
"path": "include/pipe_ret_t.h",
"chars": 485,
"preview": "#pragma once\n\nclass pipe_ret_t {\n\nprivate:\n\n bool _successFlag = false;\n std::string _msg = \"\";\n\npublic:\n\n pipe"
},
{
"path": "include/server_observer.h",
"chars": 328,
"preview": "#pragma once\n\n#include <string>\n#include <functional>\n#include \"client.h\"\n\nstruct server_observer_t {\n\tstd::string wante"
},
{
"path": "include/tcp_client.h",
"chars": 1304,
"preview": "#pragma once\n\n#include <iostream>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <string.h>\n#includ"
},
{
"path": "include/tcp_server.h",
"chars": 1808,
"preview": "#pragma once\n\n#include <vector>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <sys/types.h>\n#inclu"
},
{
"path": "src/client.cpp",
"chars": 3122,
"preview": "#include <cstdio>\n#include <cstring>\n#include <cerrno>\n#include <unistd.h>\n#include <stdexcept>\n#include <sys/socket.h>\n"
},
{
"path": "src/common.cpp",
"chars": 829,
"preview": "#include <cstdint>\n#include \"../include/file_descriptor.h\"\n#include \"../include/common.h\"\n\n#include <sys/select.h>\n\n#def"
},
{
"path": "src/pipe_ret_t.cpp",
"chars": 242,
"preview": "#include <string>\n#include \"../include/pipe_ret_t.h\"\n\npipe_ret_t pipe_ret_t::failure(const std::string &msg) {\n retur"
},
{
"path": "src/tcp_client.cpp",
"chars": 4990,
"preview": "\n#include \"../include/tcp_client.h\"\n#include \"../include/common.h\"\n\nTcpClient::TcpClient() {\n _isConnected = false;\n "
},
{
"path": "src/tcp_server.cpp",
"chars": 9418,
"preview": "\n#include <functional>\n#include <thread>\n#include <algorithm>\n#include \"../include/tcp_server.h\"\n#include \"../include/co"
}
]
About this extraction
This page contains the full source code of the elhayra/tcp_server_client GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (41.3 KB), approximately 10.4k tokens, and a symbol index with 42 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.