Qt套接字编程 (《精通Qt4编程》文件传输实例分析 & 多客户端连接服务器)

网络编程,OSI(开放式系统互联参考模型)七层参考模型:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
套接字(Socket)是网络通信的基本构建模块,又分为流式套接字(Stream Socket)和数据报套接字(Datagram Socket)两种类型的套接字。
TCP:传送控制协议(Transmission Control Protocol),这是一种提供给用户的可靠的全双工字节流面向连接的协议。
UDP:用户数据报协议(User Datagram Protocol),这是提供给用户进程的无连接协议,用于传送数据而不执行正确性检查。
当然TCP、UDP都归属于传输层协议。

对所用的网络知识简短的介绍,下面步入正题,开始Qt套接字编程~

在TCP/IP网络中两个进程间的相互作用的主要模式是客户机/服务器模式(Client/Server model),是构造分布式应用程序最常用的模式。
Qt中几乎所有的QtNetwork类都是异步的,一般情况下没有必要Socket使用在多线程中。

■、UDP
UDP是不可信赖的,它是基于包的协议。一些应用程序层的协议使用UDP是因为它比TCP更加小巧,数据是从一个主机到另一个主机以包的形式发送的。这里没有连接到的概念,并且如果一个UDP包没有被正确交付,它不会向系统报告任何错误。
下面写一个简单的广播示例,由客户端和服务器两部分组成。

//客户端发送数据
void Client::sendDatagram()
{
QByteArray datagram;
QDataStream out(&datagram, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_3);
out << QDateTime::currentDateTime() << "vic.MINg!" << 3.14;

QUdpSocket udpSocket(this);
udpSocket.writeDatagram(datagram, QHostAddress::Broadcast, 1981);
}

在QByteArray型局部变量datagram中构建待发送的数据包,然后通过QUdpSocket类的 writeDatagram ( const QByteArray & datagram, const QHostAddress & host, quint16 port );函数将数据包发出。值得注意的是,这里的地址使用了QHostAddress::Broadcast值,它对应IPv4下的广播地址,如果将该值更换成单机地址(如本机地址QHostAddress::LocalHost),将变成一个普通的点对点的UDP程序。

//服务器接收数据
void Server::initSocket()
{
udpSocket = new QUdpSocket(this);
udpSocket->bind(1981);

connect(udpSocket, SIGNAL(readyRead()),
this, SLOT(readPendingDatagrams()));
}

初始化生成QUdpSocket实例,并绑定与客户端约定的端口(1981)。这里多说几句,在编写网络程序时应该使用1024以上的端口号,1024以下的端口号通常被系统保留,紧密的绑定了一些服务(如80端口是http服务、21端口是ftp服务)。

void Server::readPendingDatagrams()
{
while (udpSocket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;

udpSocket->readDatagram(datagram.data(), datagram.size(),
&sender, &senderPort);
QDateTime dateTime;
QString name;
double data;
QDataStream in(&datagram, QIODevice::ReadOnly);
in.setVersion(QDataStream::Qt_4_3);
in >> dateTime >> name >> data;
}
}

接受数据函数首先调用QUdpSocket类的成员函数hasPendingDatagrams()以判断是否有可供读取的数据。如果有则通过pendingDatagramSize()获取当前可供读取的UDP报文大小,并据此大小分配接收缓冲区,最后读取相应数据。


■、TCP
TCP是一个基于流的协议。对于应用程序,数据表现为一个长长的流,而不是一个大大的平面文件。基于TCP的高层协议通常是基于行的或者基于块的。
●、基于行的协议把数据作为一行文本进行传输,每行都以一个换行符结尾。
●、基于块的协议把数据作为二进制块进行传输,每块是由一个size大小字段和紧跟它的一个size字节的数据组成。
QTcpSocket通过器父类QAbstractSocket继承了QIODevice,因此他可以通过使用QTextStream和QDataStream来进行读取和写入。
QTcpServer类在服务器端处理来自TCP客户端连接数据,需要注意的是,该类直接继承于QObject基类,而不是QAbstractSocket抽象套接字类。

下面介绍一个TCP应用示例,示例来自《精通Qt4编程》,感觉十分不错,它也是由客户端和服务器两部分组成,客户端选择本地文件,并通过TCP连接将它上传到服务器端。
由于使用了TCP协议,所以可以轻松的传递大文件,而无需担心传输过程造成文件损坏。
其中客户端程序SendFile从本地文件系统中选中一个已有文件并在成功连接服务器后开始发送,服务器端程序ReceiveFile则将该文件保存在当前目录下,两端均以进度条和数据两种形式分别显示文件传输进度和详细的数据传输字节数。
客户端程序SendFile的用户界面是一个简单的对话框,上面布置一个QProgressBar进度条,一个用于显示状态的QLabel,三个QPushButton按钮,分别用来选择文件、发送文件和退出程序。
Qt的QFileDialog类提供了一个文件选择对话框,用户使用它可以很容易的进行目录或文件的选择。
下面将Dialog类部分代码陈列出来,它是QDialog的子类,实现客户端的全部功能。

class Dialog : public QDialog
{
Q_OBJECT

public:
Dialog(QWidget *parent = 0);

public slots:
void start();
void startTransfer();
void updateClientProgress(qint64 numBytes);
void displayError(QAbstractSocket::SocketError socketError);
void openFile();

private:
QProgressBar *clientProgressBar;
QLabel *clientStatusLabel;
QPushButton *startButton;
QPushButton *quitButton;
QPushButton *openButton;
QDialogButtonBox *buttonBox;

QTcpSocket tcpClient; //客户端套接字
qint64 TotalBytes; //总共需发送的字节数
qint64 bytesWritten; //已发送字节数
qint64 bytesToWrite; //待发送字节数
qint64 loadSize; //被初始化为一个4Kb的常量
QString fileName; //待发送的文件的文件名
QFile *localFile; //待发送的文件
QByteArray outBlock; //缓存一次发送的数据
};

为了发送较大的文件,变量使用了qint64类型,Qt保证该类型数据在所有其所支持的平台下均为64位大小,这几乎可以表示一个无限大的文件了
loadSize用来尽可能的将一个较大的文件分割,每次发送4Kb大小,余下不足4Kb的按实际大小发送。

Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
loadSize = 4*1024; // 4Kb
TotalBytes = 0;
bytesWritten = 0;
bytesToWrite = 0;
clientProgressBar = new QProgressBar;
clientStatusLabel = new QLabel(tr("客户端就绪"));
startButton = new QPushButton(tr("开始"));
quitButton = new QPushButton(tr("退出"));
openButton = new QPushButton (tr("打开"));
startButton->setEnabled(false);
buttonBox = new QDialogButtonBox;
buttonBox->addButton(startButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(openButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);

connect(startButton, SIGNAL(clicked()), this, SLOT(start()));
connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));
connect(openButton, SIGNAL(clicked()), this, SLOT(openFile()));
connect(&tcpClient, SIGNAL(connected()), this, SLOT(startTransfer()));
connect(&tcpClient, SIGNAL(bytesWritten(qint64)),
this, SLOT(updateClientProgress(qint64)));
connect(&tcpClient, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(displayError(QAbstractSocket::SocketError)));

QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(clientProgressBar);
mainLayout->addWidget(clientStatusLabel);
mainLayout->addStretch(1);
mainLayout->addSpacing(10);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("发送文件"));
}

这里关联了QTcpSocket的三个重要信号,它们分别是成功与服务器建立连接后产生的connected()信号,数据成功发送后产生的bytesWritten()信号和产生错误的error()信号。

void Dialog::openFile()
{
fileName = QFileDialog::getOpenFileName(this);
if (!fileName.isEmpty())
startButton->setEnabled(true);
}

用户在客户端界面按下"打开"按钮后,openFile()槽函数将被调用。该函数通过Qt文件选择对画框QFileDialog所提供的静态函数getOpenFileName(),能够很容易地返回用户所选取的文件名,这里将其保存在私有成员变量fileName中。如果选中返回的文件名非空,将激活"开始"按钮。

void Dialog::start()
{
startButton->setEnabled(false);
QApplication::setOverrideCursor(Qt::WaitCursor);
bytesWritten = 0;
clientStatusLabel->setText(tr("连接中..."));
tcpClient.connectToHost(QHostAddress::LocalHost, 16689);
}

用户在客户端界面按下"开始"按钮后,start()槽函数将被调用。该函数的主要功能是连接服务器,它使用了QTcpSocket类的connectToHost()函数,其中的两个参数分别是服务器主机地址及其监听端口,读者可以根据实际应用需求进行修改。

void Dialog::startTransfer()
{
localFile = new QFile(fileName);
if (!localFile->open(QFile::ReadOnly )) {
QMessageBox::warning(this, tr("应用程序"),
tr("无法读取文件 %1:\n%2.")
.arg(fileName)
.arg(localFile->errorString()));
return;
}
TotalBytes = localFile->size();
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_4_3);

QString currentFile = fileName.right(fileName.size() - fileName.lastIndexOf('/') - 1);
sendOut << qint64(0) << qint64(0) << currentFile;
TotalBytes += outBlock.size();
sendOut.device()->seek(0);
sendOut << TotalBytes << qint64((outBlock.size() - sizeof(qint64) * 2));
bytesToWrite = TotalBytes - tcpClient.write(outBlock);
clientStatusLabel->setText(tr("已连接"));
qDebug() << currentFile << TotalBytes;
outBlock.resize(0);
}

一旦连接建立成功,QTcpSocket类将发出connected()消息,继而调用startTransfer()槽函数。该函数首先向服务器端发送一个文件头结构。
文件头结构由三个字段组成,分别是64位的总长度(包括文件数据长度和文件头自身长度),64位的文件名长度和文件名。
函数startTransfer()首先以只读方式打开选中的文件,然后通过QFile类的size()函数获取待发送文件的大小,并将该值暂存于TotalBytes变量中。
接下来将发送缓冲区outBlock封装在一个QDataStream类型的变量中,这样做可以很方便的通过重载的"<<"操作符填写文件头结构。
设置文件头结构的操作有些小技巧,这里首先通过QString类的right()函数去掉文件的路径部分,仅将文件部分保存在currentFile变量中,然后通过sendOut << qint64(0) << qint64(0) << currentFile操作构造一个临时的文件头,将该值追加到TotalBytes字段,从而完成实际需发送字节数的记录。
接着通过sendOut.device()->seek(0)函数将读写操作指向从头开始,并且调用类似操作sendOut << TotalBytes << qint64((outBlock.size() - sizeof(qint64) * 2)),填写实际的总长度和文件长度。
需要注意的是,不能错误地通过QString::size()函数获取文件名的大小,该函数返回的是QString类型文件名所包含的字节数,而不是实际所占存储空间的大小,由于字节编码和QString类存储管理的原因,两者往往并不相等。

完成了文件头结构的填写后,调用tcpClient.write(outBlock)函数将该文件头发出,同时修改待发送字节数bytesToWrite。最后,调用outBlock.resize(0)函数清空发送缓冲区以备下次使用。

void Dialog::updateClientProgress(qint64 numBytes)
{
bytesWritten += (int)numBytes;
if (bytesToWrite > 0) {
outBlock = localFile->read(qMin(bytesToWrite, loadSize));
bytesToWrite -= (int)tcpClient.write(outBlock);
outBlock.resize(0);
}
else{
localFile->close();
}
clientProgressBar->setMaximum(TotalBytes);
clientProgressBar->setValue(bytesWritten);
clientStatusLabel->setText(tr("已发送 %1MB").arg(bytesWritten / (1024 * 1024)));
}

一旦数据发出,QTcpSocket类将会产生bytesWritten()信号,继而调用updateClientProgress(qint64)槽函数,参数表示实际已发出的字节数。如果待发送数据计数bytesToWritten大于0,将尽可能地从发送文件中读取4Kb数据,并将其发送,否则发送完毕关闭文件。还需要在此更新亦发和待发数据计数,并以此更新发送进度条和状态显示。

void Dialog::displayError(QAbstractSocket::SocketError socketError)
{
if (socketError == QTcpSocket::RemoteHostClosedError)
return;

QMessageBox::information(this, tr("网络"),
tr("产生如下错误: %1.").arg(tcpClient.errorString()));

tcpClient.close();
clientProgressBar->reset();
clientStatusLabel->setText(tr("客户端就绪"));
startButton->setEnabled(true);
QApplication::restoreOverrideCursor();
}

如果连接或数据传输过程中的某次操作发生错误,QTcpSocket类发出error()信号,并触发错误处理槽函数displayError()。该函数的错误处理方式比较简单,仅是显示出错误对话框并关闭连接。
main()函数实现与以前的例子类似,这里不再叙述了。


服务器端程序ReceiveFile完成的功能与客户端程序恰恰相反,它负责从TCP连接上接收数据,并将其写入当前目录下的指定文件中。
其界面也是一个简单的对话框,上面布置一个QProgressBar进度条,一个用来显示状态的QLabel,两个QPushButton按钮分别用来开启监听和退出程序。
该程序的主要功能也是在一个从QDialog类继承而来的Dialog类中完成的。

class Dialog : public QDialog
{
Q_OBJECT

public:
Dialog(QWidget *parent = 0);

public slots:
void start();
void acceptConnection();
void updateServerProgress();
void displayError(QAbstractSocket::SocketError socketError);

private:
QProgressBar *clientProgressBar;
QProgressBar *serverProgressBar;
QLabel *serverStatusLabel;
QPushButton *startButton;
QPushButton *quitButton;
QPushButton *openButton;
QDialogButtonBox *buttonBox;

QTcpServer tcpServer; //服务器套接字
QTcpSocket *tcpServerConnection; //连接后服务器返回的套接字
qint64 TotalBytes; //总共需接收的字节数
qint64 bytesReceived; //已接收字节数
qint64 fileNameSize; //待接收文件名字节数
QString fileName; //待接收文件的文件名
QFile *localFile; //待接收文件
QByteArray inBlock;
};

Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
TotalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
serverProgressBar = new QProgressBar;
serverStatusLabel = new QLabel(tr("服务端就绪"));

startButton = new QPushButton(tr("接收"));
quitButton = new QPushButton(tr("退出"));

buttonBox = new QDialogButtonBox;
buttonBox->addButton(startButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);

connect(startButton, SIGNAL(clicked()), this, SLOT(start()));
connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));
connect(&tcpServer, SIGNAL(newConnection()), this, SLOT(acceptConnection()));

QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(serverProgressBar);
mainLayout->addWidget(serverStatusLabel);
mainLayout->addStretch(1);
mainLayout->addSpacing(10);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);

setWindowTitle(tr("接收文件"));
}

构造函数负责初始化界面,并将开始和退出按钮与各自的槽函数关联。这里还关联了QTcpServer的newConnection()信号,该信号在有可用的TCP连接是发出。

void Dialog::start()
{
startButton->setEnabled(false);

QApplication::setOverrideCursor(Qt::WaitCursor);
bytesReceived = 0;

while (!tcpServer.isListening() && !tcpServer.listen(QHostAddress::LocalHost,16689)) {
QMessageBox::StandardButton ret = QMessageBox::critical(this,
tr("回环"),
tr("无法开始测试: %1.").arg(tcpServer.errorString()),
QMessageBox::Retry | QMessageBox::Cancel);
if (ret == QMessageBox::Cancel)
return;
}
serverStatusLabel->setText(tr("监听"));
}

当用户按下"接收"按钮后,start()函数开始执行,它调用QTcpServer的isListening()函数和listen()函数判断当前服务器是否已处在监听状态以及在本地16689端口建立监听是否成功。
如果一切正常,服务器端就已经成功监听,随时等待处理客户端的TCP连接请求,否则弹出错误信息,报告错误后返回。

void Dialog::acceptConnection()
{
tcpServerConnection = tcpServer.nextPendingConnection();
connect(tcpServerConnection, SIGNAL(readyRead()),
this, SLOT(updateServerProgress()));
connect(tcpServerConnection, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(displayError(QAbstractSocket::SocketError)));

serverStatusLabel->setText(tr("接受连接"));
tcpServer.close();
}

有客户端请求到来时,QTcpSocket类将会发出newConnection()信号,从而触发acceptConnection()函数。
QTcpServer类在接受了外来TCP连接请求后,可以通过nextPendingConnection()函数获取一个新的已建立连接的子套接字,(该套接字封装在QTcpSocket类中)并返回QTcpSocket类指针,将返回值保存在tcpServerConnection私有变量中。
接下来关联QTcpSocket类的readyRead()信号和error()信号,其中readyRead()信号在新连接中有可读数据时发出,而当新连接中产生错误是会发出error()信号。
由于本例只处理一个客户端请求,因此在返回一个连接后,就调用QTcpSocket类的close()函数关闭服务器端的监听,后面的工作均在新建的tcpServerConnection连接上完成。

void Dialog::updateServerProgress()
{
QDataStream in(tcpServerConnection);
in.setVersion(QDataStream::Qt_4_3);

if(bytesReceived <= sizeof(qint64)*2){
if((tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2)&&(fileNameSize ==0)){
in >> TotalBytes >> fileNameSize;
bytesReceived += sizeof(qint64)*2;
}
if((tcpServerConnection->bytesAvailable() >= fileNameSize)&&(fileNameSize !=0)){
in >> fileName;
bytesReceived += fileNameSize;
localFile = new QFile(fileName);
if (!localFile->open(QFile::WriteOnly )) {
QMessageBox::warning(this, tr("应用程序"),
tr("无法读取文件 %1:\n%2.").arg(fileName).arg(localFile->errorString()));
return;
}
}else{
return;
}
}

if (bytesReceived < TotalBytes){
bytesReceived += tcpServerConnection->bytesAvailable();
inBlock = tcpServerConnection->readAll();
localFile->write(inBlock);
inBlock.resize(0);
}
serverProgressBar->setMaximum(TotalBytes);
serverProgressBar->setValue(bytesReceived);
qDebug()<<bytesReceived;
serverStatusLabel->setText(tr("已接收 %1MB").arg(bytesReceived / (1024 * 1024)));

if (bytesReceived == TotalBytes) {
tcpServerConnection->close();
startButton->setEnabled(true);
QApplication::restoreOverrideCursor();
}
}

当建立的连接有新的可供读取的数据时,QTcpSocket类会发出readyRead()信号,从而触发updateServerProgress()函数。该函数完成数据的接收、存储,并更新进度显示。
首先将上面返回的TCP连接tcpServerConnection封装的QDataStream类型变量in中,同时设置流化数据格式类型为QDataStream::Qt_4_3,与客户端保持一致。现在可以很方便的通过重载后的"<<"操作符读取TCP连接上的数据了。
由于流数据是没有结构的,为了知道接收的文件名以及文件何时接收完毕,必须首先获取文件头结构,这里还有个小问题,由于开始时所传输文件名的长度是未知的,导致文件头结构的长度也是未知的,因此无法知道TCP数据流中前多少字节属于文件头结构部分。实际上文件头结构的接收可分两布完成:
1、从TCP数据流中接收前16个字节(两个qint64结构长),用来确定总共需接收的字节数和文件名长度,并将这两个值保存在私有成员TotalBytes和fileNameSize中,然后根据fileNameSize值接收文件名。值得注意的是,无法保证在上述接收文件头结构过程中,TCP连接上总是有足够的数据,因此在第一步中,需要通过tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2) && (fileNameSize ==0)操作确保至少有16字节的可用数据且文件名长度为0(表示未从TCP连接接收文件名长度字段,仍处于第一步操作),然后调用in >> TotalBytes >> fileNameSize操作读取总共需接收的数据和文件名长度。
2、类似的通过(tcpServerConnection->bytesAvailable() >= fileNameSize) && (fileNameSize !=0)操作确保连接上的数据已包含完整的文件名且文件名长度不为0(表示已从TCP连接接收文件名长度字段,处于第二步操作中),然后调用in >> fileName操作读取文件名,并根据该文件名在本地以只写方式打开一个同名文件localFile,用来保存接收到的数据。
接下来的工作是读取实际的文件数据并保存,以及更新进度显示,直到接收到完全的数据。由于所发送的文件内容自身也是无格式的流,因此在接收文件内容时,只要TCP连接上有数据,就调用tcpServerConnection->readAll()操作将当前全部可读数据读入接收缓冲inBlock中,随后再将该缓冲中的数据写入文件localFile中。当已收到的数据bytesReceived等于TotalBytes时,接收完毕,这时通过tcpServerConnection->close()操作关闭连接。
最后,错误处理函数displayError()和主函数main()与客户端程序类似,这里不再多说了~


