ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

局域网聊天室单文件c++版

2026/8/7 8:24:33 拓冰建站 浏览量
局域网聊天室单文件c++版 局域网聊天室单文件c版*此项目由AI生成运行效果控制台网页代码直接上代码#if defined(_MSC_VER) #pragma execution_character_set(utf-8) #endif #include winsock2.h #include ws2tcpip.h #include windows.h #include iostream #include fstream #include sstream #include string #include vector #include mutex #include thread #include ctime #include algorithm #pragma comment(lib, Ws2_32.lib) // 全局变量 std::mutex g_mtx; std::vectorstd::string g_messages; // 内存中的消息列表 const int PORT 8080; const std::string HISTORY_FILE chat_history.txt; // 工具函数 std::string getCurrentTime() { time_t now time(nullptr); tm ltm; localtime_s(ltm, now); char buf[64]; sprintf_s(buf, %04d-%02d-%02d %02d:%02d:%02d, 1900 ltm.tm_year, 1 ltm.tm_mon, ltm.tm_mday, ltm.tm_hour, ltm.tm_min, ltm.tm_sec); return std::string(buf); } void log(const std::string msg) { std::lock_guardstd::mutex lock(g_mtx); std::cout [ getCurrentTime() ] msg std::endl; } // JSON 转义 std::string jsonEscape(const std::string s) { std::string result; for (char c : s) { switch (c) { case : result \\\; break; case \\: result \\\\; break; case \n: result \\n; break; case \r: result \\r; break; case \t: result \\t; break; default: result c; break; } } return result; } // URL 解码 std::string urlDecode(const std::string src) { std::string result; for (size_t i 0; i src.size(); i) { if (src[i] % i 2 src.size()) { int hex 0; std::istringstream iss(src.substr(i 1, 2)); if (iss std::hex hex) { result static_castchar(hex); i 2; } else { result src[i]; } } else if (src[i] ) { result ; } else { result src[i]; } } return result; } // 从 JSON 中提取字段值简易解析 std::string extractJsonField(const std::string json, const std::string field) { std::string key \ field \; size_t pos json.find(key); if (pos std::string::npos) return ; pos json.find(:, pos key.size()); if (pos std::string::npos) return ; pos json.find(, pos 1); if (pos std::string::npos) return ; size_t end json.find(, pos 1); while (end ! std::string::npos json[end - 1] \\) { end json.find(, end 1); } if (end std::string::npos) return ; return json.substr(pos 1, end - pos - 1); } // 消息存储 void saveMessageToFile(const std::string line) { std::ofstream file(HISTORY_FILE, std::ios::app); if (file.is_open()) { file line std::endl; file.close(); } } void loadHistoryFromFile() { std::ifstream file(HISTORY_FILE); if (file.is_open()) { std::string line; while (std::getline(file, line)) { if (!line.empty()) { g_messages.push_back(line); } } file.close(); log(已从文件加载 std::to_string(g_messages.size()) 条历史消息); } } // HTML 页面内嵌 // HTML 页面内嵌 std::string getHtmlPage() { return RHTML(!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title局域网聊天室/title style * { margin:0; padding:0; box-sizing:border-box; } body { font-family:Segoe UI,Microsoft YaHei,sans-serif; background:#f0f2f5; height:100vh; display:flex; justify-content:center; align-items:center; } /* 登录页 */ .login-box { background:#fff; border-radius:12px; padding:40px; box-shadow:0 8px 30px rgba(0,0,0,.12); text-align:center; width:360px; } .login-box h2 { color:#07c160; margin-bottom:24px; font-size:22px; } .login-box input { width:100%; padding:12px 16px; border:1px solid #ddd; border-radius:8px; font-size:15px; outline:none; transition:border .2s; } .login-box input:focus { border-color:#07c160; } .login-box button { width:100%; padding:12px; margin-top:16px; background:#07c160; color:#fff; border:none; border-radius:8px; font-size:16px; cursor:pointer; transition:background .2s; } .login-box button:hover { background:#06ad56; } /* 聊天页 */ .chat-app { width:420px; height:640px; background:#fff; border-radius:12px; box-shadow:0 8px 30px rgba(0,0,0,.12); display:none; flex-direction:column; overflow:hidden; } .chat-header { background:#07c160; color:#fff; padding:14px 20px; display:flex; justify-content:space-between; align-items:center; } .chat-header h3 { font-size:17px; font-weight:600; } .header-right { display:flex; align-items:center; gap:12px; } .logout-btn { background:rgba(255,255,255,0.2); border:none; color:#fff; padding:4px 10px; border-radius:4px; font-size:12px; cursor:pointer; } .logout-btn:hover { background:rgba(255,255,255,0.4); } .notify-toggle { display:flex; align-items:center; gap:6px; font-size:13px; cursor:pointer; user-select:none; } .toggle-switch { width:36px; height:20px; background:#ccc; border-radius:10px; position:relative; transition:background .3s; } .toggle-switch.on { background:#fff; } .toggle-switch::after { content:; position:absolute; width:16px; height:16px; background:#fff; border-radius:50%; top:2px; left:2px; transition:left .3s; box-shadow:0 1px 3px rgba(0,0,0,.3); } .toggle-switch.on::after { left:18px; background:#07c160; } .toggle-switch:not(.on)::after { background:#999; } /* 消息区 */ .msg-area { flex:1; overflow-y:auto; padding:16px; background:#f5f5f5; } .msg-row { display:flex; margin-bottom:14px; } .msg-row.self { flex-direction:row-reverse; } .msg-avatar { width:36px; height:36px; border-radius:6px; background:#07c160; color:#fff; display:flex; align-items:center; justify-content:center; font-size:14px; font-weight:bold; flex-shrink:0; } .msg-row.self .msg-avatar { background:#5b93d0; } .msg-body { max-width:65%; margin:0 10px; } .msg-info { font-size:11px; color:#999; margin-bottom:4px; } .msg-row.self .msg-info { text-align:right; } .msg-bubble { padding:10px 14px; border-radius:10px; font-size:14px; line-height:1.5; word-wrap:break-word; position:relative; } .msg-row:not(.self) .msg-bubble { background:#fff; border-top-left-radius:2px; } .msg-row.self .msg-bubble { background:#95ec69; border-top-right-radius:2px; } /* 输入区 */ .input-area { display:flex; padding:12px; border-top:1px solid #eee; background:#fff; } .input-area input { flex:1; padding:10px 14px; border:1px solid #ddd; border-radius:20px; font-size:14px; outline:none; } .input-area input:focus { border-color:#07c160; } .input-area button { margin-left:10px; padding:0 22px; background:#07c160; color:#fff; border:none; border-radius:20px; font-size:14px; cursor:pointer; } .input-area button:hover { background:#06ad56; } /* Toast 提醒 */ .toast-container { position:fixed; top:20px; right:20px; z-index:9999; display:flex; flex-direction:column; gap:8px; } .toast { background:#fff; border-left:4px solid #07c160; padding:14px 20px; border-radius:8px; box-shadow:0 4px 16px rgba(0,0,0,.15); min-width:260px; max-width:340px; animation:slideIn .3s ease; } .toast .toast-title { font-weight:600; font-size:14px; color:#333; margin-bottom:4px; } .toast .toast-body { font-size:13px; color:#666; } keyframes slideIn { from{transform:translateX(100%);opacity:0} to{transform:translateX(0);opacity:1} } keyframes slideOut { from{transform:translateX(0);opacity:1} to{transform:translateX(100%);opacity:0} } .toast.hide { animation:slideOut .3s ease forwards; } /style /head body !-- 登录页 -- div classlogin-box idloginPage h2 局域网聊天室/h2 input typetext idnameInput placeholder请输入你的昵称 maxlength20 onkeydownif(event.keyEnter)doJoin() button onclickdoJoin()加入聊天/button /div !-- 聊天页 -- div classchat-app idchatPage div classchat-header h3 聊天室/h3 div classheader-right div classnotify-toggle onclicktoggleNotify() span idnotifyLabel提醒/span div classtoggle-switch idnotifySwitch/div /div button classlogout-btn onclickdoLogout() title清除缓存并退出切换/button /div /div div classmsg-area idmsgArea/div div classinput-area input typetext idmsgInput placeholder输入消息回车发送... onkeydownif(event.keyEnter)sendMsg() button onclicksendMsg()发送/button /div /div !-- Toast 容器 -- div classtoast-container idtoastContainer/div script let myName ; let lastCount 0; let notifyOn localStorage.getItem(notifyOn) true; let pollTimer null; // 页面加载时自动检查缓存 window.addEventListener(DOMContentLoaded, () { const savedName localStorage.getItem(chat_username); if (savedName) { // 有缓存直接跳过登录页进入聊天 myName savedName; enterChatRoom(); } // 请求浏览器通知权限 if (Notification in window Notification.permission default) { setTimeout(() Notification.requestPermission(), 1000); } }); // 进入聊天室公共逻辑 function enterChatRoom() { document.getElementById(loginPage).style.display none; document.getElementById(chatPage).style.display flex; initNotify(); loadMessages(); if (pollTimer) clearInterval(pollTimer); pollTimer setInterval(loadMessages, 1500); } // 退出/切换账号 function doLogout() { if (confirm(确定要清除本地缓存并切换账号吗)) { localStorage.removeItem(chat_username); location.reload(); // 刷新页面回到登录 } } // 初始化提醒开关 function initNotify() { const sw document.getElementById(notifySwitch); const lb document.getElementById(notifyLabel); if (notifyOn) { sw.classList.add(on); lb.textContent 提醒开; } else { sw.classList.remove(on); lb.textContent 提醒关; } } function toggleNotify() { notifyOn !notifyOn; localStorage.setItem(notifyOn, notifyOn); initNotify(); if (notifyOn Notification in window Notification.permission default) { Notification.requestPermission(); } } // 登录 function doJoin() { const name document.getElementById(nameInput).value.trim(); if (!name) { alert(请输入昵称); return; } myName name; // 【核心改动】将名字存入浏览器本地缓存 localStorage.setItem(chat_username, name); enterChatRoom(); } // 发送消息 function sendMsg() { const input document.getElementById(msgInput); const text input.value.trim(); if (!text) return; fetch(/api/send, { method: POST, headers: {Content-Type:application/json}, body: JSON.stringify({sender: myName, content: text}) }); input.value ; input.focus(); } // 加载消息 function loadMessages() { fetch(/api/history) .then(r r.json()) .then(msgs { if (msgs.length lastCount) return; const isNew msgs.length lastCount; const newMsgs msgs.slice(lastCount); lastCount msgs.length; renderMessages(msgs); if (isNew) { newMsgs.forEach(m { if (m.sender ! myName) { showNotify(m.sender, m.content); } }); } }) .catch(() {}); } // 渲染消息 function renderMessages(msgs) { const area document.getElementById(msgArea); area.innerHTML ; msgs.forEach(m { const isSelf m.sender myName; const row document.createElement(div); row.className msg-row (isSelf ? self : ); const initial m.sender.charAt(0).toUpperCase(); row.innerHTML div classmsg-avatar initial /div div classmsg-body div classmsg-info escapeHtml(m.sender) · m.time /div div classmsg-bubble escapeHtml(m.content) /div /div; area.appendChild(row); }); area.scrollTop area.scrollHeight; } function escapeHtml(s) { const d document.createElement(div); d.textContent s; return d.innerHTML; } // 消息提醒 function showNotify(sender, content) { if (!notifyOn) return; showToast(sender, content); if (Notification in window Notification.permission granted) { new Notification( sender, { body: content }); } } function showToast(title, body) { const container document.getElementById(toastContainer); const toast document.createElement(div); toast.className toast; toast.innerHTML div classtoast-title escapeHtml(title) /div div classtoast-body escapeHtml(body) /div; container.appendChild(toast); setTimeout(() { toast.classList.add(hide); setTimeout(() toast.remove(), 300); }, 3000); } /script /body /html)HTML; } // HTTP 处理 void handleClient(SOCKET clientSock) { char buf[8192] { 0 }; int totalRecv 0; // 先接收头部 int n recv(clientSock, buf, sizeof(buf) - 1, 0); if (n 0) { closesocket(clientSock); return; } totalRecv n; std::string request(buf, n); // 如果有 Content-Length继续接收 body std::string clHeader Content-Length:; size_t clPos request.find(clHeader); if (clPos std::string::npos) clPos request.find(content-length:); if (clPos ! std::string::npos) { size_t valStart clPos clHeader.size(); size_t valEnd request.find(\r\n, valStart); int contentLen atoi(request.substr(valStart, valEnd - valStart).c_str()); size_t bodyStart request.find(\r\n\r\n); if (bodyStart ! std::string::npos) { bodyStart 4; int bodyReceived (int)(request.size() - bodyStart); while (bodyReceived contentLen) { char tmp[4096]; int r recv(clientSock, tmp, sizeof(tmp), 0); if (r 0) break; request.append(tmp, r); bodyReceived r; } } } // 解析请求行 std::istringstream reqStream(request); std::string method, path, version; reqStream method path version; std::string responseBody; std::string contentType text/html; charsetutf-8; int statusCode 200; // ---- GET / ---- if (method GET path /) { responseBody getHtmlPage(); log(页面请求: GET /); } // ---- GET /api/history ---- else if (method GET path /api/history) { std::lock_guardstd::mutex lock(g_mtx); responseBody [; for (size_t i 0; i g_messages.size(); i) { if (i 0) responseBody ,; responseBody g_messages[i]; } responseBody ]; contentType application/json; charsetutf-8; } // ---- POST /api/send ---- else if (method POST path /api/send) { size_t bodyPos request.find(\r\n\r\n); if (bodyPos ! std::string::npos) { std::string body request.substr(bodyPos 4); std::string sender extractJsonField(body, sender); std::string content extractJsonField(body, content); if (!sender.empty() !content.empty()) { std::string timeStr getCurrentTime(); // 构建 JSON 对象 std::string jsonMsg {\sender\:\ jsonEscape(sender) \,\content\:\ jsonEscape(content) \,\time\:\ timeStr \}; { std::lock_guardstd::mutex lock(g_mtx); g_messages.push_back(jsonMsg); } // 保存到文件 saveMessageToFile(jsonMsg); log([ sender ] 发送消息: content); responseBody {\status\:\ok\}; contentType application/json; charsetutf-8; } else { statusCode 400; responseBody {\status\:\error\,\msg\:\invalid data\}; contentType application/json; charsetutf-8; log(错误: 收到无效消息数据); } } } // ---- 404 ---- else { statusCode 404; responseBody Not Found; contentType text/plain; charsetutf-8; log(警告: 404 未找到路径 path); } // 构建 HTTP 响应 std::ostringstream resp; resp HTTP/1.1 statusCode OK\r\n Content-Type: contentType \r\n Content-Length: responseBody.size() \r\n Access-Control-Allow-Origin: *\r\n Connection: close\r\n \r\n responseBody; std::string respStr resp.str(); send(clientSock, respStr.c_str(), (int)respStr.size(), 0); closesocket(clientSock); } // 主函数 int main() { system(chcp 65001); // 设置控制台为 UTF-8) SetConsoleOutputCP(65001); // 支持中文输出 log( 局域网聊天室服务器 ); // 加载历史消息 loadHistoryFromFile(); // 初始化 Winsock WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), wsaData) ! 0) { log(错误: Winsock 初始化失败); return 1; } log(Winsock 初始化成功); // 创建 Socket SOCKET serverSock socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (serverSock INVALID_SOCKET) { log(错误: 创建 Socket 失败, 错误码: std::to_string(WSAGetLastError())); WSACleanup(); return 1; } // 允许端口重用 int opt 1; setsockopt(serverSock, SOL_SOCKET, SO_REUSEADDR, (char*)opt, sizeof(opt)); // 绑定 sockaddr_in addr{}; addr.sin_family AF_INET; addr.sin_addr.s_addr INADDR_ANY; addr.sin_port htons(PORT); if (bind(serverSock, (sockaddr*)addr, sizeof(addr)) SOCKET_ERROR) { log(错误: 绑定端口 std::to_string(PORT) 失败, 错误码: std::to_string(WSAGetLastError())); closesocket(serverSock); WSACleanup(); return 1; } log(成功绑定端口 std::to_string(PORT)); // 监听 if (listen(serverSock, SOMAXCONN) SOCKET_ERROR) { log(错误: 监听失败); closesocket(serverSock); WSACleanup(); return 1; } log(开始监听...); // 获取本机 IP char hostname[256] { 0 }; gethostname(hostname, sizeof(hostname)); log(本机主机名: std::string(hostname)); struct addrinfo hints {}, * result nullptr; hints.ai_family AF_INET; if (getaddrinfo(hostname, nullptr, hints, result) 0 result) { char ipBuf[INET_ADDRSTRLEN]; sockaddr_in* sin (sockaddr_in*)result-ai_addr; inet_ntop(AF_INET, sin-sin_addr, ipBuf, sizeof(ipBuf)); log(本机局域网 IP: std::string(ipBuf)); log(其他电脑请访问: http:// std::string(ipBuf) : std::to_string(PORT)); freeaddrinfo(result); } log(本机访问: http://127.0.0.1: std::to_string(PORT)); log(等待连接中...\n); // 主循环 while (true) { SOCKET clientSock accept(serverSock, nullptr, nullptr); if (clientSock ! INVALID_SOCKET) { std::thread(handleClient, clientSock).detach(); } } closesocket(serverSock); WSACleanup(); return 0; }编译要求VS选控制台应用ISOC20标准编译后单文件就可以运行不需要其他的很方便怎么自己加标签页的图标如果控制台出现这一行不用管他是标签页上的图标没找到如果在意可以按以下步骤准备一张小图png 格式32×32 左右最好用在线工具搜图片转 base64把它转成一串 base64 文本getHtmlPage()的 HTML 中title后面加上这一行link relicon typeimage/png hrefdata:image/png;base64,这里粘贴base64字符串资源下载自己不想编译的可以去我主页资源下载也可以下载此文档绑定的资源