【C++联机小游戏】机房聊天工具(C++Chat)
·
Part 0x3f. 闲话
今天是 ykq 大佬的生日,趁机把上学期在家写的代码发出来。
祝 ykq 生日快乐!
Part 0. 前言
这是一款基于 WebSocket 和 C++ 控制台开发的一款多线程简易聊天工具。它可以实现包括但不限于以下功能:
- 主要功能:
- 加入局域网内聊天室并在线聊天;
- 发送全局消息、私信(对应服务端为公告、私信);
- 发送弹窗提醒(对应服务端为特别警告);
- 实时查看聊天室成员状态、聊天记录(服务端);
- 禁言 & 解除禁言、封禁
IP& 解除封禁、锁定聊天室 & 解锁(服务端); - 昵称、命令自动补全;
- 辅助功能:
- 一键隐藏窗口(弹窗内容同步更改为
Hint,默认为Alt+Q); - 窗口置顶(默认为
Ctrl+T) - 连续申请窗口置顶(用于对付某些顽固程序,默认为
Ctrl+J); - 调整字体大小等;
- UI / UX:
- 聊天记录、命令内容实时彩色显示;
- 聊天记录滚动(鼠标滚轮);
- 灰体显示待补全内容、光标闪烁等。
- 用不同颜色(或格式)明显区分自己的消息记录、他人的消息记录、管理员(服务端)的消息记录。
聊天室内的编辑器为笔者自己编写,它可以处理普通编辑器的大多数操作(包括但不限于左右控制箭头、Insert、Home、End、Delete、Backspace、复制粘贴等,并可实时侦测输入内容以实现实时彩显、自动补全和显示补全提示、自动更改显示格式类型等附加功能。
本工具为笔者独立编写(未使用 AI 等),创作不易,若有不妥之处或严重影响体验的 Bug,欢迎评论或私信指正,不胜感激!
Part 1. 操作简介
- 将项目源码粘贴至
Windows环境下的Dev-Cpp 5.11中(也可使用其他编辑器),并编译运行。
请在编译选项中加入-lws2_32 -std=c++14,并将字符集改为gbk(如出现乱码现象)。 - 创建服务端,并使用客户端连接至服务端。

正常显示界面应如上图所示。 - 尝试发送消息。

对于客户端,发送消息应分为三种格式:
- 以
Content的格式,发送全局(Global)消息; - 以
@<Username#1>&<Username#2>&... <Content>的格式(不包括尖括号),分别对每个目标发送私信(Whisper); - 以
@@<Username#1>&<Username#2>&... <Content>的格式,分别对每个目标发送弹窗提醒(Hint);
示例:@Alice&Bob&Oliver&Mike&Jack Hello everyone!。
对于服务端,发送消息应以命令的形式,即 broadcast <Content>(全局公告),whisper <Username#1> <Username#2> ... <Content>(私信)或 warn <Username#1> <Username#2> ... <Content>(特别警告)。
示例:warn Alice Bob Mike Oliver Jack That's NOT allowed!。
- 尝试发送命令

在命令行直接输入命令即可。命令格式详见命令帮助:
- 祝大家聊天愉快!
Part 2. 注意事项 & 花絮
以下列举了使用过程中的常见注意事项:
- 编译时请不要开
O2优化,否则多线程将失效; - 若出现连接失败的问题,则可能是自动申请防火墙规则失败,请尝试手动添加防火墙出入站规则(端口即为聊天室端口)或关闭防火墙;
- 若被误检测为病毒,则应为申请防火墙规则造成杀毒软件误解。请积极告知笔者或删除申请防火墙规则的那段代码(笔者也曾给
Microsoft提交过误报报告并凭一己之力更改了Windows Defender的一个版本); - 若出现少许中文重叠,属正常现象(如图片中
发和送的重叠); - 若出现表格框线出界的情况,则可能是您使用了旧版控制台(造成上述问题的原因是在新版控制台中表格框线显示为一个字符,而旧版控制台显示为两个字符)。请尝试更改为新版控制台(
Windows 10+)或于代码中横向删除约一半的表格框线(Windows 7等)。
开发过程中遇到的部分典型问题:
- 申请线程锁顺序不统一导致多线程死锁问题;
- 循环等待条件时未加
Sleep导致 CPU 占用过多(这在单线程中几乎可以忽略,但在多线程中却是严重问题); - 需禁用拖拽边框,否则可能会出现误拖拽时格式混乱;
- 需特判以避免其余一些神级操作造成的不良后果(如
Ctrl+Letter导致编辑器格式爆炸等)。 - 输入汉字再退格造成的乱码问题(解决方案:判断并一次退两个字符)
Part 3. 常见 Q&A
- Q:为什么禁言、封禁等要以 IP 为单位,而不是用户昵称?
A:原因是考虑到若惩罚只对单一昵称,则受罚者很容易更换昵称重新加入聊天室以逃避惩罚。 - Q:为什么要定义那么多线程函数、线程锁,而不是一锁到底?
A:是为了明确每个锁和每个函数的功能,以清晰代码架构、增强代码的可读性。 - Q:为什么如此执着于将惩罚计时、消息格式合法性等判定集中于服务端,而非使用客户端与服务端协同工作的方式?
A:这样可以防止客户端通过篡改代码,从而实现逃避惩罚、使用不当格式造成服务器崩溃等不良后果。 - Q:为什么服务端权力如此之大,以至于能看到几乎所有数据?
A:一方面是为了方便服务端管理;另一方面,所有的客户端消息都无法避免经过服务端,而服务器拥有者很容易通过修改代码捕获客户端之间的通信(除非使用客户端拥有者之间以事先商量好密钥的方式进行加密通讯,但这显然没有客户端拥有者自己创建一个服务端来得方便)。
Part 4. 源代码
#include <bits/stdc++.h>
#include <windows.h>
#include <conio.h>
#pragma comment (lib,"ws2_32")
#define sz 901 //缓冲区长度
#define debug cerr<<"**ykqAKIOI**\n"; // stO ykq Orz
#define pii pair<int,int>
#define endl '\n'
#define For(i,l,r) for(int i=l,i##end=r;i<=i##end;++i)
#define Rof(i,l,r) for(int i=l,i##end=r;i>=i##end;--i)
using namespace std;
// 在这里修改配置
// 更改时无需进行其他操作
const int BasicMove=2; // 显示时每次鼠标滚轮的滚动移动消息记录的条数
const int FlashingInterval=666; // 输入光标闪烁间隔时间(毫秒)
const int MultipleJoin=3; // 同一个 IP 同时以不同昵称在线于同一聊天室的最大数量限制(针对服务端)
// 更改时需要同步调整 UI
const int DisplayNum=20; // 屏幕上显示消息记录条数
const int LengthLimit=60; // 单个消息长度限制(字符)
const int PeopleLimit=15; // 聊天室人数限制(针对服务端)
struct Message{
string type; // 显示的消息类型,分为 global,whispr,hinttt,warnnn,brdcst,system
string content; // 消息内容
string time; // 消息时间(针对服务端)
int sender; // 消息发送者
set<int> targets; // 消息指定的目标(针对私信、提醒和警告)
};
mutex varlocker; // 变量锁
mutex outputlocker; // 输出锁
mutex changelocker; // 初始界面内容的更新锁
condition_variable sndbufactv; // 申请处理并发送输入内容的标记
condition_variable bufactv; // 输入内容的更新标记
condition_variable rcdactv; // 消息记录的更新标记
condition_variable sbufactv; // 重新显示输入内容的标记
condition_variable cursoractv; // 文本编辑器光标的更新标记
condition_variable cmdrcdactv; // 命令记录更新标记(针对服务端)
condition_variable onlineactv; // 在线列表更新标记(针对服务端)
int port=1337; // 端口号
int lstx,lsty; // 上一次移动输出位置时的目标
int rcdup; // 消息记录中显示时向上位移几条消息
int cspos; // 输入文本字符串中插入位置,最开始为 0
int cursorline; // 屏幕显示的光标位置(行)
int cursorpos; // 屏幕显示的光标位置(列)
int CharSize=18; // 字体大小
string buf; // 当前输入内容
string unfilled; // 待补全的内容(按 Tab 之后应用至输入内容)
vector<Message> msgrcd; // 处理后的消息记录
queue<string> msgque; // 待处理消息
queue<string> hints,warnings; // 提示和警告暂存
bool Hi=0; // 是否隐藏窗口
bool insmark; // 是否为插入模式
bool showcs; // 编辑器中是否显示光标(用于光标闪烁)
bool Tp=0,Ct=0; // 是否置顶,是否一直置顶
const int black=0,blue=1,green=2,cyan=3,red=4,purple=5,yellow=6,none=7,gray=8,light_blue=9,light_green=10,light_cyan=11,light_red=12,light_purple=13,light_yellow=14,white=15; // 颜色对应的编号
bool kd(int vKey) {return GetAsyncKeyState(vKey) & 0x8000?1:0;} // 检测某键是否按下
void gotoxy(int x,int y){lstx=x,lsty=y;COORD pos={(short)(y-1),(short)(x-1)};HANDLE hOut=GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(hOut,pos);return;}
void HideConsoleCursor(){CONSOLE_CURSOR_INFO cur={1,0};SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cur);} // 隐藏光标
void ShowConsoleCursor(){CONSOLE_CURSOR_INFO cur={1,1};SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cur);} // 显示光标
void SetColor(int fr_col=7,int bc_col=0){HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(handle,bc_col*16 | FOREGROUND_INTENSITY | fr_col);} // 设置字体颜色,默认为原色
void SetSize(int x){HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_FONT_INFOEX cfi={sizeof(cfi)};GetCurrentConsoleFontEx(handle,FALSE,&cfi);cfi.dwFontSize.X=x;cfi.dwFontSize.Y=x;SetCurrentConsoleFontEx(handle,FALSE,&cfi);} // 设置字体大小
void CancelQuickEditMode(){HANDLE hStdin=GetStdHandle(STD_INPUT_HANDLE);DWORD mode;GetConsoleMode(hStdin,&mode);mode&=~ENABLE_QUICK_EDIT_MODE;mode&=~ENABLE_INSERT_MODE;mode&=~ENABLE_MOUSE_INPUT;SetConsoleMode(hStdin,mode);} // 禁用快速编辑模式
void DisableResizing(){HWND hwnd=GetConsoleWindow();SetWindowLongPtr(hwnd,GWL_STYLE,GetWindowLongPtr(hwnd,GWL_STYLE)&~WS_THICKFRAME);} // 禁用拖拽边框改变窗口大小
void DisableFullScreen(){HWND consoleWindow=GetConsoleWindow();LONG_PTR style=GetWindowLongPtr(consoleWindow,GWL_STYLE);style&=~WS_MAXIMIZEBOX;SetWindowLongPtr(consoleWindow,GWL_STYLE,style);SetWindowPos(consoleWindow,NULL,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_FRAMECHANGED);} // 禁用最大化按钮
string GetIP(){WSADATA WSAData;char hostName[256];if (!WSAStartup(MAKEWORD(2, 0),&WSAData)){if(!gethostname(hostName,sizeof(hostName))){hostent *host=gethostbyname(hostName);if(host!=NULL){return inet_ntoa(*(struct in_addr*)*host->h_addr_list);}}}return ".-1";} // 获取 IP 地址
string GetPrev(){
string ret=GetIP();
while (*ret.rbegin()!='.') ret.pop_back();
return ret;
}
bool isIP(string st) {
stringstream str(st);
string part;
int num,cntt=0,shift=3;
while (getline(str,part,'.')) {
stringstream strr(part);
if (!(strr>>num)||num<0||num>255) return 0; // 数不合法
++cntt;
shift+=(int)to_string(num).size();
}
if (cntt!=4||shift!=(int)st.size()) return 0; // 数的数量不为 4 或非 0 数开头为 0
return 1;
}
int to_int(string st){
int ret=0;
for (char ch:st){
if (!isdigit(ch)) return -1;
ret=ret*10+(ch-'0');
}
return ret;
}
void ApplyRule(){ // 添加防火墙出入站规则
system(((string)"netsh advfirewall firewall add rule name=ChatTool dir=in action=allow protocol=tcp localport="+to_string(port)).data());
system(((string)"netsh advfirewall firewall add rule name=ChatTool dir=out action=allow protocol=tcp localport="+to_string(port)).data());
}
void nxtline(){ // 切换下一行,并与上一行对齐
gotoxy(lstx+1,lsty);
}
void Print(string st, int fr_col=7,int bc_col=0){ // 彩色打印内容,默认为原色
SetColor(fr_col,bc_col); // 设置颜色
cout<<st; // 输出
SetColor(); // 设置回原色
}
void PrintVoid(string st,int voidpos,int fr_col=7,int bc_col=0){
HANDLE hConsole=GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(hConsole, &csbi);
int x,y;
x=csbi.dwCursorPosition.Y+1;
y=csbi.dwCursorPosition.X+1;
if (y<=voidpos&&voidpos<=y+(int)st.size()-1)
Print(st.substr(0,voidpos-y)+" "+st.substr(voidpos-y,(int)st.size()-voidpos+y),fr_col,bc_col);
else Print(st,fr_col,bc_col);
}
void DelContent(int sx, int sy, int ex, int ey){ // 删除屏幕上一块内容
For(i, sx, ex){
gotoxy(i, sy);
For(j, sy, ey){
cout<<" ";
}
}
}
void ScrollUp(){ // 滚轮向上
int old=rcdup;
rcdup=max(min(0,DisplayNum-(int)msgrcd.size()),rcdup-BasicMove);
if (rcdup!=old) rcdactv.notify_all(); // 有更新,解开锁
}
void ScrollDown(){ // 滚轮向下
int old=rcdup;
rcdup=min(max(min((int)msgrcd.size(),DisplayNum)-1,0),rcdup+BasicMove);
if (rcdup!=old) rcdactv.notify_all(); // 有更新,解开锁
}
void SlowPrint(string st,string fix="",int dtime=60,int fr_col=7,int fix_col=7,int bc_col=0){
HANDLE hConsole=GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(hConsole, &csbi);
int x,y;
x=csbi.dwCursorPosition.Y+1;
y=csbi.dwCursorPosition.X+1;
For(i, 0, (int)st.size()-1){
gotoxy(x, y+i);
string ss="";ss+=st[i];
Print(ss,fr_col,bc_col);
Print(fix,fix_col,bc_col);
changelocker.unlock();
Sleep(dtime);
changelocker.lock();
}
}
string ToTime(int sec){ // 获取秒数对应的的时间
time_t timep;
timep=(time_t)sec;
struct tm *t=localtime(&timep);
char ret[105];
sprintf(ret,"[%02d:%02d:%02d]",t->tm_hour,t->tm_min,t->tm_sec);
return ret;
}
int GetSec(int late=0){ // 获取延迟几分钟后的秒数(默认为 0)
time_t timep;
time(&timep);
return (int)timep+late*60;
}
string GetTime(int late=0){ // 获取延迟几分钟后的时间(默认为 0)
return ToTime(GetSec(late));
}
DWORD WINAPI wtop(LPVOID param){ // 分线程:申请窗口置顶
HWND hWnd=GetConsoleWindow();
while(1){
if(kd(17)){ // Ctrl
int t=clock();
while (clock()<=t+300){
if (kd('T')){
if (Tp) ::SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 100, 100, SWP_NOMOVE | SWP_NOSIZE),Tp=0;
else if (!Hi) ::SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 100, 100, SWP_NOMOVE | SWP_NOSIZE),Tp=1;
break;
}
}
}
Sleep(150);
}
}
DWORD WINAPI wtopp(LPVOID param){ // 分线程:不断申请窗口置顶
HWND hWnd=GetConsoleWindow();
while(1){
if(kd(17)){ // Ctrl
int t=clock();
while (clock()<=t+300){
if (kd('J')){
if(Ct) ::SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 100, 100, SWP_NOMOVE | SWP_NOSIZE),Ct=0;
else if (!Hi) Ct=1;
Sleep(150);
break;
}
}
}
if (Ct==1&&!Hi) ::SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 100, 100, SWP_NOMOVE | SWP_NOSIZE);
Sleep(5);
}
}
DWORD WINAPI wSetSize(LPVOID param){ // 分线程:不断申请窗口置顶
HWND hWnd=GetConsoleWindow();
while(1){
if(kd(17)){ // Ctrl
int t=clock();
while (clock()<=t+300){
if (kd(187)){ // +
if (CharSize<40) SetSize(++CharSize),Sleep(130);
}
if (kd(189)){ // -
if (CharSize>5) SetSize(--CharSize),Sleep(130);
}
}
}
Sleep(150);
}
}
DWORD WINAPI FlashingCursor(LPVOID param){
while (1) {
varlocker.lock();
showcs^=1;
varlocker.unlock();
cursoractv.notify_all();
Sleep(FlashingInterval);
}
}
DWORD WINAPI EditBuf(LPVOID param){ // 分线程:消息文本编辑器,同时处理鼠标滚轮
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode;
GetConsoleMode(hStdin, &mode);
SetConsoleMode(hStdin, mode | ENABLE_MOUSE_INPUT);
INPUT_RECORD irInBuf[128];
DWORD cNumRead;
bool flag=0,upd=0; // 是否是一个汉字的第二个字符,是否更新
while (1) {
ReadConsoleInput(hStdin,irInBuf,128,&cNumRead);
for (DWORD i=0;i<cNumRead;++i) {
varlocker.lock();
if (irInBuf[i].EventType==MOUSE_EVENT){
MOUSE_EVENT_RECORD mer=irInBuf[i].Event.MouseEvent;
if (mer.dwEventFlags==MOUSE_WHEELED){
short delta=HIWORD(mer.dwButtonState);
if (delta>0) ScrollUp();
else ScrollDown();
}
}else if (irInBuf[i].EventType==KEY_EVENT){
KEY_EVENT_RECORD mer=irInBuf[i].Event.KeyEvent;
if (mer.bKeyDown){
switch(mer.wVirtualKeyCode){ // 部分不可打印字符
case 37: if (cspos>0) cspos-=buf[cspos-1]<0?2:1,upd=1; break; // 左箭头
case 39: if (cspos<(int)buf.size()) cspos+=buf[cspos]<0?2:1,upd=1; break; // 右箭头
case 36: cspos=0,upd=1; break; // Home
case 35: cspos=(int)buf.size(),upd=1; break; // End
case 45: insmark^=1,upd=1; break; // Insert
case 46: if (cspos<(int)buf.size()) buf=buf.substr(0,cspos)+buf.substr(cspos+(buf[cspos]<0?2:1),(int)buf.size()-cspos-(buf[cspos]<0?2:1)),upd=1; break; // Delete
}
if (mer.uChar.AsciiChar){ // 可打印字符
char ch=mer.uChar.AsciiChar;
if (ch==8) { // BackSpace
if (cspos>0){
int dis=(buf[cspos-1]<0?2:1);
buf=buf.substr(0,cspos-dis)+buf.substr(cspos,(int)buf.size()-cspos);
cspos-=dis;
upd=1;
}
}else if (ch==13){ // Enter
if (buf.size()){
sndbufactv.notify_all();
cspos=0;
}
}else if (ch==9){ // tab
buf=buf.substr(0,cspos)+unfilled+buf.substr(cspos,(int)buf.size()-cspos); // 自动补全
if (unfilled.size()) cspos+=(int)unfilled.size(),unfilled="",bufactv.notify_all();
}else if (ch!=27&&!kd(17)&&!kd(18)){ // 可见字符,按住 Ctrl 或 Alt 打的不处理
if (!insmark||cspos==(int)buf.size()||flag) {
if ((int)buf.size()<LengthLimit||((int)buf.size()==LengthLimit&&flag)){
if (ch<0) flag^=1;
buf=buf.substr(0,cspos)+ch+buf.substr(cspos,(int)buf.size()-cspos);
++cspos;
upd=1;
}
}else{
if (ch<0) flag^=1;
buf=buf.substr(0,cspos)+ch+buf.substr(cspos+(buf[cspos]<0?2:1),(int)buf.size()-cspos-(buf[cspos]<0?2:1));
++cspos;
upd=1;
}
}
}
}
}
varlocker.unlock();
}
if (upd) upd=0,bufactv.notify_all(); // 有更新,解开锁
}
}
DWORD WINAPI PopHint(LPVOID param){ // 分线程:弹出提示窗口
while (1){
while (!warnings.empty()){
string st=warnings.front();
warnings.pop();
MessageBox(NULL,st.data(),"Warning",MB_OK|MB_ICONEXCLAMATION);
}
if (!hints.empty()) {
string st=hints.front();
hints.pop();
MessageBox(NULL,st.data(),"Hint",MB_OK|MB_ICONASTERISK);
}
Sleep(10);
}
}
DWORD WINAPI ShowCursor(LPVOID Param){
while (1) {
mutex locker;
unique_lock<mutex> lckk(locker);
cursoractv.wait(lckk);
lock_guard<mutex> lck1(outputlocker);
lock_guard<mutex> lck2(varlocker);
gotoxy(cursorline,cursorpos);
if (showcs) {
if (!insmark) Print("|",cyan);
else Print("_",red);
}else Print(" ");
}
}
namespace Client{
SOCKET server; // 服务器 Socket
bool ban,block; // 是否禁言,是否封禁
int nolist[1005]; // 发送目标编号暂存
char rc[1005]; // 接收消息暂存
string name; // 名称
string roomid=""; // 服务端 IP+端口号
map<string,int> member; // 房间成员列表(名字对应编号)
map<int,string> toname; // 编号对应名字(包括已退出的)
queue<string> sndque; // 发送队列
void popmsg(string st){
hints.push(st);
}
void popwarning(string st){
warnings.push(st);
}
DWORD WINAPI whide(LPVOID param){ // 分线程:老板键隐显窗口
HWND hWnd=GetConsoleWindow();
while(1){
if(kd(18)){ // Alt
int t=clock();
while (clock()<=t+300){
if (kd('Q')){
if(Hi) Hi=0;
else Hi=1;
break;
}
}
}
if(Hi) ShowWindow(hWnd,SW_HIDE); // 隐藏窗口
else ShowWindow(hWnd,SW_SHOW); // 显示窗口
Sleep(150);
}
}
DWORD WINAPI SendMessage(LPVOID param){ // 分线程:发送消息至服务端
while (1){
while (!sndque.empty()) {
varlocker.lock();
string noww=sndque.front();
sndque.pop();
varlocker.unlock();
send(server,(noww+"\n").data(),(int)noww.size()+1,0);
Sleep(30);
}
Sleep(10);
}
}
DWORD WINAPI ReceiveMessage(LPVOID param){ // 分线程:从服务端接收消息
while (1) {
memset(rc,0,sizeof (rc));
int reg=recv(server,rc,sz,0);
if (reg==-1) {
popmsg("Connection Lost");
break;
}
string st=rc;
while (st.find('\n')!=string::npos) st.pop_back();
varlocker.lock();
msgque.push(st);
varlocker.unlock();
Sleep(10);
}
}
DWORD WINAPI AutoFill(LPVOID Param) { // 实时更新待补全文本
while (1) {
mutex locker;
unique_lock<mutex> lckk(locker);
bufactv.wait(lckk);
lock_guard<mutex> lck1(varlocker);
sbufactv.notify_all();
unfilled="";
if (!cspos) continue;
stringstream str(buf.substr(0,cspos));
string fst;
str>>fst;
if ((int)fst.size()!=cspos) continue;
if (fst[0]=='@') {
while (fst.size()&&fst[0]=='@') fst=fst.substr(1,(int)fst.size()-1);
while (fst.find('&')!=string::npos) {
int pos=fst.find('&');
string stt=fst.substr(0,pos);
if (pos+1>=(int)fst.size()||!member.count(stt)) goto End;
fst=fst.substr(pos+1,(int)fst.size()-pos-1);
}
int len=(int)fst.size();
for (auto it:member){
if (it.first!=name&&len<(int)it.first.size()&&it.first.substr(0,len)==fst){
if ((int)buf.size()+(int)it.first.size()-len<=LengthLimit)
unfilled=it.first.substr(len,(int)it.first.size()-len);
goto End;
}
}
}
End:;
}
}
DWORD WINAPI DealBuf(LPVOID Param){ // 处理待发送信息
while (1) {
mutex locker;
unique_lock<mutex> lckk(locker);
sndbufactv.wait(lckk);
lock_guard<mutex> lk(varlocker); // 上锁
if (!buf.size()) continue;
if (ban) { // 当前禁言
msgrcd.push_back({"system","请等待禁言结束。"});
buf="";
bufactv.notify_all();
rcdactv.notify_all();
continue;
}
if (block) { // 当前封禁
msgrcd.push_back({"system","请等待封禁结束。"});
buf="";
bufactv.notify_all();
rcdactv.notify_all();
continue;
}
string st=buf;
string msg="";
bool flag=0;
if (st[0]=='@'&&st[1]=='@'){
msg="hint ";
st=st.substr(2,(int)st.size()-2);
flag=1;
} else if (st[0]=='@'){
msg="whisper ";
st=st.substr(1,(int)st.size()-1);
flag=1;
} else {
msg="global ";
}
int cur=0;
if (flag){
stringstream str;str<<st;
string stt;str>>stt;
if ((int)stt.size()==(int)st.size()){
flag=0;
st=buf;
msg="global ";
goto Send;
}
st=st.substr((int)stt.size()+1,(int)st.size()-(int)stt.size()-1);
string nme="";
stt+='&';
while (stt.size()){
if (stt[0]=='&') {
if (!member.count(nme)||nme==name){
flag=0;
st=buf;
msg="global ";
goto Send;
}
nolist[++cur]=member[nme];
nme="";
}else nme+=stt[0];
stt=stt.substr(1,(int)stt.size()-1);
}
nolist[++cur]=0;
For(i, 1, cur){
msg+=to_string(nolist[i])+" ";
}
}
Send:
msg+=st;
sndque.push(msg);
if (msg.substr(0,7)=="whisper"){ // 将私信添加到自己的消息记录中
Message msgg={};
msgg.type="wispme";
For(i, 1, cur){
if (nolist[i]) msgg.targets.insert(nolist[i]);
}
msgg.content=st;
msgrcd.push_back(msgg);
rcdactv.notify_all();
}
buf="";
bufactv.notify_all();
}
}
DWORD WINAPI DealMessage(LPVOID param){ // 分线程:处理接收的消息
rcdactv.notify_all();
while (1) {
if (!msgque.empty()){
varlocker.lock();
string stt=msgque.front();
msgque.pop();
varlocker.unlock();
stringstream str;str<<stt;
string st;str>>st;
Message msg={};
if (st=="Repeated"){
popwarning("昵称与他人重复!");
}else if (st=="Full"){
popwarning("当前聊天室人数已满。");
}else if (st=="MultijoinLimitEx"){
popwarning("当前 IP 加入当前聊天室的人数过多!");
}else if (st=="Inhibited"){
popwarning("当前 IP 被禁止加入当前聊天室。");
}else if (st=="Locked"){
popwarning("当前聊天室已被锁定。");
}else if (st=="ban"){
int no;str>>no;
int sec;str>>sec;
msg.type="system";
if (no!=member[name]) {
msg.content=toname[no]+" 已被管理员禁言,将于 "+ToTime(sec)+" 自动解禁。";;
}else{
msg.content="您被管理员禁言了,将于 "+ToTime(sec)+" 自动解禁(或联系管理员手动解禁)。";
ban=1;
}
}else if (st=="unban"){
int no;str>>no;
msg.type="system";
if (no!=member[name]){
msg.content=toname[no]+" 已被解除禁言。";
}else{
msg.content="管理员已解除对您的禁言,现在您可以继续发消息了!";
ban=0;
}
}else if (st=="block"){
int no;str>>no;
int sec;str>>sec;
msg.type="system";
if (no!=member[name]) {
msg.content=toname[no]+" 已被管理员封禁,将于 "+ToTime(sec)+" 自动解禁。";
}else{
msg.content="您被管理员封禁了,将于 "+ToTime(sec)+" 自动解禁(或联系管理员手动解禁)。";
block=1;
}
}else if (st=="unblock"){
int no;str>>no;
msg.type="system";
if (no!=member[name]){
msg.content=toname[no]+" 已被解除封禁。";
}else{
msg.content="管理员已解除对您的封禁,现在您可以继续收发消息了!";
block=0;
}
}else if (st=="global") {
msg.type="global";
int no;str>>no;
msg.sender=no;
msg.content=stt.substr((int)to_string(no).size()+8,(int)stt.size()-(int)to_string(no).size()-8);
}else if (st=="whisper") {
msg.type="whispr";
int no;str>>no;
msg.sender=no;
msg.content=stt.substr((int)to_string(no).size()+9,(int)stt.size()-(int)to_string(no).size()-9);
}else if (st=="hint") {
msg.type="hinttt";
int no;str>>no;
msg.sender=no;
int shiftt=0;
while (1){
int noo;str>>noo;
shiftt+=(int)to_string(noo).size()+1;
if (noo==0) break;
msg.targets.insert(noo);
}
msg.content=stt.substr((int)to_string(no).size()+shiftt+6,(int)stt.size()-(int)to_string(no).size()-shiftt-6);
if (msg.targets.count(member[name])) popmsg(Hi?"Hint":msg.content);
}else if (st=="warn") {
msg.type="warnnn";
popwarning(Hi?"Hint":stt.substr(5,(int)stt.size()-5));
msg.content=stt.substr(5,(int)stt.size()-5);
}else if (st=="broadcast") {
msg.type="brdcst";
msg.content=stt.substr(10,(int)stt.size()-10);
}else if (st=="join") {
int no;string nme;
str>>no>>nme;
member[nme]=no;
toname[no]=nme;
msg.type="system";
msg.content=nme+" 已加入聊天室,欢迎!";
}else if (st=="leave"){
int no;
str>>no;
string nme=toname[no];
toname[no]="("+nme+")"; // 表示已离开
member.erase(nme);
msg.type="system";
msg.content=nme+" 已离开聊天室。";
}else if (st=="member"){
int no;string nme;
str>>no>>nme;
member[nme]=no;
toname[no]=nme;
msg.type="system";
msg.content="其他成员:"+nme;
}else if (st=="kick"){
int no;
str>>no;
string nme=toname[no];
toname[no]="("+nme+")"; // 表示已离开
member.erase(nme);
msg.type="system";
if (nme==name){
popmsg(Hi?"Hint":"您已被踢出聊天室。");
msg.content="您已被管理员踢出聊天室。";
}else{
msg.content=nme+" 已被踢出聊天室。";
}
}else if (st=="lock"){
msg.type="system";
msg.content="聊天室已被锁定,任何人无法加入。";
}else if (st=="unlock"){
msg.type="system";
msg.content="聊天室已被解锁。";
}
varlocker.lock();
if (msg.type.size()) msgrcd.push_back(msg);
varlocker.unlock();
rcdactv.notify_all(); // 有更新,解开锁
}
Sleep(10);
}
}
DWORD WINAPI ShowMessageRecord(LPVOID Param){ // 分线程:显示消息记录
while (1){
mutex locker;
unique_lock<mutex> lckk(locker);
rcdactv.wait(lckk);
outputlocker.lock();
DelContent(5,2,24,89);
gotoxy(5,2);
varlocker.lock();
For(i,max(0,(int)msgrcd.size()-DisplayNum)+rcdup,(int)msgrcd.size()+min(rcdup,0)-1){
Message noww=msgrcd[i];
if (noww.type=="system"){
Print("[System] "+noww.content,gray);
}else if (noww.type=="global"){
Print("[Global] ");
Print(toname[noww.sender],noww.sender==member[name]?yellow:light_blue);
Print(": "+noww.content);
}else if (noww.type=="whispr"){
Print("[Whisper] ",blue);
if (noww.sender==114514){
Print("@ADMIN",red);
}else{
Print(toname[noww.sender],light_blue);
}
Print(": "+noww.content);
}else if (noww.type=="hinttt"){
Print("[Hint] ",noww.targets.count(member[name])?yellow:white);
Print(toname[noww.sender],noww.sender==member[name]?yellow:light_blue);
Print(": ");
for (int i:noww.targets){
Print("@",yellow);
Print(toname[i],cyan);
}
Print(" "+noww.content);
}else if (noww.type=="warnnn"){
Print("[Warning] ",red);
Print("@ADMIN",red);
Print(": "+noww.content);
}else if (noww.type=="brdcst"){
Print("[Broadcast] ",cyan);
Print("@ADMIN",red);
Print(": "+noww.content);
}else if (noww.type=="wispme"){
Print("[",blue);
Print(name,yellow);
Print(" -> ",green);
for (int i:noww.targets){
Print(toname[i],cyan);
if (i!=(*noww.targets.rbegin())) Print(",",yellow);
}
Print("] ",blue);
Print(noww.content);
}
nxtline();
}
gotoxy(25,16);
Print(to_string(rcdup)+" ",cyan,gray);
varlocker.unlock();
outputlocker.unlock();
}
}
DWORD WINAPI ShowBuffer(LPVOID Param){ // 分线程:显示输入内容
while (1){
mutex locker;
unique_lock<mutex> lck(locker);
sbufactv.wait(lck);
outputlocker.lock();
int msgtype=1;
varlocker.lock();
stringstream str;str<<buf;
string fst;str>>fst;
string cont=buf; // 消息内容
if ((int)fst.size()<(int)buf.size()){
if ((int)fst.size()>2&&fst[0]=='@'&&fst[1]=='@'){
bool flagg=1;
string stt=fst.substr(2,(int)fst.size()-2);
string nme="";
stt+='&';
while (stt.size()){
if (stt[0]=='&') {
if (!member.count(nme)||nme==name) {
flagg=0;
break;
}
nme="";
}else nme+=stt[0];
stt=stt.substr(1,(int)stt.size()-1);
}
if (flagg){ // 是 Hint 并且目标有效
msgtype=3;
}
}else if ((int)fst.size()>1&&fst[0]=='@'){
bool flagg=1;
string stt=fst.substr(1,(int)fst.size()-1);
string nme="";
stt+='&';
while (stt.size()){
if (stt[0]=='&') {
if (!member.count(nme)||nme==name) {
flagg=0;
break;
}
nme="";
}else nme+=stt[0];
stt=stt.substr(1,(int)stt.size()-1);
}
if (flagg){ // 是 Whisper 并且目标有效
msgtype=2;
}
}
if (msgtype!=1) {
cont=buf.substr((int)fst.size()+1,(int)buf.size()-(int)fst.size()-1);
}
}
cursorpos=cspos+(int)name.size()+13;
if (msgtype!=1&&cspos<=(int)fst.size()){
cursorpos=cspos+3;
fst=fst.substr(0,cspos)+" "+fst.substr(cspos,(int)fst.size()-cspos);;
}else if (msgtype!=1){
cursorpos-=7;
cont=cont.substr(0,cspos-fst.size()-1)+" "+cont.substr(cspos-fst.size()-1,(int)cont.size()-cspos+fst.size()+1);
}else{
cont=cont.substr(0,cspos)+" "+cont.substr(cspos,(int)cont.size()-cspos);
}
varlocker.unlock();
DelContent(29,2,29,89);
gotoxy(29,2);
switch(msgtype) {
case 1: Print("[Global]",white); break; // Global
case 2: Print("["+fst+"]",blue); break; // Whisper
case 3: Print("["+fst+"]",yellow); break; // Hint
}
Print(" ");Print(name,yellow);Print(": "+cont);
if (showcs) cursoractv.notify_all();
if (cspos&&unfilled.size()&&cspos==(int)buf.size()) PrintVoid(unfilled,cursorpos,gray);
outputlocker.unlock();
}
}
void MainInterface(){ // 主线程:开始界面
SetSize(CharSize);
system("mode con cols=90 lines=30"); // 设置窗口大小
CreateThread(NULL,0,whide,NULL,0,NULL); //开老板键线程
CreateThread(NULL,0,wSetSize,NULL,0,NULL); //开调节大小线程
system("cls");
SetIP:
cout<<"这是机房聊天工具(C++Chat)的客户端,请连接至服务端。\n";
cout<<"服务端编号(IP地址最后一部分):";
ShowConsoleCursor();
string st;cin>>st;
string ip=GetPrev()+st;
if (ip==GetIP()) ip="127.0.0.1"; // 本地直接连
cout<<"服务端端口:";
cin>>port;
while (1){
cout<<"您的昵称(请不要包含\'@\'、\'&\'或空格,1~15 个字符,中文计为 2 个字符):";
cin>>name;
bool flag=1;
if ((int)name.size()<1||(int)name.size()>15)
for (char ch:name){
if (ch=='@'||ch=='&') flag=0;
}
if (!flag){
cout<<"昵称不合规,请重试。\n\n";
}else{
break;
}
}
HideConsoleCursor();
cout<<"确认以 \""<<name<<"\" 连接至 "<<ip<<":"<<port<<" ?(Y/N)\n";
while (1) {
if (kbhit()) {
char ch=getch();
if (ch=='Y'||ch=='y') {
break;
}else if (ch=='N'||ch=='n'){
goto SetIP;
}
}
}
system("cls");
ApplyRule(); // 申请防火墙规则
system("cls");
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0) exit(0);
server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(server==(SOCKET)(~0)) WSACleanup(),exit(0);
sockaddr_in conser;
conser.sin_family=AF_INET;
conser.sin_port=htons(port);
conser.sin_addr.S_un.S_addr=inet_addr(ip.c_str());
For(i, 1, 5){
if(connect(server,(LPSOCKADDR)&conser,sizeof(conser))==(SOCKET)(~0)){
if(i==5){
puts("连接失败!请检查服务端 IP 地址和端口号。\n");
goto SetIP;
}
}else break;
}
roomid=ip+":"+to_string(port);
sndque.push(name);
// 打印初始界面
Print("聊天室:"+roomid+"\n",green);
Print("您的昵称:"+name+"\n",cyan);
Print("消息记录\n",purple,light_yellow);
Print("┌────────────────────────────────────────────────────────────────────────────────────────┐\n",blue);
For(i, 5,25) gotoxy(i, 1),Print("│",blue),gotoxy(i,90),Print("│",blue);
gotoxy(25,2),Print("记录显示上移:",yellow,gray);
gotoxy(26,1);
Print("└────────────────────────────────────────────────────────────────────────────────────────┘\n",blue);
Print("发送消息\n",blue,light_yellow);
Print("┌────────────────────────────────────────────────────────────────────────────────────────┐\n",cyan);
Print("│",cyan);gotoxy(29,90),Print("│\n",cyan);
Print("└────────────────────────────────────────────────────────────────────────────────────────┘",cyan);
cursorline=29,cursorpos=(int)name.size()+13;
// 开启线程
CreateThread(NULL,0,SendMessage,NULL,0,NULL);
CreateThread(NULL,0,ReceiveMessage,NULL,0,NULL);
CreateThread(NULL,0,PopHint,NULL,0,NULL);
CreateThread(NULL,0,DealMessage,NULL,0,NULL);
CreateThread(NULL,0,EditBuf,NULL,0,NULL);
CreateThread(NULL,0,DealBuf,NULL,0,NULL);
CreateThread(NULL,0,ShowMessageRecord,NULL,0,NULL);
CreateThread(NULL,0,AutoFill,NULL,0,NULL);
CreateThread(NULL,0,FlashingCursor,NULL,0,NULL);
CreateThread(NULL,0,ShowCursor,NULL,0,NULL);
CreateThread(NULL,0,ShowBuffer,NULL,0,NULL);
Sleep(10);
bufactv.notify_all();
rcdactv.notify_all();
while (1) Sleep(10);
}
}
namespace Server{
struct CmdRcd{
bool success; // 是否成功
string cont; // 记录内容
};
SOCKET server,sock[10005];
string name[10005];
string ip[10005];
string onlinetime[10005];
bool locked; // 房间是否锁定
HANDLE hrecv[10005]; // 成员对应的接收消息线程的 handle
int no[10005];
int unblocktime[10005];
int unbantime[10005];
int status[10005]; // 0: 正常 1: 禁言 2: 封禁
int tot=0;
queue<string> broadcast;
queue<string> whisper[10005];
set<int> online;
set<string> inhibitlist;
map<string,int> banlist,blocklist; // 禁言列表和封禁列表
vector<CmdRcd> cmdrcd;
int to_no(string st){
for (int id:online){
if (name[id]==st){
return id;
}
}
return -1;
}
bool OpenServer() { //尝试打开服务器
WSADATA wsaData; //WSA
if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0){ //打开WSA
puts("错误1:WSA打开失败!");
return 0;
}
server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//初始化协议
if(server==(SOCKET)(~0)) { //协议初始化失败
puts("错误2:协议初始化失败!");
WSACleanup(); //释放WSA
return 0;
}
sockaddr_in bindser; //绑定
bindser.sin_family=AF_INET; //一个一个设置过去
bindser.sin_port=htons(port); //指向端口号
bindser.sin_addr.s_addr=htonl(INADDR_ANY); //指向全部人可连接
if(bind(server,(const struct sockaddr*)&bindser,sizeof(bindser))==(SOCKET)(~0)) { //尝试绑定
puts("错误3:本地已经存在当前配置的服务器!");
WSACleanup(); //释放WSA
closesocket(server); //释放socket
return 0;
}
if(listen(server,PeopleLimit)==(SOCKET)(~0)) { //尝试监听
puts("错误4:监听失败!");
WSACleanup();//释放
closesocket(server);//释放
return 0;
}
return 1;
}
DWORD WINAPI whide(LPVOID param){ // 分线程:老板键隐显窗口
HWND hWnd=GetConsoleWindow();
while(1){
if(kd(16)){ // Shift
int t=clock();
while (clock()<=t+300){
if (kd('Z')){
if(Hi) Hi=0;
else Hi=1;
break;
}
}
}
if(Hi) ShowWindow(hWnd,SW_HIDE); // 隐藏窗口
else ShowWindow(hWnd,SW_SHOW); // 显示窗口
Sleep(150);
}
}
DWORD WINAPI SendMessage(LPVOID param){ // 分线程:发送消息至客户端
while (1){
while (!broadcast.empty()) {
varlocker.lock();
string noww=broadcast.front();
broadcast.pop();
for (int i:online){
if (!(status[i]==2&&(noww.substr(0,6)=="global"||noww.substr(0,4)=="hint"||noww.substr(0,9)=="broadcast"))) send(sock[i],(noww+"\n").data(),(int)noww.size()+1,0); // 封禁状态下不发广播
Sleep(10);
}
varlocker.unlock();
Sleep(20);
}
for (int i:online){
while (!whisper[i].empty()){
varlocker.lock();
string noww=whisper[i].front();
whisper[i].pop();
varlocker.unlock();
if (!(status[i]==2&&noww.substr(0,7)=="whisper")) send(sock[i],(noww+"\n").data(),(int)noww.size()+1,0); // 封禁状态下不发私信
Sleep(20);
}
}
Sleep(10);
}
}
DWORD WINAPI ReceiveMessage(LPVOID param){ // 分线程:从客户端接收消息
int cid=tot;
SOCKET sclient=sock[cid];
char rc[1005];
while (1) {
memset(rc,0,sizeof(rc));
int reg=recv(sclient,rc,sz,0);
varlocker.lock();
if (reg==-1) {
msgrcd.push_back({"system","[IP: "+ip[cid]+"] [昵称: "+name[cid]+"] 离开了聊天室!",GetTime()});
broadcast.push("leave "+to_string(cid)); //广播消息
online.erase(cid);
varlocker.unlock();
onlineactv.notify_all();
rcdactv.notify_all();
break;
}
string st=rc;
while (st.find('\n')!=string::npos) st.pop_back();
if (!status[cid]&&st.size()) msgque.push(to_string(cid)+" "+st); // 禁言、封禁状态下不收消息
varlocker.unlock();
Sleep(10);
}
}
DWORD WINAPI AutoUnban(LPVOID Param){ // 分线程:自动封禁解禁
while (1){
int sec=GetSec();
varlocker.lock();
for (int i:online){
if (inhibitlist.count(ip[i])){
msgrcd.push_back({"system","已将 [IP: "+ip[i]+"] [昵称: "+name[i]+"] 踢出房间!",GetTime()});
broadcast.push("kick "+to_string(i)); //广播消息
send(sock[i],("kick "+to_string(i)+"\n").data(),(int)to_string(i).size()+6,0); // 给他也发一份,不然他收不到
TerminateThread(hrecv[i],0);
online.erase(i);
onlineactv.notify_all();
rcdactv.notify_all();
}
if (!status[i]&&banlist.count(ip[i])&&banlist[ip[i]]>sec){
status[i]=1;
unbantime[i]=banlist[ip[i]];
broadcast.push("ban "+to_string(i)+" "+to_string(unbantime[i]));
msgrcd.push_back({"system","已执行对 [IP:"+ip[i]+"] [Name:"+name[i]+"] 的禁言。",GetTime()});
onlineactv.notify_all();
rcdactv.notify_all();
}
if (!status[i]&&blocklist.count(ip[i])&&blocklist[ip[i]]>sec){
status[i]=2;
unblocktime[i]=blocklist[ip[i]];
broadcast.push("block "+to_string(i)+" "+to_string(unblocktime[i]));
msgrcd.push_back({"system","已执行对 [IP:"+ip[i]+"] [Name:"+name[i]+"] 的封禁。",GetTime()});
onlineactv.notify_all();
rcdactv.notify_all();
}
if (status[i]==1&&(unbantime[i]<=sec||!banlist.count(ip[i]))){
status[i]=0;
broadcast.push("unban "+to_string(i));
msgrcd.push_back({"system","已解除对 [IP:"+ip[i]+"] [Name:"+name[i]+"] 的禁言。",GetTime()});
banlist.erase(ip[i]);
onlineactv.notify_all();
rcdactv.notify_all();
}
if (status[i]==2&&(unblocktime[i]<=sec||!blocklist.count(ip[i]))){
status[i]=0;
broadcast.push("unblock "+to_string(i));
msgrcd.push_back({"system","已解除对 [IP:"+ip[i]+"] [Name:"+name[i]+"] 的封禁。",GetTime()});
blocklist.erase(ip[i]);
onlineactv.notify_all();
rcdactv.notify_all();
}
}
varlocker.unlock();
Sleep(40);
}
}
DWORD WINAPI AutoFill(LPVOID Param) { // 文本自动补全
string com[30]={"","help","whisper ","warn ","broadcast ","ban ","block ","unban ","unblock ","lock","unlock","inhibit ","allow "};
string fillname[30]={"","whisper","warn"};
int cnt=12,fcnt=2;
while (1) {
mutex locker;
unique_lock<mutex> lckk(locker);
bufactv.wait(lckk);
lock_guard<mutex> lck1(varlocker);
sbufactv.notify_all();
unfilled="";
if (!cspos) continue;
string st=buf.substr(0,cspos);
For(i,1,cnt){
if ((int)st.size()<(int)com[i].size()&&com[i].substr(0,(int)st.size())==st){
st=com[i];
goto End;
}
}
For(i,1,fcnt){
if ((int)st.size()>(int)fillname[i].size()&&st.substr(0,(int)fillname[i].size()+1)==fillname[i]+" ") {
string stt=st.substr((int)fillname[i].size()+1,(int)st.size()-(int)fillname[i].size()-1);
string sub="";
while (stt.size()) {
if (stt[0]==' '){
if (to_no(sub)==-1) goto End;
sub="";
}else sub+=stt[0];
stt=stt.substr(1,(int)stt.size()-1);
}
for (int id:online){
if (sub.size()&&(int)sub.size()<(int)name[id].size()&&name[id].substr(0,(int)sub.size())==sub){
st=st.substr(0,(int)st.size()-(int)sub.size())+name[id]+" ";
goto End;
}
}
break;
}
}
End:
if ((int)buf.size()+(int)st.size()-cspos<=LengthLimit)
unfilled=st.substr(cspos,(int)st.size()-cspos);
}
}
DWORD WINAPI AutoFillIP(LPVOID Param) { // 自动补全 IP 前缀
string com[30]={"","ban","block","unban","unblock","inhibit","allow"};
int cnt=6;
bool filled=0;
while (1) {
mutex locker;
unique_lock<mutex> lckk(locker);
sbufactv.wait(lckk);
lock_guard<mutex> lck1(varlocker);
string st;
bool flag=0;
For(i, 1, cnt) {
if ((int)buf.size()>=(int)com[i].size()&&buf.substr(0,(int)com[i].size())==com[i]){
flag=1;
}
}
if (!flag) filled=0;
if (filled||cspos!=(int)buf.size()||(*buf.rbegin()!=' ')) goto End;
st=buf.substr(0,(int)buf.size()-1);
For(i,1,cnt){
if (st==com[i]){
buf+=GetPrev();
cspos=(int)buf.size();
bufactv.notify_all();
filled=1;
break;
}
}
End:;
}
}
DWORD WINAPI DealBuf(LPVOID Param){ // 处理命令
while (1) {
mutex locker;
unique_lock<mutex> lckk(locker);
sndbufactv.wait(lckk);
lock_guard<mutex> lk(varlocker); // 上锁
if (!buf.size()) continue;
string st=buf;
string msg="";
CmdRcd rcd={};
if (st.substr(0,4)=="ban "){
st=st.substr(4,(int)st.size()-4);
int tim=0;
For (i, 0, (int)st.size()-1) {
if (st[i]==' '){
stringstream str;
str<<st.substr(i,(int)st.size()-i);
st=st.substr(0,i);
string minn;
str>>minn;
tim=to_int(minn);
break;
}
}
if (!isIP(st)) {
rcd.success=0;
rcd.cont="指定的 IP 地址不合法。";
}else if (tim<1||tim>114514){
rcd.success=0;
rcd.cont="指定的时间不合法。";
}else{
rcd.success=1;
rcd.cont="成功禁言 [IP:"+st+"] "+to_string(tim)+" 分钟!";
blocklist.erase(st);
banlist[st]=GetSec(tim);
}
}else if (st.substr(0,6)=="unban "){
st=st.substr(6,(int)st.size()-6);
if (!isIP(st)) {
rcd.success=0;
rcd.cont="指定的 IP 地址不合法。";
}else if (!banlist.count(st)){
rcd.success=0;
rcd.cont="未在禁言名单中找到 [IP:"+st+"]。";
}else{
rcd.success=1;
rcd.cont="成功解除对 [IP:"+st+"] 的禁言!";
banlist.erase(st);
}
}else if (st.substr(0,6)=="block "){
st=st.substr(6,(int)st.size()-6);
int tim=0;
For (i, 0, (int)st.size()-1) {
if (st[i]==' '){
stringstream str;
str<<st.substr(i,(int)st.size()-i);
st=st.substr(0,i);
string minn;
str>>minn;
tim=to_int(minn);
break;
}
}
if (!isIP(st)) {
rcd.success=0;
rcd.cont="指定的 IP 地址不合法。";
}else if (tim<1||tim>114514){
rcd.success=0;
rcd.cont="指定的时间不合法。";
}else{
rcd.success=1;
rcd.cont="成功封禁 [IP:"+st+"] "+to_string(tim)+" 分钟!";
banlist.erase(st);
blocklist[st]=GetSec(tim);
}
}else if (st.substr(0,8)=="unblock "){
st=st.substr(8,(int)st.size()-8);
if (!isIP(st)) {
rcd.success=0;
rcd.cont="指定的 IP 地址不合法。";
}else if (!blocklist.count(st)){
rcd.success=0;
rcd.cont="未在封禁名单中找到 [IP:"+st+"]。";
}else{
rcd.success=1;
rcd.cont="成功解除对 [IP:"+st+"] 的封禁!";
blocklist.erase(st);
}
}else if (st.substr(0,8)=="inhibit "){
st=st.substr(8,(int)st.size()-8);
if (!isIP(st)) {
rcd.success=0;
rcd.cont="指定的 IP 地址不合法。";
}else if (inhibitlist.count(st)){
rcd.success=0;
rcd.cont="指定的 IP 当前已经被禁止加入!";
}else{
rcd.success=1;
rcd.cont="成功禁止 [IP:"+st+"] 加入聊天室!";
inhibitlist.insert(st);
}
}else if (st.substr(0,6)=="allow "){
st=st.substr(6,(int)st.size()-6);
if (!isIP(st)) {
rcd.success=0;
rcd.cont="指定的 IP 地址不合法。";
}else if (!inhibitlist.count(st)){
rcd.success=0;
rcd.cont="未在禁止加入名单中找到 [IP:"+st+"]。";
}else{
rcd.success=1;
rcd.cont="成功解除对 [IP:"+st+"] 的加入限制!";
inhibitlist.erase(st);
}
}else if (st=="lock"){
if (locked){
rcd.success=0;
rcd.cont="当前聊天室已经处于锁定状态!";
}else{
locked=1;
rcd.success=1;
rcd.cont="成功锁定聊天室!";
}
}else if (st=="unlock"){
if (!locked){
rcd.success=0;
rcd.cont="当前聊天室未被锁定!";
}else{
locked=0;
rcd.success=1;
rcd.cont="成功解锁聊天室!";
}
}else if (st.substr(0,10)=="broadcast "){
rcd.success=1;
rcd.cont="成功发送广播消息!";
broadcast.push(st);
msgrcd.push_back({"brdcst",st.substr(10,(int)st.size()-10),GetTime()});
rcdactv.notify_all();
}else if (st.substr(0,8)=="whisper "){
stringstream str;
st=st.substr(8,(int)st.size()-8);
str<<st;
set<int> targets;
targets.clear();
int shift=0;
while (1) {
string nme;
str>>nme;
if (to_no(nme)!=-1&&st.substr(shift,(int)nme.size())==nme){
shift+=(int)nme.size()+1;
if (status[to_no(nme)]!=2) {
targets.insert(to_no(nme));
}
}else{
break;
}
}
if (!targets.size()){
rcd.success=0;
rcd.cont="未找到有效目标。";
}else{
rcd.success=1;
rcd.cont="成功发送私信至:";
for (int id:targets){
rcd.cont+=name[id]+", ";
}
For(i,1,2) rcd.cont.pop_back();
rcd.cont+="。";
st=st.substr(shift,(int)st.size()-shift);
for (int id:targets){
whisper[id].push("whisper 114514 "+st);
}
msgrcd.push_back({"whispr",st,GetTime(),114514,targets});
rcdactv.notify_all();
}
}else if (st.substr(0,5)=="warn "){
stringstream str;
st=st.substr(5,(int)st.size()-5);
str<<st;
set<int> targets;
targets.clear();
int shift=0;
while (1) {
string nme;
str>>nme;
if (to_no(nme)!=-1&&st.substr(shift,(int)nme.size())==nme){
shift+=(int)nme.size()+1;
targets.insert(to_no(nme));
}else{
break;
}
}
if (!targets.size()){
rcd.success=0;
rcd.cont="未找到有效目标。";
}else{
rcd.success=1;
rcd.cont="成功发送对 ";
for (int id:targets){
rcd.cont+=name[id]+", ";
}
For(i,1,2) rcd.cont.pop_back();
rcd.cont+=" 的特别警告。";
st=st.substr(shift,(int)st.size()-shift);
for (int id:targets){
whisper[id].push("warn "+st);
}
msgrcd.push_back({"warnnn",st,GetTime(),114514,targets});
rcdactv.notify_all();
}
}else if (st=="help"){
string help="";
help+="C++ 机房聊天工具(C++Chat)服务端命令帮助\n\n";
help+="消息类型命令:\n";
help+="·broadcast <Content>:发送全局广播;\n";
help+="·whisper <Target_1> <Target_2> ... <Content>:发送私信(非目标不可见);\n";
help+="·warn <Target_1> <Target_2> ... <Content>:发送警告,并弹框提醒(非目标不可见)。\n\n";
help+="控制类型命令:\n";
help+="·ban <IP> <Time>:禁言指定 IP 对应的所有成员(包括生效过程中加入的成员)<Time> 分钟;\n";
help+="·unban <IP>:手动提前终止生效中的禁言;\n";
help+="·block <IP> <Time>:封禁指定 IP 对应的所有成员(封禁生效过程中无法发送消息或接收除系统消息和警告外的所有消息) <Time> 分钟;\n";
help+="·unblock <IP>:手动提前终止生效中的封禁;\n";
help+="·inhibit <IP>:将指定 IP 对应的所有成员踢出聊天室,同时禁止指定 IP 加入当前聊天室;\n";
help+="·allow <IP>:取消阻止指定 IP 加入当前聊天室;\n";
help+="·lock:锁定当前聊天室,使任何人不得加入当前聊天室(保留现有成员);\n";
help+="·unlock:解锁当前聊天室。\n\n";
help+="禁言、封禁命令会覆盖当前正在生效中的禁言、封禁效果。\n";
hints.push(help);
rcd.success=1;
rcd.cont="成功显示帮助。";
}else{
rcd.success=0;
rcd.cont="未找到命令或格式错误。";
}
cmdrcd.push_back(rcd);
cmdrcdactv.notify_all();
buf="";
bufactv.notify_all();
}
}
DWORD WINAPI ShowOnline(LPVOID param){
int tab[15]={0,15,15,10};
int pos[15]={0,102,118,134,145};
while (1) {
mutex locker;
unique_lock<mutex> lckk(locker);
onlineactv.wait(lckk);
lock_guard<mutex> lk2(outputlocker); // 这里一定要先锁output再锁var,避免双线程死锁
lock_guard<mutex> lk1(varlocker);
int ppl=(int)online.size();
++ppl;
DelContent(3,101,35,144);
gotoxy(3,101),Print("┌",purple);
gotoxy(3,144),Print("┐",purple);
gotoxy(3+ppl*2,101),Print("└",purple);
gotoxy(3+ppl*2,144),Print("┘",purple);
For(i, 1, ppl-1){
gotoxy(3+i*2,101),Print("├",purple);
gotoxy(3+i*2,144),Print("┤",purple);
}
For(j, 102, 143){
bool flag=0;
For(k, 2, 3) if (j==pos[k]-1) flag=1;
gotoxy(3,j);
if (flag) Print("┬",purple);
else Print("─",purple);
gotoxy(3+ppl*2,j);
if (flag) Print("┴",purple);
else Print("─",purple);
}
For(i, 1, ppl-1){
For(j, 102, 143){
gotoxy(3+i*2,j);
bool flag=0;
For(k, 2, 3) if (j==pos[k]-1) flag=1;
if (flag) Print("┼",purple);
else Print("─",purple);
}
}
For(i, 1, ppl){
For(j, 101, 144){
For(k, 1, 4) {
if (j==pos[k]-1) gotoxy(3+i*2-1,j),Print("│",purple);
}
}
}
gotoxy(4,pos[1]+5),Print("IP地址",red);
gotoxy(4,pos[2]+6),Print("昵称",red);
gotoxy(4,pos[3]+1),Print("上线时间",red);
int now=2;
for (int id:online){
string val=ip[id];
gotoxy(3+now*2-1,pos[1]+ceil((tab[1]-(int)val.size())*1.0/2));
Print(val,yellow);
val=name[id];
gotoxy(3+now*2-1,pos[2]+ceil((tab[2]-(int)val.size())*1.0/2));
Print(val,status[id]==0?cyan:status[id]==1?blue:gray);
val=onlinetime[id];
gotoxy(3+now*2-1,pos[3]+ceil((tab[3]-(int)val.size())*1.0/2));
Print(val,green);
++now;
}
}
}
DWORD WINAPI ShowCommandRecord(LPVOID param){
while (1) {
mutex locker;
unique_lock<mutex> lckk(locker);
cmdrcdactv.wait(lckk);
lock_guard<mutex> lk2(outputlocker); // 这里一定要先锁output再锁var,避免双线程死锁
lock_guard<mutex> lk1(varlocker);
DelContent(27,2,31,99);
For(i, max((int)cmdrcd.size()-5,0), (int)cmdrcd.size()-1){
gotoxy(i-max((int)cmdrcd.size()-5,0)+27,2);
if (cmdrcd[i].success) Print("[Success] ",green);
else Print("[Failed] ",red);
Print(cmdrcd[i].cont, yellow);
}
}
}
DWORD WINAPI DealMessage(LPVOID param){ // 分线程:处理接收的消息
while (1) {
if (!msgque.empty()){
varlocker.lock();
string stt=msgque.front();
msgque.pop();
varlocker.unlock();
stringstream str;str<<stt;
int cid;str>>cid;
string st;str>>st;
string sndmsg="";
Message rcdmsg={};
if (st=="global") {
sndmsg="global "+to_string(cid)+" ";
rcdmsg.type="global";
rcdmsg.sender=cid;
string cont=stt.substr((int)to_string(cid).size()+8,(int)stt.size()-(int)to_string(cid).size()-8);
rcdmsg.content=cont,sndmsg+=cont;
broadcast.push(sndmsg);
}else if (st=="whisper") {
sndmsg="whisper "+to_string(cid)+" ";
rcdmsg.type="whispr";
rcdmsg.sender=cid;
string cont=stt.substr((int)to_string(cid).size()+9,(int)stt.size()-(int)to_string(cid).size()-9);
stringstream strr;
strr<<cont;
int len=0;
while (1) {
int target;strr>>target;
len+=(int)to_string(target).size()+1;
if (!target) break;
rcdmsg.targets.insert(target);
}
cont=cont.substr(len,(int)cont.size()-len);
rcdmsg.content=cont;
sndmsg+=cont;
for (int i:rcdmsg.targets){
whisper[i].push(sndmsg);
}
}else if (st=="hint") {
sndmsg="hint "+to_string(cid)+" ";
rcdmsg.type="hinttt";
rcdmsg.sender=cid;
string cont=stt.substr((int)to_string(cid).size()+6,(int)stt.size()-(int)to_string(cid).size()-6);
sndmsg+=cont;
stringstream strr;
strr<<cont;
int len=0;
while (1) {
int target;strr>>target;
len+=(int)to_string(target).size()+1;
if (!target) break;
rcdmsg.targets.insert(target);
}
cont=cont.substr(len,(int)cont.size()-len);
rcdmsg.content=cont;
broadcast.push(sndmsg);
}
rcdmsg.time=GetTime();
varlocker.lock();
if (rcdmsg.type.size())msgrcd.push_back(rcdmsg);
varlocker.unlock();
rcdactv.notify_all(); // 有更新,解开锁
}
Sleep(10);
}
}
DWORD WINAPI ShowMessageRecord(LPVOID Param){ // 分线程:显示消息记录
while (1){
mutex locker;
unique_lock<mutex> lckk(locker);
rcdactv.wait(lckk);
outputlocker.lock();
DelContent(4,2,23,99);
gotoxy(4,2);
varlocker.lock();
For(i,max(0,(int)msgrcd.size()-DisplayNum)+rcdup,(int)msgrcd.size()+min(rcdup,0)-1){
Message noww=msgrcd[i];
Print(noww.time+" ",cyan);
if (noww.type=="system"){
Print("[System] "+noww.content,gray);
}else if (noww.type=="global"){
Print("[Global] ");
Print(name[noww.sender],light_blue);
Print(": "+noww.content);
}else if (noww.type=="whispr"){
Print("[",blue);
if (noww.sender==114514) Print("@ADMIN",red);
else Print(name[noww.sender],yellow);
Print(" -> ",green);
for (int i:noww.targets){
Print(name[i],cyan);
if (i!=(*noww.targets.rbegin())) Print(",",yellow);
}
Print("] ",blue);
Print(noww.content);
}else if (noww.type=="hinttt"){
Print("[Hint] ",yellow);
Print(name[noww.sender],light_blue);
Print(": ");
for (int i:noww.targets){
Print("@",yellow);
Print(name[i],cyan);
}
Print(" "+noww.content);
}else if (noww.type=="warnnn"){
Print("[",red);
Print("@ADMIN",red);
Print(" -> ",green);
for (int i:noww.targets){
Print(name[i],cyan);
if (i!=(*noww.targets.rbegin())) Print(",",yellow);
}
Print("] ",red);
Print(noww.content);
}else if (noww.type=="brdcst"){
Print("[Broadcast] ",cyan);
Print("@ADMIN",red);
Print(": "+noww.content);
}
nxtline();
}
gotoxy(24,16),Print(" ",cyan,gray);
gotoxy(24,16),Print(to_string(rcdup),cyan,gray);
varlocker.unlock();
outputlocker.unlock();
}
}
DWORD WINAPI FindClient(LPVOID Param){
sockaddr_in client;
int client_len=sizeof(client);
char rc[1005];
memset(rc,0,sizeof(rc));
while(1) {
sock[tot+1]=accept(server,(sockaddr FAR*)&client,&client_len); //来新人了
SOCKET sclient=sock[tot+1];
if(sclient==(SOCKET)(~0)) {
continue;
}
++tot;
Sleep(5);
recv(sclient,rc,sz,0);
varlocker.lock();
no[sclient]=tot; // 记录编号
int cid=no[sclient];
name[cid]=rc;
status[cid]=0;
onlinetime[cid]=GetTime();
ip[cid]=inet_ntoa(client.sin_addr);
while ((int)name[cid].find('\n')!=string::npos) name[cid].pop_back();
int cnt=0;
if (locked){
--tot;
varlocker.unlock();
Sleep(10);
send(sclient,"Locked\n",7,0);
goto End;
}
if (inhibitlist.count(ip[cid])){
--tot;
varlocker.unlock();
Sleep(10);
send(sclient,"Inhibited\n",10,0);
goto End;
}
if ((int)online.size()>=PeopleLimit) {
--tot;
varlocker.unlock();
Sleep(10);
send(sclient,"Full\n",5,0);
goto End;
}
for (int i:online){
if (name[i]==name[cid]){ // 昵称重复
--tot;
varlocker.unlock();
Sleep(10);
send(sclient,"Repeated\n",9,0);
goto End;
}
if (ip[i]==ip[cid]) ++cnt;
}
if (cnt>=MultipleJoin){
--tot;
varlocker.unlock();
Sleep(10);
send(sclient,"MultijoinLimitEx\n",17,0);
goto End;
}
online.insert(no[sclient]); // 添加到在线列表
msgrcd.push_back({"system","[IP: "+ip[cid]+"] [昵称: "+name[cid]+"] 加入了聊天室!",GetTime()}); //广播消息
broadcast.push("join "+to_string(tot)+" "+name[cid]);
for (int i:online){
if (i!=cid){
whisper[cid].push("member "+to_string(i)+" "+name[i]);
}
}
hrecv[cid]=CreateThread(NULL,0,ReceiveMessage,NULL,0,NULL); // 建立接收它消息的线程
varlocker.unlock();
onlineactv.notify_all();
rcdactv.notify_all();
Sleep(10);
End:;
}
}
DWORD WINAPI ShowBuffer(LPVOID Param){ // 分线程:显示输入内容
string com[30]={"","help","whisper ","warn ","broadcast ","ban ","block ","unban ","unblock ","lock","unlock","inhibit ","allow "};
int col[30]={0,cyan,blue,red,cyan,blue,gray,blue,gray,red,green,red,green};
int fillname[30]={0,2,3};
int fillip[30]={0,5,6,7,8,11,12};
int cnt=12,fcnt=2,ffcnt=6;
while (1){
mutex locker;
unique_lock<mutex> lck(locker);
sbufactv.wait(lck);
outputlocker.lock();
varlocker.lock();
cursorpos=cspos+12;
DelContent(34,12,34,79);
gotoxy(34,12);
string st=buf;
int msgtype=-1;
For(i,1,cnt){
if ((int)com[i].size()<=(int)st.size()&&st.substr(0,(int)com[i].size())==com[i]){
st=st.substr((int)com[i].size(),(int)st.size()-(int)com[i].size());
PrintVoid(com[i],cursorpos,col[i]);
msgtype=i;
break;
}
}
For(i,1,fcnt){
if (msgtype==fillname[i]) {
string stt=st,sub="";
while (stt.size()) {
if (stt[0]==' '){
if (to_no(sub)==-1) break;
PrintVoid(sub+" ",cursorpos,cyan);
sub="";
}else sub+=stt[0];
stt=stt.substr(1,(int)stt.size()-1);
}
st=sub+stt;
break;
}
}
For(i,1,ffcnt){
if (msgtype==fillip[i]) {
string stt=st;
int tim=0;
For(i,0,(int)stt.size()-1) {
if (stt[i]==' '){
stt=stt.substr(0,i);
break;
}
}
if (isIP(stt)){
PrintVoid(stt,cursorpos,yellow);
st=st.substr((int)stt.size(),(int)st.size()-(int)stt.size());
}
break;
}
}
PrintVoid(st,cursorpos);
if (cspos&&unfilled.size()&&cspos==(int)buf.size()) PrintVoid(unfilled,cursorpos,gray);
varlocker.unlock();
outputlocker.unlock();
cursoractv.notify_all();
}
}
void MainInterface(){
SetSize(CharSize-=1);
system("mode con cols=145 lines=35"); // 设置窗口大小
CreateThread(NULL,0,wSetSize,NULL,0,NULL); //开调节大小线程
CreateThread(NULL,0,whide,NULL,0,NULL); //开老板键线程
string ip=GetIP(),id="";
while (ip[(int)ip.size()-1]!='.') id.push_back(*ip.rbegin()),ip.pop_back();
reverse(id.begin(),id.end());
Configure:
system("cls");
Print("这是机房聊天工具的服务端,请设置端口号。\n");
Print("IP地址:"+GetIP()+" "+"编号:"+id+"\n",cyan);
Print("端口号:");
ShowConsoleCursor();
cin>>port;
HideConsoleCursor();
system("cls");
ApplyRule();
system("cls");
if (!OpenServer()){
system("pause");
goto Configure;
}
// 打印初始界面
HideConsoleCursor();
Print("机房聊天工具(C++Chat):管理界面\n",red);
Print("聊天室:"+GetIP()+":"+to_string(port)+"\n",cyan);
Print("┌──────────────────────────────────────────────────────────────────────────────────────────────────┐\n",blue);
For(i, 4,24) gotoxy(i, 1),Print("│",blue),gotoxy(i,100),Print("│",blue);
gotoxy(24,2),Print("记录显示上移:",yellow,gray);
gotoxy(25,1);
Print("└──────────────────────────────────────────────────────────────────────────────────────────────────┘\n",blue);
Print("┌──────────────────────────────────────────────────────────────────────────────────────────────────┐\n",green);
For(i, 27,31) gotoxy(i, 1),Print("│",green),gotoxy(i,100),Print("│",green);
gotoxy(32,1);
Print("└──────────────────────────────────────────────────────────────────────────────────────────────────┘\n",green);
Print("┌──────────────────────────────────────────────────────────────────────────────────────────────────┐\n",gray);
Print("│",gray);Print("[Command] ",yellow);
gotoxy(33,80),Print("┬",gray);
gotoxy(34,80),Print("│",gray),Print(" help",cyan),Print(" 查看帮助",yellow);
gotoxy(34,100),Print("│\n",gray);
Print("└──────────────────────────────────────────────────────────────────────────────────────────────────┘",gray);
gotoxy(35,80),Print("┴",gray);
cursorline=34,cursorpos=12;
// 开启线程
CreateThread(NULL,0,AutoUnban,NULL,0,NULL);
CreateThread(NULL,0,ShowCommandRecord,NULL,0,NULL);
CreateThread(NULL,0,DealBuf,NULL,0,NULL);
CreateThread(NULL,0,FindClient,NULL,0,NULL);
CreateThread(NULL,0,SendMessage,NULL,0,NULL);
CreateThread(NULL,0,DealMessage,NULL,0,NULL);
CreateThread(NULL,0,EditBuf,NULL,0,NULL);
CreateThread(NULL,0,ShowMessageRecord,NULL,0,NULL);
CreateThread(NULL,0,PopHint,NULL,0,NULL);
CreateThread(NULL,0,ShowOnline,NULL,0,NULL);
CreateThread(NULL,0,AutoFill,NULL,0,NULL);
CreateThread(NULL,0,ShowCursor,NULL,0,NULL);
CreateThread(NULL,0,FlashingCursor,NULL,0,NULL);
CreateThread(NULL,0,AutoFillIP,NULL,0,NULL);
CreateThread(NULL,0,ShowBuffer,NULL,0,NULL);
Sleep(10);
onlineactv.notify_all();
rcdactv.notify_all();
while (1) Sleep(10);
}
}
DWORD WINAPI ShowExample(LPVOID Param){ // 初始界面显示示例
changelocker.lock();
gotoxy(7,53),Print("示例",purple);
gotoxy(21,34),Print("[Global] "),Print("William",yellow),Print(": ");
SlowPrint("How's it going today? ","│",60,white,cyan);
DelContent(21,52,21,80);
gotoxy(9,34),Print("[Global] "),Print("William",yellow),Print(": How's it going today?");
gotoxy(21,52),Print("│",cyan);
changelocker.unlock();
Sleep(1500);
changelocker.lock();
gotoxy(10,34),Print("[Whisper] ",blue),Print("James",blue),Print(": Everything goes well. Thanks.");
changelocker.unlock();
Sleep(1500);
changelocker.lock();
gotoxy(11,34),Print("[Whisper] ",blue),Print("Jack",blue),Print(": Fine, Thank you.");
changelocker.unlock();
Sleep(1500);
changelocker.lock();
gotoxy(12,34),Print("[Whisper] ",blue),Print("James",blue),Print(": I'm excited to use C++Chat!");
changelocker.unlock();
Sleep(1500);
changelocker.lock();
gotoxy(13,34),Print("[Whisper] ",blue),Print("Jack",blue),Print(": I heard it has many functions!");
changelocker.unlock();
Sleep(1300);
changelocker.lock();
gotoxy(21,52),SlowPrint("@James&Jack ","│",80,white,cyan);
gotoxy(21,34),Print("[@James&Jack] ",blue);
DelContent(21,57,21,80);
gotoxy(21,57),Print("│",cyan);
gotoxy(21,48),Print("William",yellow),Print(": ");
changelocker.unlock();
Sleep(60);
changelocker.lock();
gotoxy(21,57),SlowPrint("Yeah, it's really GOOD!","│",60,white,cyan);
DelContent(21,34,21,80);
gotoxy(21,52),Print("│",cyan);
gotoxy(21,34),Print("[Global] "),Print("William",yellow),Print(": ");
gotoxy(14,34),Print("[",blue),Print("William",yellow),Print(" -> ",green),Print("James",cyan),
Print(",",yellow),Print("Jack",cyan),Print("] ",blue);
Print("Yeah, It's really GOOD!");
changelocker.unlock();
Sleep(1500);
changelocker.lock();
gotoxy(15,34),Print("[Global] "),Print("James",blue),Print(": Wow, let's get started!");
changelocker.unlock();
Sleep(1000);
changelocker.lock();
gotoxy(21,52),SlowPrint("I can't help doing this! ","│",70,white,cyan);
DelContent(21,52,21,80);
gotoxy(21,52),Print("│",cyan);
gotoxy(16,34),Print("[Global] "),Print("William",yellow),Print(": I can't help doing this!");
changelocker.unlock();
}
string interf[]={"",
"进入客户端",
"进入服务端",
" 操作指北",
" 查看博客",
" 退出程序"
};
int sline=15,pos=1;
int main(){
CancelQuickEditMode();
DisableResizing();
DisableFullScreen();
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof cfi;
cfi.nFont = 0;
cfi.dwFontSize.X = 15;
cfi.dwFontSize.Y = 30;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_NORMAL;
wcscpy(cfi.FaceName, L"SimHei");
SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
HideConsoleCursor();
CreateThread(NULL,0,wtop,NULL,0,NULL); //开置顶线程
CreateThread(NULL,0,wtopp,NULL,0,NULL); //开连续置顶线程
bool upd=1;
while (1) {
HANDLE th;
if (upd) {
upd=0;
system("mode con cols=80 lines=25"); // 设置窗口大小
SetSize(23);
system("cls");
gotoxy(2,27);Print("C++ 机房聊天工具(C++Chat)",cyan);
For(i, 5, 22) gotoxy(i, 30),Print("│",blue);
gotoxy(7,16);Print("选项",green);
For(i, 1, 5) gotoxy(i*2+7,13),Print(interf[i],pos==i?yellow:white);
gotoxy(pos*2+7,10),Print(">",cyan);
gotoxy(20,10),Print("按 W、S 选择项目",cyan);
gotoxy(21,12),Print("按空格键进入",yellow);
For(i, 33, 76) gotoxy(20,i),Print("─",gray);
th=CreateThread(NULL,0,ShowExample,NULL,0,NULL);
}
if (kbhit()) {
char ch = getch();
if ((ch=='s'||ch==80)&&pos<5) {
changelocker.lock();
DelContent(pos*2+7,10,pos*2+9,23);
gotoxy(pos*2+7,13),Print(interf[pos]);
gotoxy(pos*2+9,13),Print(interf[pos+1],yellow);
gotoxy((++pos)*2+7,10),Print(">",cyan);
changelocker.unlock();
}
if ((ch=='w'||ch==72)&&pos>1) {
changelocker.lock();
DelContent(pos*2+5,10,pos*2+7,23);
gotoxy(pos*2+7,13),Print(interf[pos]);
gotoxy(pos*2+5,13),Print(interf[pos-1],yellow);
gotoxy((--pos)*2+7,10),Print(">",cyan);
changelocker.unlock();
}
if (ch == ' ') {
if (pos==1){
changelocker.lock();
TerminateThread(th,0);
changelocker.unlock();
Client::MainInterface();
upd=1;
}else if (pos==2){
changelocker.lock();
TerminateThread(th,0);
changelocker.unlock();
Server::MainInterface();
upd=1;
}else if (pos==3){
changelocker.lock();
TerminateThread(th,0);
changelocker.unlock();
SetSize(22);
system("mode con cols=83 lines=35"); // 设置窗口大小
Print("C++ 机房聊天工具(C++Chat)操作指北\n\n",cyan);
Print("快捷键\n",red,light_yellow);
Print("老板键:客户端 "),Print("Alt+Q",green),Print(",服务端 "),Print("Shift+Z",green);Print("。\n");
Print("老板键功能:按一次隐藏窗口,同时特别提醒弹窗内容变为\"Hint\";再按一次恢复原状。\n\n");
Print("窗口置顶(申请一次):"),Print("Ctrl+T",green);Print("。\n");
Print("窗口置顶(不断申请,用于极域控屏等):"),Print("Ctrl+J",green);Print("。\n\n");
Print("调大字体(窗口大小同步变化,字体大小上限为 40):"),Print("Ctrl+'+'",green);Print(";\n");
Print("调小字体(窗口大小同步变化,字体大小下限为 5):"),Print("Ctrl+'-'",green);Print("。\n\n");
Print("滚动消息记录:"),Print("鼠标滚轮",purple),Print("。\n\n");
Print("工具功能\n",green,light_yellow);
Print("在客户端里,默认发送消息为全局(Global),所有人均可以看到;\n");
Print("在客户端里,使用 "),Print("@name1&name2&name3&... Content",cyan),Print(" 的格式发送私信("),Print("Whisper",blue),Print("),只有被指定的目标才能看到;\n");
Print("在客户端里,使用 "),Print("@@name1&name2&name3&... Content",cyan),Print(" 的格式发送特别提醒("),Print("Hint",yellow),Print(")");Print(",所有人均可以看到,且被指定的人会被弹窗提醒。\n");
Print("在服务端里,可在命令行中输入相关命令,实现禁言 IP、封禁 IP、禁止特定 IP 加入聊天室、锁定聊天室、发送公告("),Print("Broadcast",cyan),Print(")、发送私信("),Print("Whisper",blue),Print(")、发送特别警告("),Print("Warning",red),Print(")等功能。\n");
Print("在服务端里,输入 "),Print("help",cyan),Print(" 并回车以查看命令帮助。\n\n");
Print("按 ",yellow),Print("Enter",green),Print(" 发送消息或执行命令。\n",yellow);
Print("按 ",yellow),Print("Tab",green),Print(" 可自动补全目标名称或命令名称。\n\n",yellow);
Print("请不要滥用上述功能或擅自修改代码,否则对于客户端可能会被服务端予以禁言、封禁等惩罚(惩罚针对 IP,理论上惩罚无法通过修改客户端代码直接解除)。\n\n",red);
system("pause");
upd=1;
}else if (pos==4){
system("start https://www.csdn.net/");
}else if (pos==5){
changelocker.lock();
TerminateThread(th,0);
changelocker.unlock();
system("cls");
system("mode con cols=50 lines=20"); // 设置窗口大小
SetSize(23);
Print("本工具作者:@linziyang1。\n",cyan);
Print("感谢 @yyf525 C++ chat 的灵感(虽然此篇博客的代码是我给他的,本代码也并未参考此篇博客)。\n",gray);
Print("创作不易,如有不妥,敬请斧正!\n",yellow);
Print("(也别忘了一键三连,感谢支持)\n\n",cyan);
system("pause");
exit(0);
}
}
}
Sleep(1);
}
return 0;
}
感谢阅读,祝大家游玩愉快!
更多推荐


所有评论(0)