Qt 网络聊天室项目

本文详细介绍了使用QTcpServer和QTcpSocket在C++中实现TCP通信的过程,包括服务器的创建、监听客户端连接、接收和发送消息的逻辑,以及客户端的连接、发送和接收消息的步骤。同时,文章提到了处理客户端断开连接的策略,确保服务器能够有效管理多个客户端的连接。
摘要由CSDN通过智能技术生成

1. 需求分析

在这里插入图片描述
在这里插入图片描述

2. 概要设计

在这里插入图片描述

2.1 服务器概要设计

在这里插入图片描述

2.2 客户端概要设计

在这里插入图片描述

3. 详细设计

在这里插入图片描述

3.1 服务端的伪代码

在这里插入图片描述

  • QTcpServer::listen()类似于TCP编程中的bind(将套接字和ip、端口号 绑定在一起)
  • QHostAddress::Any 是 0.0.0.0 代表本机任意网卡的地址

3.1.0 创建TCP服务器

在这里插入图片描述

3.1.1 响应客户端请求

  • newConnection()信号触发自定义槽函数onNewConnection(), 槽函数中会调用 nextPendingConnection() 获取和客户端通信的套接字
  • 然后把套接字(连接?)保存到容器当中。 然后当客户端通过套接字发过来消息时,将触发readyRead()信号

*

3.1.2 接收客户端请求

  • 当消息发来时,容器中可能会有多个套接字,需要遍历检查是哪个客户端发来的消息
  • readyRead()信号触发槽函数onReadyRead(), 这个槽函数去遍历检查是哪个客户端发来的消息,然后完成消息的接收。

在这里插入图片描述

3.2 客户端的伪代码

3.2.0 创建TCP套接字和服务器建立连接

在这里插入图片描述

  • connectToHost() 类似TCP编程中的connect(), 向指定的IP和端口发送连接请求,建立三次握手。
  • 如果希望连接成功时有什么操作,可以通过onConnected()槽函数来完成。
  • 通信套接字收到对方发来的消息时,都会产生readyRead()信号, onReadyRead()槽函数完成消息的接收。

3.2.1 发送聊天消息

  • 发送消息控件的槽函数
    在这里插入图片描述

3.2.2 接收聊天消息

  • readyRead()消息的槽函数, 下面if的作用是如果有消息才去读取消息,不然如果没有消息时去读取会阻塞
    在这里插入图片描述

4. 代码编写

github

4.1 服务器代码示例

在这里插入图片描述

  • 在工程文件里加上network
  • list Widget控件可以显示聊天过程,然后可以把聊天消息保存到本地,这里就不写保存的操作了

在这里插入图片描述
遍历容器如果有删除操作需要注意的点。

容器元素 : s1 s2 s3 s4 s5
下标    :  0  1  2  3  4  
通过下标遍历检查容器中保存的客户端通信套接字是否已经断开连接,如果是则删除
如果当遍历到下标为2的元素s3对应的套接字已经断开连接,那么就删除s3, s4会自动向前移动到下标为2的位置
然后从下标3开始继续遍历,这样就会跳过s4.
所以,当遍历时发现需要删除元素时,下标要自减1,才能继续遍历。

sreverdialog.h

#ifndef SREVERDIALOG_H
#define SREVERDIALOG_H

#include <QDialog>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QTimer>

namespace Ui {
class SreverDialog;
}

class SreverDialog : public QDialog
{
    Q_OBJECT

public:
    explicit SreverDialog(QWidget *parent = 0);
    ~SreverDialog();

private slots:
    // slot func of "create server" button
    void on_pushButton_clicked();
    // slot func of corresponding the client connection request (NewConnection())
    void onNewConnection();
    // slot func of receiving the client message (ReadyRead)
    void onReadyRead();
    // slot func of forwording the message to other client
    void sendMessge(const QByteArray& buf);
    // slot func of timer
    void onTimeout(void);

private:
    Ui::SreverDialog *ui;
    QTcpServer tcpServer;
    quint16 port; // server port
    QList <QTcpSocket*> tcpClientList; //vector: save all sockets that communicate with client
    QTimer timer; // timer (ji shi qi)

};

#endif // SREVERDIALOG_H

sreverdialog.cpp

#include "sreverdialog.h"
#include "ui_sreverdialog.h"

