第十四章:Qt网络编程

回顾:
第一章:Qt的概述
第二章:在Ubuntu编写第一个Qt程序
第三章:Qt的字符串和字符编码
第四章:Qt的信号和槽
第五章:Qt容器窗口(父窗口)
第六章:面向对象的Qt编程
第七章:Qt设计师使用(designer)
第八章:Qt创造器的使用(qtcreator)
第九章:资源和图像
第十章:目录与定时器
第十一章:鼠标和键盘事件
第十二章:Qt数据库(sqlite)
第十三章:QT多线程(QThread)

Qt网络编程

1、网络协议层次(OSI七层)

  1. 应用层
  2. 表示层
  3. 会话层
  4. 传输层:UDP、TCP
  5. 网络层:IPv4,IPv6
  6. 数据链路层
  7. 物理层

2、socket编程

  1. TCP
  2. UDP

3、Qt的socket

  1. QHostAddress //IP地址
    QHostAddress ip("192.168.x.x");
    QHostAddress::LocalHost //127.0.0.1,本地的环回地址
    QHostAddress::Broadcast //255.255.255.255,广播地址
    QHostAddress::Any //0.0.0.0,可以对应任意网卡地址
  2. QAbstractSocket
    bind(ip, port);//绑定IP和端口
    connectToHost(ip, port);//建立和服务器连接
    disconnectFromHost();//断开连接
    state(); //获取套接字状态
    bytesAvailable();//获取套接字等待读取数据的字节数
    信号函数:
    connected()[signal]//连接服务器成功时发送
    disconnected()[signal]//断开服务器成功时发送
    error(QAbstractSocket::SocketError socketError)[signal]//网络异常时发送
    readyRead() [signal] //套接字有数据到来时发送
  3. QUdpSocket
    hasPendingDatagrams() //判断是否有等待读取的数据包
    pendingDatagramSize() //获取等待读取数据包的大小
    readDatagram() //udp读操作
    writeDatagram() //udp写操作

案例:udp广播

  • 发送端
    • 发送网络地址:255.255.255.255
    • 指定发送的端口:8888
    • 输入广播消息
    • 每秒发送一次广播
      SenderDialog.h
#ifndef SENDERDIALOG_H
#define SENDERDIALOG_H

#include 
#include 
#include 

QT_BEGIN_NAMESPACE
namespace Ui { class SenderDialog; }
QT_END_NAMESPACE

class SenderDialog : public QDialog
{
    Q_OBJECT

public:
    SenderDialog(QWidget *parent = nullptr);
    ~SenderDialog();

private slots:
    void on_pushButton_clicked();

    //timed transmission messages slot function
    void sendMessage();

private:
    Ui::SenderDialog *ui;
    QUdpSocket *udpSocket;//UDP socket
    QTimer *timer;//timer
    bool isStarted;//start or stop to broadcast
};
#endif // SENDERDIALOG_H

SenderDialog.cpp

#include "SenderDialog.h"
#include "ui_SenderDialog.h"

SenderDialog::SenderDialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::SenderDialog)
{
    ui->setupUi(this);
    udpSocket =  new QUdpSocket(this);
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(sendMessage()));
    isStarted = false;
}

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


void SenderDialog::on_pushButton_clicked()
{
    if(isStarted == false){
        isStarted = true;
        timer->start(1000);
        ui->pushButton->setText("stop to broadcast");
        ui->messageEdit->setEnabled(false);
        ui->portEdit->setEnabled(false);
    }
    else{
        isStarted = false;
        timer->stop();
        ui->pushButton->setText("start to broadcast");
        ui->messageEdit->setEnabled(true);
        ui->portEdit->setEnabled(true);
    }

}

//timed transmission messages slot function
void SenderDialog::sendMessage()
{
    //get IP and port
    QString msg = ui->messageEdit->text();
    quint16 port = ui->portEdit->text().toShort();
    //send message
    //toUtf8():QString->QByteArray(char [])
    udpSocket->writeDatagram(msg.toUtf8(), QHostAddress::Broadcast, port);
}

  • 接收端
    • 指定接收端口:8888
    • bind(8888)
    • 接收广播消息并显示
      ReceiverDialog.h
#ifndef RECEIVERDIALOG_H
#define RECEIVERDIALOG_H

#include 
#include 

QT_BEGIN_NAMESPACE
namespace Ui { class ReceiverDialog; }
QT_END_NAMESPACE

class ReceiverDialog : public QDialog
{
    Q_OBJECT

public:
    ReceiverDialog(QWidget *parent = nullptr);
    ~ReceiverDialog();

private slots:
    void on_pushButton_clicked();
    //receive message slot function
    void messageReceive();

private:
    Ui::ReceiverDialog *ui;
    QUdpSocket *udpSocket;
    quint16 port;
    bool isStarted;//start or stop receiver
};
#endif // RECEIVERDIALOG_H

ReceiverDialog.cpp

#include "ReceiverDialog.h"
#include "ui_ReceiverDialog.h"

ReceiverDialog::ReceiverDialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::ReceiverDialog)
{
    ui->setupUi(this);
    udpSocket = new QUdpSocket(this);
    isStarted = false;
}

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