通常QTcpSocket类和QTcpServer类以异步方式工作,但可以通过调用其waitFor...()类型的函数实现同步操作,这类操作将阻塞调用线程直到某个信号发出。
例如:在调用了非阻塞的QTcpSocket::connectToHost()函数后紧接着调用QTcpSocket::waitForConnected()函数以阻塞调用线程,知道connected()信号发出。
一般而言,同步操作往往可以简化代码的控制流程,但也存在较大的缺点,调用waitFor...()函数将阻塞事件的处理,对于GUI线程会引起用户界面的冻结。
因此,Qt建议在GUI线程中不使用同步套接字,此时QTcpSocket也不在需要事件循环。


已经写了不少,累呀:( ,可是还有例子要举...

下一个例子,其实是想讲解一个Socket编程最为典型的例子程序了,自己写的聊天程序,这个例子主要讲解的是单服务器、多客户端进行的处理过程
但是,由于一个字"懒"的原因,这里就只对服务端如何实现进行多客户端进行简短的讲解,其实在聊天程序的比较主要的知识点,在下面这个多线程网络程序中也涉及到了~~

现在让我们看看服务器包含的两个类:QCharServer和QCharClient。
QCharServer类继承了QServerSocker,QTcpServer类允许接受外来TCP连接,每当检测到外来TCP连接请求时,会自动调用QTcpServer::incomingConnection()函数,参数为标识socket ID的int型变量。

QCharServer* serverSocket = new QCharServer(this);
if (!serverSocket->listen(QHostAddress::Any, m_port))
{
QMessageBox::critical(this, tr("CharServer"),
tr("Unable To Start The Server: %1.")
.arg(serverSocket->errorString()));
serverSocket->close();
}

在主界面下创建和监听,等待客户端连接。

class QCharServer : public QTcpServer
{
Q_OBJECT
public:
QCharServer(QObject *parent = 0);
private:
void incomingConnection( int socketDescriptor );
signals:
void error(QTcpSocket::SocketError socketError);
};

QCharServer::QCharServer(QObject *parent)
: QTcpServer(parent)
{
}

void QCharServer::incomingConnection(int socketDescriptor)
{
QCharClient *socket = new QCharClient(this);
if (!socket->setSocketDescriptor(socketDescriptor))
{
emit error(socket->error());
return;
}
}

设置socketDescriptor并且将QCharClient保存到一个内部列表中,从而在任何时候,在内存中QCharClient对象的数量和正在服务的客户端数量都是一样的。

QCharClient继承了QTcpSocket并且封装了一个单独的客户端的状态。

class QCharClient : public QTcpSocket
{
Q_OBJECT
public:
QCharClient(QObject *parent = 0);

private slots:
void recvData();
void tryTest();
void clientDisconnected();

private:
void sendData();
};

QCharClient::QCharClient(QObject *parent)
: QTcpSocket(parent)
{
connect(this, SIGNAL(connected()), this, SLOT(clientConnected()));
connect(this, SIGNAL(readyRead()), this, SLOT(recvData()));
connect(this, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
}

void QCharClient::clientConnected()
{
...
}

void QCharClient::recvData()
{
QDataStream in(this);
char buffer[MAX_RECV_BUFFER_SIZE];
memset(buffer, 0, MAX_RECV_BUFFER_SIZE);
unsigned int len = in.readRawData(buffer, MAX_RECV_BUFFER_SIZE);
}

void QCharClient::clientDisconnected()
{
...
deleteLater();
}

void QCharClient::sendData()
{
QDataStream out(this);
char *buffer;
buffer = "vic.MINg";
int len = strlen( buffer );
out.writeRawData(buffer, len);
}

这里没有什么新内容,不做多废话了~


一个多线程的网络时间服务器,这个程序也是来自《精通Qt4编程》一书,每当由客户请求到达时,这个服务器将启动一个新线程为它返回当前的时间,服务器完毕后这个线程将自动退出,同时用户界面会显示当前以接受请求的次数

class TimeServer : public QTcpServer
{
Q_OBJECT
public:
TimeServer(QObject *parent = 0);

protected:
void incomingConnection(int socketDescriptor);
private:
Dialog *dlg;
};

首先需要实现一个TCP服务端类TimeServer,这里直接从QTcpServer类继承,并重写了其虚函数void incomingConnection( int socketDescriptor )。这个函数在TCP服务端有新的连接时被调用,参数这是界面指针,借用这个指针,将线程发出的消息关联到界面的槽函数中。

TimeServer::TimeServer(QObject *parent)
: QTcpServer(parent)
{
dlg = (Dialog*)parent;
}

构造函数十分简单,这里用传入的父类指针parent初始化私有变量dlg就可以了。

void TimeServer::incomingConnection(int socketDescriptor)
{
TimeThread *thread = new TimeThread(socketDescriptor,this);
connect(thread, SIGNAL(finished()), dlg, SLOT(showResult()),Qt::QueuedConnection);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}

在重写的虚函数incomingConnection()中,首先以返回的套接字描述符socketDescriptor创建一个工作线程TimeThread,然后将这个线程的结束消息finished()分别关联到界面显示类的槽函数showResult()用于显示请求计数,以及线程自身的槽函数deleteLater()用于结束线程。
一切准备工作完成后启动这个线程。需要注意的是,在第一个connect操作中,使用了排队连接方式,第二个connect操作中使用了直接连接方式,原因在于前一个信号是跨线程的,后一个信号是在同一个线程中,当然也可以省略connect()函数的最后一个参数,而采用Qt的自动连接选择方式。另一个需要注意的是,由于工作线程中存在网络事件,因此不能被外界线程销毁,这里使用了延迟销毁函数deleterLater()保证由工作线程自身销毁。

class TimeThread : public QThread
{
Q_OBJECT
public:
TimeThread(int socketDescriptor, QObject *parent);
void run();

signals:
void error(QTcpSocket::SocketError socketError);
private:
int socketDescriptor;
};

工作线程TimeThread由QThread类继承而来,这里将重写重要的虚函数run()。此外,还定义了一个出错信号void error(QTcpSocket::SocketError socketError)和一个私有的套接字描述符socketDescriptor。

TimeThread::TimeThread(int socketDescriptor,QObject *parent)
: QThread(parent), socketDescriptor(socketDescriptor)
{
}

构造函数十分简单,这里仅是初始化了私有套接字描述符。

void TimeThread::run()
{
QTcpSocket tcpSocket;
if (!tcpSocket.setSocketDescriptor(socketDescriptor)) {
emit error(tcpSocket.error());
return;
}

QDateTime time;
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_3);
uint time2u = QDateTime::currentDateTime().toTime_t();
out << time2u;
tcpSocket.write(block);
tcpSocket.disconnectFromHost();
tcpSocket.waitForDisconnected();
}

虚函数run()是工作线程的实质所在,当在TimeServer::incomingConnection()函数中调用了start()函数后,这个虚函数开始执行。它首先创建一个QTcpSocket类并置以从构造函数中传入的套接字描述符,用来向客户端传回服务器端的当前时间。如果出错,发出error(tcpSocket.error())信号报告错误;否则,开始获取当前时间并将它传回客户端,然后断开连接等待返回。
这里介绍以下时间数据的传输格式,Qt虽然可以很方便的通过QDateTime类的静态函数currentDateTime()获取一个时间对象,但类结构是无法直接在网络间传输的,此时需要将它转换成一个标准的数据类型后再传输。幸好的是QDateTime类提供了uint toTime_t()const函数,这个函数返回当前自1970-01-01 00:00:00经过了多少秒,为一个uint类型,可以将这个值传输给客户端。在客户端方面,使用QDateTime类void setTime_t(uint seconds)将这个时间还原。

class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
public slots:
void showResult();
private:
QLabel *statusLabel;
QLabel *reqStatusLable;
QPushButton *quitButton;
TimeServer *server;
int count;
};

界面类Dialog比较简单,它实际上就是一个对话框。在此定义了一个用于显示请求次数的槽函数void showResult(),以及用于显示监听端口的标签statusLabel,用于显示请求次数的标签reqStatusLabel,退出按钮quitButton,TCP服务器server和请求次数计数器count。

Dialog::Dialog(QWidget *parent)
: QDialog(parent),count(0)
{
server = new TimeServer(this);
statusLabel = new QLabel;
reqStatusLable = new QLabel;
quitButton = new QPushButton(tr("退出"));
quitButton->setAutoDefault(false);
if (!server->listen()) {
QMessageBox::critical(this, tr("多线程时间服务器"),
tr("无法启动服务器: %1.").arg(server->errorString()));
close();
return;
}

statusLabel->setText(tr("时间服务器运行在端口: %1.\n")
.arg(server->serverPort()));
connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));

QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addStretch(1);
buttonLayout->addWidget(quitButton);
buttonLayout->addStretch(1);

QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(statusLabel);
mainLayout->addWidget(reqStatusLable);
mainLayout->addLayout(buttonLayout);
setLayout(mainLayout);
setWindowTitle(tr("多线程时间服务器"));
}

构造函数Dialog完成了两件事,一件是初始化界面,另一件是启动服务器端的网络监听

void Dialog::showResult()
{
reqStatusLable->setText(tr("第%1次请求完毕.\n").arg(++count));
}

槽函数showResult()功能十分简单,它在标签reqStatusLable上显示当前的请求次数,并将请求计数count加1。

诶呀妈呀!终于写完了,花了我两天的时间,讨厌打字...

