include <iostream>
#include <winsock2.h>
#include <thread>
#include <mutex>
#include <vector>
#include <string>
#include <conio.h> // 按键检测(Windows专用)
using namespace std;
#pragma comment(lib, "ws2_32.lib")
const int PORT = 12345;
const char* SERVER_IP = "127.0.0.1"; // 本地测试,局域网需改实际IP
struct Message { // 与服务器一致的消息结构
int type; // 0:公聊 1:私聊 2:上线 3:离线 4:用户列表
char sender[64];
char receiver[64];
char content[256];
};
vector<string> onlineUsers; // 在线用户列表(排除自己)
string myNickname; // 自己的昵称
bool inPrivateChat = false; // 是否在私聊模式
string privateTarget; // 私聊对象
mutex mtx; // 保护onlineUsers的锁
// 发送消息到服务器
bool sendMsg(SOCKET sock, const Message& msg) {
int sent = send(sock, (const char*)&msg, sizeof(msg), 0);
return sent == sizeof(msg);
}
// 接收线程:处理服务器消息
void recvThread(SOCKET clientSock) {
Message msg;
while (true) {
int recvLen = recv(clientSock, (char*)&msg, sizeof(msg), 0);
if (recvLen <= 0) { // 服务器断开
cout << "\n服务器连接中断!" << endl;
closesocket(clientSock);
exit(0);
}
lock_guard<mutex> lock(mtx);
switch (msg.type) {
case 0: // 公聊消息
cout << "\n" << msg.sender << ": " << msg.content << endl;
break;
case 1: // 私聊消息
cout << "\n[私聊][" << msg.sender << "]: " << msg.content << endl;
break;
case 2: // 他人上线
onlineUsers.push_back(msg.sender);
cout << "\n" << msg.sender << " 上线了!" << endl;
break;
case 3: // 他人离线
for (auto it = onlineUsers.begin(); it != onlineUsers.end(); ++it) {
if (*it == msg.sender) {
onlineUsers.erase(it);
break;
}
}
cout << "\n" << msg.sender << " 离线了!" << endl;
break;
case 4: // 在线用户列表
onlineUsers.clear();
char* p = msg.content;
while (*p) {
onlineUsers.push_back(p);
p += strlen(p) + 1; // 跳过换行符
}
// 移除自己的昵称
for (auto it = onlineUsers.begin(); it != onlineUsers.end(); ++it) {
if (*it == myNickname) {
onlineUsers.erase(it);
break;
}
}
break;
}
// 提示当前模式
if (inPrivateChat) {
cout << "[私聊模式:" << privateTarget << "] 输入消息(ESC退出):";
} else {
cout << "[公聊模式] 输入消息(Tab选私聊对象):";
}
cout.flush();
}
}
int main() {
// 1. 输入昵称
cout << "请输入你的昵称:";
cin >> myNickname;
// 2. 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cerr << "WSAStartup失败: " << WSAGetLastError() << endl;
return 1;
}
// 3. 创建套接字并连接服务器
SOCKET clientSock = socket(AF_INET, SOCK_STREAM, 0);
if (clientSock == INVALID_SOCKET) {
cerr << "socket创建失败: " << WSAGetLastError() << endl;
WSACleanup();
return 1;
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
serverAddr.sin_port = htons(PORT);
if (connect(clientSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
cerr << "连接服务器失败: " << WSAGetLastError() << endl;
closesocket(clientSock);
WSACleanup();
return 1;
}
// 4. 发送上线消息(告知服务器昵称)
Message onlineMsg;
onlineMsg.type = 2;
strcpy(onlineMsg.sender, myNickname.c_str());
sendMsg(clientSock, onlineMsg);
// 5. 启动接收线程
thread recvThrd(recvThread, clientSock);
recvThrd.detach();
// 6. 处理用户输入(公聊/私聊切换)
cout << "\n连接成功!进入聊天室:" << endl;
cout << "[公聊模式] 输入消息(Tab选私聊对象,ESC退出私聊):";
while (true) {
if (_kbhit()) { // 检测按键
int key = _getch(); // 读取按键(不回显)
if (key == 9) { // Tab键:进入私聊模式,切换目标
if (!onlineUsers.empty() && !inPrivateChat) {
static int index = 0;
privateTarget = onlineUsers[index];
index = (index + 1) % onlineUsers.size(); // 循环切换
inPrivateChat = true;
cout << "\r[私聊模式:" << privateTarget << "] 输入消息(ESC退出):";
}
} else if (key == 27) { // ESC键:退出私聊模式
inPrivateChat = false;
privateTarget.clear();
cout << "\r[公聊模式] 输入消息(Tab选私聊对象):";
} else { // 普通字符:继续输入消息
_putch(key); // 回显按键
string content;
getline(cin, content); // 读取完整行
if (content.empty()) continue;
Message msg;
if (inPrivateChat) { // 私聊消息
msg.type = 1;
strcpy(msg.sender, myNickname.c_str());
strcpy(msg.receiver, privateTarget.c_str());
strcpy(msg.content, content.c_str());
} else { // 公聊消息
msg.type = 0;
strcpy(msg.sender, myNickname.c_str());
strcpy(msg.content, content.c_str());
}
sendMsg(clientSock, msg);
}
}
}
closesocket(clientSock);
WSACleanup();
return 0;
}
这个是client
#include <iostream>
#include <winsock2.h>
#include <thread>
#include <mutex>
#include <vector>
#include <string>
using namespace std;
#pragma comment(lib, "ws2_32.lib") // 强制链接Winsock库
const int PORT = 12345;
const int BUFFER_SIZE = 1024;
// 消息结构(协议)
struct Message {
int type; // 0:公聊 1:私聊 2:上线 3:离线 4:用户列表
char sender[64];
char receiver[64];
char content[256];
};
// 客户端信息
struct Client {
SOCKET sock;
string nickname;
bool online;
};
vector<Client> clients; // 在线客户端列表
mutex mtx; // 线程安全锁
// 发送消息到指定套接字
bool sendMsg(SOCKET sock, const Message& msg) {
int sent = send(sock, (const char*)&msg, sizeof(msg), 0);
return sent == sizeof(msg);
}
// 广播消息(可排除发送者)
void broadcast(const Message& msg, SOCKET excludeSock = INVALID_SOCKET) {
lock_guard<mutex> lock(mtx);
for (auto& client : clients) {
if (client.online && client.sock != excludeSock) {
sendMsg(client.sock, msg);
}
}
}
// 发送在线用户列表给指定客户端
void sendUserList(SOCKET sock) {
Message msg;
msg.type = 4;
lock_guard<mutex> lock(mtx);
string list;
for (const auto& client : clients) {
if (client.online) list += client.nickname + "\n";
}
strcpy(msg.content, list.c_str());
sendMsg(sock, msg);
}
// 处理单个客户端的线程函数
void handleClient(SOCKET clientSock) {
Message msg;
Client newClient;
newClient.sock = clientSock;
newClient.online = true;
// 1. 接收客户端昵称(上线消息)
int recvLen = recv(clientSock, (char*)&msg, sizeof(msg), 0);
if (recvLen <= 0 || msg.type != 2) {
closesocket(clientSock);
return;
}
newClient.nickname = msg.sender;
// 2. 添加到在线列表,广播上线通知
{
lock_guard<mutex> lock(mtx);
clients.push_back(newClient);
}
Message onlineMsg;
onlineMsg.type = 2;
strcpy(onlineMsg.sender, newClient.nickname.c_str());
strcpy(onlineMsg.content, "已上线");
broadcast(onlineMsg);
// 3. 发送当前用户列表给新客户端
sendUserList(clientSock);
// 4. 循环处理消息
while (true) {
recvLen = recv(clientSock, (char*)&msg, sizeof(msg), 0);
if (recvLen <= 0) { // 客户端断开
// 广播离线通知
Message offlineMsg;
offlineMsg.type = 3;
strcpy(offlineMsg.sender, newClient.nickname.c_str());
strcpy(offlineMsg.content, "已离线");
broadcast(offlineMsg);
// 标记为离线
lock_guard<mutex> lock(mtx);
for (auto& c : clients) {
if (c.sock == clientSock) {
c.online = false;
break;
}
}
break;
}
// 处理消息类型
if (msg.type == 0) { // 公聊:广播给所有人
broadcast(msg, clientSock);
} else if (msg.type == 1) { // 私聊:转发给目标用户
lock_guard<mutex> lock(mtx);
for (auto& c : clients) {
if (c.online && c.nickname == msg.receiver) {
sendMsg(c.sock, msg);
break;
}
}
}
}
closesocket(clientSock);
}
int main() {
// 初始化Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cerr << "WSAStartup失败: " << WSAGetLastError() << endl;
return 1;
}
// 创建监听套接字
SOCKET listenSock = socket(AF_INET, SOCK_STREAM, 0);
if (listenSock == INVALID_SOCKET) {
cerr << "socket创建失败: " << WSAGetLastError() << endl;
WSACleanup();
return 1;
}
// 绑定端口
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有IP
serverAddr.sin_port = htons(PORT);
if (bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
cerr << "bind失败: " << WSAGetLastError() << endl;
closesocket(listenSock);
WSACleanup();
return 1;
}
// 开始监听
if (listen(listenSock, 5) == SOCKET_ERROR) {
cerr << "listen失败: " << WSAGetLastError() << endl;
closesocket(listenSock);
WSACleanup();
return 1;
}
cout << "服务器启动,监听端口 " << PORT << " ..." << endl;
// 接受客户端连接(循环)
while (true) {
sockaddr_in clientAddr;
int clientAddrLen = sizeof(clientAddr);
SOCKET clientSock = accept(listenSock, (sockaddr*)&clientAddr, &clientAddrLen);
if (clientSock == INVALID_SOCKET) {
cerr << "accept失败: " << WSAGetLastError() << endl;
continue;
}
// 启动线程处理客户端
thread clientThread(handleClient, clientSock);
clientThread.detach(); // 分离线程(自动回收)
}
closesocket(listenSock);
WSACleanup();
return 0;
}
这个是Server
为什么料天室中输入的消息的第一个必须要是符号,比如我打C太好了,别人只能看到太好了,以及打太厉害,我只能输出一个奇怪的符号加上厉害,别人也看不到这个奇怪的符号
私聊也有问题,我画出圈的地方到底是在和谁私聊,为什么谁也收不到这些消息
第三四张图中为什么我一直尝试用A去私聊B,但无论如何B都收不到呢
请你分析一下原因,之后输出改进代码