-
有个固定店面(IP地址和端口) -
每天准时开门营业(一直运行) -
负责帮客人传话(转发消息) -
同时能招呼好多客人(多用户支持)
-
得知道咖啡厅在哪儿(连接服务器) -
可以点单聊天(发送消息) -
也能听别人聊天(接收消息) -
想走随时能走(断开连接)
一次完整的聊天,就是信息在“寄件人 → 邮局 → 收件人”这个路径上成功走了一圈。
第一步,完成服务端的搭建
#define _CRT_SECURE_NO_WARNINGS#define _WINSOCK_DEPRECATED_NO_WARNINGS#define WIN32_LEAN_AND_MEAN#include <winsock2.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <windows.h>#include <locale.h>#pragma comment(lib, "ws2_32.lib")#define PORT 8080#define BUFFER_SIZE 1024#define MAX_CLIENTS 10// 彻底解决中文编码问题void SetupConsole() { // 使用GBK编码(中文Windows默认) SetConsoleOutputCP(936); SetConsoleCP(936); setlocale(LC_ALL, "chs");}int main() { SetupConsole(); WSADATA wsaData; SOCKET server_socket, client_socket; struct sockaddr_in server_addr, client_addr; int client_addr_len = sizeof(client_addr); char buffer[BUFFER_SIZE]; fd_set read_fds; SOCKET client_sockets[MAX_CLIENTS] = { 0 }; char client_names[MAX_CLIENTS][50] = { 0 }; int client_count = 0; printf("=== 聊天服务器 ===\n"); printf("正在初始化...\n"); // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup失败: %d\n", WSAGetLastError()); return 1; } // 创建服务器socket server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == INVALID_SOCKET) { printf("创建socket失败: %d\n", WSAGetLastError()); WSACleanup(); return 1; } // 设置服务器地址 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); // 绑定socket if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) { printf("绑定失败: %d\n", WSAGetLastError()); closesocket(server_socket); WSACleanup(); return 1; } // 开始监听 if (listen(server_socket, MAX_CLIENTS) == SOCKET_ERROR) { printf("监听失败: %d\n", WSAGetLastError()); closesocket(server_socket); WSACleanup(); return 1; } printf("服务器启动成功,监听端口: %d\n", PORT); printf("等待客户端连接...\n\n"); // 初始化客户端socket数组 for (int i = 0; i < MAX_CLIENTS; i++) { client_sockets[i] = INVALID_SOCKET; } // 主循环 while (1) { // 清空socket集合 FD_ZERO(&read_fds); // 添加服务器socket FD_SET(server_socket, &read_fds); // 添加客户端sockets for (int i = 0; i < MAX_CLIENTS; i++) { if (client_sockets[i] != INVALID_SOCKET) { FD_SET(client_sockets[i], &read_fds); } } // 设置超时时间 struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; // 等待socket活动 int activity = select(0, &read_fds, NULL, NULL, &timeout); if (activity == SOCKET_ERROR) { printf("select错误: %d\n", WSAGetLastError()); break; } // 检查新连接 if (FD_ISSET(server_socket, &read_fds)) { client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len); if (client_socket == INVALID_SOCKET) { printf("接受连接失败: %d\n", WSAGetLastError()); continue; } // 查找空闲位置存放新客户端 int client_index = -1; for (int i = 0; i < MAX_CLIENTS; i++) { if (client_sockets[i] == INVALID_SOCKET) { client_sockets[i] = client_socket; client_index = i; client_count++; break; } } if (client_index == -1) { printf("服务器已满,拒绝新连接\n"); const char* msg = "服务器已满,请稍后重试\n"; send(client_socket, msg, (int)strlen(msg), 0); closesocket(client_socket); continue; } // 接收客户端用户名 int name_len = recv(client_socket, client_names[client_index], sizeof(client_names[client_index]) - 1, 0); if (name_len > 0) { client_names[client_index][name_len] = '\0'; // 尝试修复中文用户名乱码 // 如果用户名看起来是乱码,尝试转换编码 int has_chinese = 0; for (int j = 0; j < name_len; j++) { if ((unsigned char)client_names[client_index][j] > 127) { has_chinese = 1; break; } } if (has_chinese) { // 简单处理:直接使用接收到的数据,假设客户端和服务端编码一致 // 在实际应用中,这里应该进行编码转换 } } else { strcpy_s(client_names[client_index], sizeof(client_names[client_index]), "匿名用户"); } printf("新客户端连接: %s (IP: %s)\n", client_names[client_index], inet_ntoa(client_addr.sin_addr)); // 发送欢迎消息 char welcome_msg[BUFFER_SIZE]; _snprintf_s(welcome_msg, sizeof(welcome_msg), _TRUNCATE, "欢迎 %s 加入聊天室!当前在线用户: %d\n", client_names[client_index], client_count); send(client_socket, welcome_msg, (int)strlen(welcome_msg), 0); // 广播新用户加入消息 char join_msg[BUFFER_SIZE]; _snprintf_s(join_msg, sizeof(join_msg), _TRUNCATE, "系统: %s 加入了聊天室\n", client_names[client_index]); for (int i = 0; i < MAX_CLIENTS; i++) { if (client_sockets[i] != INVALID_SOCKET && i != client_index) { send(client_sockets[i], join_msg, (int)strlen(join_msg), 0); } } } // 检查客户端消息 for (int i = 0; i < MAX_CLIENTS; i++) { if (client_sockets[i] == INVALID_SOCKET) continue; if (FD_ISSET(client_sockets[i], &read_fds)) { int bytes_received = recv(client_sockets[i], buffer, BUFFER_SIZE - 1, 0); if (bytes_received <= 0) { // 客户端断开连接 printf("客户端断开: %s\n", client_names[i]); // 广播离开消息 char leave_msg[BUFFER_SIZE]; _snprintf_s(leave_msg, sizeof(leave_msg), _TRUNCATE, "系统: %s 离开了聊天室\n", client_names[i]); for (int j = 0; j < MAX_CLIENTS; j++) { if (client_sockets[j] != INVALID_SOCKET && j != i) { send(client_sockets[j], leave_msg, (int)strlen(leave_msg), 0); } } closesocket(client_sockets[i]); client_sockets[i] = INVALID_SOCKET; client_count--; continue; } // 处理收到的消息 buffer[bytes_received] = '\0'; // 移除换行符 if (bytes_received > 0 && buffer[bytes_received - 1] == '\n') { buffer[bytes_received - 1] = '\0'; } // 显示消息 printf("%s: %s\n", client_names[i], buffer); // 检查退出命令 if (strcmp(buffer, "quit") == 0 || strcmp(buffer, "exit") == 0) { const char* goodbye = "再见!\n"; send(client_sockets[i], goodbye, (int)strlen(goodbye), 0); closesocket(client_sockets[i]); client_sockets[i] = INVALID_SOCKET; client_count--; continue; } // 广播消息给所有其他客户端 char broadcast_msg[BUFFER_SIZE + 100]; _snprintf_s(broadcast_msg, sizeof(broadcast_msg), _TRUNCATE, "%s: %s\n", client_names[i], buffer); for (int j = 0; j < MAX_CLIENTS; j++) { if (client_sockets[j] != INVALID_SOCKET && j != i) { send(client_sockets[j], broadcast_msg, (int)strlen(broadcast_msg), 0); } } } } // 减少CPU占用 Sleep(10); } // 清理资源 for (int i = 0; i < MAX_CLIENTS; i++) { if (client_sockets[i] != INVALID_SOCKET) { closesocket(client_sockets[i]); } } closesocket(server_socket); WSACleanup(); printf("服务器已关闭\n"); return 0;}
重点:这个窗口千万别关!它就是你聊天室的“场地”。
#define _CRT_SECURE_NO_WARNINGS#define _WINSOCK_DEPRECATED_NO_WARNINGS#define WIN32_LEAN_AND_MEAN#include <winsock2.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <conio.h>#include <windows.h>#include <process.h>#include <locale.h>#pragma comment(lib, "ws2_32.lib")#define PORT 8080#define BUFFER_SIZE 1024// 彻底解决中文编码问题void SetupConsole() { // 使用GBK编码(中文Windows默认) SetConsoleOutputCP(936); SetConsoleCP(936); setlocale(LC_ALL, "chs");}// 接收消息的线程函数unsigned __stdcall ReceiveThread(void* socket_ptr) { SOCKET client_socket = *(SOCKET*)socket_ptr; char buffer[BUFFER_SIZE]; while (1) { int bytes_received = recv(client_socket, buffer, sizeof(buffer) - 1, 0); if (bytes_received > 0) { buffer[bytes_received] = '\0'; printf("%s", buffer); printf("> "); fflush(stdout); } else if (bytes_received == 0) { printf("\n服务器连接已断开\n"); break; } else { int error = WSAGetLastError(); if (error == WSAECONNRESET) { printf("\n连接被服务器重置\n"); break; } else if (error != WSAEWOULDBLOCK) { printf("\n接收错误: %d\n", error); break; } } Sleep(100); } return 0;}// 获取控制台输入,处理中文int GetConsoleInput(char* buffer, int buffer_size) { if (fgets(buffer, buffer_size, stdin) == NULL) { return 0; } // 移除换行符 int len = (int)strlen(buffer); if (len > 0 && buffer[len - 1] == '\n') { buffer[len - 1] = '\0'; len--; } return len;}int main() { SetupConsole(); WSADATA wsaData; SOCKET client_socket; struct sockaddr_in server_addr; char buffer[BUFFER_SIZE]; char username[50]; HANDLE receive_thread; printf("=== 聊天客户端 ===\n\n"); // 获取用户名 - 使用专门的函数处理 printf("请输入您的用户名: "); fflush(stdout); int username_len = GetConsoleInput(username, sizeof(username)); // 默认用户名 if (username_len == 0) { strcpy_s(username, sizeof(username), "匿名用户"); username_len = (int)strlen(username); } printf("正在连接到服务器...\n"); // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup失败: %d\n", WSAGetLastError()); return 1; } // 创建客户端socket client_socket = socket(AF_INET, SOCK_STREAM, 0); if (client_socket == INVALID_SOCKET) { printf("创建socket失败: %d\n", WSAGetLastError()); WSACleanup(); return 1; } // 设置服务器地址 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本地服务器 // 连接到服务器 if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) { printf("连接服务器失败: %d\n", WSAGetLastError()); printf("请确保服务器正在运行\n"); closesocket(client_socket); WSACleanup(); return 1; } // 立即发送用户名到服务器 - 这是关键修复 // 在连接建立后立即发送用户名,避免与其他数据混淆 send(client_socket, username, (int)strlen(username), 0); printf("连接成功!\n"); printf("输入消息开始聊天,输入 'quit' 退出\n\n"); // 设置socket为非阻塞模式 u_long mode = 1; ioctlsocket(client_socket, FIONBIO, &mode); // 创建接收线程 receive_thread = (HANDLE)_beginthreadex(NULL, 0, ReceiveThread, &client_socket, 0, NULL); if (receive_thread == NULL) { printf("创建接收线程失败\n"); closesocket(client_socket); WSACleanup(); return 1; } // 主循环 - 发送消息 while (1) { printf("> "); fflush(stdout); int input_len = GetConsoleInput(buffer, sizeof(buffer)); if (input_len == 0) { continue; } // 检查退出命令 if (strcmp(buffer, "quit") == 0 || strcmp(buffer, "exit") == 0) { send(client_socket, buffer, (int)strlen(buffer), 0); break; } // 发送消息 if (send(client_socket, buffer, (int)strlen(buffer), 0) == SOCKET_ERROR) { printf("发送失败: %d\n", WSAGetLastError()); break; } } // 清理资源 WaitForSingleObject(receive_thread, 2000); CloseHandle(receive_thread); closesocket(client_socket); WSACleanup(); printf("客户端已退出\n"); return 0;}
本文链接:https://kinber.cn/post/5740.html 转载需授权!
推荐本站淘宝优惠价购买喜欢的宝贝:

支付宝微信扫一扫,打赏作者吧~