转自:http://blog.chinaunix.net/uid-25318628-id-2942375.html

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
初 级 篇 \第1章 Qt初步实践 2 \1.1 第一个Qt程序 2 \1.1.1 建立主程序 2 \1.1.2 建立工程 3 \1.1.3 编译/运行第一个Qt应用程序 8 \1.1.4 第一个Qt程序的代码分析 8 \1.2 使用Qt布局管理器 11 \1.3 关联操作 12 \1.4 小结 13 \第2章 对话框——QDialog 14 \2.1 自定义对话框 14 \2.1.1 建立新类 14 \2.1.2 添加子窗口部件 15 \2.2 加入主程序 22 \2.3 Qt内建(built-in)对话框 24 \2.4 小结 34 \第3章 基础窗口部件——QWidget 35 \3.1 Qt设计器绘制窗口部件 35 \3.1.1 Qt设计器基础 35 \3.1.2 绘制窗口部件 40 \3.2 程序中引入自定义窗口部件 47 \3.2.1 直接使用方式 47 \3.2.2 单一继承方式 49 \3.2.3 多继承方式 51 \3.3 Qt的信号和槽机制 53 \3.3.1 基本原理 53 \3.3.2 设计信号和槽 55 \3.3.3 信号和槽的自动关联 62 \3.4 窗口标志及几何布局 63 \3.4.1窗口标志 64 \3.4.2窗口部件的几何布局 66 \ \3.5 Qt样式表 74 \3.5.1 样式表语法 74 \3.5.2 样式表的应用 76 \3.6 Qt对象模型 79 \3.6.1 元对象系统 79 \3.6.2 属性系统 80 \3.6.3 对象树 83 \3.7 小结 86 \第4章 程序主窗口——QMainWindow 87 \4.1 QMainWindow主窗口框架 87 \4.2 Qt设计器绘制主窗口 88 \4.2.1 菜单 90 \4.2.2 工具栏 93 \4.2.3 中心部件 96 \4.3 代码创建主窗口 98 \4.3.1 创建资源文件 98 \4.3.2 定义主窗口类 98 \4.4 锚接部件 102 \4.5 状态栏 105 \4.6 实现文本编辑器功能 107 \4.7 多文档 118 \4.8 打印文档 119 \4.9 小结 120 \第5章 布局管理 121 \5.1 Qt布局管理器——QLayout 121 \5.1.1 Qt布局管理器简介 121 \5.1.2 布局管理器及窗口部件大小策略 \5.1.2 的应用 125 \5.2 分裂器部件QSplitter 132 \5.3 栈部件QStackedWidget 134 \5.4 工作空间部件QWorkspace 135 \5.5 多文档区部件QMdiArea 148 \5.6 小结 150 \ \中 级 篇 \第6章 2D绘图 152 \6.1 Arthur绘图基础 152 \6.1.1 绘图 152 \6.1.2 绘图设备 174 \6.2 坐标系统与坐标变换 175 \6.2.1 坐标系统 175 \6.2.2 坐标变换 175 \6.3 用不同的字体 177 \6.4 绘图路径——QPainterPath 180 \6.5 QImage与QPixmap绘图设备 182 \6.5.1 QImage 182 \6.5.2 Pixmap 183 \6.6 组合模式绘图 192 \6.7 Graphics View框架 200 \6.7.1 Graphics View体系结构 200 \6.7.2 Graphics View坐标系统 201 \6.7.3 深入Graphics View 202 \6.8 图形图像打印 208 \6.8.1 普通打印过程 208 \6.8.2 特殊窗口部件的打印 210 \6.9 小结 211 \第7章 拖放操作和剪贴板 212 \7.1 拖放操作 212 \7.1.1 拖放操作 212 \7.1.2 定义新的拖放操作类型 214 \7.1.3 Graphics View框架下的拖放 \7.1.3 操作 215 \7.2 使用剪贴板 217 \7.3 小结 218 \第8章 文件处理 219 \8.1 读写文本文件 219 \8.2 操作二进制文件 220 \8.3 临时文件 222 \8.4 目录操作和文件管理 222 \8.4.1 目录操作 222 \8.4.2 文件管理 224 \8.5 监视文件系统变化 225 \8.6 文件引擎 226 \8.7 小结 226 \第9章 网络 227 \9.1 FTP客户端 227 \9.2 HTTP客户端 235 \9.3 UDP应用 239 \9.4 TCP应用 243 \9.5 高级应用 253 \9.5.1 底层操作 253 \9.5.2 使用代理 256 \9.5.3 扩展Qt网络功能 256 \9.5.4 效率问题 260 \9.6 小结 260 \第10章 多线程 261 \10.1 启动一个线程 261 \10.2 线程互斥与同步 264 \10.2.1 临界区问题 265 \10.2.2 使用QMutex 265 \10.2.3 使用QSemaphore 266 \10.2.4 使用QWaitConditon 269 \10.3 线程的其他问题 271 \10.3.1 优先级问题 271 \10.3.2 死锁及优先级反转问题 274 \10.3.3 本地存储问题 275 \10.4 Qt的线程机制 276 \10.4.1 可重入与线程安全 276 \10.4.2 线程与事件循环 277 \10.4.3 线程与信号/槽机制 278 \10.4.4 多线程网络示例 279 \10.5 小结 282 \第11章 事件处理 283 \11.1 事件机制 283 \11.1.1 事件来源与类型 283 \11.1.2 事件处理方法 284 \11.2 事件处理器 285 \11.3 事件过滤器 290 \11.4 加快用户界面响应 292 \11.4.1 使用processEvents()函数 293 \11.4.2 使用定时器 294 \11.5 小结 296 \第12章 数据库 297 \12.1 连接数据库 297 \12.2 常用数据库操作 301 \12.2.1 使用SQL语句 302 \12.2.2 事务操作 304 \12.2.3 使用SQL模型类 304 \12.2.4 数据表示 308 \12.3 Qt数据库应用 310 \12.3.1 使用嵌入式数据库 310 \12.3.2 使用Oracle数据库 313 \12.4 小结 325 \第13章 Qt的模板库和工具类 326 \13.1 Qt容器类 326 \13.1.1 QList、QLinkedList和QVector 327 \13.1.2 QMap、QHash 332 \13.2 QString 334 \13.2.1 隐式共享 335 \13.2.2 内存分配策略 336 \13.2.3 操作字符串 336 \13.2.4 查询字符串数据 337 \13.2.5 字符串的转换 338 \13.3 QVariant 339 \13.4 Qt的算法 341 \13.5 正则表达式 342 \13.5.1 基本的正则表达式 342 \13.5.2 文字捕获 344 \13.6 小结 345 \高 级 篇 \第14章 XML 348 \14.1 DOM 348 \14.1.1 DOM入门 348 \14.1.2 使用DOM 348 \14.1.3 使用DOM写XML文件 352 \14.2 SAX 354 \14.3 基于流的XML API 359 \14.4 小结 365 \第15章 模型/视图结构 366 \15.1 模型/视图结构与MVC设计 \15.1 模式 366 \15.1.1 模型 366 \15.1.2 视图 367 \15.1.3 代理 368 \15.2 使用已有的模型视图类 368 \15.2.1 使用已有的模型和视图类 368 \15.2.2 QListWidget、QtreeWidget \15.2.2 和QTableWidget 370 \15.3 模型(Models) 381 \15.3.1 模型索引 381 \15.3.2 模型角色 382 \15.3.3 自定义模型 382 \15.3.4 代理模型 385 \15.4 视图(Views) 390 \15.4.1 自定义视图 390 \15.4.2 数据-窗口部件映射 390 \15.5 代理(Delegates) 396 \15.5.1 使用已有的代理 396 \15.5.2 自定义代理 396 \15.6 拖放与选中 401 \15.6.1 拖放操作 401 \15.6.2 选中模式 404 \15.7 小结 405 \第16章 高级绘图 406 \16.1 3D绘图——使用OpenGL 406 \16.1.1 创建OpenGL窗口 406 \16.1.2 着色 410 \16.1.3 3D和旋转 411 \16.1.4 纹理贴图 414 \16.2 SVG 417 \16.2.1 绘制SVG图形 418 \16.2.2 生成SVG文件 419 \16.3 小结 420 \第17章 进程与进程间通信 421 \17.1 使用QProcess 421 \17.2 Linux进程间通信 423 \17.3 新型进程间通信——D-Bus 425 \17.3.1 D-Bus简介 425 \17.3.2 安装QtDBus模块 427 \17.3.3 接口与适配器 429 \17.3.4 QtDBus应用实例 432 \17.4 小结 441 \第18章 Qt插件 442 \18.1 Qt插件开发基础 442 \18.2 Qt设计器插件 443 \18.2.1 使用Scratchpad 443 \18.2.2 提升自定义窗口部件 444 \18.2.3 Qt设计器插件开发 444 \18.3 编写数据库插件 451 \18.4 自定义风格插件 455 \18.5 小结 458 \第19章 脚本——QtScript 459 \19.1 执行ECMAScript脚本 459 \19.2 QtScript中的信号和槽 460 \19.3 使用JavaScript操作Qt对象 463 \19.4 基于Prototype的继承 467 \19.5 小结 467 \第20章 国际化 468 \20.1 Unicode与字符编码 468 \20.1.1 Unicode 468 \20.1.2 汉字编码 469 \20.1.3 编码转换 469 \20.2 Qt Linguist 471 \20.2.1 发布管理器 472 \20.2.2 翻译器 474 \20.2.3 加载翻译文件 476 \20.3 语言切换 477 \20.4 小结 477 \第21章 Qt单元测试框架 478 \21.1 QTestLib框架 478 \21.1.1 QTestLib 478 \21.1.2 第一个Qt单元测试 478 \21.2 数据驱动测试 480 \21.3 GUI测试 481 \21.2.1 仿真GUI事件 481 \21.2.2 重放GUI事件 483 \21.3 小结 484 \附录A Qt安装 485 \附录B Qt集成开发环境 492 \附录C qmake速查 501 \附录D 深入Qt源代码 506 \附录E Qt资源 512 序言/前言    前言 \两年前,当我们准备在Linux系统下开发GUI应用软件时,首先想到的就是选择一个GUI应用框架来简化开发。在三大GUI框架GTK+、Qt和wxWidgets 之间,我们选择了Qt 4工具包。作为重量级桌面系统KDE多年的坚实基础,Qt应该是经受了足够的考验。当我们准备编写自己的应用软件时,却发现图书市场上没有一本关于Qt 4的书籍,仅有的只是一些关于Qt 3的资料。由于Qt 3到Qt 4的变化很大,甚至源代码都不兼容,所以这些资料的参考价值并不是太大。于是,我们通过阅读Qt的assistant和examples来学习并使用Qt 4。在逐渐掌握Qt 4的过程中,我们萌发了编写一本关于Qt 4的书来帮助初学者入门的想法。最终,在电子工业出版社博文视点资讯有限公司的大力支持下,我们的想法终于得以付诸实施。 \关于Qt \Qt是挪威的Trolltech公司的旗舰产品,作为跨平台的应用程序框架,是开源的桌面系统KDE的基石。Google Earth,Skype,Opera,Adobe Photoshop Elements,Peforce Visual Client等软件都是基于Qt写成。自Trolltech公司1996年推出Qt 1.0版以来,Qt已经从2.x,3.x发展到了现在的Qt 4.3,本书就是基于最新的Qt 4.3写成。因为Qt 4框架设计得非常优秀,在2006年的第16届Jolt大奖上,Qt 4获得了类库、框架和组件类别的Jolt生产力奖。 \和Java的“一次编译,到处运行”跨平台不同的是,Qt是源代码级的跨平台,一次编写,随处编译。一次开发的Qt应用程序可以移植到不同的平台上,只需重新编译即可运行。Qt支持的平台有: \? Microsoft Windows,包括Windows 98/NT 4.0/2000/XP/Vista; \? UNIX/X11,包括Linux,Sun Solaris,HP-UX,HP Tru64 UNIX,IBM AIX,SGI IRIX等; \? Mac OS X,支持Mac OS X 10.3以上版本; \? 嵌入式Linux,包括支持framebuffer的所有Linux平台。 \Qt还支持嵌入式系统,Qt的嵌入式版本称为Qtopia Core,可以在多种处理器上运行,目标操作系统通常是嵌入式Linux。Qtopia Core应用程序直接使用framebuffer,而不是笨重的X Window系统。Qt相关的另一个产品——Qt Jambi,则是基于Qt库构建的,面向Java程序员的应用程序框架。另外,还有一些开源的在其他语言上的Qt绑定,如C#/Mono的绑定Qyoto,Python的绑定PyQt,Ruby的绑定QtRuby等。有了这些产品,编写Qt程序不再是C++程序员的专利了。 \Qt的发行版本有商业版和开源版。开源版遵循QPL(Q Public License)和GPL(GNU General Public License)协议,商业版则提供了一些特有的模块,如Windows平台上的ActiveQt框架,Oralce、DB2等商业数据库的驱动。本书主要介绍开源版的Qt 4.3。 \阅读本书的基础 \阅读本书的读者需要具有基本的C++程序设计知识,毕竟Qt是用C++编写的应用程序框架。如果要学习QtScript,还需要了解JavaScript。 \本书的结构 \本书共21章,每章讨论一个专题。章节安排上基本采用循序渐进、由浅到深的原则。但最后的高级篇中的章节没有很强的关联,可以按照随意的顺序阅读。每章内容及作者分述如下: \篇章 章 名 作者 内 容 简 介 页码 \初级篇 第1章 Qt初步实践 卢传富 建立了第一个较简单的Qt应用程序,在GUI用户界面中显示一行中文。 2 \ 第2章 对话框 \——QDialog 卢传富介绍了Qt的对话框类QDialog,实现了一个自定义的登录对话框,举例说明了Qt提供的内建对话框类的应用。 14 \ 第3章 基础窗口部件——QWidget 卢传富 \蔡志明首次引入Qt设计器的使用,绘制并实现了一个查找文件功能的部件,介绍了Qt应用程序中使用ui文件的基本方法以及Qt样式表;较深入地分析Qt对象模型的一些基本知识,涉及信号和槽机制、Qt元对象系统、属性系统和对象树机制,以及部件类型和部件的几何布局等内容。 35 \ 第4章 程序主窗口—— QMainWindow 卢传富 Qt应用程序的主窗口是由多个部件/组件构成的框架,本章通过一个简单文本编辑器的例子,介绍了主窗口的菜单、工具条、中心部件、锚接部件和状态条,并通过Qt设计器绘制和手写代码两种方法实现了简单文本编辑器主窗口界面的排布和管理。 87 \ 第5章 布局管理 卢传富布局管理是GUI应用程序编程的一个重要方面。Qt提供了多种布局管理部件,包括Qt布局管理器、分裂器、栈部件、工作空间部件和多文档区部件等。本章一一介绍了这些部件,并举例说明了它们在图形用户界面编程中的应用。 121 \中级篇 第6章 2D绘图 蔡志明本章内容较多,包括Qt的绘图要素、图形变换与坐标系统、绘图设备、图像处理、图像打印等。最后讲解了Qt 4图形系统的模型视图框架——Graphics View框架。 152 \ 第7章 拖放操作与剪贴板 蔡志明 本章简要地说明了基于MIME的拖放操作和剪贴板的使用,关于Graphics View框架的拖放操作也在本章。 212 \ 第8章 文件处理 蔡志明介绍了Qt的文件处理,包括基于流的文本文件和二进制文件处理,文件信息和目录操作,目录以及文件的变化监控,文件引擎的编写。 219 \ 第9章 网络 李立夏介绍了Qt的网络处理,包括编写常见的FTP、HTTP、UDP和TCP程序,以及访问底层网络接口信息和扩展Qt网络模块功能的方法。 227 \ 第10章 多线程 李立夏介绍了Qt的多线程处理,包括两方面内容:传统的线程操作,以及与Qt事件机制相关的操作。这一章还涉及较多的基本概念,并逐一做了介绍。 261 \ 第11章 事件机制 李立夏介绍了Qt的事件处理模型,详细介绍了在Qt程序设计中处理事件的五种方法,并讨论了如何利用Qt事件机制加快用户界面响应速度。 283 \ 第12章 数据库 李立夏介绍了Qt的数据库处理,重点介绍了如何在Qt中使用SQL语句进行数据库操作和如何利用QSqlTableModel这类高层次类进行常见的数据库编程。 297 \ 第13章 Qt的模板库和工具类 卢传富 \蔡志明 Qt提供了丰富的模板库和工具类,本章只是介绍了部分内容。在这一章,重点介绍了Qt的容器类、QString和QVariant类,简介了Qt的算法和Qt正则表达式的使用。 326 \ \ \续表 \篇章 章 名 作者 内 容 简 介 页码 \高级篇 第14章 XML 蔡志明对Qt中的三种XML解析方式(DOM、SAX和基于流的解析)进行了比较和举例。还讲解了如何使用API写XML文件。 348 \ 第15章 模型/视图结构 蔡志明阐述了Qt的模型/视图结构,分别对模型视图的三个组成部分(模型、视图和代理)进行了介绍,演示了如何自定义这些组成部分,并简要说明了拖放以及选中操作。 366 \ 第16章 高级绘图 蔡志明叙述了在Qt中如何使用OpenGL绘图,对基本的OpenGL绘图进行了讲解,介绍了矢量图型文件SVG的读写操作。 406 \ 第17章 进程间通信 李立夏 介绍进程和进程间通信的知识,重点介绍了Qt中桌面环境下基于D-Bus的多进程应用程序开发。 421 \ 第18章 Qt插件 蔡志明 说明了Qt的插件系统,并对Qt Designer插件、数据库插件、风格插件进行了较详细的介绍。 442 \ 第19章 脚本——QtScript 蔡志明 这是Qt 4.3中引入的最新内容,使得Qt能够支持ECMAScript脚本。本章简要地举例说明了在Qt中如何使用脚本,如何将C++对象暴露给脚本。 459 \ 第20章 国际化 骆艳 本章包括编码的处理,Qt Linguist的使用步骤,动态语言切换的内容。 468 \ 第21章 Qt单元测试框架 蔡志明 本章阐述了如何使用QTestLib框架进行数据测试、GUI测试。 478 \ 附录A~E 蔡志明附录中包括Qt在Linux、Windows、Solaris上的安装,KDevelop、Eclipse集成开发环境的使用,qmake的基本应用,Qt源代码分析举例,Qt资源。 485 \如何获取源代码 \由于Qt是跨平台的,因此书中的内容应用能够在Windows、Linux、UNIX和Mac OS上运行,书中的程序可能是在下列三种平台之一上编写:Windows XP/Vista、Linux(SuSE、Fedora Core或红旗)以及Solaris 10 SPARC/X86。因此书中的屏幕截图可能来源于其中的任何一种操作系统。 \要获取本书的源代码,可以访问博文视点资讯有限公司网站获取: \ www.broadview.com.cn。 \致谢 \本书在写作出版的过程中,得到了电子工业出版社孙学瑛编辑的大力帮助,没有她细致的工作和有益的建议,本书难以最终出版,在此,作者向孙学瑛编辑表示诚挚的谢意。 \问题反馈 \欢迎广大读者和专家对本书提出建议和批评。如果您认为书有错误或对我们有什么建议,可以联系jsj@phei.com.cn。 \ \蔡志明 卢传富 李立夏 \2007年11月30日于武汉
要实现一个Qt TCP服务器连接多个客户端,可以使用Qt Network模块中的QTcpServer和QTcpSocket类。 首先要创建一个QTcpServer对象,并在其中的某个函数(如`incomingConnection()`)中监听新的客户端连接请求。当有新的客户端连接请求时,可以通过调用`nextPendingConnection()`函数来获得一个QTcpSocket对象,表示与该客户端连接。 接下来,可以将该QTcpSocket对象添加到一个存储所有客户端连接的容器中(如QList<QTcpSocket*>),并对其进行读写操作。可以使用`readyRead()`信号来接收客户端发送的数据,使用`write()`函数来向客户端发送数据。 当客户端连接断开时,可以使用`disconnected()`信号来处理该事件,并从存储所有客户端连接的容器中将该QTcpSocket对象移除。 以下是一个简单的示例代码: ```cpp #include <QTcpServer> #include <QTcpSocket> #include <QList> class MyServer : public QTcpServer { Q_OBJECT public: MyServer(QObject *parent = nullptr) : QTcpServer(parent) {} protected: void incomingConnection(qintptr socketDescriptor) override { QTcpSocket *clientSocket = new QTcpSocket(this); if (!clientSocket->setSocketDescriptor(socketDescriptor)) { delete clientSocket; return; } m_clients.append(clientSocket); connect(clientSocket, &QTcpSocket::readyRead, this, &MyServer::readData); connect(clientSocket, &QTcpSocket::disconnected, this, &MyServer::disconnected); } private slots: void readData() { QTcpSocket *clientSocket = static_cast<QTcpSocket*>(sender()); if (!clientSocket) { return; } QByteArray data = clientSocket->readAll(); // 处理接收到的数据 // 回复客户端 clientSocket->write("Hello, client!"); } void disconnected() { QTcpSocket *clientSocket = static_cast<QTcpSocket*>(sender()); if (!clientSocket) { return; } m_clients.removeOne(clientSocket); clientSocket->deleteLater(); } private: QList<QTcpSocket*> m_clients; }; ``` 在上面的代码中,`MyServer`继承自`QTcpServer`,并重写了`incomingConnection()`函数处理新的客户端连接请求。在该函数中,创建一个新的QTcpSocket对象表示与该客户端连接,并将其添加到存储所有客户端连接的容器中。 在`readData()`函数中,处理接收到的数据并向客户端回复数据。在`disconnected()`函数中,处理客户端断开连接的事件并将其从存储所有客户端连接的容器中移除。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值