Qt学习之路十三—— 再谈TCP/IP(多客户端连接服务器)

一、TCP和UDP的区别

这里我会用一个表格来显示这两者的区别

比较项TCPUDP
是否连接面向连接无连接
传输是否可靠可靠不可靠
流量控制提供不提供
工作方式全双工可以是全双工
应用场合大量数据少量数据
速度
二、incomingConnection函数

这个函数和之前讲过的newConnection信号功能差不多,只要有新的连接出现,就会自动调用这个函数。

然后我们只需在这个函数中新建一个QTcpSocket对象,并且将这个套接字指定为这个函数的参数socketDescriptor,然后将这个套接字存放到套接字列表中就可以实现多个客户端同时登陆了。

这里我们简单看一下这个函数里的内容

void Server::incomingConnection(int socketDescriptor)
{
    TcpClientSocket *tcpclientsocket = new TcpClientSocket(this);//只要有新的连接就生成一个新的通信套接字
    //将新创建的通信套接字描述符指定为参数socketdescriptor
    tcpclientsocket->setSocketDescriptor(socketDescriptor);

    //将这个套接字加入客户端套接字列表中
    tcpclientsocketlist.append(tcpclientsocket);

}

Server这个类是继承于QTcpServer类的,所以我们需要在Server类中重写incomingConnection函数。

三、多个客户端同时登陆的小聊天室示例。

首先说明一下这个小示例的功能,当有一个客户端进入聊天室的时候,会向服务器发送一个信息,告诉服务器这个账号进入了聊天室, 然后服务器会在界面中显示哪个账号进入了聊天室,并且服务器会向所有的客户端发送消息,告诉所有客户端有哪个账号进入了聊天室。某个账号发送信息的时候也是如此,服务器会将收到的信息发送给所有的客户端。

如果需要实现网络通信,就必须在pro文件中加入 QT += network

服务器端

在服务器端需要添加三个类,一个类用来创建界面,一个类用来监听,这个类的基类是QTcpServer,最后一个类用来通信,通信的类的基类是QTcpSocket类。

1、用来创建界面的Tcpserver类

#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <QWidget>
#include "server.h"

namespace Ui {
class TcpServer;
}

class TcpServer : public QWidget
{
    Q_OBJECT

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

private:
    Ui::TcpServer *ui;
    int port;
    Server *server;

protected slots:
    void slotupdateserver(QString, int);//接收到server发过来的信号就更新界面的信息


private slots:
    void on_pushButtonCreateChattingRoom_clicked();
};

#endif // TCPSERVER_H
 
#include "tcpserver.h"
#include "ui_tcpserver.h"


TcpServer::TcpServer(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TcpServer)
{
    ui->setupUi(this);
    port = 8888;

}

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

void TcpServer::on_pushButtonCreateChattingRoom_clicked()
{
    server  = new Server(this, port);
    connect(server, &Server::updateserver, this, &TcpServer::slotupdateserver);
    ui->pushButtonCreateChattingRoom->setEnabled(false);
}

void TcpServer::slotupdateserver(QString msg, int length)
{
    ui->textEdit->append(msg);
}

2、用来监听的类Server

#ifndef SERVER_H
#define SERVER_H

#include <QTcpServer>
#include <QObject>
#include <QList>
#include "tcpclientsocket.h"

class Server : public QTcpServer
{
    Q_OBJECT //为了实现信号和槽的通信
public:
    Server(QObject *parent = 0, int port = 0);
    QList<TcpClientSocket*> tcpclientsocketlist;
protected:
    void incomingConnection(int socketDescriptor);//只要出现一个新的连接,就会自动调用这个函数
protected slots:
    void sliotupdateserver(QString, int);//用来处理tcpclient发过来的信号
    void slotclientdisconnect(int);

signals:
    void updateserver(QString, int);//发送信号给界面,让界面更新信息

};

#endif // SERVER_H
#include "server.h"
#include <QHostAddress>

Server::Server(QObject *parent, int port):QTcpServer(parent)
{
    listen(QHostAddress::Any, port); //监听
}

void Server::incomingConnection(int socketDescriptor)
{
    TcpClientSocket *tcpclientsocket = new TcpClientSocket(this);//只要有新的连接就生成一个新的通信套接字
    //将新创建的通信套接字描述符指定为参数socketdescriptor
    tcpclientsocket->setSocketDescriptor(socketDescriptor);

    //将这个套接字加入客户端套接字列表中
    tcpclientsocketlist.append(tcpclientsocket);


    //接收到tcpclientsocket发送过来的更新界面的信号
    connect(tcpclientsocket, &TcpClientSocket::updateserver, this, &Server::sliotupdateserver);
    connect(tcpclientsocket, &TcpClientSocket::clientdisconnected, this, &Server::slotclientdisconnect);

}

