Skip to content

Instantly share code, notes, and snippets.

@vlaleli
Created March 21, 2026 17:45
Show Gist options
  • Select an option

  • Save vlaleli/b4e145801b41e6abb50b7a3ab501b33b to your computer and use it in GitHub Desktop.

Select an option

Save vlaleli/b4e145801b41e6abb50b7a3ab501b33b to your computer and use it in GitHub Desktop.
#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;
}
#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