From 94b88e75b9ebeaf9abb2adef130fdf971884e7b4 Mon Sep 17 00:00:00 2001 From: Sebastien Braun Date: Thu, 15 Jul 2010 01:12:17 +0200 Subject: * Upgrade OpenPGM to current trunk * Implement wait-for-shutdown for McastPGMSocket * Work around bug in UDP encapsulation --- src/net/mcast/CMakeLists.txt | 4 - src/net/mcast/McastConfiguration.h | 2 +- src/net/mcast/McastConstants.h | 3 +- src/net/mcast/McastPGMSocket.cpp | 323 ++++++++++++++--------------- src/net/mcast/McastPGMSocket.h | 5 + src/net/mcast/McastSender.cpp | 44 ++-- src/net/mcast/McastSender.h | 1 + src/net/mcast/trial_programs/mcastsend.cpp | 3 +- 8 files changed, 195 insertions(+), 190 deletions(-) (limited to 'src') diff --git a/src/net/mcast/CMakeLists.txt b/src/net/mcast/CMakeLists.txt index e418c64..914c9d3 100644 --- a/src/net/mcast/CMakeLists.txt +++ b/src/net/mcast/CMakeLists.txt @@ -34,10 +34,6 @@ SET(pvsmcast_SRCS McastSender.cpp ) -INCLUDE_DIRECTORIES( - ${CMAKE_BINARY_DIR}/3rdparty/libpgm-src/openpgm/pgm/include -) - QT4_WRAP_CPP( pvsmcast_MOC_SRCS ${pvsmcast_MOC_HDRS} diff --git a/src/net/mcast/McastConfiguration.h b/src/net/mcast/McastConfiguration.h index 4e0e2ad..f10d487 100644 --- a/src/net/mcast/McastConfiguration.h +++ b/src/net/mcast/McastConfiguration.h @@ -156,7 +156,7 @@ public: quint16 multicastUDPMPort() const { - return _multicastUDPPortBase + 1; + return _multicastUDPPortBase; } void commit() diff --git a/src/net/mcast/McastConstants.h b/src/net/mcast/McastConstants.h index b4c71a5..624e195 100644 --- a/src/net/mcast/McastConstants.h +++ b/src/net/mcast/McastConstants.h @@ -26,10 +26,11 @@ #define DEFAULT_MULTICAST_DPORT 6965 #define DEFAULT_MULTICAST_USEUDP true #define DEFAULT_MULTICAST_UDPPORT 6966 +#define DEFAULT_MULTICAST_WSIZ 30 #define DEFAULT_MULTICAST_RATE (100*1024) -#define DEFAULT_MULTICAST_WSIZ 3000 #define DEFAULT_MULTICAST_MTU 1400 #define DEFAULT_MULTICAST_APDU 1200 #define DEFAULT_MULTICAST_CHUNK 1024 +#define DEFAULT_MULTICAST_SHUTDOWN_TIMEOUT 10000 #endif /* MCASTMAGIC_H_ */ diff --git a/src/net/mcast/McastPGMSocket.cpp b/src/net/mcast/McastPGMSocket.cpp index ba51444..8aee209 100644 --- a/src/net/mcast/McastPGMSocket.cpp +++ b/src/net/mcast/McastPGMSocket.cpp @@ -16,31 +16,27 @@ #include +#include +#include + #include +#include #include #include #include #include #include -// #include -// #define SIZE_MAX UINT64_MAX -// #include -// pgm redefined bool to int. Undo that. -#undef bool - -#include #include "McastPGMSocket.h" +using namespace std; + class McastPGMSocket_priv { public: McastPGMSocket_priv() : socket(0), - recv_notif(0), - repair_notif(0), - pending_notif(0), send_notif(0) { } @@ -48,37 +44,28 @@ public: { if (socket) pgm_close(socket, 0); - if (recv_notif) - delete recv_notif; - if (repair_notif) - delete repair_notif; - if (pending_notif) - delete pending_notif; + Q_FOREACH(QSocketNotifier* notif, _notifs) + { + delete notif; + } if (send_notif) delete send_notif; } pgm_sock_t* socket; McastPGMSocket::Direction direction; - QSocketNotifier* recv_notif; - QSocketNotifier* repair_notif; - QSocketNotifier* pending_notif; QSocketNotifier* send_notif; + QList _notifs; QSocketNotifier* notifier_for(int fd) { - if (recv_notif && (fd == recv_notif->socket())) - { - return recv_notif; - } - else if (repair_notif && (fd == repair_notif->socket())) - { - return repair_notif; - } - else if (pending_notif && (fd == pending_notif->socket())) - { - return pending_notif; - } - return 0; + Q_FOREACH(QSocketNotifier* notif, _notifs) + { + if(notif->socket() == fd) + { + return notif; + } + } + return 0; } }; @@ -102,7 +89,9 @@ McastPGMSocket::McastPGMSocket(QObject* parent) : _finished(false), _nakTimeout(new QTimer()), _dataTimeout(new QTimer()), - _sendTimeout(new QTimer()) + _sendTimeout(new QTimer()), + _shutdownTimer(0), + _shutdown_timeout(0) { _ensurePGMInited(); @@ -166,9 +155,11 @@ bool McastPGMSocket::open(McastConfiguration const* config, Direction direction) // write-only socket const int send_only = 1, spm_heartbeat[] = - { 16 * 1000, 16 * 1000, 16 * 1000, 16 * 1000, 32 * 1000, 64 * 1000, 128 - * 1000, 256 * 1000, 512 * 1000, 1024 * 1000, 2048 * 1000, 4096 - * 1000 }, + { 256 * 1000, + 512 * 1000, + 1024 * 1000, + 2048 * 1000, + 4096 * 1000 }, max_rate = config->multicastRate(), max_window = config->multicastWinSize(); // const int max_window_sqns = 3000; @@ -182,8 +173,8 @@ bool McastPGMSocket::open(McastConfiguration const* config, Direction direction) // Transmit window pgm_setsockopt(_priv->socket, PGM_TXW_MAX_RTE, &max_rate, sizeof(max_rate)); - // pgm_setsockopt(_priv->socket, PGM_TXW_SECS, &max_window, sizeof(max_window)); - pgm_setsockopt(_priv->socket, PGM_TXW_SQNS, &max_window, sizeof(max_window)); + pgm_setsockopt(_priv->socket, PGM_TXW_SECS, &max_window, sizeof(max_window)); + // pgm_setsockopt(_priv->socket, PGM_TXW_SQNS, &max_window, sizeof(max_window)); } else { @@ -191,41 +182,36 @@ bool McastPGMSocket::open(McastConfiguration const* config, Direction direction) const int recv_only = 1, passive = 0, max_window = config->multicastWinSize(), - max_winsqns = 0, + max_rate = config->multicastRate(), peer_expiry = ambient_spm * 5, spmr_expiry = 250 * 1000, nak_bo_ivl = 100 * 1000, - nak_rpt_ivl = 100 * 1000, - nak_rdata_ivl = 200 * 1000, + nak_rpt_ivl = 400 * 1000, + nak_rdata_ivl = 400 * 1000, nak_data_retries = 50, - nak_ncf_retries = 50; + nak_ncf_retries = 50, + no_rxw_sqns = 0; pgm_setsockopt(_priv->socket, PGM_RECV_ONLY, &recv_only, sizeof(recv_only)); pgm_setsockopt(_priv->socket, PGM_PASSIVE, &passive, sizeof(passive)); - // pgm_setsockopt(_priv->socket, PGM_RXW_SECS, &max_window, sizeof(max_window)); - pgm_setsockopt(_priv->socket, PGM_RXW_SQNS, &max_window, sizeof(max_window)); + pgm_setsockopt(_priv->socket, PGM_RXW_MAX_RTE, &max_rate, sizeof(max_rate)); + pgm_setsockopt(_priv->socket, PGM_RXW_SECS, &max_window, sizeof(max_window)); + pgm_setsockopt(_priv->socket, PGM_SPMR_EXPIRY, &spmr_expiry, sizeof(spmr_expiry)); pgm_setsockopt(_priv->socket, PGM_PEER_EXPIRY, &peer_expiry, sizeof(peer_expiry)); - pgm_setsockopt(_priv->socket, PGM_SPMR_EXPIRY, &spmr_expiry, sizeof(spmr_expiry)); pgm_setsockopt(_priv->socket, PGM_NAK_BO_IVL, &nak_bo_ivl, sizeof(nak_bo_ivl)); pgm_setsockopt(_priv->socket, PGM_NAK_RPT_IVL, &nak_rpt_ivl, sizeof(nak_rpt_ivl)); pgm_setsockopt(_priv->socket, PGM_NAK_RDATA_IVL, &nak_rdata_ivl, sizeof(nak_rdata_ivl)); pgm_setsockopt(_priv->socket, PGM_NAK_DATA_RETRIES, &nak_data_retries, sizeof(nak_data_retries)); pgm_setsockopt(_priv->socket, PGM_NAK_NCF_RETRIES, &nak_ncf_retries, sizeof(nak_ncf_retries)); + pgm_setsockopt(_priv->socket, PGM_RXW_SQNS, &no_rxw_sqns, sizeof(no_rxw_sqns)); } - // MTU + const int use_pgmcc = 1; + pgm_setsockopt(_priv->socket, PGM_USE_PGMCC, &use_pgmcc, sizeof(use_pgmcc)); + + // MTU int const mtu = config->multicastMTU(); pgm_setsockopt(_priv->socket, PGM_MTU, &mtu, sizeof(mtu)); - // UDP Encapsulation - if(config->multicastUseUDP()) - { - const quint16 uport = config->multicastUDPUPort(); - const quint16 mport = config->multicastUDPMPort(); - - pgm_setsockopt(_priv->socket, PGM_UDP_ENCAP_MCAST_PORT, &mport, sizeof(mport)); - pgm_setsockopt(_priv->socket, PGM_UDP_ENCAP_UCAST_PORT, &uport, sizeof(uport)); - } - pgm_sockaddr_t addr; addr.sa_addr.sport = config->multicastSPort(); addr.sa_port = config->multicastDPort(); @@ -237,7 +223,25 @@ bool McastPGMSocket::open(McastConfiguration const* config, Direction direction) return false; } - good = pgm_bind3(_priv->socket, &addr, sizeof(addr), (struct group_req*)&addrinfo->ai_send_addrs[0], sizeof(struct group_req), (struct group_req*)&addrinfo->ai_recv_addrs[0], sizeof(struct group_req), &err); + struct pgm_interface_req_t ifreq; + ifreq.ir_interface = addrinfo->ai_send_addrs[0].gsr_interface; + ifreq.ir_scope_id = 0; + if (AF_INET6 == family) + { + ifreq.ir_scope_id = ((struct sockaddr_in6*)&addrinfo->ai_send_addrs[0])->sin6_scope_id; + } + + // UDP Encapsulation + if(config->multicastUseUDP()) + { + const int uport = config->multicastUDPUPort(); + const int mport = config->multicastUDPMPort(); + + pgm_setsockopt(_priv->socket, PGM_UDP_ENCAP_MCAST_PORT, &mport, sizeof(mport)); + pgm_setsockopt(_priv->socket, PGM_UDP_ENCAP_UCAST_PORT, &uport, sizeof(uport)); + } + + good = pgm_bind3(_priv->socket, &addr, sizeof(addr), &ifreq , sizeof(ifreq), &ifreq, sizeof(ifreq), &err); if (!good) { qCritical() << "Could not bind socket: PGM Error: " << err->message; @@ -290,20 +294,15 @@ void McastPGMSocket::setupNotifiers() int recv_sock, repair_sock, pending_sock; char const* slotname = (_priv->direction == PSOCK_WRITE) ? SLOT(handleNak(int)) : SLOT(handleData(int)); - pgm_getsockopt(_priv->socket, PGM_RECV_SOCK, &recv_sock, sizeof(recv_sock)); - _priv->recv_notif = new QSocketNotifier(recv_sock, QSocketNotifier::Read, - this); - connect(_priv->recv_notif, SIGNAL(activated(int)), this, slotname); - - pgm_getsockopt(_priv->socket, PGM_REPAIR_SOCK, &repair_sock, sizeof(repair_sock)); - _priv->repair_notif = new QSocketNotifier(repair_sock, - QSocketNotifier::Read, this); - connect(_priv->repair_notif, SIGNAL(activated(int)), this, slotname); - - pgm_getsockopt(_priv->socket, PGM_PENDING_SOCK, &pending_sock, sizeof(pending_sock)); - _priv->pending_notif = new QSocketNotifier(pending_sock, - QSocketNotifier::Read, this); - connect(_priv->pending_notif, SIGNAL(activated(int)), this, slotname); + struct pollfd pollin[10]; + int in_nfds = 10; + pgm_poll_info(_priv->socket, pollin, &in_nfds, POLLIN); + for(int i = 0; i < in_nfds; i++) + { + QSocketNotifier* notif = new QSocketNotifier(pollin[i].fd, QSocketNotifier::Read, this); + _priv->_notifs.append(notif); + connect(notif, SIGNAL(activated(int)), this, slotname); + } if(_priv->direction == PSOCK_WRITE) { @@ -317,11 +316,17 @@ void McastPGMSocket::setupNotifiers() void McastPGMSocket::handleNak(int fd) { - qDebug() << "handleNak(int)"; + qDebug() << "handleNak(" << fd << ")"; QSocketNotifier* notif = _priv->notifier_for(fd); notif->setEnabled(false); + if (_shutdownTimer) + { + _shutdownTimer->start(_shutdown_timeout); + qDebug() << "Started shutdown timer"; + } + handleNak(); notif->setEnabled(true); @@ -334,7 +339,7 @@ void McastPGMSocket::handleNak() qDebug() << "handleNak()"; - QTimer::singleShot(1000, this, SLOT(handleNakTimeout())); + // QTimer::singleShot(1000, this, SLOT(handleNakTimeout())); // to handle NAKs in OpenPGM, we need to pgm_recv: char buf[4096]; @@ -349,7 +354,8 @@ void McastPGMSocket::handleNak() if(status == PGM_IO_STATUS_TIMER_PENDING) { struct timeval tv; - pgm_getsockopt(_priv->socket, PGM_TIME_REMAIN, &tv, sizeof(tv)); + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, PGM_TIME_REMAIN, &tv, &size); const int msecs = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); qDebug() << " timer pending: " << msecs << "ms"; _nakTimeout->start(msecs); @@ -358,7 +364,8 @@ void McastPGMSocket::handleNak() else if(status == PGM_IO_STATUS_RATE_LIMITED) { struct timeval tv; - pgm_getsockopt(_priv->socket, PGM_RATE_REMAIN, &tv, sizeof(tv)); + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, PGM_RATE_REMAIN, &tv, &size); const int msecs = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); qDebug() << " rate limited: " << msecs << "ms"; _nakTimeout->start(msecs); @@ -437,7 +444,8 @@ void McastPGMSocket::handleData() else if (status == PGM_IO_STATUS_TIMER_PENDING) { struct timeval tv; - pgm_getsockopt(_priv->socket, PGM_TIME_REMAIN, &tv, sizeof(tv)); + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, PGM_TIME_REMAIN, &tv, &size); const int msecs = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); qDebug() << " timer pending: " << msecs << "ms"; _dataTimeout->start(msecs); @@ -446,7 +454,8 @@ void McastPGMSocket::handleData() else if (status == PGM_IO_STATUS_RATE_LIMITED) { struct timeval tv; - pgm_getsockopt(_priv->socket, PGM_RATE_REMAIN, &tv, sizeof(tv)); + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, PGM_RATE_REMAIN, &tv, &size); const int msecs = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); qDebug() << " rate limit pending: " << msecs << "ms"; _dataTimeout->start(msecs); @@ -493,113 +502,80 @@ void McastPGMSocket::canSend() if (_finished) return; - qDebug() << "canSend()"; + // qDebug() << "canSend()"; if (_priv->send_notif) { _priv->send_notif->setEnabled(false); } - bool reenable = true; - - while (!_q.isEmpty()) - { - int status; - QByteArray const nextPacket(_q.head()); - status = pgm_send(_priv->socket, nextPacket.constData(), nextPacket.size(), 0); - if (status == PGM_IO_STATUS_ERROR || status == PGM_IO_STATUS_RESET) - { - qCritical() << "Could not send packet: PGM Error."; - continue; - } - else if (status == PGM_IO_STATUS_WOULD_BLOCK) - { - qDebug() << " would block"; - break; - } - else if (status == PGM_IO_STATUS_RATE_LIMITED) - { - struct timeval tv; - pgm_getsockopt(_priv->socket, PGM_RATE_REMAIN, &tv, sizeof(tv)); - const int msecs = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); - qDebug() << " rate limited:" << msecs << "ms"; - _sendTimeout->start((msecs > 0) ? msecs : 1); - reenable = false; - break; - } - else if (status == PGM_IO_STATUS_NORMAL) - { - qDebug() << " sent"; - _q.dequeue(); - continue; - } - else - { - qCritical() << "Unhandled condition in McastPGMSocket::canSend()"; - } - } - - if (_priv->send_notif && reenable) + if(_q.isEmpty()) { - emit readyToSend(); + emit readyToSend(); + } + else + { + QByteArray const packet(_q.head()); + int status; + + status = pgm_send(_priv->socket, packet.constData(), packet.size(), 0); - qDebug() << " reenable notifier"; - _priv->send_notif->setEnabled(true); + if(status == PGM_IO_STATUS_NORMAL) + { + _q.dequeue(); + if(!_q.isEmpty()) + { + _priv->send_notif->setEnabled(true); + } + else + { + emit readyToSend(); + } + } + else if(status == PGM_IO_STATUS_WOULD_BLOCK) + { + _priv->send_notif->setEnabled(true); + } + else if(status == PGM_IO_STATUS_RATE_LIMITED) + { + struct timeval tv; + socklen_t size = sizeof(tv); + pgm_getsockopt(_priv->socket, PGM_RATE_REMAIN, &tv, &size); + int msecs = (tv.tv_sec * 1000) + ((tv.tv_sec + 999) / 1000); + _sendTimeout->start(msecs); + } + else + { + qCritical() << "Unhandled status in canSend()"; + } } + + if (_shutdownTimer) + _shutdownTimer->start(_shutdown_timeout); } void McastPGMSocket::sendPacket(QByteArray const& bytes) { - if(_q.isEmpty()) - { - int status = pgm_send(_priv->socket, bytes.constData(), bytes.size(), 0); + if(_shutdownTimer) + { + qCritical() << "Logic error: sendPacket() after shutdown()"; + } - if (status == PGM_IO_STATUS_ERROR || status == PGM_IO_STATUS_RESET) - { - qCritical() << "Could not send packet: PGM Error."; - return; - } - else if (status == PGM_IO_STATUS_WOULD_BLOCK) - { - _q.enqueue(bytes); - } - else if (status == PGM_IO_STATUS_RATE_LIMITED) - { - _q.enqueue(bytes); - struct timeval tv; - pgm_getsockopt(_priv->socket, PGM_RATE_REMAIN, &tv, sizeof(tv)); - _dataTimeout->start((tv.tv_sec * 1000) + (tv.tv_usec / 1000)); - } - else if (status == PGM_IO_STATUS_NORMAL) - { - return; - } - else - { - qCritical() << "Unhandled condition in McastPGMSocket::sendPacket()"; - } - } else { - _q.enqueue(bytes); - } + _q.enqueue(bytes); + _priv->send_notif->setEnabled(true); } void McastPGMSocket::finish() { - if(_priv->pending_notif) - { - delete _priv->pending_notif; - _priv->pending_notif = 0; - } - if(_priv->recv_notif) - { - delete _priv->recv_notif; - _priv->recv_notif = 0; - } - if(_priv->repair_notif) + qDebug() << "finish()"; + + Q_FOREACH(QSocketNotifier* notif, _priv->_notifs) { - delete _priv->repair_notif; - _priv->repair_notif = 0; + notif->setEnabled(false); + delete notif; } + _priv->_notifs.clear(); + if(_priv->send_notif) { delete _priv->send_notif; @@ -610,9 +586,28 @@ void McastPGMSocket::finish() _priv->socket = 0; _finished = true; + + emit connectionFinished(); + + qDebug() << "Socket finished"; } bool McastPGMSocket::finished() const { return _finished; } + +void McastPGMSocket::shutdown(int interval) +{ + if(_priv->direction == PSOCK_READ) + return; + + _shutdown_timeout = interval; + _shutdownTimer = new QTimer(this); + connect(_shutdownTimer, SIGNAL(timeout()), this, SLOT(finish())); + if (_q.isEmpty()) + { + _shutdownTimer->start(_shutdown_timeout); + qDebug() << "Started shutdown timer"; + } +} diff --git a/src/net/mcast/McastPGMSocket.h b/src/net/mcast/McastPGMSocket.h index b0007a7..ad42aa5 100644 --- a/src/net/mcast/McastPGMSocket.h +++ b/src/net/mcast/McastPGMSocket.h @@ -22,6 +22,7 @@ #include #include +#include class McastPGMSocket_priv; class QTimer; @@ -40,12 +41,14 @@ public: bool open(McastConfiguration const* config, Direction direction); bool finished() const; + void shutdown(int interval = DEFAULT_MULTICAST_SHUTDOWN_TIMEOUT); signals: void readyToSend(); void receivedPacket(QByteArray const& bytes); void connectionReset(); void connectionFinished(); + void shutdownComplete(); public slots: void sendPacket(QByteArray const& bytes); @@ -67,6 +70,8 @@ private: QTimer* _nakTimeout; QTimer* _dataTimeout; QTimer* _sendTimeout; + QTimer* _shutdownTimer; + int _shutdown_timeout; void setupNotifiers(); }; diff --git a/src/net/mcast/McastSender.cpp b/src/net/mcast/McastSender.cpp index 24a629c..f49c0df 100644 --- a/src/net/mcast/McastSender.cpp +++ b/src/net/mcast/McastSender.cpp @@ -61,36 +61,44 @@ void McastSender::readyToSend() strm << qChecksum(fpdu.constData(), fpdu.size()); _socket->sendPacket(fpdu); - // _socket->finish(); + connect(_socket, SIGNAL(connectionFinished()), this, SLOT(socketFinished())); + _socket->shutdown(); _finished = true; - emit finished(); - return; + _iodev->close(); } + else + { + QByteArray barr(DEFAULT_MULTICAST_APDU, '\0'); + qint64 len_read; + len_read = _iodev->read(barr.data(), barr.capacity()); + barr.resize((int)len_read); - QByteArray barr(DEFAULT_MULTICAST_APDU, '\0'); - qint64 len_read; - len_read = _iodev->read(barr.data(), barr.capacity()); - barr.resize((int)len_read); - - _hash.addData(barr); + _hash.addData(barr); - QByteArray pdu; - QDataStream strm(&pdu, QIODevice::WriteOnly); - strm.setByteOrder(QDataStream::BigEndian); + QByteArray pdu; + QDataStream strm(&pdu, QIODevice::WriteOnly); + strm.setByteOrder(QDataStream::BigEndian); - strm << (quint64)MCASTFT_MAGIC << _curoffs; - strm << barr; - quint16 checksum = qChecksum(pdu.constData(), pdu.size()); - strm << checksum; + strm << (quint64)MCASTFT_MAGIC << _curoffs; + strm << barr; + quint16 checksum = qChecksum(pdu.constData(), pdu.size()); + strm << checksum; - _curoffs += len_read; + _curoffs += len_read; - _socket->sendPacket(pdu); + _socket->sendPacket(pdu); + } } void McastSender::close() { _socket->finish(); } + +void McastSender::socketFinished() +{ + delete _socket; + emit finished(); +} diff --git a/src/net/mcast/McastSender.h b/src/net/mcast/McastSender.h index e713886..5c62fad 100644 --- a/src/net/mcast/McastSender.h +++ b/src/net/mcast/McastSender.h @@ -55,6 +55,7 @@ public slots: private slots: void readyToSend(); + void socketFinished(); private: McastConfiguration* _config; diff --git a/src/net/mcast/trial_programs/mcastsend.cpp b/src/net/mcast/trial_programs/mcastsend.cpp index da8ecf4..f78a9ce 100644 --- a/src/net/mcast/trial_programs/mcastsend.cpp +++ b/src/net/mcast/trial_programs/mcastsend.cpp @@ -118,6 +118,5 @@ void McastSend::run() void McastSend::finished() { cerr << "finished." << endl; - // QTimer::singleShot(30000, QCoreApplication::instance(), SLOT(quit())); - // QCoreApplication::quit(); + QCoreApplication::quit(); } -- cgit v1.2.3-55-g7522