void Server::sliotupdateserver(QString msg, int length)
{
    //将这个信号发送给界面
    emit updateserver(msg, length);

    //将收到的信息发送给每个客户端,从套接字列表中找到需要接收的套接字
    for(int i = 0; i < tcpclientsocketlist.count(); i++)
    {
        QTcpSocket *item = tcpclientsocketlist.at(i);
//        if(item->write((char*)msg.toUtf8().data(), length) != length)
//        {
//            continue;
//        }
        item->write(msg.toUtf8().data());
    }

}

void Server::slotclientdisconnect(int descriptor)
{
    for(int i = 0; i < tcpclientsocketlist.count(); i++)
    {
        QTcpSocket *item = tcpclientsocketlist.at(i);
        if(item->socketDescriptor() == descriptor)
        {
            tcpclientsocketlist.removeAt(i);//如果有客户端断开连接, 就将列表中的套接字删除
            return;
        }
    }
    return;
}
3、用来通信的类TcpClientSocket
#ifndef TCPCLIENTSOCKET_H
#define TCPCLIENTSOCKET_H

#include <QTcpSocket>

class TcpClientSocket : public QTcpSocket
{
    Q_OBJECT //添加这个宏是为了实现信号和槽的通信
public:
    TcpClientSocket(QObject *parent = 0);
protected slots:
    void receivedata();//处理readyRead信号读取数据
    void slotclientdisconnected();//客户端断开连接触发disconnected信号,这个槽函数用来处理这个信号

signals:
    void updateserver(QString, int);//用来告诉tcpserver需要跟新界面的显示
    void clientdisconnected(int); //告诉server有客户端断开连接
};

#endif // TCPCLIENTSOCKET_H
#include "tcpclientsocket.h"

TcpClientSocket::TcpClientSocket(QObject *parent)
{
    //客户端发送数据过来就会触发readyRead信号
    connect(this, &TcpClientSocket::readyRead, this, &TcpClientSocket::receivedata);
    connect(this, &TcpClientSocket::disconnected, this, &TcpClientSocket::slotclientdisconnected);
}

void TcpClientSocket::receivedata()
{
//    while(bytesAvailable() > 0)
//    {
//        int length = bytesAvailable();
//        char buf[1024]; //用来存放获取的数据
//        read(buf, length);
//        QString msg = buf;
//        //发信号给界面,让界面显示登录者的信息
//        emit updateserver(msg, length);
//    }
    int length = 10;
    QByteArray array = readAll();
    QString msg = array;
    emit updateserver(msg, length);
}

void TcpClientSocket::slotclientdisconnected()
{
    emit clientdisconnected(this->socketDescriptor());
}

客户端

客户端只需要一个类就行了,这个类我们只需要创建一个通信套接字来和服务器进行通信就可以了。

#ifndef TCPCLIENT_H
#define TCPCLIENT_H

#include <QWidget>
#include <QTcpSocket>

namespace Ui {
class TcpClient;
}

class TcpClient : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButtonEnter_clicked();
    void slotconnectedsuccess();//用来处理连接成功的信号
    void slotreceive();//接收服务器传过来的信息
    void on_pushButtonSend_clicked();
    void slotdisconnected();//用来处理离开聊天室的信号


private:
    Ui::TcpClient *ui;
    bool status;//用来判断是否进入了聊天室
    int port;
    QHostAddress *serverIP;
    QString userName;
    QTcpSocket *tcpsocket;
};

#endif // TCPCLIENT_H
 
#include "tcpclient.h"
#include "ui_tcpclient.h"
#include <QHostAddress>
#include <QMessageBox>

TcpClient::TcpClient(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TcpClient)
{
    ui->setupUi(this);
    //将进入聊天室的标志位置为false
    status = false;
    //端口为8888
    port = 8888;
    ui->lineEditServerPort->setText(QString::number(port));//界面中端口默认显示8888

    serverIP = new QHostAddress();

    //未进入聊天室内不能发送信息
    ui->pushButtonSend->setEnabled(false);
}

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

//进入聊天室
void TcpClient::on_pushButtonEnter_clicked()
{
    //首先判断这个用户是不是在聊天室中
    if(status == false)
    {
        //不在聊天室中就和服务器进行连接
        QString ip = ui->lineEditServerIp->text();//从界面获取ip地址
        if(!serverIP->setAddress(ip))//用这个函数判断IP地址是否可以被正确解析
        {
            //不能被正确解析就弹出一个警告窗口
            QMessageBox::warning(this, "错误", "IP地址不正确");
            return;
        }
        if(ui->lineEditUserName->text() == "")
        {
            //用户名不能为空
            QMessageBox::warning(this, "错误", "用户名不能为空");
            return;
        }

        //从界面获取用户名
        userName = ui->lineEditUserName->text();
        //创建一个通信套接字,用来和服务器进行通信
        tcpsocket = new QTcpSocket(this);

        //和服务器进行连接
        tcpsocket->connectToHost(*serverIP, port);

        //和服务器连接成功能会触发connected信号
        connect(tcpsocket, &QTcpSocket::connected, this, &TcpClient::slotconnectedsuccess);
        //接收到服务器的信息就会触发readyRead信号
        connect(tcpsocket, &QTcpSocket::readyRead, this, &TcpClient::slotreceive);



        //将进入聊天室的标志位置为true;
        status = true;
    }
    else//已经进入了聊天室
    {
        int length = 0;
        QString msg = userName + ":Leave Chat Room";
//        if((length = tcpsocket->write((char*)msg.toUtf8().data(), msg.length())) != msg.length())
//        {
//            return;
//        }
        tcpsocket->write(msg.toUtf8().data());
        tcpsocket->disconnectFromHost();
        status = false;
        //离开聊天室就会触发disconnected信号
        connect(tcpsocket, &QTcpSocket::disconnected, this, &TcpClient::slotdisconnected);
    }
}

//用来处理连接成功的信号
void TcpClient::slotconnectedsuccess()
{
    //进入聊天室可以发送信息了
    ui->pushButtonSend->setEnabled(true);
    //将进入聊天的按钮改为离开聊天室
    ui->pushButtonEnter->setText("离开聊天室");

    int length = 0;
    //将用户名发送给服务器
    QString msg= userName + " :Enter Chat Room";

//    if((length = tcpsocket->write((char*)msg.toUtf8().data(), msg.length())) != msg.length())
//    {
//        return;
//    }
    tcpsocket->write(msg.toUtf8().data());
}


void TcpClient::slotreceive()
{
//    while(tcpsocket->bytesAvailable() > 0 )
//    {
//        QByteArray datagram;
//        datagram.resize(tcpsocket->bytesAvailable());
//        tcpsocket->read(datagram.data(), datagram.size());
//        QString msg = datagram.data();
//        ui->textEdit->append(msg.left(datagram.size()));
//    }
    QByteArray array = tcpsocket->readAll();
    ui->textEdit->append(array);
}

void TcpClient::on_pushButtonSend_clicked()
{
    if(ui->lineEditSend->text() == "")
    {
        return;
    }
    QString msg = userName + ":" + ui->lineEditSend->text();
   // tcpsocket->write((char*)msg.toUtf8().data(), msg.length());
    tcpsocket->write(msg.toUtf8().data());
    ui->lineEditSend->clear();
}

void TcpClient::slotdisconnected()
{
    ui->pushButtonSend->setEnabled(false);
    ui->pushButtonEnter->setText("进入聊天室");
}

编译完之后运行的界面就是这样的


整个通信的步骤

【注】这里服务器的说明用蓝色的字标识出来,客户端用紫色标识。

首先我们点击服务器的创建聊天室TcpServer类的中的相应的槽函数会创建一个Server类的对象,然后调用构造函数会进行监听。一旦有客户端点击进入聊天室的按钮,客户端的tcpsocket就会和服务器进行连接只要服务器监听到新的连接,Server类对象就会自动调用incomingConnection(int socketDescripter)这个函数。接着我们只需要在这个函数中创建一个通信套接字也就是TcpClientSocket对象,并且将这个套接字描述符设定为参数socketDescripter,然后将这个套接字加入套接字列表中。这个时候客户端和服务器连接成功了,客户端就会触发一个connected信号,需要我们写一个槽函数来处理这个信号,这里的处理就是将这个用户名和“Enter Chat Room”发送给服务器。服务器一旦收到信息,就会触发readyRead信号,这个时候我们需要在写一个相应的槽函数来读取套接字中的信息。我们这里是用readall来读取套接字中的所有信息。当收到数据的时候,我们需要发一个信号给Server,将读取的信息再发送给所有在聊天室里的用户,所以在Server类中要添加一个相应的槽函数来将数据发送给所有的用户。因为同时需要在服务器界面中显示所有用户发过来的消息,所以需要在刚刚那个槽函数中将自己定义的一个信号发送给TcpServer类,让其将收到的消息显示在界面中。客户端收到消息之后也会触发readyRead信号,客户端只需要将从套接字中读取的消息显示在见面中就行了。客户端发送消息的时候需要从QLineEdit对象中读取内容,然后通过tcpsocket->write()函数将消息发送给服务器就行了。服务器同样会将收到的信息发送给所有的用户,同时显示在自己的界面中。最后客户端退出聊天室,客户端点击离开聊天室按钮,进入相应的槽函数中调用disconnectFromHost()函数,就会和服务器断开连接,一旦有调用了这个函数,就会触发disconnected信号,然后我们需要写一个相应的槽函数来处理这个信号,我们所做的处理就是将这个用户名和“Leave Chat Roo”发送给服务器服务器那一端检测到有客户端断开连接也会触发disconnected信号,这个时候我们需要将套接字列表中将对应的套接字删除就可以了。

  • 61
    点赞
  • 367
    收藏
    觉得还不错? 一键收藏
  • 32
    评论
### 回答1: 在Qt中,可以使用QTcpSocketQTcpServer类来实现TCP连接多个客户端。下面是一种实现方法: 首先,创建一个QTcpServer对象来监听客户端连接: ```cpp QTcpServer server; server.listen(QHostAddress::Any, 1234); // 监听本地端口1234 ``` 然后,在有新客户端连接时,使用QTcpServer的newConnection信号槽将其连接到一个新的QTcpSocket对象: ```cpp connect(&server, &QTcpServer::newConnection, [=]() { QTcpSocket* socket = server.nextPendingConnection(); // 将socket添加到一个容器中,以便管理多个客户端连接 }); ``` 接下来,可以使用QTcpSocket对象与客户端进行通信。可以在readyRead信号槽中处理接收到的数据,以及在disconnected信号槽中处理客户端断开连接的情况: ```cpp connect(socket, &QTcpSocket::readyRead, [=]() { QByteArray data = socket->readAll(); // 处理接收到的数据 }); connect(socket, &QTcpSocket::disconnected, [=]() { // 处理客户端断开连接的情况 socket->deleteLater(); // 清理资源 }); ``` 需要注意的是,在进行通信的过程中,可以根据具体需求设置超时时间、发送和接收数据等各种细节的处理。 另外,为了管理多个客户端连接,可以将QTcpSocket对象添加到一个容器中,例如使用QList或QVector等容器类。 以上是大致的实现思路,具体的细节和错误处理可以根据项目需求进行调整。 ### 回答2: Qt是一款跨平台的开发框架,拥有丰富的网络编程功能。要实现TCP连接多个客户端,可以采用QtQTcpServer和QTcpSocket类。 首先,创建一个QTcpServer对象,并调用其listen()函数,指定服务端的监听地址和端口号。然后,在新的客户端连接服务器时,QTcpServer会触发newConnection()信号。我们可以通过连接这个信号来处理新的客户端连接。 处理新连接的槽函数中,我们可以创建一个QTcpSocket对象,用于与客户端进行通信。通过调用QTcpServer的nextPendingConnection()函数,可以获取到与客户端连接QTcpSocket对象。可以为每个客户端连接创建一个新的QTcpSocket对象。 为了处理多个客户端连接,我们可以使用一个QList或QVector来存储所有的QTcpSocket对象。在处理新连接的槽函数中,将新的QTcpSocket对象添加到列表中。这样我们就可以通过遍历列表,对每个客户端进行操作。 当服务端从某个客户端接收到数据时,可以通过connected()信号与readyRead()信号来读取数据。当服务端要发送数据给客户端时,可以调用QTcpSocket的write()函数。 如果某个客户端断开连接QTcpSocket会触发disconnected()信号,我们可以在该信号的槽函数中将对应的QTcpSocket对象从列表中移除,并释放内存。 为了确保多个客户端可以同时进行连接和通信,可以使用多线程或者多线程框架(如QtConcurrent)来实现。每个客户端连接可以在一个单独的线程中进行处理。 总的来说,Qt提供了丰富的功能来处理TCP连接多个客户端。我们可以通过QTcpServer和QTcpSocket类来实现服务端与多个客户端之间的通信,使用容器来存储多个客户端连接对象,并使用多线程来处理多个客户端的并发连接。 ### 回答3: 在Qt中实现TCP连接多个客户端,我们可以使用Qt的网络模块来处理。 首先,我们需要创建一个TcpServer对象来接受客户端连接请求和处理数据传输。 然后,我们可以使用QObject的connect方法将TcpServer的newConnection信号与自定义的槽方法连接起来。在槽方法中,我们可以获取到新连接SocketDescriptor,并创建一个TcpSocket对象来处理与客户端的通信。 在TcpSocket对象中,我们可以使用QObject的connect方法将TcpSocket的readyRead信号连接到自定义的槽方法上。在槽方法中,我们可以获取到客户端发送的数据,并进行相应的处理。 此外,我们还可以使用QObject的connect方法将TcpSocket的disconnected信号连接到自定义的槽方法上,用于处理客户端断开连接的情况。 在main函数中,我们可以创建多个TcpSocket对象,并连接到同一个TcpServer对象上,从而实现多个客户端同时连接的功能。 最后,我们可以通过TcpSocket的write方法向客户端发送数据,也可以通过TcpSocket的close方法主动关闭与客户端连接。 通过以上步骤,我们就可以实现QtTCP连接多个客户端的功能。
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值