C++:实现服务端客户端聊天室(附带源码)

linbojue
• 阅读 0

一、项目背景详细介绍 随着网络通信的发展,聊天室是学习网络编程的经典入门项目。通过聊天室可以学习:

TCP/IP 协议的客户端/服务端通信

多线程并发处理(服务端同时处理多个客户端)

C++ 指针与对象管理

套接字编程细节(connect、bind、listen、accept、send、recv)

本项目旨在实现一个简易文本聊天室,通过命令行界面模拟多用户即时通信场景,帮助学习网络通信和多线程编程。

二、项目需求详细介绍 服务端需求:

监听指定端口

接收多个客户端连接

每个客户端消息广播给其他客户端

可以显示所有客户端的连接和断开状态

客户端需求:

连接服务端

输入消息发送到服务端

接收并显示其他客户端消息

可以退出聊天室

功能要求:

服务端多线程处理客户端

客户端可以同时接收和发送消息

使用 TCP 协议

注释详细,逻辑清晰

三、相关技术详细介绍 3.1 TCP 套接字 TCP 是面向连接的协议,保证数据可靠传输

核心函数:

socket:创建套接字

bind:绑定端口

listen:监听连接

accept:接受客户端连接

connect:客户端连接服务器

send / recv:发送和接收数据

3.2 多线程处理 服务端:每个客户端一个线程,防止阻塞

客户端:主线程发送消息,子线程接收消息

使用 C++11 std::thread 实现

3.3 广播机制 服务端维护客户端列表

接收到某个客户端消息时,将消息发送给列表中所有其他客户端

四、实现思路详细介绍 服务端:

创建 TCP 套接字

绑定端口并监听

循环 accept 客户端连接

为每个客户端创建一个线程,接收消息

消息接收后调用广播函数,将消息发送给其他客户端

客户端:

创建 TCP 套接字

连接服务端

创建接收消息线程

主线程循环读取输入并发送消息

线程安全:

访问客户端列表时加锁(使用 std::mutex)

避免同时写套接字导致冲突

五、完整实现代码 /****

  • 文件名:ChatRoom.cpp
  • 描述:C++ 服务端-客户端聊天室(多线程版)
  • ***/

#include #include #include #include #include #include #include // for memset #include <unistd.h> // for close #include <netinet/in.h> #include <arpa/inet.h>

using namespace std;

const int PORT = 12345; // 聊天室端口 const int BUFFER_SIZE = 1024; // 消息缓冲区大小

/****

  • 服务端部分
  • ***/ class ChatServer { private: int server_fd; // 服务端套接字 vector clients; // 客户端套接字列表 mutex clients_mutex; // 访问客户端列表锁

public: ChatServer() { server_fd = -1; }

// 初始化服务端
bool init()
{
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1)
    {
        cerr << "创建套接字失败!" << endl;
        return false;
    }

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) < 0)
    {
        cerr << "绑定端口失败!" << endl;
        return false;
    }

    if (listen(server_fd, 5) < 0)
    {
        cerr << "监听失败!" << endl;
        return false;
    }

    cout << "服务端启动,监听端口 " << PORT << "..." << endl;
    return true;
}

// 广播消息给所有客户端
void broadcast(const string& message, int sender_fd)
{
    lock_guard<mutex> lock(clients_mutex);
    for (int client_fd : clients)
    {
        if (client_fd != sender_fd) // 不发给自己
        {
            send(client_fd, message.c_str(), message.size(), 0);
        }
    }
}

// 客户端线程处理
void handleClient(int client_fd)
{
    char buffer[BUFFER_SIZE];
    string welcome = "欢迎进入聊天室!\n";
    send(client_fd, welcome.c_str(), welcome.size(), 0);

    while (true)
    {
        memset(buffer, 0, BUFFER_SIZE);
        int bytes = recv(client_fd, buffer, BUFFER_SIZE, 0);
        if (bytes <= 0) break; // 客户端断开

        string msg(buffer);
        cout << "客户端 " << client_fd << " 说: " << msg;
        broadcast(msg, client_fd);
    }

    // 客户端断开
    close(client_fd);
    {
        lock_guard<mutex> lock(clients_mutex);
        clients.erase(remove(clients.begin(), clients.end(), client_fd), clients.end());
    }
    cout << "客户端 " << client_fd << " 已断开" << endl;
}

