基于 C++ 的多人聊天室实现

   在现代网络编程中,实现一个聊天室是一个经典的项目。通过这个项目,我们可以学习到网络编程的基础知识,包括 Socket 编程、TCP/IP 协议、多线程处理等。本文将详细介绍如何使用 C++ 和 Winsock 库实现一个简单的聊天室。

1. 项目概述

功能需求

  • 支持客户端连接到服务器。

  • 客户端可以发送消息到服务器,服务器将消息广播给所有连接的客户端。

  • 客户端可以随时加入或退出聊天室。

技术栈

  • C++:核心编程语言。

  • Winsock:Windows 下的网络编程库。

  • TCP/IP:用于客户端和服务器之间的通信。

  • 多线程:处理多个客户端的并发连接。


2. 实现步骤

2.1 初始化 Winsock

Winsock 是 Windows 下的网络编程库,我们需要先初始化它。

#include <WinSock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <iostream>
#include <vector>
#pragma comment(lib, "ws2_32.lib")

// 初始化 Winsock
bool InitializeWinsock() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup 失败\n";
        return false;
    }
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        std::cerr << "错误的 Winsock 版本\n";
        WSACleanup();
        return false;
    }
    std::cout << "Winsock 初始化成功!\n";
    return true;
}

2.2 创建服务器

2.2.1 创建 Socket

服务器需要创建一个 Socket 来监听客户端的连接。

SOCKET CreateServerSocket() {
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET) {
        std::cerr << "创建 socket 失败: " << WSAGetLastError() << "\n";
        WSACleanup();
        return INVALID_SOCKET;
    }
    return serverSocket;
}
2.2.2 绑定地址和端口

服务器需要绑定到一个 IP 地址和端口,以便客户端可以连接。

bool BindServerSocket(SOCKET serverSocket, const char* ip, int port) {
    sockaddr_in addr = { 0 };
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &addr.sin_addr); // 使用 inet_pton 设置 IP
    addr.sin_port = htons(port);

    if (bind(serverSocket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
        std::cerr << "绑定失败: " << WSAGetLastError() << "\n";
        closesocket(serverSocket);
        WSACleanup();
        return false;
    }
    std::cout << "绑定成功!\n";
    return true;
}
2.2.3 监听客户端连接

服务器需要监听客户端的连接请求。

bool ListenForClients(SOCKET serverSocket) {
    if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) {
        std::cerr << "监听失败: " << WSAGetLastError() << "\n";
        closesocket(serverSocket);
        WSACleanup();
        return false;
    }
    std::cout << "监听成功,等待客户端连接...\n";
    return true;
}

2.3 处理客户端连接

2.3.1 接受客户端连接

服务器需要接受客户端的连接,并为每个客户端创建一个线程来处理通信。

std::vector<SOCKET> clients; // 存储所有客户端的 Socket

void HandleClient(SOCKET clientSocket) {
    char buffer[1024];
    while (true) {
        int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
        if (bytesReceived > 0) {
            buffer[bytesReceived] = '\0';
            std::cout << "收到消息: " << buffer << "\n";

            // 广播消息给所有客户端
            for (SOCKET client : clients) {
                if (client != clientSocket) {
                    send(client, buffer, bytesReceived, 0);
                }
            }
        } else if (bytesReceived == 0) {
            std::cout << "客户端断开连接\n";
            break;
        } else {
            std::cerr << "接收数据失败: " << WSAGetLastError() << "\n";
            break;
        }
    }
    closesocket(clientSocket);
}

void AcceptClients(SOCKET serverSocket) {
    while (true) {
        sockaddr_in clientAddr = { 0 };
        int clientAddrSize = sizeof(clientAddr);
        SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrSize);
        if (clientSocket == INVALID_SOCKET) {
            std::cerr << "接受客户端连接失败: " << WSAGetLastError() << "\n";
            continue;
        }
        clients.push_back(clientSocket);
        std::cout << "客户端连接成功\n";

        // 创建线程处理客户端
        std::thread clientThread(HandleClient, clientSocket);
        clientThread.detach();
    }
}

2.4 客户端实现

2.4.1 创建客户端 Socket

客户端需要创建一个 Socket 来连接服务器。

SOCKET CreateClientSocket() {
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET) {
        std::cerr << "创建 socket 失败: " << WSAGetLastError() << "\n";
        WSACleanup();
        return INVALID_SOCKET;
    }
    return clientSocket;
}
2.4.2 连接服务器