void ReceiverDialog::on_pushButton_clicked()
{
    if(isStarted == false){
        isStarted = true;
        //get prot
        port = ui->lineEdit->text().toShort();
        //bind prot
        udpSocket->bind(port);
        //when the port has message, send readyRead signal
        connect(udpSocket, SIGNAL(readyRead()), this, SLOT(messageReceive()));
        ui->lineEdit->setEnabled(false);
        ui->pushButton->setText("stop receive");
    }
    else{
        isStarted = false;
        udpSocket->close();
        ui->lineEdit->setEnabled(true);
        ui->pushButton->setText("start receive");
    }
}

//receive message slot function
void ReceiverDialog::messageReceive()
{
    //adjust has data come now
    if(udpSocket->hasPendingDatagrams()){
        //ready to receive data from buffer
        QByteArray buffer;
        buffer.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(buffer.data(), buffer.size());

        ui->listWidget->addItem(buffer);
        ui->listWidget->scrollToBottom();
    }
}

第十四章:Qt网络编程_第1张图片

  1. QTcpSocket、QTcpServer
    //有客户端和服务器连接时发送
    void QTcpServer::newConnection() [signal]
    //获取和客户端通信的套接字
    QTcpSocket *QTcpServer::nextPendingConnection()

案例:网络聊天室

  • 服务器
    • 使用QTcpServer创建Tcp服务器
    • 获取、保存与客户端通信的套接字
    • 接收客户端发送的消息
    • 转发消息给其他客户端
      mkdir NetChat
      工程名:Server
      ServerDialog.h
#ifndef SERVERDIALOG_H
#define SERVERDIALOG_H

#include 
#include 
#include 
#include 

QT_BEGIN_NAMESPACE
namespace Ui { class ServerDialog; }
QT_END_NAMESPACE

class ServerDialog : public QDialog
{
    Q_OBJECT

public:
    ServerDialog(QWidget *parent = nullptr);
    ~ServerDialog();

private slots:
	//创建服务器按钮对应的槽函数
    void on_pushButton_clicked();
    //当有客户端服务器连接时执行的槽函数
    void onNewConnect(void);
    //当有客户端给服务器发送聊天信息时执行的槽函数 
    void onReadyRead(void);

private:
	//转发聊天信息给所有在线的客户端
    void sendMessage(const QByteArray& msg);

private:
    Ui::ServerDialog *ui;
    QTcpServer server;//服务器对象
    quint16 port;//服务器端口
    //容器:保存所有和客户端通信的套接字
    QList<QTcpSocket*> tcpClientlist;
};
#endif // SERVERDIALOG_H

ServerDialog.cpp

#include "ServerDialog.h"
#include "ui_ServerDialog.h"

ServerDialog::ServerDialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::ServerDialog)
{
    ui->setupUi(this);
    //当有客户端和服务器连接时,发送信号newConnection
    connect(&server, SIGNAL(newConnection()), this, SLOT(onNewConnect()));
}

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


void ServerDialog::on_pushButton_clicked()
{
	//获取端口号
    port = ui->lineEdit->text().toShort();
    //设置监听IP和端口
    bool res = server.listen(QHostAddress::Any, port);
    if(res == true){
        qDebug("create server success!");
    }
    else{
        qDebug("create server fail!");
        return;
    }
    //禁用创建服务器按钮和端口输入
    ui->pushButton->setEnabled(false);
    ui->lineEdit->setEnabled(false);
}
//当有客户端服务器连接时执行的槽函数
void ServerDialog::onNewConnect(void)
{
	//获取和客户端通信的套接字
    QTcpSocket* tcpClientSocket = server.nextPendingConnection();
    //保存套接字到容器
    tcpClientlist.append(tcpClientSocket);
    //当客户端给服务端发送信息时,发送readyRead
    connect(tcpClientSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
}
//当有客户端服务器连接时执行的槽函数
void ServerDialog::onReadyRead(void)
{
	//检查容器中的套接字是否已经断开
    for(int i=0;i<tcpClientlist.size();i++){
        if(tcpClientlist.at(i)->state() == QAbstractSocket::UnconnectedState){
        	//剔除失效的套接字
            tcpClientlist.removeAt(i);
        }
    }
    for(int i=0;i<tcpClientlist.size();i++){
    	//byteAvailable():获取当前套接字等待读取信息的字节数
        if(tcpClientlist.at(i)->bytesAvailable()>0){
        	//读取信息
            QByteArray readBuf = tcpClientlist.at(i)->readAll();
            //显示
            ui->listWidget->addItem(readBuf);
            ui->listWidget->scrollToBottom();
            //转发消息给其他在线客户端
            sendMessage(readBuf);
        }
    }
}

//转发聊天消息给所有在线的客户端
void ServerDialog::sendMessage(const QByteArray& msg)
{
    for(int i=0;i<tcpClientlist.size();i++){
        tcpClientlist.at(i)->write(msg);
        qDebug() << "send message:" << msg;
    }
}

  • 客户端
    • 使用QTcpSocket建立和服务器的连接
    • 获取输入的聊天消息发送到服务器
    • 接收和显示服务器转发的聊天消息
      工程名:Client
      ClientDialog.h
#ifndef CLIENTDIALOG_H
#define CLIENTDIALOG_H

#include 
#include 
#include 
#include 

QT_BEGIN_NAMESPACE
namespace Ui { class ClientDialog; }
QT_END_NAMESPACE

class ClientDialog : public QDialog
{
    Q_OBJECT

public:
    ClientDialog(QWidget *parent = nullptr);
    ~ClientDialog();

private slots:
    //连接服务器按钮对应的槽函数
    void on_ConnectButton_clicked();
    //发送消息按钮对应的槽函数
    void on_sendButton_clicked();
    //和服务器连接成功时执行的槽函数
    void onConnected(void);
    //和服务器断开连接时执行的槽函数
    void onDisConnected(void);
    //接收聊天服务器转发的聊天消息
    void onReadyRead(void);
    //连接服务器按钮对应的槽函数
    void onError(QAbstractSocket::SocketError);

private:
    Ui::ClientDialog *ui;
    bool status;//标记连接状态:在线/离线
    QTcpSocket tcpSocket;//和服务器通信的套接字
    QHostAddress serverIp;//服务器IP
    quint16 serverPort;//服务器端口
    QString username;//聊天昵称
};
#endif // CLIENTDIALOG_H

ClientDialog.cpp

#include "ClientDialog.h"
#include "ui_ClientDialog.h"

ClientDialog::ClientDialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::ClientDialog)
{
    ui->setupUi(this);
    status = false;//离线
    //和服务器连接时发送信号connected
    connect(&tcpSocket, SIGNAL(connected()), this, SLOT(onConnected()));
    //和服务器断开连接时发送信号disconnected
    connect(&tcpSocket, SIGNAL(disconnected()), this, SLOT(onDisConnected()));
    //收到信息时,发送信号readyRead()
    connect(&tcpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
    //网络异常时发送信号Error
    connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
}

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

//
void ClientDialog::on_ConnectButton_clicked()
{
    if(status == false){
        //建立连接
        //获取服务器ip
        QString ip = ui->serverIpEdit->text();
        if(serverIp.setAddress(ip)==false){
            QMessageBox::critical(this, "Error", "IP is error!");
            return;
        }
        //获取服务器端口
        serverPort = ui->serverPortEdit->text().toShort();
        if(serverPort < 1024 || serverPort > 48000){
            QMessageBox::critical(this, "Error", "Port is error!");
            return;
        }
        //获取聊天昵称
        username = ui->usernameEdit->text();
        if(username == ""){
            QMessageBox::critical(this, "Error", "Username is null!");
            return;
        }
        //向服务器发送连接请求
        //成功时发送信号:connected
        //失败时发送信号:disconnected
        tcpSocket.connectToHost(serverIp,serverPort);
        //标记连接状态:在线
        status = true;
    }
    else{
        //断开连接
        //向服务器发送离开聊天室的提示
        QString msg = username + ":" + "check out the chat!";
        tcpSocket.write(msg.toUtf8());
        //关闭当前通信
        //断开连接时发送信号:disconnected
        tcpSocket.disconnectFromHost();
        //标记链接而状态:离线
        status = false;
    }
}

//
void ClientDialog::on_sendButton_clicked()
{
    //获取输入的聊天信息
    QString msg = ui->messageEdit->text();
    msg = username + ":" + msg;
    tcpSocket.write(msg.toUtf8());
    //清空信息输入
    ui->messageEdit->clear();
}

//
void ClientDialog::onConnected(void)
{
    //使能发送消息按钮
    ui->sendButton->setEnabled(true);
    //禁用ip port username
    ui->serverIpEdit->setEnabled(false);
    ui->serverPortEdit->setEnabled(false);
    ui->usernameEdit->setEnabled(false);
    //按钮文本修改:离开聊天室
    ui->ConnectButton->setText("chack out");

    //向服务器发送进入聊天室提示
    QString msg = username+":"+"check in the chat!";
    //发送信息
    tcpSocket.write(msg.toUtf8());
}
//
void ClientDialog::onDisConnected(void)
{
    //禁用相关组件
    ui->sendButton->setEnabled(false);
    ui->serverIpEdit->setEnabled(true);
    ui->serverPortEdit->setEnabled(true);
    ui->usernameEdit->setEnabled(true);
    //按钮文本修改:连接服务器
    ui->ConnectButton->setText("connect to server");
}
//
void ClientDialog::onReadyRead(void)
{
    if(tcpSocket.bytesAvailable() > 0){
        QByteArray readBuf;
        readBuf.resize(tcpSocket.bytesAvailable());
        //读取聊天信息
        tcpSocket.read(readBuf.data(), readBuf.size());
        //显示聊天信息
        ui->listWidget->addItem(readBuf);
        ui->listWidget->scrollToBottom();
    }
}
//
void ClientDialog::onError(QAbstractSocket::SocketError)
{
    QMessageBox::critical(this, "Error", tcpSocket.errorString());
}

第十四章:Qt网络编程_第2张图片

你可能感兴趣的:(socket,c++,qt5)