// 运行服务端
void run()
{
    while (true)
    {
        sockaddr_in client_addr;
        socklen_t addr_len = sizeof(client_addr);
        int client_fd = accept(server_fd, (sockaddr*)&client_addr, &addr_len);
        if (client_fd < 0)
        {
            cerr << "接受客户端失败" << endl;
            continue;
        }

        {
            lock_guard<mutex> lock(clients_mutex);
            clients.push_back(client_fd);
        }

        cout << "新客户端连接: " << client_fd << endl;
        thread t(&ChatServer::handleClient, this, client_fd);
        t.detach(); // 分离线程
    }
}

};

/****

  • 客户端部分
  • ***/ class ChatClient { private: int sock_fd;

public: ChatClient() { sock_fd = -1; }

bool connectServer(const string& ip)
{
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1)
    {
        cerr << "创建套接字失败" << endl;
        return false;
    }

    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);

    if (connect(sock_fd, (sockaddr*)&addr, sizeof(addr)) < 0)
    {
        cerr << "连接服务器失败" << endl;
        return false;
    }

    cout << "成功连接服务器!" << endl;
    return true;
}

// 接收消息线程
void receiveMessages()
{
    char buffer[BUFFER_SIZE];
    while (true)
    {
        memset(buffer, 0, BUFFER_SIZE);
        int bytes = recv(sock_fd, buffer, BUFFER_SIZE, 0);
        if (bytes <= 0)
        {
            cout << "与服务器断开连接" << endl;
            break;
        }
        cout << buffer;
    }
}

// 发送消息
void sendMessages()
{
    string msg;
    while (true)
    {
        getline(cin, msg);
        if (msg == "/quit") break;
        send(sock_fd, msg.c_str(), msg.size(), 0);
    }
    close(sock_fd);
}

void run()
{
    thread recv_thread(&ChatClient::receiveMessages, this);
    sendMessages();
    recv_thread.join();
}

};

/****

  • 主函数

  • ***/ int main() { cout << "请选择模式 (1-服务端, 2-客户端): "; int choice; cin >> choice; cin.ignore();

    if (choice == 1) {

      ChatServer server;
      if (server.init())
      {
          server.run();
      }

    } else if (choice == 2) {

      ChatClient client;
      cout << "请输入服务器IP: ";
      string ip;
      cin >> ip;
      cin.ignore();
      if (client.connectServer(ip))
      {
          client.run();
      }

    }

    return 0; } AI写代码 cpp 运行

六、代码详细解读(方法作用) ChatServer::init:初始化服务端套接字、绑定端口并监听

ChatServer::broadcast:广播消息给除发送者外的所有客户端

ChatServer::handleClient:处理单个客户端消息接收和断开

ChatServer::run:循环等待客户端连接并为每个客户端创建线程

ChatClient::connectServer:连接指定服务器 IP

ChatClient::receiveMessages:线程函数,循环接收服务器消息

ChatClient::sendMessages:主线程读取用户输入并发送

ChatClient::run:启动客户端发送接收功能

七、项目详细总结 通过此项目,你学会了:

TCP 套接字基础操作

多线程处理并发客户端

消息广播机制

客户端发送/接收消息并发实现

代码封装、线程安全与锁机制

该程序可用于:

网络编程教学

C++ 多线程示例

基础聊天室原型开发

八、项目常见问题及解答 Q1:客户端发送消息阻塞怎么办? A:采用线程分离,发送和接收分开处理。

Q2:广播消息时,客户端断开会出错? A:在广播前需加锁,并检查套接字有效性。

Q3:Windows 下如何运行? A:需包含 winsock2.h,使用 WSAStartup() 初始化。

九、扩展方向与性能优化 支持 用户名识别,显示客户端昵称

使用 select / poll / epoll 实现非阻塞 I/O

