Created
March 21, 2026 17:45
-
-
Save vlaleli/b4e145801b41e6abb50b7a3ab501b33b to your computer and use it in GitHub Desktop.
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
| #include <iostream> | |
| #include <string> | |
| #include <thread> | |
| #include <cstring> | |
| #include <unistd.h> | |
| #include <arpa/inet.h> | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| #include <cstdlib> | |
| #include <vector> | |
| #include <chrono> | |
| #include <algorithm> | |
| #include <cctype> | |
| using namespace std; | |
| using namespace std::chrono; | |
| class ChatClient { | |
| private: | |
| int clientSocket; | |
| string serverIp; | |
| int serverPort; | |
| string login; | |
| string password; | |
| int color; | |
| bool running; | |
| steady_clock::time_point lastMessageTime; | |
| bool sendString(const string& text) { | |
| string data = text + "\n"; | |
| return ::send(clientSocket, data.c_str(), data.size(), 0) >= 0; | |
| } | |
| bool recvString(string& result) { | |
| result.clear(); | |
| char ch; | |
| while (true) { | |
| ssize_t bytesReceived = ::recv(clientSocket, &ch, 1, 0); | |
| if (bytesReceived <= 0) { | |
| return false; | |
| } | |
| if (ch == '\n') { | |
| break; | |
| } | |
| if (ch != '\r') { | |
| result += ch; | |
| } | |
| } | |
| return true; | |
| } | |
| void setWindowTitle(int onlineCount) { | |
| cout << "\033]0;В чаті людей: " << onlineCount << "\007"; | |
| cout.flush(); | |
| } | |
| int inputColor() { | |
| int chosenColor; | |
| while (true) { | |
| cout << "Choose color for your messages (1-15): "; | |
| cin >> chosenColor; | |
| if (cin.fail()) { | |
| cin.clear(); | |
| cin.ignore(10000, '\n'); | |
| continue; | |
| } | |
| if (chosenColor >= 1 && chosenColor <= 15) { | |
| cin.ignore(10000, '\n'); | |
| return chosenColor; | |
| } | |
| } | |
| } | |
| bool antiFloodCheck() { | |
| auto now = steady_clock::now(); | |
| auto diff = duration_cast<seconds>(now - lastMessageTime).count(); | |
| if (diff < 1) { | |
| return false; | |
| } | |
| lastMessageTime = now; | |
| return true; | |
| } | |
| string toLowerCase(string text) { | |
| transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { | |
| return static_cast<char>(tolower(c)); | |
| }); | |
| return text; | |
| } | |
| string replaceBadWords(const string& message) { | |
| vector<string> badWords = { | |
| "fuck", | |
| "shit", | |
| "bitch", | |
| "damn", | |
| "spamword" | |
| }; | |
| string result = message; | |
| string lowerResult = toLowerCase(result); | |
| for (const auto& word : badWords) { | |
| size_t pos = 0; | |
| while ((pos = lowerResult.find(word, pos)) != string::npos) { | |
| result.replace(pos, word.length(), "%$#@#%$#@"); | |
| lowerResult.replace(pos, word.length(), "%$#@#%$#@"); | |
| pos += 10; | |
| } | |
| } | |
| return result; | |
| } | |
| void processServerLine(const string& line) { | |
| const string prefix = "ONLINE_COUNT:"; | |
| if (line.rfind(prefix, 0) == 0) { | |
| string countText = line.substr(prefix.size()); | |
| try { | |
| int count = stoi(countText); | |
| setWindowTitle(count); | |
| } catch (...) { | |
| } | |
| return; | |
| } | |
| cout << line << endl; | |
| } | |
| void receiveMessages() { | |
| while (running) { | |
| string line; | |
| if (!recvString(line)) { | |
| cout << "\nDisconnected from server.\n"; | |
| running = false; | |
| break; | |
| } | |
| processServerLine(line); | |
| cout.flush(); | |
| } | |
| } | |
| public: | |
| ChatClient(const string& ip, int port) | |
| : clientSocket(-1), | |
| serverIp(ip), | |
| serverPort(port), | |
| color(7), | |
| running(false), | |
| lastMessageTime(steady_clock::now() - seconds(2)) {} | |
| bool connectToServer() { | |
| clientSocket = ::socket(AF_INET, SOCK_STREAM, 0); | |
| if (clientSocket < 0) { | |
| cerr << "Socket creation error\n"; | |
| return false; | |
| } | |
| sockaddr_in serverAddr; | |
| memset(&serverAddr, 0, sizeof(serverAddr)); | |
| serverAddr.sin_family = AF_INET; | |
| serverAddr.sin_port = htons(serverPort); | |
| if (inet_pton(AF_INET, serverIp.c_str(), &serverAddr.sin_addr) <= 0) { | |
| cerr << "Invalid server IP\n"; | |
| ::close(clientSocket); | |
| return false; | |
| } | |
| if (::connect(clientSocket, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) < 0) { | |
| cerr << "Connection error\n"; | |
| ::close(clientSocket); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool authorize() { | |
| cout << "Enter login: "; | |
| getline(cin, login); | |
| cout << "Enter password: "; | |
| getline(cin, password); | |
| color = inputColor(); | |
| if (!sendString(login)) { | |
| return false; | |
| } | |
| if (!sendString(password)) { | |
| return false; | |
| } | |
| if (!sendString(to_string(color))) { | |
| return false; | |
| } | |
| string response; | |
| if (!recvString(response)) { | |
| return false; | |
| } | |
| if (response != "AUTH_OK") { | |
| cerr << "Authorization failed\n"; | |
| return false; | |
| } | |
| system("clear"); | |
| cout << "Connected to chat" << endl; | |
| cout << "Your color: " << color << endl; | |
| cout << "Type messages. For exit type /exit" << endl << endl; | |
| while (true) { | |
| string line; | |
| if (!recvString(line)) { | |
| return false; | |
| } | |
| processServerLine(line); | |
| if (line == "------------------------") { | |
| break; | |
| } | |
| } | |
| return true; | |
| } | |
| void startChat() { | |
| running = true; | |
| thread receiver(&ChatClient::receiveMessages, this); | |
| while (running) { | |
| string message; | |
| getline(cin, message); | |
| if (!running) { | |
| break; | |
| } | |
| if (message.empty()) { | |
| continue; | |
| } | |
| if (message != "/exit") { | |
| if (!antiFloodCheck()) { | |
| cout << "Anti-flood: you can send only 1 message per second." << endl; | |
| continue; | |
| } | |
| message = replaceBadWords(message); | |
| } | |
| if (!sendString(message)) { | |
| cout << "Send error" << endl; | |
| running = false; | |
| break; | |
| } | |
| if (message == "/exit") { | |
| running = false; | |
| break; | |
| } | |
| } | |
| ::shutdown(clientSocket, SHUT_RDWR); | |
| ::close(clientSocket); | |
| if (receiver.joinable()) { | |
| receiver.join(); | |
| } | |
| } | |
| ~ChatClient() { | |
| if (clientSocket >= 0) { | |
| ::close(clientSocket); | |
| } | |
| } | |
| }; | |
| int main() { | |
| ChatClient client("127.0.0.1", 8888); | |
| if (!client.connectToServer()) { | |
| return 1; | |
| } | |
| if (!client.authorize()) { | |
| return 1; | |
| } | |
| client.startChat(); | |
| return 0; | |
| } |
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
| #include <iostream> | |
| #include <vector> | |
| #include <string> | |
| #include <thread> | |
| #include <mutex> | |
| #include <fstream> | |
| #include <algorithm> | |
| #include <cstring> | |
| #include <ctime> | |
| #include <iomanip> | |
| #include <sstream> | |
| #include <chrono> | |
| #include <unistd.h> | |
| #include <arpa/inet.h> | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| using namespace std; | |
| using namespace std::chrono; | |
| class ChatServer { | |
| private: | |
| struct ClientInfo { | |
| string login; | |
| string password; | |
| string ip; | |
| int port; | |
| int color; | |
| }; | |
| struct ConnectedClient { | |
| int socketFd; | |
| string login; | |
| string ip; | |
| int port; | |
| int color; | |
| steady_clock::time_point connectedAt; | |
| }; | |
| int serverSocket; | |
| int serverPort; | |
| vector<ClientInfo> registeredClients; | |
| vector<ConnectedClient> connectedClients; | |
| vector<string> chatHistory; | |
| mutex dataMutex; | |
| const string clientsFileName = "clients.txt"; | |
| const string logFileName = "connections_log.txt"; | |
| bool recvString(int socketFd, string& result) { | |
| result.clear(); | |
| char ch; | |
| while (true) { | |
| ssize_t bytesReceived = ::recv(socketFd, &ch, 1, 0); | |
| if (bytesReceived <= 0) { | |
| return false; | |
| } | |
| if (ch == '\n') { | |
| break; | |
| } | |
| if (ch != '\r') { | |
| result += ch; | |
| } | |
| } | |
| return true; | |
| } | |
| bool sendString(int socketFd, const string& text) { | |
| return ::send(socketFd, text.c_str(), text.size(), 0) >= 0; | |
| } | |
| string cleanText(const string& text) { | |
| string result; | |
| for (unsigned char c : text) { | |
| if (c >= 32 && c <= 126) { | |
| result += static_cast<char>(c); | |
| } | |
| } | |
| return result; | |
| } | |
| string getCurrentTime() { | |
| time_t now = time(nullptr); | |
| tm* localTime = localtime(&now); | |
| stringstream ss; | |
| ss << setfill('0') | |
| << setw(2) << localTime->tm_hour << ":" | |
| << setw(2) << localTime->tm_min << ":" | |
| << setw(2) << localTime->tm_sec; | |
| return ss.str(); | |
| } | |
| string formatDuration(steady_clock::duration value) { | |
| long long totalSeconds = duration_cast<seconds>(value).count(); | |
| int hours = static_cast<int>(totalSeconds / 3600); | |
| int minutes = static_cast<int>((totalSeconds % 3600) / 60); | |
| int secondsPart = static_cast<int>(totalSeconds % 60); | |
| stringstream ss; | |
| ss << setfill('0') | |
| << setw(2) << hours << ":" | |
| << setw(2) << minutes << ":" | |
| << setw(2) << secondsPart; | |
| return ss.str(); | |
| } | |
| void writeConnectionLog(const string& text) { | |
| lock_guard<mutex> lock(dataMutex); | |
| ofstream file(logFileName, ios::app); | |
| if (file.is_open()) { | |
| file << text << endl; | |
| file.close(); | |
| } | |
| } | |
| void loadClientsFromFile() { | |
| lock_guard<mutex> lock(dataMutex); | |
| registeredClients.clear(); | |
| ifstream file(clientsFileName); | |
| if (!file.is_open()) { | |
| return; | |
| } | |
| ClientInfo client; | |
| while (file >> client.login >> client.password >> client.ip >> client.port >> client.color) { | |
| registeredClients.push_back(client); | |
| } | |
| file.close(); | |
| } | |
| bool registerClientIfNeeded(const string& login, const string& password, const string& ip, int port, int color) { | |
| lock_guard<mutex> lock(dataMutex); | |
| for (const auto& client : registeredClients) { | |
| if (client.login == login && client.password == password) { | |
| return true; | |
| } | |
| } | |
| ClientInfo newClient; | |
| newClient.login = login; | |
| newClient.password = password; | |
| newClient.ip = ip; | |
| newClient.port = port; | |
| newClient.color = color; | |
| registeredClients.push_back(newClient); | |
| ofstream file(clientsFileName, ios::app); | |
| if (file.is_open()) { | |
| file << newClient.login << " " | |
| << newClient.password << " " | |
| << newClient.ip << " " | |
| << newClient.port << " " | |
| << newClient.color << "\n"; | |
| file.close(); | |
| } | |
| cout << "New client registered: " << login | |
| << " | " << ip << ":" << port | |
| << " | color: " << color << endl; | |
| return false; | |
| } | |
| int getStoredColor(const string& login, const string& password, int defaultColor) { | |
| lock_guard<mutex> lock(dataMutex); | |
| for (const auto& client : registeredClients) { | |
| if (client.login == login && client.password == password) { | |
| return client.color; | |
| } | |
| } | |
| return defaultColor; | |
| } | |
| void sendOnlineCountToAll() { | |
| lock_guard<mutex> lock(dataMutex); | |
| string message = "ONLINE_COUNT:" + to_string(connectedClients.size()) + "\n"; | |
| for (const auto& client : connectedClients) { | |
| ::send(client.socketFd, message.c_str(), message.size(), 0); | |
| } | |
| } | |
| void addConnectedClient(int socketFd, const string& login, const string& ip, int port, int color) { | |
| lock_guard<mutex> lock(dataMutex); | |
| ConnectedClient client; | |
| client.socketFd = socketFd; | |
| client.login = login; | |
| client.ip = ip; | |
| client.port = port; | |
| client.color = color; | |
| client.connectedAt = steady_clock::now(); | |
| connectedClients.push_back(client); | |
| } | |
| void removeConnectedClient(int socketFd) { | |
| lock_guard<mutex> lock(dataMutex); | |
| connectedClients.erase( | |
| remove_if( | |
| connectedClients.begin(), | |
| connectedClients.end(), | |
| [socketFd](const ConnectedClient& client) { | |
| return client.socketFd == socketFd; | |
| } | |
| ), | |
| connectedClients.end() | |
| ); | |
| } | |
| void broadcastMessage(const string& message, int excludeSocket = -1) { | |
| lock_guard<mutex> lock(dataMutex); | |
| for (const auto& client : connectedClients) { | |
| if (client.socketFd != excludeSocket) { | |
| ::send(client.socketFd, message.c_str(), message.size(), 0); | |
| } | |
| } | |
| } | |
| void sendHistory(int socketFd) { | |
| lock_guard<mutex> lock(dataMutex); | |
| string historyBlock = "----- Chat history -----\n"; | |
| for (const auto& line : chatHistory) { | |
| historyBlock += line + "\n"; | |
| } | |
| historyBlock += "------------------------\n"; | |
| sendString(socketFd, historyBlock); | |
| } | |
| void printOnlineInfo() { | |
| lock_guard<mutex> lock(dataMutex); | |
| if (connectedClients.empty()) { | |
| cout << "No clients online." << endl; | |
| return; | |
| } | |
| auto now = steady_clock::now(); | |
| for (const auto& client : connectedClients) { | |
| cout << client.login << " - " << formatDuration(now - client.connectedAt) << endl; | |
| } | |
| } | |
| void serverCommandsLoop() { | |
| while (true) { | |
| string command; | |
| getline(cin, command); | |
| if (command == "info") { | |
| printOnlineInfo(); | |
| } | |
| } | |
| } | |
| void handleClient(int clientSocket, sockaddr_in clientAddr) { | |
| string clientIp = inet_ntoa(clientAddr.sin_addr); | |
| int clientPort = ntohs(clientAddr.sin_port); | |
| string login; | |
| string password; | |
| string colorText; | |
| if (!recvString(clientSocket, login)) { | |
| ::close(clientSocket); | |
| return; | |
| } | |
| if (!recvString(clientSocket, password)) { | |
| ::close(clientSocket); | |
| return; | |
| } | |
| if (!recvString(clientSocket, colorText)) { | |
| ::close(clientSocket); | |
| return; | |
| } | |
| login = cleanText(login); | |
| password = cleanText(password); | |
| colorText = cleanText(colorText); | |
| int color = 7; | |
| try { | |
| color = stoi(colorText); | |
| } catch (...) { | |
| color = 7; | |
| } | |
| if (color < 1 || color > 15) { | |
| color = 7; | |
| } | |
| bool wasRegisteredBefore = registerClientIfNeeded(login, password, clientIp, clientPort, color); | |
| int finalColor = getStoredColor(login, password, color); | |
| addConnectedClient(clientSocket, login, clientIp, clientPort, finalColor); | |
| sendString(clientSocket, "AUTH_OK\n"); | |
| if (wasRegisteredBefore) { | |
| sendString(clientSocket, "з поверненням, " + login + "\n"); | |
| } | |
| sendHistory(clientSocket); | |
| sendOnlineCountToAll(); | |
| string joinInfo = "[" + getCurrentTime() + "] " + login + " connected"; | |
| writeConnectionLog(joinInfo + " | " + clientIp + ":" + to_string(clientPort)); | |
| broadcastMessage(joinInfo + "\n", clientSocket); | |
| cout << joinInfo << endl; | |
| while (true) { | |
| string messageText; | |
| if (!recvString(clientSocket, messageText)) { | |
| break; | |
| } | |
| if (messageText == "/exit") { | |
| break; | |
| } | |
| string finalMessage = login + ": " + messageText; | |
| { | |
| lock_guard<mutex> lock(dataMutex); | |
| chatHistory.push_back(finalMessage); | |
| } | |
| broadcastMessage(finalMessage + "\n"); | |
| cout << finalMessage << endl; | |
| } | |
| removeConnectedClient(clientSocket); | |
| sendOnlineCountToAll(); | |
| string leaveInfo = "[" + getCurrentTime() + "] " + login + " disconnected"; | |
| writeConnectionLog(leaveInfo + " | " + clientIp + ":" + to_string(clientPort)); | |
| broadcastMessage(leaveInfo + "\n", clientSocket); | |
| cout << leaveInfo << endl; | |
| ::close(clientSocket); | |
| } | |
| public: | |
| ChatServer(int port) : serverSocket(-1), serverPort(port) {} | |
| bool start() { | |
| loadClientsFromFile(); | |
| serverSocket = ::socket(AF_INET, SOCK_STREAM, 0); | |
| if (serverSocket < 0) { | |
| cerr << "Socket creation error\n"; | |
| return false; | |
| } | |
| int opt = 1; | |
| ::setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| sockaddr_in serverAddr; | |
| memset(&serverAddr, 0, sizeof(serverAddr)); | |
| serverAddr.sin_family = AF_INET; | |
| serverAddr.sin_addr.s_addr = INADDR_ANY; | |
| serverAddr.sin_port = htons(serverPort); | |
| if (::bind(serverSocket, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) < 0) { | |
| cerr << "Bind error\n"; | |
| ::close(serverSocket); | |
| return false; | |
| } | |
| if (::listen(serverSocket, 10) < 0) { | |
| cerr << "Listen error\n"; | |
| ::close(serverSocket); | |
| return false; | |
| } | |
| cout << "Server started on port " << serverPort << endl; | |
| cout << "Loaded clients: " << registeredClients.size() << endl; | |
| cout << "Type info to see online clients time." << endl; | |
| thread commandsThread(&ChatServer::serverCommandsLoop, this); | |
| commandsThread.detach(); | |
| while (true) { | |
| sockaddr_in clientAddr; | |
| socklen_t clientLen = sizeof(clientAddr); | |
| int clientSocket = ::accept(serverSocket, reinterpret_cast<sockaddr*>(&clientAddr), &clientLen); | |
| if (clientSocket < 0) { | |
| cerr << "Accept error\n"; | |
| continue; | |
| } | |
| thread clientThread(&ChatServer::handleClient, this, clientSocket, clientAddr); | |
| clientThread.detach(); | |
| } | |
| return true; | |
| } | |
| ~ChatServer() { | |
| if (serverSocket >= 0) { | |
| ::close(serverSocket); | |
| } | |
| } | |
| }; | |
| int main() { | |
| ChatServer server(8888); | |
| server.start(); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment