main.cpp
#include <QApplication>
#include "client.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
//[since 5.0] applicationDisplayName : QString
//此属性包含此应用程序的用户可见名称
//该名称显示给用户,例如在窗口标题中。 如果需要,它可以被翻译。
//如果未设置,应用程序显示名称默认为应用程序名称。
QGuiApplication::setApplicationDisplayName(Client::tr("Local Fortune Client"));
Client client;
client.show();
return app.exec();
}
client.h
#ifndef CLIENT_H
#define CLIENT_H
#include <QDialog>
#include <QDataStream>
#include <QLocalSocket>
QT_BEGIN_NAMESPACE
//QLabel 用于显示文本或图像。
//不提供用户交互功能。
//标签的视觉外观可以通过多种方式配置
//它可以用于为另一个小部件指定一个焦点助记键
//QLabel 可以包含以下任何内容类型:
// 纯文本将 QString 传递给 setText()。
// 富文本将包含富文本的 QString 传递给 setText()。
// 一个像素图将一个 QPixmap 传递给 setPixmap()。
// 一个电影通过一个 QMovie 到 setMovie()。
// 数字将 int 或 double 传递给 setNum(),它将数字转换为纯文本。
// 没有与空的纯文本相同的内容。 这是默认设置。 由 clear() 设置。
//警告:当将 QString 传递给构造函数或调用 setText() 时,
//请确保清理您的输入
//,因为 QLabel 会尝试猜测它是将文本显示为纯文本还是富文本(HTML 4 标记的子集)
//您可能希望显式调用 setTextFormat()
//例如 以防您希望文本为纯格式但无法控制文本源(例如,当显示从 Web 加载的数据时)
//当使用这些功能中的任何一个更改内容时,任何先前的内容都会被清除。
//默认情况下,标签显示左对齐、垂直居中的文本和图像
//其中要显示的文本中的任何选项卡都会自动展开
//但是,可以通过多种方式调整和微调 QLabel 的外观。
//可以使用 setAlignment() 和 setIndent() 调整 QLabel 小部件区域内内容的定位。
//文本内容还可以使用 setWordWrap() 沿单词边界换行。
//例如,此代码在右下角设置了一个带有两行文本的下沉面板(两行与标签右侧齐平):
/*
QLabel *label = new QLabel(this);
label->setFrameStyle(QFrame::Panel | QFrame::Sunken);
label->setText("first line\nsecond line");
label->setAlignment(Qt::AlignBottom | Qt::AlignRight);
*/
//QLabel 继承自 QFrame 的属性和函数也可用于指定要用于任何给定标签的小部件框架。
//QLabel 通常用作交互式小部件的标签。
//为此,QLabel 提供了一种有用的机制来添加助记符
//该助记符会将键盘焦点设置到其他小部件(称为 QLabel 的“伙伴”)
/*
QLineEdit *phoneEdit = new QLineEdit(this);
QLabel *phoneLabel = new QLabel("&Phone:",this);
phoneLabel->setBuddy(phoneEdit);
*/
//在此示例中,当用户按下 Alt+P 时,键盘焦点将转移到标签的伙伴(QLineEdit)。
//如果好友是一个按钮(从 QAbstractButton 继承)
//触发助记符将模拟按钮点击。
class QLabel;
class QLineEdit;
class QPushButton;
QT_END_NAMESPACE
class Client : public QDialog
{
Q_OBJECT
public:
explicit Client(QWidget *parent = nullptr);
private slots:
void requestNewFortune();
void readFortune();
void displayError(QLocalSocket::LocalSocketError socketError);
void enableGetFortuneButton();
private:
QLineEdit *hostLineEdit;
QPushButton *getFortuneButton;
QLabel *statusLabel;
//在 Windows 上这是一个命名管道,在 Unix 上这是一个本地域套接字。
//如果发生错误,则 error() 返回错误类型
//并且可以调用 errorString() 以获取对发生的事情的人类可读描述
//尽管 QLocalSocket 是为与事件循环一起使用而设计的
//但也可以在没有事件循环的情况下使用它。
//在这种情况下,您必须使用 waitForConnected()、waitForReadyRead()、
//waitForBytesWritten() 和 waitForDisconnected(),它们会阻塞直到操作完成或超时到期。
QLocalSocket *socket;
//数据流是编码信息的二进制流,它 100% 独立于主机的操作系统、CPU 或字节顺序。
//例如,Windows 下的 PC 写入的数据流可以被运行 Solaris 的 Sun SPARC 读取
//您还可以使用数据流来读取/写入未编码的原始二进制数据
//如果您想要“解析”输入流,请参阅 QTextStream。
//QDataStream 类实现了 C++ 基本数据类型的序列化
//如 char、short、int、char * 等
//更复杂数据的序列化是通过将数据分解为原始单元来实现的。
//数据流与 QIODevice 密切合作
//QIODevice 代表一种可以从中读取数据和向其写入数据的输入/输出介质。
//QFile 类是 I/O 设备的一个示例。
/*
QFile file("file.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file); //we will serialize the data into the file
out<<QString("the answer is"); //serialize a string
out<<(qint32)43; //serialze an integer
*/
//示例(从流中读取二进制数据):
/*
QFile file("file.dat");
file.open(QIODevice::ReadOnly);
QDataStream in(&file); //read the data serialized from the file
QString str;
qint32 a;
in>>str>>a; //extra "the answer is" and 42
*/
//写入流的每个项目都以预定义的二进制格式写入
//该格式因项目的类型而异
//支持的 Qt 类型包括 QBrush、QColor、QDateTime、QFont、
//QPixmap、QString、QVariant 等
//有关支持数据流的所有 Qt 类型的完整列表
//请参阅序列化 Qt 数据类型。
//对于整数,最好始终转换为 Qt 整数类型进行写入
//然后读回相同的 Qt 整数类型
//这可确保您获得所需大小的整数,并使您免受编译器和平台差异的影响。
//枚举可以通过 QDataStream 进行序列化,而无需手动定义流操作符。
//枚举类使用声明的大小进行序列化。
//举一个例子,一个 char * 字符串被写成一个 32 位整数
//等于包含 '\0' 字节的字符串的长度,后跟包含 '\0' 字节的字符串的所有字符
//读取 char * 字符串时,读取 4 个字节以创建 32 位长度值,然后读取包含 '\0' 终止符的 char * 字符串的许多字符。
//初始 I/O 设备通常在构造函数中设置,但可以使用 setDevice() 更改
//如果您已经到达数据的末尾(或者如果没有设置 I/O 设备)atEnd() 将返回 true。
//QDataStream 的二进制格式自 Qt 1.0 以来一直在发展
//并且可能会继续发展以反映 Qt 中所做的更改。
//在输入或输出复杂类型时,确保使用相同版本的流 (version()) 进行读取和写入非常重要
//如果您需要向前和向后兼容,您可以在应用程序中硬编码版本号:
//stream.setVersion(QDataStream::Qt_4_0);
//如果您正在生成新的二进制数据格式
//例如应用程序创建的文档的文件格式
//您可以使用 QDataStream 以可移植格式写入数据。
//常,您会编写一个简短的标题,其中包含一个魔法字符串和一个版本号,以便为自己未来的扩展留出空间
/*
QFile file("file.xxx");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
//Write a header with a "magic number" and a version
out<<(quint32)0xA0B0C0D0;
out<<(qint32)123;
out.setVersion(QDataStream::Qt_4_0);
//Write the data
out<<lots_of_interesting_data;
*/
//Then read it in with:
/*
QFile file("file.xxx");
file.open(QIODevice::ReadOnly);
QDataStream in(&file);
//Read and check the header
quint32 magic;
in>>magic;
if(magic != 0xA0B0C0D0)
return XXX_BAD_FILE_FORMAT;
//Read the version
qint32 version;
in >> version;
if(version < 100)
return XXX_BAD_FILE_TOO_OLD;
if(version > 123)
return XXX_BAD_FILE_TOO_NEW;
if(version <= 100)
in.setVersion(QDataStream::Qt_3_2);
else
in.setVersion(QDataStream::Qt_4_0);
//Read the data
in>>lots_of_interesting_data;
if(version >= 120)
in>>data_new_in_XXX_version_1_2;
in>>other_interesting_data;
*/
//您可以选择在序列化数据时使用的字节顺序
//默认设置为大端(MSB 优先)。
//将其更改为小端会破坏可移植性(除非读者也更改为小端)
//此设置,除非您有特殊要求。您可以选择序列化数据时使用的字节顺序。
//Reading and Writing Raw Binary Data
//您可能希望直接从数据流读取/写入您自己的原始二进制数据
//可以使用 readRawData() 从流中将数据读取到预分配的 char * 中。
//类似地,可以使用 writeRawData() 将数据写入流
//请注意,数据的任何编码/解码都必须由您完成。
//一对类似的函数是 readBytes() 和 writeBytes()。
//它们不同于它们的原始对应物如下:
//readBytes() 读取一个 quint32,它被视为要读取的数据的长度
//然后将该字节数读入预分配的 char *;
//writeBytes() 写入包含数据长度的 quint32,后跟数据。 请注意
//数据的任何编码/解码(除了长度 quint32)都必须由您完成。
//Reading and Writing Qt Collection Classes
//Qt 容器类也可以序列化为 QDataStream。
//其中包括 QList、QSet、QHash 和 QMap。 流运算符被声明为类的非成员
//Reading and Writing Other Qt Classes
//除了此处记录的重载流运算符之外,您可能想要序列化为
//QDataStream 的任何 Qt 类都将具有声明为非类成员的适当流运算符:
/*
QDataStream &operator<<(QDataStream& const Qxxx &);
QDataStream &operator>>(QDataStream&, QXxx&);
*/
//例如,以下是声明为 QImage 类的非成员的流运算符:
/*
QDataStream & operator<<(QDataStream& stream, const QImage& image);
QDataStream & operator>>(QDataStream& stream, QImage& image);
*/
//要查看您最喜欢的 Qt 类是否定义了类似的流运算符
//请查看类文档页面的相关非成员部分。
//使用读取事务
//当数据流在异步设备上运行时,数据块可以在任意时间点到达。
//QDataStream 类实现了一种事务机制,
//该机制提供了使用一系列流操作符以原子方式读取数据的能力。
//例如,您可以通过在连接到 readyRead() 信号的插槽中使用事务来处理来自套接字的未完成读取:
/*
in.startTransaction();
QString str;
qint32 a;
in>>str>>a;//try to read packet atomically
if(!in.commitTransction())
return; //wait for more data
*/
QDataStream in;
quint32 blockSize;
QString currentFortune;
};
#endif
client.cpp
#include <QtWidgets>
#include <QtNetwork>
#include "client.h"
Client::Client(QWidget *parent)
: QDialog(parent),
hostLineEdit(new QLineEdit("fortune")),
getFortuneButton(new QPushButton(tr("Get Fortune"))),
statusLabel(new QLabel(tr("This examples requires that you run the "
"Local Fortune Server example as well."))),
socket(new QLocalSocket(this))
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
QLabel *hostLabel = new QLabel(tr("&Server name:"));
hostLabel->setBuddy(hostLineEdit);
statusLabel->setWordWrap(true);
getFortuneButton->setDefault(true);
QPushButton *quitButton = new QPushButton(tr("Quit"));
//void QButtonGroup::addButton(QAbstractButton *button, int id = -1)
//将给定的按钮添加到按钮组
//如果 id 为 -1,则会为按钮分配一个 id。
//自动分配的 ID 保证为负,从 -2 开始。 如果您要分配自己的 ID
//请使用正值以避免冲突。
//对话框和消息框通常以符合该平台界面指南的布局呈现按钮
//不同平台的对话框总是有不同的布局
//QDialogButtonBox 允许开发人员向其添加按钮
//并将自动为用户的桌面环境使用适当的布局。
//对话框的大多数按钮都遵循某些角色。 此类角色包括:
//接受或拒绝对话。
//寻求帮助。
//对对话框本身执行操作(例如重置字段或应用更改)。
//也可以有其他方式关闭对话框,这可能会导致破坏性结果。
//大多数对话框都有几乎可以被视为标准的按钮(例如确定和取消按钮)。
//有时以标准方式创建这些按钮会很方便。
//有几种使用 QDialogButtonBox 的方法
//一种方法是自己创建按钮(或按钮文本)并将它们添加到按钮框
//指定它们的作用。
/*
findButton = new QPushButton(tr("&Find"));
findButton->setDefault(true);
moreButton = new QPushButton(tr("&More"));
moreButton->setCheckable(true);
moreButton->setAutoDefault(false);
*/
//或者,QDialogButtonBox 提供了几个您可以使用的标准按钮
//(例如确定、取消、保存)。 它们作为标志存在,因此您可以在构造函数中将它们组合在一起。
/*
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
| QDialogButtonBox::Cancel);
connect(buttonBox,&QDialogButtonBox::accept,this,&QDialog::accept);
connect(buttonBox,&QDialogButtonBox::rejected,this,&QDialog::reject);
*/
//您可以混合搭配普通按钮和标准按钮。
//目前,如果按钮框是水平的,按钮的布局方式如下:
QDialogButtonBox *buttonBox = new QDialogButtonBox;
buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
//void QDataStream::setDevice(QIODevice *d)
//将 I/O 设备设置为 d,可以为 nullptr 以取消设置为当前 I/O 设备。
in.setDevice(socket);
//void QDataStream::setVersion(int v)
//将数据序列化格式的版本号设置为 v,即 Version 枚举的值。
in.setVersion(QDataStream::Qt_5_10);
connect(hostLineEdit, &QLineEdit::textChanged,
this, &Client::enableGetFortuneButton);
connect(getFortuneButton, &QPushButton::clicked,
this, &Client::requestNewFortune);
connect(quitButton, &QPushButton::clicked, this, &Client::close);
connect(socket, &QLocalSocket::readyRead, this, &Client::readFortune);
connect(socket, &QLocalSocket::errorOccurred, this, &Client::displayError);
QGridLayout *mainLayout = new QGridLayout(this);
mainLayout->addWidget(hostLabel, 0, 0);
mainLayout->addWidget(hostLineEdit, 0, 1);
mainLayout->addWidget(statusLabel, 2, 0, 1, 2);
mainLayout->addWidget(buttonBox, 3, 0, 1, 2);
setWindowTitle(QGuiApplication::applicationDisplayName());
hostLineEdit->setFocus();
}
void Client::requestNewFortune()
{
getFortuneButton->setEnabled(false);
blockSize = 0;
//中止当前连接并重置套接字。
//与 disconnectFromServer() 不同,
//此函数会立即关闭套接字,清除写入缓冲区中的所有挂起数据。
socket->abort();
//[since 5.1] void QLocalSocket::connectToServer(QIODeviceBase::OpenMode openMode = ReadWrite)
//尝试与 serverName() 建立连接。
//在打开连接之前必须调用 setServerName()。
//或者,您可以使用 connectToServer(const QString &name, OpenMode openMode);
//套接字在给定的 openMode 中打开并首先进入 ConnectingState。
//如果建立了连接,QLocalSocket 进入 ConnectedState 并发出 connected()。
//调用此函数后,套接字可以发出 errorOccurred() 以表示发生了错误。
socket->connectToServer(hostLineEdit->text());
}
//[virtual] qint64 QIODevice::bytesAvailable() const
//返回可供读取的字节数。
//此函数通常与顺序设备一起使用,以确定读取前要在缓冲区中分配的字节数。
//重新实现此函数的子类必须调用基本实现以包含 QIODevice 的缓冲区大小。 例子:
/*
qint64 CustomDevice::bytesAvailable() const
{
return buffer.size() + QIODevice::bytesAvailable();
}
*/
void Client::readFortune()
{
if (blockSize == 0) {
// Relies on the fact that QDataStream serializes a quint32 into
// sizeof(quint32) bytes
if (socket->bytesAvailable() < (int)sizeof(quint32))
return;
in >> blockSize;
}
if (socket->bytesAvailable() < blockSize || in.atEnd())
return;
QString nextFortune;
in >> nextFortune;
if (nextFortune == currentFortune) {
QTimer::singleShot(0, this, &Client::requestNewFortune);
return;
}
currentFortune = nextFortune;
statusLabel->setText(currentFortune);
getFortuneButton->setEnabled(true);
}
void Client::displayError(QLocalSocket::LocalSocketError socketError)
{
switch (socketError) {
case QLocalSocket::ServerNotFoundError:
QMessageBox::information(this, tr("Local Fortune Client"),
tr("The host was not found. Please make sure "
"that the server is running and that the "
"server name is correct."));
break;
case QLocalSocket::ConnectionRefusedError:
QMessageBox::information(this, tr("Local Fortune Client"),
tr("The connection was refused by the peer. "
"Make sure the fortune server is running, "
"and check that the server name "
"is correct."));
break;
case QLocalSocket::PeerClosedError:
break;
default:
QMessageBox::information(this, tr("Local Fortune Client"),
tr("The following error occurred: %1.")
.arg(socket->errorString()));
}
getFortuneButton->setEnabled(true);
}
void Client::enableGetFortuneButton()
{
getFortuneButton->setEnabled(!hostLineEdit->text().isEmpty());
}