一、说明:
在标准 C++ 没有提供专门用于套接字通信的类,所以只能使用操作系统提供的基于 C 的 API 函数,基于这些 C 的 API 函数我们也可以封装自己的 C++ 类
但是 Qt 就不一样了,它是 C++ 的一个框架并且里边提供了用于套接字通信的类(TCP、UDP)这样就使得我们的操作变得更加简单了(当然,在Qt中使用标准C的API进行套接字通信也是完全没有问题的)。以下使用相关类的进行 TCP 通信
使用 Qt 提供的类进行基于 TCP 的套接字通信需要用到两个类:
1、QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接
2、QTcpSocket:通信的套接字类,客户端、服务器端都需要使用
二、QTcpServer 类:
1、公共成员函数:
//构造一个用于监听的服务器端对象
QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);
//设置监听,参数一:绑定本地的ip,参数二:指定的端口
bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
// 判断当前对象是否在监听, 是返回true,没有监听返回false
bool QTcpServer::isListening() const;
// 如果当前对象正在监听返回监听的服务器地址信息, 否则返回 QHostAddress::Null
QHostAddress QTcpServer::serverAddress() const;
// 如果服务器正在侦听连接,则返回服务器的端口; 否则返回0
quint16 QTcpServer::serverPort() const
参数:
address:通过类 QHostAddress 可以封装 IPv4、IPv6 格式的 IP 地址,QHostAddress::Any 表示自动绑定
port:如果指定为 0 表示随机绑定一个可用端口
返回值:绑定成功返回 true,失败返回 false
得到和客户端建立连接之后用于通信的 QTcpSocket 套接字对象,它是 QTcpServer 的一个子对象,当 QTcpServer 对象析构的时候会自动析构这个子对象,当然也可自己手动析构,建议用完之后自己手动析构这个通信的 QTcpSocket 对象。
QTcpSocket *QTcpServer::nextPendingConnection();
阻塞等待客户端发起的连接请求,不推荐在单线程程序中使用,建议使用非阻塞方式处理新连接,即使用信号 newConnection()
bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR);
参数:
msec:指定阻塞的最大时长,单位为毫秒(ms)
timeout:传出参数,如果操作超时 timeout 为 true,没有超时 timeout 为 false
2、信号:
//当接受新连接导致错误时,将发射如下信号。socketError 参数描述了发生的错误相关的信息
[signal] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError);
//每次有新连接可用时都会发出 newConnection () 信号
[signal] void QTcpServer::newConnection();
三、QTcpSocket类:
1、说明:
QTcpSocket 是一个套接字通信类,不管是客户端还是服务器端都需要使用。在 Qt 中发送和接收数据也属于 IO 操作(网络 IO),其继承关系如下:
2、公共成员函数:
QTcpSocket::QTcpSocket(QObject *parent = Q_NULLPTR);
[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
[virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite);
注:
在 Qt 中不管调用读操作函数接收数据,还是调用写函数发送数据,操作的对象都是本地的由 Qt 框架维护的一块内存。因此,调用了发送函数数据不一定会马上被发送到网络中,调用了接收函数也不是直接从网络中接收数据,关于底层的相关操作是不需要使用者来维护的
// 指定可接收的最大字节数 maxSize 的数据到指针 data 指向的内存中
qint64 QIODevice::read(char *data, qint64 maxSize);
// 指定可接收的最大字节数 maxSize,返回接收的字符串
QByteArray QIODevice::read(qint64 maxSize);
// 将当前可用操作数据全部读出,通过返回值返回读出的字符串
QByteArray QIODevice::readAll();
// 发送指针 data 指向的内存中的 maxSize 个字节的数据
qint64 QIODevice::write(const char *data, qint64 maxSize);
// 发送指针 data 指向的内存中的数据,字符串以 \0 作为结束标记
qint64 QIODevice::write(const char *data);
// 发送参数指定的字符串
qint64 QIODevice::write(const QByteArray &byteArray);
3、信号:
在使用 QTcpSocket 进行套接字通信的过程中,如果该类对象发射出 readyRead() 信号,说明对端发送的数据达到了,之后就可以调用 read 函数接收数据了
/*
在使用 QTcpSocket 进行套接字通信的过程中,如果该类对象发射出 readyRead() 信号,
说明对端发送的数据达到了,之后就可以调用read 函数接收数据了
*/
[signal] void QIODevice::readyRead();
//调用 connectToHost() 函数并成功建立连接之后发出 connected() 信号
[signal] void QAbstractSocket::connected();
//在套接字断开连接时发出 disconnected() 信号
[signal] void QAbstractSocket::disconnected();
四、通信流程:
1、流程:
①、创建套接字服务器 QTcpServer 对象
②、通过 QTcpServer 对象设置监听,即:QTcpServer::listen()
③、基于 QTcpServer::newConnection() 信号检测是否有新的客户端连接
④、如果有新的客户端连接调用 QTcpSocket *QTcpServer::nextPendingConnection() 得到通信的套接字对象
⑤、使用通信的套接字对象 QTcpSocket 和客户端进行通信
2、服务器端程序:
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
RESOURCES += \
ear.qrc
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpServer> //套接字模块的头文件
#include <QTcpSocket> //套接字通信的头文件
#include <QLabel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_jtfw_clicked();
void on_fsxx_clicked();
private:
Ui::MainWindow *ui;
QTcpServer* m_s;
QTcpSocket* m_tcp; //创建套接字通信对象
QLabel* m_status;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("服务器"); //给窗口添加标题
//2.1、设置默认端口,用于方便测试
ui->duankou->setText("7788");
//1、创建监听的服务器对象
m_s = new QTcpServer(this); //需要在指定父对象this
//等待客户端连接的槽函数,如果有客户端连接,则m_s会发出newConnection信号
connect(m_s,&QTcpServer::newConnection,this,[=]()
{
m_tcp = m_s->nextPendingConnection(); //得到一个用于通讯的套接字对象:m_tcp
m_status->setPixmap(QPixmap(":/img/pppp.jpg").scaled(20,20)); //连接状态,scaled函数设置图标大小
//检测是否可以接受数据 */
connect(m_tcp,&QTcpSocket::readyRead,this,[=]() //当有客户端发起连接,就发射出 readyRead 信号
{
QByteArray data = m_tcp->readAll(); //读取接收的数据
ui->record->append("客户端:" + data); //将接收到的是数据显示(追加)在信息框
});
//4.1、当m_tcp发射出eradyRead信号,就说明客户端断开了连接
connect(m_tcp,&QTcpSocket::disconnected,this,[=]()
{
//m_tcp->close(); //服务器也断开连接(关闭连接)
ui->record->append("客户端已经断开了连接");
m_tcp->deleteLater(); //释放m_tcp指向的内存,可直接调用deleteLater,因为里面封装了delete
m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20)); //断开状态,scaled函数设置图标大小
});
});
//4、状态栏的处理的动作
m_status = new QLabel(this); //可以不指定父对象,因为是再状态栏里
//默认状态,scaled函数设置图标大小
m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20));
//设置到状态栏中
ui->statusbar->addWidget(new QLabel("连接状态:"));
ui->statusbar->addWidget(m_status);
}
MainWindow::~MainWindow()
{
delete ui;
}
//2、当点击”启动监听服务“按钮,启动监听
void MainWindow::on_jtfw_clicked()
{
//2.2、读取控件的端口号,是unsigned short类型,toUShort()是类型转换
unsigned short duankou = ui->duankou->text().toUShort();
//2.3、调用listen()进行监听,参数一:绑定本地的ip,参数二:指定的端口
m_s->listen(QHostAddress::Any,duankou);
//2.4、当启动监听后,”启动监听服务“按钮不可再按
ui->jtfw->setDisabled(true);
}
//3、当点击”发送信息“按钮,
void MainWindow::on_fsxx_clicked()
{
QString msg = ui->msg->toPlainText(); //以toPlainText(纯文本)的方式读取“发送的信息”框的内容
m_tcp->write(msg.toUtf8());
ui->record->append("服务器端:" + msg); //将接收到的是数据显示(追加)在信息框
ui->msg->clear(); //发送完后清空输入框的内容
}
3、客户端程序:
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
RESOURCES += \
ear.qrc
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpSocket> //套接字通信的头文件
#include <QLabel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_jtfw_clicked();
void on_dklj_clicked();
void on_fsxx_clicked();
private:
Ui::MainWindow *ui;
QTcpSocket* m_tcp; //创建套接字通信对象
QLabel* m_status;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//设置默认端口,用于方便测试
ui->duankou->setText("7788");
ui->ip->setText("192.168.194.172");
setWindowTitle("客户端"); //给窗口添加标题
ui->dklj->setDisabled(true); //“断开连接”按钮不可按
//ui->dklj->setEnabled(false);
//创建监听的服务器对象
m_tcp = new QTcpSocket; //需要在指定父对象this
//检测是否可以接受数据 */
connect(m_tcp,&QTcpSocket::readyRead,this,[=]() //当有客户端发起连接,就发射出 readyRead 信号
{
QByteArray data = m_tcp->readAll(); //读取接收的数据
ui->record->append("客户端:" + data); //将接收到的是数据显示(追加)在信息框
});
//当m_tcp发射出eradyRead信号,就说明客户端断开了连接
connect(m_tcp,&QTcpSocket::disconnected,this,[=]()
{
m_tcp->close(); //服务器也断开连接
m_tcp->deleteLater(); //释放m_tcp指向的内存,可直接调用deleteLater,因为里面封装了delete
m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20)); //断开状态,scaled函数设置图标大小
ui->record->append("服务器已和客户端断开了连接");
ui->dklj->setDisabled(true); //“断开连接”按钮不可按
ui->jtfw->setEnabled(false); //“连接服务器”按钮可按
});
connect(m_tcp,&QTcpSocket::connected,this,[=]()
{
m_status->setPixmap(QPixmap(":/img/pppp.jpg").scaled(20,20));
//将连接成功的信息显示(追加)在信息框
ui->record->append("已经连接到了服务器");
ui->dklj->setDisabled(false);
//ui->dklj->setEnabled(true); //“断开连接”按钮可按
ui->jtfw->setEnabled(false); //“连接服务器”按钮不可按
});
//状态栏的处理的动作
m_status = new QLabel(this); //可以不指定父对象,因为是再状态栏里
m_status->setPixmap(QPixmap(":/img/guoqi.jpg").scaled(20,20)); //默认状态,scaled函数设置图标大小
//设置到状态栏中
ui->statusbar->addWidget(new QLabel("连接状态:"));
ui->statusbar->addWidget(m_status);
}
MainWindow::~MainWindow()
{
delete ui;
}
//3、当点击”发送信息“按钮,
void MainWindow::on_fsxx_clicked()
{
QString msg = ui->msg->toPlainText(); //以toPlainText(纯文本)的方式读取“发送的信息”框的内容
m_tcp->write(msg.toUtf8());
ui->record->append("客户端:" + msg); //将接收到的是数据显示(追加)在信息框
ui->msg->clear(); //发送完后清空输入框的内容
}
//当点击”连接服务器“按钮,与服务器建立联接
void MainWindow::on_jtfw_clicked()
{
QString ip = ui->ip->text(); //获取输入的IP
unsigned short duankou = ui->duankou->text().toUShort(); //获取输入的端口
//连接服务器
m_tcp->connectToHost(QHostAddress(ip),duankou);
}
void MainWindow::on_dklj_clicked()
{
m_tcp->close(); //关闭连接
ui->dklj->setDisabled(true); //“断开连接”按钮不可按
ui->jtfw->setEnabled(true); //“连接服务器”按钮不可按
}