增加 群聊与私聊功能

消息加密(SSL/TLS)

GUI 客户端(Qt / wxWidgets / ImGui) ———————————————— 版权声明:本文为CSDN博主「南城花随雪。」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_61840987/article/details/156952124 https://infogram.com/untitled-1h0r6rzwyo93w4e https://infogram.com/untitled-1h9j6q759ol1v4g https://infogram.com/untitled-1h0r6rzwyo7ew4e https://infogram.com/microsoft-office-word-2007-docx-1h7v4pd0lv1r84k https://infogram.com/untitled-1hxj48mqg09zq2v https://infogram.com/untitled-1h9j6q759on3v4g https://infogram.com/untitled-1h984wv15wyqz2p https://infogram.com/untitled-1h0r6rzwyo1vw4e https://infogram.com/untitled-1h0n25opq079z4p https://infogram.com/untitled-1h7v4pd0lvg084k

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
4年前
C++实现简单的RPC框架
简介    RPC是远程过程调用(RemoteProcedureCall)的缩写形式 ,RPC的目的是为了简化网络通信,让用户可以专注于业务处理,不用关心网络层的处理,真正实现在客户端A中调用函数F就可以调用服务端B中的函数F的目的。    RPC模型引入存根进程(stub)的概念, 对于服务端的服务类A,在客户端通过A::s
Wesley13 Wesley13
4年前
Java——网络编程(实现基于命令行的多人聊天室)
目录:1.ISO和TCP/IP分层模型2.IP协议3.TCP/UDP协议4.基于TCP的网络编程5.基于UDP的网络编程6.基于TCP的多线程的聊天室的实现1.ISO和TCP/IP分层模型:!(https://static.oschina.net/upload
Stella981 Stella981
4年前
Python网络编程—TCP客户端和服务器
Python网络编程—TCP客户端和服务器客户端importsocket'''客户端创建步骤:1、创建网络套接字2、连接到目标IP地址和端口3、收发数据4、关闭套接字'''IPso
Wesley13 Wesley13
4年前
Java服务端与C#客户端实现websocket通信(发送消息和文件)
设计思路使用websocket通信,客户端采用C开发界面,服务端使用Java开发,最终实现Java服务端向C客户端发送消息和文件,C客户端实现语音广播的功能。Java服务端设计packageservlet.websocket;importjava.io.IOException;importjava
Stella981 Stella981
4年前
Netty堆外内存泄露排查与总结
导读Netty是一个异步事件驱动的网络通信层框架,用于快速开发高可用高性能的服务端网络框架与客户端程序,它极大地简化了TCP和UDP套接字服务器等网络编程。Netty底层基于JDK的NIO,我们为什么不直接基于JDK的NIO或者其他NIO框架:1.使用JDK自带的NIO需要了解太多的概念,编程复杂。2
Stella981 Stella981
4年前
Netty权威指南 第2章NIO 入门读书笔记
2.1传统的BIO编程采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的一请求一应答通信模型。如果不创建线程,还是在主线程中处理请求,则整个服务端是单线程处理能力,待第一个客户端请
Stella981 Stella981
4年前
NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战
前言本文将演示一个iOS客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo。服务端将分别用MINA2和Netty4进行实现,而通信时服务端你只需选其一就行了。同时用MINA2和Netty4分别实现服务端的目的,是因为很多人都在纠结到底是用MINA还是Netty来实现高并发的Java网络通信服务端
Wesley13 Wesley13
4年前
NIO入门之传统的BIO编程
网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务器监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信。在基于传统同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听
程序员小五 程序员小五
1年前
融云IM干货丨【 IM 服务】为什么聊天室自动销毁了?怎样能让聊天室一直存在?
聊天室自动销毁的原因通常与设置的自动销毁机制有关。根据搜索结果,聊天室具有自动销毁机制,如果聊天室在指定时间内(默认1个小时)没有人说话,且没有人加入聊天室时,服务端会把聊天室内所有成员踢出聊天室并销毁聊天室。这种“不活跃”是指连续时间段内无成员进出且无新