Created
March 13, 2026 09:00
-
-
Save sunmeat/d97aa6eff3c8741944195120d6c59318 to your computer and use it in GitHub Desktop.
one server multiple clients C++ example (TCP) - MacOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| CLIENT SIDE: | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| #include <arpa/inet.h> | |
| #include <unistd.h> | |
| #include <iostream> | |
| #include <string> | |
| #include <thread> | |
| #include <cstring> | |
| #include <csignal> | |
| #define DEFAULT_BUFLEN 4096 | |
| #define SERVER_IP "127.0.0.1" | |
| #define DEFAULT_PORT 8888 | |
| int client_socket = -1; | |
| volatile sig_atomic_t running = 1; | |
| void signal_handler(int sig) { | |
| running = 0; | |
| std::cout << "\nвимкнення...\n"; | |
| if (client_socket >= 0) { | |
| send(client_socket, "off", 3, 0); | |
| shutdown(client_socket, SHUT_RDWR); | |
| close(client_socket); | |
| } | |
| // можна додати _exit(0) якщо дуже потрібно миттєво завершити | |
| } | |
| void Sender() { | |
| while (running) { | |
| std::string query; | |
| std::getline(std::cin, query); | |
| if (!running) break; | |
| if (query.empty()) continue; | |
| if (send(client_socket, query.c_str(), query.size(), 0) < 0) { | |
| std::cerr << "Помилка відправки\n"; | |
| break; | |
| } | |
| } | |
| } | |
| void Receiver() { | |
| char buffer[DEFAULT_BUFLEN]; | |
| while (running) { | |
| ssize_t received = recv(client_socket, buffer, DEFAULT_BUFLEN - 1, 0); | |
| if (received <= 0) { | |
| if (received < 0 && running) | |
| std::cerr << "Помилка отримання даних\n"; | |
| break; | |
| } | |
| buffer[received] = '\0'; | |
| std::cout << buffer << "\n"; | |
| } | |
| } | |
| int main() { | |
| signal(SIGINT, signal_handler); | |
| signal(SIGTERM, signal_handler); | |
| client_socket = socket(AF_INET, SOCK_STREAM, 0); | |
| if (client_socket < 0) { | |
| std::cerr << "Не вдалося створити сокет\n"; | |
| return 1; | |
| } | |
| sockaddr_in server_addr{}; | |
| server_addr.sin_family = AF_INET; | |
| server_addr.sin_port = htons(DEFAULT_PORT); | |
| inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); | |
| if (connect(client_socket, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) { | |
| std::cerr << "Не вдалося підключитися до сервера\n"; | |
| close(client_socket); | |
| return 2; | |
| } | |
| std::cout << "Підключено до " << SERVER_IP << ":" << DEFAULT_PORT << "\n\n"; | |
| std::thread sender_thread(Sender); | |
| std::thread receiver_thread(Receiver); | |
| sender_thread.detach(); // або .join() якщо хочете чекати | |
| receiver_thread.detach(); | |
| // головний потік просто чекає | |
| while (running) { | |
| std::this_thread::sleep_for(std::chrono::milliseconds(300)); | |
| } | |
| shutdown(client_socket, SHUT_RDWR); | |
| close(client_socket); | |
| std::cout << "Клієнт завершено.\n"; | |
| return 0; | |
| } | |
| ================================================================================================================== | |
| SERVER SIDE: | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| #include <arpa/inet.h> | |
| #include <unistd.h> | |
| #include <iostream> | |
| #include <vector> | |
| #include <string> | |
| #include <cstring> | |
| #include <csignal> | |
| #define MAX_CLIENTS 10 | |
| #define DEFAULT_BUFLEN 4096 | |
| #define PORT 8888 | |
| int server_socket = -1; | |
| volatile sig_atomic_t running = 1; | |
| std::vector<std::string> history; | |
| void signal_handler(int sig) { | |
| running = 0; | |
| std::cout << "\nСервер завершує роботу...\n"; | |
| if (server_socket >= 0) { | |
| close(server_socket); | |
| } | |
| } | |
| int main() { | |
| signal(SIGINT, signal_handler); | |
| signal(SIGTERM, signal_handler); | |
| std::cout << "Запуск сервера...\n"; | |
| server_socket = socket(AF_INET, SOCK_STREAM, 0); | |
| if (server_socket < 0) { | |
| std::cerr << "Не вдалося створити сокет\n"; | |
| return 1; | |
| } | |
| int opt = 1; | |
| setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| sockaddr_in server{}; | |
| server.sin_family = AF_INET; | |
| server.sin_addr.s_addr = INADDR_ANY; | |
| server.sin_port = htons(PORT); | |
| if (bind(server_socket, (sockaddr*)&server, sizeof(server)) < 0) { | |
| std::cerr << "Помилка bind\n"; | |
| close(server_socket); | |
| return 2; | |
| } | |
| if (listen(server_socket, MAX_CLIENTS) < 0) { | |
| std::cerr << "Помилка listen\n"; | |
| close(server_socket); | |
| return 3; | |
| } | |
| std::cout << "Сервер запущено. Очікування з'єднань на порту " << PORT << "...\n\n"; | |
| fd_set readfds; | |
| int client_sockets[MAX_CLIENTS] = {}; | |
| while (running) { | |
| FD_ZERO(&readfds); | |
| FD_SET(server_socket, &readfds); | |
| int max_fd = server_socket; | |
| for (int i = 0; i < MAX_CLIENTS; i++) { | |
| int s = client_sockets[i]; | |
| if (s > 0) { | |
| FD_SET(s, &readfds); | |
| if (s > max_fd) max_fd = s; | |
| } | |
| } | |
| int activity = select(max_fd + 1, &readfds, nullptr, nullptr, nullptr); | |
| if (activity < 0 && running) { | |
| std::cerr << "Помилка select\n"; | |
| break; | |
| } | |
| if (!running) break; | |
| // нове з'єднання | |
| if (FD_ISSET(server_socket, &readfds)) { | |
| sockaddr_in client_addr{}; | |
| socklen_t addrlen = sizeof(client_addr); | |
| int new_socket = accept(server_socket, (sockaddr*)&client_addr, &addrlen); | |
| if (new_socket < 0) { | |
| if (running) std::cerr << "Помилка accept\n"; | |
| continue; | |
| } | |
| std::cout << "Нове з'єднання → fd: " << new_socket | |
| << ", ip: " << inet_ntoa(client_addr.sin_addr) | |
| << ", порт: " << ntohs(client_addr.sin_port) << "\n"; | |
| // відправляємо історію новому клієнту | |
| for (const auto& msg : history) { | |
| send(new_socket, msg.c_str(), msg.size(), 0); | |
| } | |
| // додаємо сокет у список | |
| bool added = false; | |
| for (int i = 0; i < MAX_CLIENTS; i++) { | |
| if (client_sockets[i] == 0) { | |
| client_sockets[i] = new_socket; | |
| added = true; | |
| break; | |
| } | |
| } | |
| if (!added) { | |
| std::cout << "Досягнуто максимальної кількості клієнтів\n"; | |
| close(new_socket); | |
| } | |
| } | |
| // повідомлення від клієнтів | |
| for (int i = 0; i < MAX_CLIENTS; i++) { | |
| int s = client_sockets[i]; | |
| if (s > 0 && FD_ISSET(s, &readfds)) { | |
| char buffer[DEFAULT_BUFLEN]; | |
| ssize_t len = recv(s, buffer, DEFAULT_BUFLEN - 1, 0); | |
| if (len <= 0) { | |
| std::cout << "Клієнт #" << i << " від'єднався\n"; | |
| close(s); | |
| client_sockets[i] = 0; | |
| continue; | |
| } | |
| buffer[len] = '\0'; | |
| std::string msg = buffer; | |
| if (msg == "off") { | |
| std::cout << "Клієнт #" << i << " від'єднався (off)\n"; | |
| close(s); | |
| client_sockets[i] = 0; | |
| continue; | |
| } | |
| // зберігаємо в історію та розсилаємо всім | |
| history.push_back(msg); | |
| for (int j = 0; j < MAX_CLIENTS; j++) { | |
| int dest = client_sockets[j]; | |
| if (dest > 0) { | |
| send(dest, buffer, len, 0); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // закриваємо всі сокети | |
| close(server_socket); | |
| for (int i = 0; i < MAX_CLIENTS; i++) { | |
| if (client_sockets[i] > 0) { | |
| close(client_sockets[i]); | |
| } | |
| } | |
| std::cout << "Сервер зупинено.\n"; | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment