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

一、TCP和UDP的区别

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

比较项 TCP UDP
是否连接 面向连接 无连接
传输是否可靠 可靠 不可靠
流量控制 提供 不提供
工作方式 全双工 可以是全双工
应用场合 大量数据 少量数据
速度
二、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信号,这个时候我们需要将套接字列表中将对应的套接字删除就可以了。

猜你喜欢

转载自blog.csdn.net/y____xiang/article/details/80571798