SreverDialog::SreverDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::SreverDialog)
{
    ui->setupUi(this);
    // When a client sends a request to the server, newConnection()  signal is generated
    connect(&tcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
    // send timeout signal every three seconds
    connect(&timer, SIGNAL(timeout()), SLOT(onTimeout()));
}

SreverDialog::~SreverDialog()
{
    delete ui;
}

// slot func of "create server" button
void SreverDialog::on_pushButton_clicked()
{
    // get server port
    port = ui->lineEdit->text().toShort();
    // set up server ip and port
    if (tcpServer.listen(QHostAddress::Any, port) == true){
        qDebug()<<"create server sucessfully!";
        // disiable pushButton and lineEdit
        ui->pushButton->setEnabled(false);
        ui->lineEdit->setEnabled(false);
    }
    else {
        qDebug()<< "faied to create server.";
    }
}

// slot func of corresponding the client connection request
void SreverDialog::onNewConnection(){
    // get the socket of communicating with the client
    QTcpSocket* tcpClient = tcpServer.nextPendingConnection();
    // save socket to vector
    tcpClientList.append(tcpClient);
    // when a client sent a message to server, soket send readyRead() signal
    connect(tcpClient, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
}

// slot func of receiving the client message
void SreverDialog::onReadyRead(){
    //
    for(int i=0; i<tcpClientList.size(); i++){
        // 遍历容器找到是哪个客户端发来的消息
        // bytesAvailable()获取当前套接字等待读取消息字节数
        // 返回0表示没有消息需要读,大于0说明有消息要来
        if(tcpClientList.at(i)->bytesAvailable()){
            // read message and save it
            QByteArray buf = tcpClientList.at(i)->readAll();
            // display message
            ui->listWidget->addItem(buf);
            ui->listWidget->scrollToBottom();
            // start timer
            timer.start(3000);
            // forward message to other client
            sendMessge(buf);
        }
    }
}
// slot func of forwording the message to other client
void SreverDialog::sendMessge(const QByteArray& buf){
    for(int i =0; i<tcpClientList.size(); i++) {
        tcpClientList.at(i)->write(buf);
    }
}

// slot func of timer
void SreverDialog::onTimeout(void){
    qDebug()<<"ni ma";
    // 遍历检查容器中保存的客户端通信套接字是否已经断开连接,如果是则删除
    for(int i=0; i<tcpClientList.size(); i++){
        if(tcpClientList.at(i)->state() ==
                QAbstractSocket::UnconnectedState){
            tcpClientList.removeAt(i);
            --i;
        }
    }
}

4.2 客户端代码示例

在这里插入图片描述
在这里插入图片描述
clientdialog.h

#ifndef CLIENTDIALOG_H
#define CLIENTDIALOG_H

#include <QDialog>
#include <QTcpSocket>
#include <QHostAddress>
#include <QMessageBox>
#include <QDebug>

namespace Ui {
class ClientDialog;
}

class ClientDialog : public QDialog
{
    Q_OBJECT

public:
    explicit ClientDialog(QWidget *parent = 0);
    ~ClientDialog();

private slots:
    // slot func of sendButton
    void on_sendButton_clicked();
    // slot func of connectButton
    void on_connectButton_clicked();
    // slot func for successful connection with server (connected())
    void onConnected();
    // slot func for disconnection with server
    void disConnected();
    // slot fucn of receive message from server
    void onReadyRead();
    // slot fucn of network exception
    void onError();

private:
    Ui::ClientDialog *ui;
    bool status;            // client status: online/outline
    QTcpSocket tcpSocket;   // socket
    QHostAddress serverIp;  // server ip
    quint16 serverPort;     // server port
    QString username;       // username
};

#endif // CLIENTDIALOG_H

clientdialog.cpp

#include "clientdialog.h"
#include "ui_clientdialog.h"

ClientDialog::ClientDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::ClientDialog)
{
    status = false;
    ui->setupUi(this);
    connect(&tcpSocket, SIGNAL(connected()), this, SLOT(onConnected()));
    connect(&tcpSocket, SIGNAL(disconnected()), this, SLOT(disConnected()));
    connect(&tcpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
    connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError()));
}

ClientDialog::~ClientDialog()
{
    delete ui;
}

// slot func of sendButton
void ClientDialog::on_sendButton_clicked()
{
    // get user input message
    QString msg = ui->messageEdit->text();
    if(msg == ""){
     return;
    }
    msg = username + ": " + msg;
    // send message
    tcpSocket.write(msg.toUtf8());
    // clean messageEdit
    ui->messageEdit->clear();
}

// slot func of connectButton
void ClientDialog::on_connectButton_clicked()
{
    // if outline, connect to server
    if (status == false){
        // get server ip
        serverIp.setAddress(ui->serverIpEdit->text());
        // get server port
        serverPort = ui->serverPortEdit->text().toShort();
        // get username
        username = ui->usernameEdit->text();
        // send connection request
        // if success, generate connected singal, esle generate error singal
        tcpSocket.connectToHost(serverIp, serverPort);
    }
    // if online, disconnect from server
    else {
        // send server with message that leaving chat room
        QString msg = username + ": leaved!";
        tcpSocket.write(msg.toUtf8());
        // disconnect from server, and generate disconnected singal
        tcpSocket.disconnectFromHost();
    }
}

// slot func for successful connection with server (connected())
void ClientDialog::onConnected()
{
    status = true;
    ui->sendButton->setEnabled(true);
    ui->serverIpEdit->setEnabled(false);
    ui->serverPortEdit->setEnabled(false);
    ui->usernameEdit->setEnabled(false);
    ui->connectButton->setText("leave chat room");
    // send message to server
    QString msg = username  + ": entered the chat room!";
    // toUtf8(): transform QString to QByteArray
    tcpSocket.write(msg.toUtf8());
}

// slot func for disconnection with server
void ClientDialog::disConnected()
{
    status = false;
    ui->sendButton->setEnabled(true);
    ui->serverIpEdit->setEnabled(true);
    ui->serverPortEdit->setEnabled(true);
    ui->usernameEdit->setEnabled(true);
    ui->connectButton->setText("connect server");
}

// slot fucn of receive message from server
void ClientDialog::onReadyRead()
{
    if(tcpSocket.bytesAvailable()){
        // receive message
        QByteArray buf = tcpSocket.readAll();
        // display message
        ui->listWidget->addItem(buf);
        // dispaly bottom message
        ui->listWidget->scrollToBottom();
    }
}

// slot fucn of network exception
void ClientDialog::onError()
{
    // errorString(): get reason of network exception
    QMessageBox::critical(this, "ERROR", tcpSocket.errorString());
}

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值