客户端需要连接到服务器的 IP 地址和端口。

bool ConnectToServer(SOCKET clientSocket, const char* ip, int port) {
    sockaddr_in addr = { 0 };
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &addr.sin_addr);
    addr.sin_port = htons(port);

    if (connect(clientSocket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
        std::cerr << "连接服务器失败: " << WSAGetLastError() << "\n";
        closesocket(clientSocket);
        WSACleanup();
        return false;
    }
    std::cout << "连接服务器成功\n";
    return true;
}
2.4.3 发送和接收消息

客户端可以发送消息到服务器,并接收服务器广播的消息。

void ClientCommunication(SOCKET clientSocket) {
    std::thread receiveThread([clientSocket]() {
        char buffer[1024];
        while (true) {
            int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
            if (bytesReceived > 0) {
                buffer[bytesReceived] = '\0';
                std::cout << "收到消息: " << buffer << "\n";
            } else {
                std::cerr << "服务器断开连接\n";
                break;
            }
        }
    });

    char buffer[1024];
    while (true) {
        std::cin.getline(buffer, sizeof(buffer));
        send(clientSocket, buffer, strlen(buffer), 0);
    }
}

3. 运行效果

  1. 启动服务器:

Winsock 初始化成功!
绑定成功!
监听成功,等待客户端连接...

2.启动客户端:

连接服务器成功

3.客户端发送消息:

请输入要发送的消息: Hello!

4.服务器广播消息:

收到消息: Hello!

4. 总结

通过这个项目,简单描述了如何使用 C++ 和 Winsock 实现一个简单的聊天室。我们掌握了以下知识点:

  • Winsock 的初始化和清理。

  • Socket 的创建、绑定、监听和连接。

  • 多线程处理客户端连接。

  • 消息的发送和接收。

原代码如下:

4.1服务端

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <windows.h>

using namespace std;

int main() {
    // 1. 初始化 Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup 失败\n");
        return -1;
    }
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        printf("错误的 Winsock 版本\n");
        WSACleanup();
        return -1;
    }
    printf("WSAStartup 成功!\n");

    // 2. 创建 socket
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET) {
        printf("创建 socket 失败: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }

    // 3. 设置服务器地址
    SOCKADDR_IN addr = { 0 };
    addr.sin_family = AF_INET;
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服务器 IP 地址
    addr.sin_port = htons(9527); // 服务器端口

    // 4. 连接服务器
    if (connect(clientSocket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
        printf("连接服务器失败: %d\n", WSAGetLastError());
        closesocket(clientSocket);
        WSACleanup();
        return -1;
    }
    printf("连接服务器成功\n");

    // 5. 通信
    char buffer[1024];
    while (1) {
        printf("请输入要发送的数据: ");
        scanf_s("%s", buffer, (unsigned)_countof(buffer)); // 用户输入
        send(clientSocket, buffer, strlen(buffer), 0);
    }

    // 6. 关闭 socket 和清理
    closesocket(clientSocket);
    WSACleanup();

    return 0;
}

4.2客户端

#define _WINSOCK_DEPRECATED_NO_WARNINGS     //保证一定的兼容性
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <windows.h>

using namespace std;

int main() {
    // 1. 初始化 Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup 失败\n");
        return -1;
    }
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        printf("错误的 Winsock 版本\n");
        WSACleanup();
        return -1;
    }
    printf("WSAStartup 成功!\n");

    // 2. 创建 socket
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET) {
        printf("创建 socket 失败: %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }

    // 3. 设置服务器地址
    SOCKADDR_IN addr = { 0 };
    addr.sin_family = AF_INET;
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服务器 IP 地址
    addr.sin_port = htons(9527); // 服务器端口

    // 4. 连接服务器
    if (connect(clientSocket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
        printf("连接服务器失败: %d\n", WSAGetLastError());
        closesocket(clientSocket);
        WSACleanup();
        return -1;
    }
    printf("连接服务器成功\n");

    // 5. 通信
    char buffer[1024];
    while (1) {
        printf("请输入要发送的数据: ");
        scanf_s("%s", buffer, (unsigned)_countof(buffer)); // 用户输入
        send(clientSocket, buffer, strlen(buffer), 0);
    }

    // 6. 关闭 socket 和清理
    closesocket(clientSocket);
    WSACleanup();

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值