source: trunk/src/network/kernel/qhostinfo.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 21.6 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtNetwork module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qhostinfo.h"
43#include "qhostinfo_p.h"
44
45#include "QtCore/qscopedpointer.h"
46#include <qabstracteventdispatcher.h>
47#include <qcoreapplication.h>
48#include <qmetaobject.h>
49#include <qstringlist.h>
50#include <qthread.h>
51#include <qurl.h>
52
53#ifdef Q_OS_UNIX
54# include <unistd.h>
55#endif
56
57QT_BEGIN_NAMESPACE
58
59#ifndef QT_NO_THREAD
60Q_GLOBAL_STATIC(QHostInfoLookupManager, theHostInfoLookupManager)
61#endif
62
63//#define QHOSTINFO_DEBUG
64
65/*!
66 \class QHostInfo
67 \brief The QHostInfo class provides static functions for host name lookups.
68
69 \reentrant
70 \inmodule QtNetwork
71 \ingroup network
72
73 QHostInfo uses the lookup mechanisms provided by the operating
74 system to find the IP address(es) associated with a host name,
75 or the host name associated with an IP address.
76 The class provides two static convenience functions: one that
77 works asynchronously and emits a signal once the host is found,
78 and one that blocks and returns a QHostInfo object.
79
80 To look up a host's IP addresses asynchronously, call lookupHost(),
81 which takes the host name or IP address, a receiver object, and a slot
82 signature as arguments and returns an ID. You can abort the
83 lookup by calling abortHostLookup() with the lookup ID.
84
85 Example:
86
87 \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 0
88
89
90 The slot is invoked when the results are ready. (If you use
91 Qt for Embedded Linux and disabled multithreading support by defining
92 \c QT_NO_THREAD, lookupHost() will block until the lookup has
93 finished.) The results are stored in a QHostInfo object. Call
94 addresses() to get the list of IP addresses for the host, and
95 hostName() to get the host name that was looked up.
96
97 If the lookup failed, error() returns the type of error that
98 occurred. errorString() gives a human-readable description of the
99 lookup error.
100
101 If you want a blocking lookup, use the QHostInfo::fromName() function:
102
103 \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 1
104
105 QHostInfo supports Internationalized Domain Names (IDNs) through the
106 IDNA and Punycode standards.
107
108 To retrieve the name of the local host, use the static
109 QHostInfo::localHostName() function.
110
111 \note Since Qt 4.6.1 QHostInfo is using multiple threads for DNS lookup
112 instead of one dedicated DNS thread. This improves performance,
113 but also changes the order of signal emissions when using lookupHost()
114 compared to previous versions of Qt.
115 \note Since Qt 4.6.3 QHostInfo is using a small internal 60 second DNS cache
116 for performance improvements.
117
118 \sa QAbstractSocket, {http://www.rfc-editor.org/rfc/rfc3492.txt}{RFC 3492}
119*/
120
121static QBasicAtomicInt theIdCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
122
123/*!
124 Looks up the IP address(es) associated with host name \a name, and
125 returns an ID for the lookup. When the result of the lookup is
126 ready, the slot or signal \a member in \a receiver is called with
127 a QHostInfo argument. The QHostInfo object can then be inspected
128 to get the results of the lookup.
129
130 The lookup is performed by a single function call, for example:
131
132 \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 2
133
134 The implementation of the slot prints basic information about the
135 addresses returned by the lookup, or reports an error if it failed:
136
137 \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 3
138
139 If you pass a literal IP address to \a name instead of a host name,
140 QHostInfo will search for the domain name for the IP (i.e., QHostInfo will
141 perform a \e reverse lookup). On success, the resulting QHostInfo will
142 contain both the resolved domain name and IP addresses for the host
143 name. Example:
144
145 \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 4
146
147 \note There is no guarantee on the order the signals will be emitted
148 if you start multiple requests with lookupHost().
149
150 \sa abortHostLookup(), addresses(), error(), fromName()
151*/
152int QHostInfo::lookupHost(const QString &name, QObject *receiver,
153 const char *member)
154{
155#if defined QHOSTINFO_DEBUG
156 qDebug("QHostInfo::lookupHost(\"%s\", %p, %s)",
157 name.toLatin1().constData(), receiver, member ? member + 1 : 0);
158#endif
159 if (!QAbstractEventDispatcher::instance(QThread::currentThread())) {
160 qWarning("QHostInfo::lookupHost() called with no event dispatcher");
161 return -1;
162 }
163
164 qRegisterMetaType<QHostInfo>("QHostInfo");
165
166 int id = theIdCounter.fetchAndAddRelaxed(1); // generate unique ID
167
168 if (name.isEmpty()) {
169 QHostInfo hostInfo(id);
170 hostInfo.setError(QHostInfo::HostNotFound);
171 hostInfo.setErrorString(QCoreApplication::translate("QHostInfo", "No host name given"));
172 QScopedPointer<QHostInfoResult> result(new QHostInfoResult);
173 QObject::connect(result.data(), SIGNAL(resultsReady(QHostInfo)),
174 receiver, member, Qt::QueuedConnection);
175 result.data()->emitResultsReady(hostInfo);
176 return id;
177 }
178
179#ifdef QT_NO_THREAD
180 QHostInfo hostInfo = QHostInfoAgent::fromName(name);
181 hostInfo.setLookupId(id);
182 QScopedPointer<QHostInfoResult> result(new QHostInfoResult);
183 QObject::connect(result.data(), SIGNAL(resultsReady(QHostInfo)),
184 receiver, member, Qt::QueuedConnection);
185 result.data()->emitResultsReady(hostInfo);
186#else
187 QHostInfoLookupManager *manager = theHostInfoLookupManager();
188 if (manager) {
189 // the application is still alive
190 if (manager->cache.isEnabled()) {
191 // check cache first
192 bool valid = false;
193 QHostInfo info = manager->cache.get(name, &valid);
194 if (valid) {
195 info.setLookupId(id);
196 QHostInfoResult result;
197 QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection);
198 result.emitResultsReady(info);
199 return id;
200 }
201 }
202 // cache is not enabled or it was not in the cache, do normal lookup
203 QHostInfoRunnable* runnable = new QHostInfoRunnable(name, id);
204 QObject::connect(&runnable->resultEmitter, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection);
205 manager->scheduleLookup(runnable);
206 }
207#endif
208
209 return id;
210}
211
212/*!
213 Aborts the host lookup with the ID \a id, as returned by lookupHost().
214
215 \sa lookupHost(), lookupId()
216*/
217void QHostInfo::abortHostLookup(int id)
218{
219#ifndef QT_NO_THREAD
220 theHostInfoLookupManager()->abortLookup(id);
221#else
222 // we cannot abort if it was non threaded.. the result signal has already been posted
223 Q_UNUSED(id);
224#endif
225}
226
227/*!
228 Looks up the IP address(es) for the given host \a name. The
229 function blocks during the lookup which means that execution of
230 the program is suspended until the results of the lookup are
231 ready. Returns the result of the lookup in a QHostInfo object.
232
233 If you pass a literal IP address to \a name instead of a host name,
234 QHostInfo will search for the domain name for the IP (i.e., QHostInfo will
235 perform a \e reverse lookup). On success, the returned QHostInfo will
236 contain both the resolved domain name and IP addresses for the host name.
237
238 \sa lookupHost()
239*/
240QHostInfo QHostInfo::fromName(const QString &name)
241{
242#if defined QHOSTINFO_DEBUG
243 qDebug("QHostInfo::fromName(\"%s\")",name.toLatin1().constData());
244#endif
245
246 QHostInfo hostInfo = QHostInfoAgent::fromName(name);
247 QHostInfoLookupManager *manager = theHostInfoLookupManager();
248 manager->cache.put(name, hostInfo);
249 return hostInfo;
250}
251
252/*!
253 \enum QHostInfo::HostInfoError
254
255 This enum describes the various errors that can occur when trying
256 to resolve a host name.
257
258 \value NoError The lookup was successful.
259 \value HostNotFound No IP addresses were found for the host.
260 \value UnknownError An unknown error occurred.
261
262 \sa error(), setError()
263*/
264
265/*!
266 Constructs an empty host info object with lookup ID \a id.
267
268 \sa lookupId()
269*/
270QHostInfo::QHostInfo(int id)
271 : d(new QHostInfoPrivate)
272{
273 d->lookupId = id;
274}
275
276/*!
277 Constructs a copy of \a other.
278*/
279QHostInfo::QHostInfo(const QHostInfo &other)
280 : d(new QHostInfoPrivate(*other.d.data()))
281{
282}
283
284/*!
285 Assigns the data of the \a other object to this host info object,
286 and returns a reference to it.
287*/
288QHostInfo &QHostInfo::operator=(const QHostInfo &other)
289{
290 *d.data() = *other.d.data();
291 return *this;
292}
293
294/*!
295 Destroys the host info object.
296*/
297QHostInfo::~QHostInfo()
298{
299}
300
301/*!
302 Returns the list of IP addresses associated with hostName(). This
303 list may be empty.
304
305 Example:
306
307 \snippet doc/src/snippets/code/src_network_kernel_qhostinfo.cpp 5
308
309 \sa hostName(), error()
310*/
311QList<QHostAddress> QHostInfo::addresses() const
312{
313 return d->addrs;
314}
315
316/*!
317 Sets the list of addresses in this QHostInfo to \a addresses.
318
319 \sa addresses()
320*/
321void QHostInfo::setAddresses(const QList<QHostAddress> &addresses)
322{
323 d->addrs = addresses;
324}
325
326/*!
327 Returns the name of the host whose IP addresses were looked up.
328
329 \sa localHostName()
330*/
331QString QHostInfo::hostName() const
332{
333 return d->hostName;
334}
335
336/*!
337 Sets the host name of this QHostInfo to \a hostName.
338
339 \sa hostName()
340*/
341void QHostInfo::setHostName(const QString &hostName)
342{
343 d->hostName = hostName;
344}
345
346/*!
347 Returns the type of error that occurred if the host name lookup
348 failed; otherwise returns NoError.
349
350 \sa setError(), errorString()
351*/
352QHostInfo::HostInfoError QHostInfo::error() const
353{
354 return d->err;
355}
356
357/*!
358 Sets the error type of this QHostInfo to \a error.
359
360 \sa error(), errorString()
361*/
362void QHostInfo::setError(HostInfoError error)
363{
364 d->err = error;
365}
366
367/*!
368 Returns the ID of this lookup.
369
370 \sa setLookupId(), abortHostLookup(), hostName()
371*/
372int QHostInfo::lookupId() const
373{
374 return d->lookupId;
375}
376
377/*!
378 Sets the ID of this lookup to \a id.
379
380 \sa lookupId(), lookupHost()
381*/
382void QHostInfo::setLookupId(int id)
383{
384 d->lookupId = id;
385}
386
387/*!
388 If the lookup failed, this function returns a human readable
389 description of the error; otherwise "Unknown error" is returned.
390
391 \sa setErrorString(), error()
392*/
393QString QHostInfo::errorString() const
394{
395 return d->errorStr;
396}
397
398/*!
399 Sets the human readable description of the error that occurred to \a str
400 if the lookup failed.
401
402 \sa errorString(), setError()
403*/
404void QHostInfo::setErrorString(const QString &str)
405{
406 d->errorStr = str;
407}
408
409/*!
410 \fn QString QHostInfo::localHostName()
411
412 Returns the host name of this machine.
413
414 \sa hostName()
415*/
416
417/*!
418 \fn QString QHostInfo::localDomainName()
419
420 Returns the DNS domain of this machine.
421
422 Note: DNS domains are not related to domain names found in
423 Windows networks.
424
425 \sa hostName()
426*/
427
428#ifndef QT_NO_THREAD
429QHostInfoRunnable::QHostInfoRunnable(QString hn, int i) : toBeLookedUp(hn), id(i)
430{
431 setAutoDelete(true);
432}
433
434// the QHostInfoLookupManager will at some point call this via a QThreadPool
435void QHostInfoRunnable::run()
436{
437 QHostInfoLookupManager *manager = theHostInfoLookupManager();
438 // check aborted
439 if (manager->wasAborted(id)) {
440 manager->lookupFinished(this);
441 return;
442 }
443
444 QHostInfo hostInfo;
445
446 // QHostInfo::lookupHost already checks the cache. However we need to check
447 // it here too because it might have been cache saved by another QHostInfoRunnable
448 // in the meanwhile while this QHostInfoRunnable was scheduled but not running
449 if (manager->cache.isEnabled()) {
450 // check the cache first
451 bool valid = false;
452 hostInfo = manager->cache.get(toBeLookedUp, &valid);
453 if (!valid) {
454 // not in cache, we need to do the lookup and store the result in the cache
455 hostInfo = QHostInfoAgent::fromName(toBeLookedUp);
456 manager->cache.put(toBeLookedUp, hostInfo);
457 }
458 } else {
459 // cache is not enabled, just do the lookup and continue
460 hostInfo = QHostInfoAgent::fromName(toBeLookedUp);
461 }
462
463 // check aborted again
464 if (manager->wasAborted(id)) {
465 manager->lookupFinished(this);
466 return;
467 }
468
469 // signal emission
470 hostInfo.setLookupId(id);
471 resultEmitter.emitResultsReady(hostInfo);
472
473 // now also iterate through the postponed ones
474 {
475 QMutexLocker locker(&manager->mutex);
476 QMutableListIterator<QHostInfoRunnable*> iterator(manager->postponedLookups);
477 while (iterator.hasNext()) {
478 QHostInfoRunnable* postponed = iterator.next();
479 if (toBeLookedUp == postponed->toBeLookedUp) {
480 // we can now emit
481 iterator.remove();
482 hostInfo.setLookupId(postponed->id);
483 postponed->resultEmitter.emitResultsReady(hostInfo);
484 }
485 }
486 }
487
488 manager->lookupFinished(this);
489
490 // thread goes back to QThreadPool
491}
492
493QHostInfoLookupManager::QHostInfoLookupManager() : mutex(QMutex::Recursive), wasDeleted(false)
494{
495 moveToThread(QCoreApplicationPrivate::mainThread());
496 connect(QCoreApplication::instance(), SIGNAL(destroyed()), SLOT(waitForThreadPoolDone()), Qt::DirectConnection);
497 threadPool.setMaxThreadCount(5); // do 5 DNS lookups in parallel
498}
499
500QHostInfoLookupManager::~QHostInfoLookupManager()
501{
502 wasDeleted = true;
503
504 // don't qDeleteAll currentLookups, the QThreadPool has ownership
505 clear();
506}
507
508void QHostInfoLookupManager::clear()
509{
510 {
511 QMutexLocker locker(&mutex);
512 qDeleteAll(postponedLookups);
513 qDeleteAll(scheduledLookups);
514 qDeleteAll(finishedLookups);
515 postponedLookups.clear();
516 scheduledLookups.clear();
517 finishedLookups.clear();
518 }
519
520 threadPool.waitForDone();
521 cache.clear();
522}
523
524void QHostInfoLookupManager::work()
525{
526 if (wasDeleted)
527 return;
528
529 // goals of this function:
530 // - launch new lookups via the thread pool
531 // - make sure only one lookup per host/IP is in progress
532
533 QMutexLocker locker(&mutex);
534
535 if (!finishedLookups.isEmpty()) {
536 // remove ID from aborted if it is in there
537 for (int i = 0; i < finishedLookups.length(); i++) {
538 abortedLookups.removeAll(finishedLookups.at(i)->id);
539 }
540
541 finishedLookups.clear();
542 }
543
544 if (!postponedLookups.isEmpty()) {
545 // try to start the postponed ones
546
547 QMutableListIterator<QHostInfoRunnable*> iterator(postponedLookups);
548 while (iterator.hasNext()) {
549 QHostInfoRunnable* postponed = iterator.next();
550
551 // check if none of the postponed hostnames is currently running
552 bool alreadyRunning = false;
553 for (int i = 0; i < currentLookups.length(); i++) {
554 if (currentLookups.at(i)->toBeLookedUp == postponed->toBeLookedUp) {
555 alreadyRunning = true;
556 break;
557 }
558 }
559 if (!alreadyRunning) {
560 iterator.remove();
561 scheduledLookups.prepend(postponed); // prepend! we want to finish it ASAP
562 }
563 }
564 }
565
566 if (!scheduledLookups.isEmpty()) {
567 // try to start the new ones
568 QMutableListIterator<QHostInfoRunnable*> iterator(scheduledLookups);
569 while (iterator.hasNext()) {
570 QHostInfoRunnable *scheduled = iterator.next();
571
572 // check if a lookup for this host is already running, then postpone
573 for (int i = 0; i < currentLookups.size(); i++) {
574 if (currentLookups.at(i)->toBeLookedUp == scheduled->toBeLookedUp) {
575 iterator.remove();
576 postponedLookups.append(scheduled);
577 scheduled = 0;
578 break;
579 }
580 }
581
582 if (scheduled && currentLookups.size() < threadPool.maxThreadCount()) {
583 // runnable now running in new thread, track this in currentLookups
584 threadPool.start(scheduled);
585 iterator.remove();
586 currentLookups.append(scheduled);
587 } else {
588 // was postponed, continue iterating
589 continue;
590 }
591 };
592 }
593}
594
595// called by QHostInfo
596void QHostInfoLookupManager::scheduleLookup(QHostInfoRunnable *r)
597{
598 if (wasDeleted)
599 return;
600
601 QMutexLocker locker(&this->mutex);
602 scheduledLookups.enqueue(r);
603 work();
604}
605
606// called by QHostInfo
607void QHostInfoLookupManager::abortLookup(int id)
608{
609 if (wasDeleted)
610 return;
611
612 QMutexLocker locker(&this->mutex);
613
614 // is postponed? delete and return
615 for (int i = 0; i < postponedLookups.length(); i++) {
616 if (postponedLookups.at(i)->id == id) {
617 delete postponedLookups.takeAt(i);
618 return;
619 }
620 }
621
622 // is scheduled? delete and return
623 for (int i = 0; i < scheduledLookups.length(); i++) {
624 if (scheduledLookups.at(i)->id == id) {
625 delete scheduledLookups.takeAt(i);
626 return;
627 }
628 }
629
630 if (!abortedLookups.contains(id))
631 abortedLookups.append(id);
632}
633
634// called from QHostInfoRunnable
635bool QHostInfoLookupManager::wasAborted(int id)
636{
637 if (wasDeleted)
638 return true;
639
640 QMutexLocker locker(&this->mutex);
641 return abortedLookups.contains(id);
642}
643
644// called from QHostInfoRunnable
645void QHostInfoLookupManager::lookupFinished(QHostInfoRunnable *r)
646{
647 if (wasDeleted)
648 return;
649
650 QMutexLocker locker(&this->mutex);
651 currentLookups.removeOne(r);
652 finishedLookups.append(r);
653 work();
654}
655
656// This function returns immediately when we had a result in the cache, else it will later emit a signal
657QHostInfo qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *member, bool *valid, int *id)
658{
659 *valid = false;
660 *id = -1;
661
662 // check cache
663 QHostInfoLookupManager* manager = theHostInfoLookupManager();
664 if (manager && manager->cache.isEnabled()) {
665 QHostInfo info = manager->cache.get(name, valid);
666 if (*valid) {
667 return info;
668 }
669 }
670
671 // was not in cache, trigger lookup
672 *id = QHostInfo::lookupHost(name, receiver, member);
673
674 // return empty response, valid==false
675 return QHostInfo();
676}
677
678void qt_qhostinfo_clear_cache()
679{
680 QHostInfoLookupManager* manager = theHostInfoLookupManager();
681 if (manager) {
682 manager->clear();
683 }
684}
685
686void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e)
687{
688 QHostInfoLookupManager* manager = theHostInfoLookupManager();
689 if (manager) {
690 manager->cache.setEnabled(e);
691 }
692}
693
694// cache for 60 seconds
695// cache 64 items
696QHostInfoCache::QHostInfoCache() : max_age(60), enabled(true), cache(64)
697{
698#ifdef QT_QHOSTINFO_CACHE_DISABLED_BY_DEFAULT
699 enabled = false;
700#endif
701}
702
703bool QHostInfoCache::isEnabled()
704{
705 return enabled;
706}
707
708// this function is currently only used for the auto tests
709// and not usable by public API
710void QHostInfoCache::setEnabled(bool e)
711{
712 enabled = e;
713}
714
715
716QHostInfo QHostInfoCache::get(const QString &name, bool *valid)
717{
718 QMutexLocker locker(&this->mutex);
719
720 *valid = false;
721 if (cache.contains(name)) {
722 QHostInfoCacheElement *element = cache.object(name);
723 if (element->age.elapsed() < max_age*1000)
724 *valid = true;
725 return element->info;
726
727 // FIXME idea:
728 // if too old but not expired, trigger a new lookup
729 // to freshen our cache
730 }
731
732 return QHostInfo();
733}
734
735void QHostInfoCache::put(const QString &name, const QHostInfo &info)
736{
737 // if the lookup failed, don't cache
738 if (info.error() != QHostInfo::NoError)
739 return;
740
741 QHostInfoCacheElement* element = new QHostInfoCacheElement();
742 element->info = info;
743 element->age = QTime();
744 element->age.start();
745
746 QMutexLocker locker(&this->mutex);
747 cache.insert(name, element); // cache will take ownership
748}
749
750void QHostInfoCache::clear()
751{
752 QMutexLocker locker(&this->mutex);
753 cache.clear();
754}
755
756#endif // QT_NO_THREAD
757
758QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.