转QT中ftp、http、tcpsocket或者udpsocket一些注意事项
当使用网络的时候,不管是ftp、http、tcpsocket或者udpsocket都需要在配置文件中添加:QT += network
一、ftp
说明:qt5没有ftp,qt5只支持QNetworkAccessManager;具体如何让qt5支持ftp,另外说明。
1、ftp简易读取文件
QUrl url
ftp.connectToHost(url.host(), url.port(21));
ftp.login(user,passwd);
ftp.get(url.path(), &file);
ftp.close();
如上所示,可以进行简易的读取ftp文件;ps(如果不用用户名和密码,这里可以直接ftp.login()即可); 这里ftp.get()是异步完成的,它会直接返回;文件还没读取完成,ftp就会被close;虽然被close了,但是文件会继续传输;并且ftp的传输结束,会发送信号done(bool error); 所以在执行之前,可以添加检测传输结束的状态标志
connect(&http, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));
void FtpGet::ftpDone(bool error)
{
if (error) {
std::cerr << "Error: " << qPrintable(ftp.errorString())
<< std::endl;
} else {
std::cerr << "File downloaded as "
<< qPrintable(file.fileName()) << std::endl;
}
file.close();
//到这里,该文件已经传输结束
}
当ftp访问的是目录的时候,则需要使用
connect(&ftp, SIGNAL(listInfo(const QUrlInfo&)),this, SLOT(ftpListInfo(const QUrlInfo&)));
则在ftpListInfo中可以读取到当前目录下面的所有文件及文件夹;文件可以直接通过 ftp.get(urlInfo.name(),file); 来读取文件;文件夹则可以等待当前目录的文件下载完毕之后再进行访问文件夹里面的文件;即在ftpDone(bool error); 函数中继续访问该文件夹中的文件夹;
类似于linux里面的命令:
ftp.cd(currentDir);
ftp.list();
cd命令则是进入currentDir的目录中;list命令则是获取该文件夹中的信息(该目录下所有文件和文件夹的信息)
如上的操作如果中间下载文件的时候发生错误则整个流程会中断;所以可以先递归读完并保存ftp目录下的所有文件的名字(路径名字);然后再get所有的文件;这样子,即使get的时候,中间发生错误,程序的控制在咱们这边,而不会出现整个下载任务终止的情况。
二、http客户端
三、QTcpSocket和QTcpServer
tcpsocket链接服务器的流程:
1、链接服务器:tcpSocket.connectToHost(QHostAddress::LocalHost, 6178);
2、向服务器发送数据:tcpSocket.write(block);
3、读取服务器的数据:QDataStream in(&tcpSocket); in >> date >> departureTime;
4、关闭跟服务器的链接: tcpSocket.close();
tcpsocket在操作各个流程的时候都会发送signal,咱们可以根据发送的信号来进行下一步的操作
connect(&tcpSocket, SIGNAL(connected()), this, SLOT(sendRequest()));
connect(&tcpSocket, SIGNAL(disconnected()),
this, SLOT(connectionClosedByServer()));
connect(&tcpSocket, SIGNAL(readyRead()),
this, SLOT(updateTableWidget()));
connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(error()));
当服务器连接成功的时候会发送connected()信号,接下去咱们可以在链接成功之后进行接下去的操作
如果链接失败则会发送error(QAbstractSocket::SocketError)的信号;咱们捕获该信号之后,可以立即执行 tcpSocket.close();
当跟服务器断开的时候则会向客户端发送disconnected()信号;
当客户端跟服务器之间的通信出现异常的时候,则会发送error的信号
当服务器向客户端发送了消息之后,会发送readyRead()的信号,告诉客户端,可以去读接收到的数据了;
QDataStream in(&tcpSocket);
in.setVersion(QDataStream::Qt_4_3);
tcpSocket.bytesAvailable()
in >> date >> departureTime >> duration
如上所示,在读取tcpSocket获取到的数据的时候,可以通过QDataStream直接来读取数据。 tcpSocket.bytesAvailable()能够知道当前tcpSocket缓存了多少数据;如果数据达到想要的长度马上读取,低于目标长度,可以等待数据达到目标长度再读取。
2、tcpserver
通过监听的方式等待客户端的请求链接 server.listen(QHostAddress::Any, 6178)
这里的6178是端口号,可以根据自身的需求进行设置。
注意
重写tcpserver;
当重写incomingConnection函数的时候,正确的流程需要增加 addPendingConnection(socket); 将该连接放入pending之中,等待后续的操作;当newConnection()这个信号传回的时候,则表示新的链接已经成功了;如下是QTcpServer所默认的操作;
void QTcpServer::incomingConnection(int socketDescriptor)
{
#if defined (QTCPSERVER_DEBUG)
qDebug("QTcpServer::incomingConnection(%i)", socketDescriptor);
#endif
QTcpSocket *socket = new QTcpSocket(this);
socket->setSocketSocketDescriptor(socketDescriptor);
addPendingConnection(socket);
}
这里的QTcpSocket跟客户端的是一样的;相当于端到端的连接;也是在监听 readyRead的操作。
注:当QTcpSocket使用的是组合模式而不是继承模式的时候,tcpserver一定要加addPendingConnection()然后通过等待newConnection的这个signal传出来 ; 因为setSocketSocketDescriptor(没有具体说明,应该是三次握手的过程;内部进行初始化,并有打开io端口等操作) 不一定会成功,当传进了两个相同的socket号的时候则失败。
三、udp
udp则不需要tcp那么繁琐,connect的过程;udp则只需要绑定端口号,等待对方给自己发送数据即可;如下所示;每次别人给自己发送udp数据包的时候,都会触发readyRead信号。
udpSocket.bind(5824);
connect(&udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
void WeatherStation::processPendingDatagrams()
{
QByteArray datagram;
do {
datagram.resize(udpSocket.pendingDatagramSize());
udpSocket.readDatagram(datagram.data(), datagram.size());
} while (udpSocket.hasPendingDatagrams());
QDataStream in(&datagram, QIODevice::ReadOnly);
in.setVersion(QDataStream::Qt_4_3);
in >> dateTime >> temperature >> humidity >> altitude;
}
udp发送数据则是,直接将数据发送给指定的ip地址还有端口号。如下所示;
udpSocket.writeDatagram(datagram, QHostAddress::LocalHost, 5824);
这里跟tcp不一样的地方是,只接收ip地址,不能使用主机名称;
这里能否发送成功与否还是会有信号返回(只是程序简单的返回,不会做其他处理)
并且建议每次发送的数据小于512字节;但是理论情况,每次可以发送8192字节,因为发送数据出去之后,中间路由器会拥堵等情况造成数据包被部分抛弃或者全部抛弃,所以8192字节只是在网络畅通的情况下。
qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddress &address,
quint16 port)
{
if (!d->ensureInitialized(address))
return -1;
qint64 sent = d->socketEngine->writeDatagram(data, size, address, port);
d->cachedSocketDescriptor = d->socketEngine->socketDescriptor();
if (sent >= 0) {
emit bytesWritten(sent);
} else {
d->socketError = d->socketEngine->error();
setErrorString(d->socketEngine->errorString());
emit error(d->socketError);
}
return sent;
}
具体的udp发送数据,如上所述。
在《Qt如何实现tcp通信》?中说到服务端获取与客户端连上的socket是通过连接QTcpServer的newConnection信号,然后在对应的槽函数中使用nextPendingConnection获取socket指针就可以通信了。
除了上述方法外,还有一种方法:
1. 新建一个类,继承至QTcpServer。
2. 重写void incomingConnection(qintptr handle)函数。
3. 在incomingConnection中创建一个socket。设置socket描述符为handle(socket->setSocketDescriptor(handle))。
4.使用socket与客户端通信。
例:
void MyServer::incomingConnection(qintptr handle)
{
QTcpSocket* socket = new QTcpSocket;
socket->setSocketDescriptor(handle);
socket->write("hello");
}
MyServer继承至QTcpServer,在incomingConnection中创建一个QTcpSocket,然后调用socket的setSocketDescriptor函数,传入handle。接着就可以给客户端socket发送数据了。
如果此时也连接newConnection信号,会发现在槽函数中通过nextPendingConnection获取的socket是空的。实际上新连接中的socket就是在incomingConnection中创建的。QTcpServer源码中incomingConnection的实现如下:
void QTcpServer::incomingConnection(qintptr socketDescriptor)
{
#if defined (QTCPSERVER_DEBUG)
qDebug("QTcpServer::incomingConnection(%i)", socketDescriptor);
#endif
QTcpSocket *socket = new QTcpSocket(this);
socket->setSocketDescriptor(socketDescriptor);
addPendingConnection(socket);
}
可以看到主要步骤和我们写的incomingConnection差不多,就多了一步addPendingConnection。既然这样那为什么还要重写这个函数呢?。其实有一点可以看到,默认的socket都是创建在主线程中的,我们想要在子线程处理网络操作就没办法了。而重写这个函数,得到handle可以在子线程中创建socket,socket自己创建自己管理主动权在手也能玩出更多的花样。