#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include <vector>
#include <string>
#include <map>
#include <ctime>
#include <thread>
#include <mutex>
#include <algorithm>
#include <fstream>
#include <sstream>
#include <atomic> // 必须包含这个头文件
#pragma comment(lib, "ws2_32.lib")
#define PORT 8888
#define BUF_SIZE 1024
#define MAX_NICKNAME_LENGTH 20
#define MAX_MESSAGE_LENGTH 100
using std::string;
using std::vector;
using std::map;
using std::mutex;
using std::lock_guard;
using std::ofstream;
using std::ifstream;
using std::getline;
using std::cout;
using std::endl;
using std::thread;
ofstream log_file("chat_log.txt", std::ios::app);
ofstream sensitive_log_file("sensitive_log.txt", std::ios::app);
mutex log_mutex;
mutex sensitive_log_mutex;
struct User {
SOCKET sock;
string ip;
string nickname;
int sensitive_count;
vector<time_t> msg_times;
};
class ChatServer {
public:
ChatServer(int port) : port(port), server_socket(INVALID_SOCKET), running(true) {
load_sensitive_words();
start_server();
}
~ChatServer() {
stop();
}
void stop() {
running = false;
if (server_socket != INVALID_SOCKET) {
shutdown(server_socket, SD_BOTH);
closesocket(server_socket);
server_socket = INVALID_SOCKET;
}
WSACleanup();
}
void start_server() {
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
log_message("WSAStartup failed!");
exit(-1);
}
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == INVALID_SOCKET) {
log_message("Socket creation failed!");
WSACleanup();
exit(-1);
}
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
if (bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
log_message("Bind failed!");
stop();
exit(-1);
}
if (listen(server_socket, 10) == SOCKET_ERROR) {
log_message("Listen failed!");
stop();
exit(-1);
}
log_message("服务器启动,等待连接...");
input_thread = thread(&ChatServer::server_input_thread, this);
input_thread.detach();
while (running) {
sockaddr_in client_addr{};
int client_addr_len = sizeof(client_addr);
SOCKET client_sock = accept(server_socket, (sockaddr*)&client_addr, &client_addr_len);
if (client_sock == INVALID_SOCKET) {
log_message("Accept failed!");
continue;
}
thread(&ChatServer::handle_client, this, client_sock).detach();
}
}
private:
int port;
SOCKET server_socket;
std::atomic<bool> running; // 添加 atomic 成员
vector<string> sensitive_words;
vector<string> blacklisted_ips;
map<SOCKET, User> users;
map<string, int> ip_connection_count;
thread input_thread;
mutex user_mutex;
mutex ip_mutex;
mutex word_mutex;
void load_sensitive_words() {
ifstream file("words.txt");
string word;
while (file >> word)
sensitive_words.push_back(word);
file.close();
}
void log_message(const string& content) {
lock_guard<mutex> lock(log_mutex);
time_t now = time(nullptr);
tm* ltm = localtime(&now);
std::stringstream ss;
ss << "[" << ltm->tm_year + 1900 << "-" << ltm->tm_mon + 1 << "-" << ltm->tm_mday << " "
<< ltm->tm_hour << ":" << ltm->tm_min << ":" << ltm->tm_sec << "] " << content;
::log_file << ss.str() << endl;
::log_file.flush();
cout << content << endl;
}
void log_sensitive_message(const string& ip, const string& msg) {
lock_guard<mutex> lock(sensitive_log_mutex);
time_t now = time(nullptr);
tm* ltm = localtime(&now);
std::stringstream ss;
ss << "[" << ltm->tm_year + 1900 << "-" << ltm->tm_mon + 1 << "-" << ltm->tm_mday << " "
<< ltm->tm_hour << ":" << ltm->tm_min << ":" << ltm->tm_sec << "] IP: " << ip
<< " 发送敏感词: " << msg;
::sensitive_log_file << ss.str() << endl;
::sensitive_log_file.flush();
}
bool is_sensitive(const string& msg) {
lock_guard<mutex> lock(word_mutex);
for (const string& word : sensitive_words) {
if (msg.find(word) != string::npos)
return true;
}
return false;
}
string replace_sensitive_words(const string& msg) {
lock_guard<mutex> lock(word_mutex);
string result = msg;
for (const string& word : sensitive_words) {
size_t pos = result.find(word);
while (pos != string::npos) {
result.replace(pos, word.length(), string(word.length(), '*'));
pos = result.find(word, pos + word.length());
}
}
return result;
}
void broadcast_to_clients(const string& msg, SOCKET sender = INVALID_SOCKET) {
lock_guard<mutex> lock(user_mutex);
string message = msg + "\n";
for (auto& pair : users) {
if (pair.first != sender) {
send(pair.first, message.c_str(), static_cast<int>(message.size()), 0);
}
}
}
void send_system_msg(SOCKET sock, const string& reason) {
string msg = "[系统] " + reason + "\n";
send(sock, msg.c_str(), static_cast<int>(msg.size()), 0);
}
bool check_spam(SOCKET sock) {
time_t now = time(nullptr);
lock_guard<mutex> lock(user_mutex);
User& user = users[sock];
user.msg_times.push_back(now);
if (user.msg_times.size() >= 5) {
if (difftime(user.msg_times.back(), user.msg_times[user.msg_times.size() - 5]) < 5) {
return true;
}
}
return false;
}
void handle_client(SOCKET client_sock) {
char buffer[BUF_SIZE];
User user;
user.sock = client_sock;
user.sensitive_count = 0;
sockaddr_in addr;
int addr_len = sizeof(addr);
getpeername(client_sock, (sockaddr*)&addr, &addr_len);
user.ip = inet_ntoa(addr.sin_addr);
if (is_blacklisted_ip(user.ip)) {
string msg = "你的 IP 已被封禁,无法连接。\n";
send(client_sock, msg.c_str(), static_cast<int>(msg.size()), 0);
closesocket(client_sock);
return;
}
int len = recv(client_sock, buffer, BUF_SIZE - 1, 0);
if (len <= 0) {
log_message("接收昵称失败");
closesocket(client_sock);
return;
}
buffer[len] = '\0';
string nickname = string(buffer, len);
if (nickname.length() > MAX_NICKNAME_LENGTH) {
string msg = "昵称过长,已断开连接\n";
log_message("[" + user.ip + "] " + nickname + " 昵称过长");
send_system_msg(client_sock, msg);
closesocket(client_sock);
return;
}
if (is_sensitive_nickname(nickname)) {
string msg = "因昵称包含敏感词,已断开连接\n";
log_message("[" + user.ip + "] " + nickname + " 昵称包含敏感词");
send_system_msg(client_sock, msg);
closesocket(client_sock);
return;
}
{
lock_guard<mutex> lock(ip_mutex);
if (ip_connection_count[user.ip] >= 2) {
string msg = "该 IP 已连接两个账号,拒绝连接\n";
log_message("[" + user.ip + "] 拒绝连接:IP 已连接两个账号");
send_system_msg(client_sock, msg);
closesocket(client_sock);
return;
}
ip_connection_count[user.ip]++;
}
user.nickname = nickname;
user.msg_times.clear();
{
lock_guard<mutex> lock(user_mutex);
users[client_sock] = user;
}
string join_msg = "[" + user.ip + "] " + user.nickname + " : 加入了聊天室";
string client_join_msg = "[系统] " + user.nickname + " 加入了聊天室。\n";
log_message(join_msg);
broadcast_to_clients(client_join_msg);
while (running) {
len = recv(client_sock, buffer, BUF_SIZE - 1, 0);
if (len <= 0) break;
buffer[len] = '\0';
string msg(buffer, len);
if (msg.empty()) continue;
if (msg.length() > MAX_MESSAGE_LENGTH) {
kick_user(client_sock, "发送消息过长(刷屏)");
return;
}
if (check_spam(client_sock)) {
kick_user(client_sock, "刷屏");
return;
}
string filtered_msg = replace_sensitive_words(msg);
if (is_sensitive(msg)) {
log_sensitive_message(user.ip, msg);
lock_guard<mutex> lock(user_mutex);
users[client_sock].sensitive_count++;
}
string final_msg = "[" + user.ip + "] " + user.nickname + " : " + filtered_msg;
string client_msg = user.nickname + " : " + filtered_msg;
log_message(final_msg);
broadcast_to_clients(client_msg, client_sock);
}
string leave_msg = "[" + user.ip + "] " + user.nickname + " : 离开了聊天室";
string client_leave_msg = "[系统] " + user.nickname + " 离开了聊天室。\n";
log_message(leave_msg);
broadcast_to_clients(client_leave_msg);
closesocket(client_sock);
{
lock_guard<mutex> lock(user_mutex);
users.erase(client_sock);
}
{
lock_guard<mutex> lock(ip_mutex);
ip_connection_count[user.ip]--;
if (ip_connection_count[user.ip] == 0) {
ip_connection_count.erase(user.ip);
}
}
}
void kick_user(SOCKET sock, const string& reason) {
User user;
{
lock_guard<mutex> lock(user_mutex);
auto it = users.find(sock);
if (it == users.end()) return;
user = it->second;
users.erase(it);
}
string msg = "[系统] 用户 " + user.nickname + " 被踢出聊天室(原因:" + reason + ")。\n";
send_system_msg(sock, "你已被踢出聊天室(原因:" + reason + ")");
broadcast_to_clients(msg, sock);
closesocket(sock);
{
lock_guard<mutex> lock(ip_mutex);
ip_connection_count[user.ip]--;
if (ip_connection_count[user.ip] == 0) {
ip_connection_count.erase(user.ip);
}
}
}
bool is_sensitive_nickname(const string& nickname) {
lock_guard<mutex> lock(word_mutex);
for (const string& word : sensitive_words) {
if (nickname.find(word) != string::npos)
return true;
}
return false;
}
bool is_blacklisted_ip(const string& ip) {
lock_guard<mutex> lock(word_mutex);
for (const string& blocked : blacklisted_ips) {
if (ip == blocked)
return true;
}
return false;
}
void server_input_thread() {
while (running) {
string cmd;
getline(std::cin, cmd);
if (cmd.substr(0, 9) == "/addword ") {
string new_word = cmd.substr(9);
if (!new_word.empty()) {
lock_guard<mutex> lock(word_mutex);
sensitive_words.push_back(new_word);
log_message("新增敏感词: " + new_word);
ofstream out("words.txt", std::ios::app);
out << new_word << endl;
out.close();
}
} else if (cmd.substr(0, 9) == "/delword ") {
string word_to_remove = cmd.substr(9);
lock_guard<mutex> lock(word_mutex);
auto it = std::find(sensitive_words.begin(), sensitive_words.end(), word_to_remove);
if (it != sensitive_words.end()) {
sensitive_words.erase(it);
log_message("已删除敏感词: " + word_to_remove);
ofstream out("words.txt");
for (const string& word : sensitive_words)
out << word << endl;
out.close();
} else {
log_message("未找到敏感词: " + word_to_remove);
}
} else if (cmd == "/listwords") {
log_message("当前敏感词列表:");
lock_guard<mutex> lock(word_mutex);
for (const string& word : sensitive_words) {
log_message(" - " + word);
}
} else if (cmd.substr(0, 9) == "/blockip ") {
string ip_to_block = cmd.substr(9);
if (!ip_to_block.empty()) {
lock_guard<mutex> lock(word_mutex);
blacklisted_ips.push_back(ip_to_block);
log_message("已将 IP 加入黑名单: " + ip_to_block);
}
} else if (cmd.substr(0, 11) == "/unblockip ") {
string ip_to_remove = cmd.substr(11);
lock_guard<mutex> lock(word_mutex);
auto it = std::find(blacklisted_ips.begin(), blacklisted_ips.end(), ip_to_remove);
if (it != blacklisted_ips.end()) {
blacklisted_ips.erase(it);
log_message("已从黑名单中移除 IP: " + ip_to_remove);
} else {
log_message("未找到黑名单中的 IP: " + ip_to_remove);
}
} else if (cmd == "/kick") {
cout << "请输入要踢出的昵称: ";
string name;
getline(std::cin, name);
for (auto it = users.begin(); it != users.end(); ++it) {
if (it->second.nickname == name) {
kick_user(it->first, "管理员手动踢出");
break;
}
}
} else if (cmd == "/list") {
log_message("在线用户列表:");
lock_guard<mutex> lock(user_mutex);
for (auto it = users.begin(); it != users.end(); ++it) {
log_message(" - " + it->second.nickname + " [" + it->second.ip + "]");
}
} else if (cmd == "/admin") {
cout << "请输入管理员消息: ";
string admin_msg;
getline(std::cin, admin_msg);
admin_msg = "[管理员] : " + admin_msg + "\n";
log_message("[管理员] : " + admin_msg);
broadcast_to_clients(admin_msg);
} else if (cmd == "/listblocked") {
log_message("当前黑名单 IP 列表:");
lock_guard<mutex> lock(word_mutex);
for (const string& ip : blacklisted_ips) {
log_message(" - " + ip);
}
}
}
}
};
int main() {
ChatServer server(PORT);
return 0;
}
服务器端显示"xx被踢出聊天室 (原因:)" 同时增加服务器端可以设置管理员(权限:踢人,增加敏感词,删除敏感词,各种查看,不过无法设置黑名单,当管理员和服务器的命令冲突时,优先执行服务器的命令)
最新发布