Qt TCP通信,多线程服务器端

相信许多初学Qt的同学都会和我一样遇到这样的问题:

  • 更新于2019-06-15:
    感谢sleikang的评论,因为在serverThread的线程函数中调用了exec(),就算client退出后,该线程也将一直处于事件循环中,不会退出。只需要在退出时,调用quit()函数就好了。
    还有就是一点,此例子也只是仅供参考,因为在实际中为每一个连接去建立一个线程还是比较“奢侈”的事情。本身这里只需要将新接入的MySocket进行一下管理就好了,还望各位熟知。

一、Qt TCP通信在使用nextPendingConnect后,服务器端就只会与最后接入的客户端通信,这个时候就会考虑继承QThread实现多线程,从而实现多个客户端与服务器端通信,每当一个新的客户端连接时,通过标识码socketDescriptor,实现与对应的客户端通信。这里的Server类继承于QTcpServer,重写其中的void incomingConnection(int sockDesc)方法,该方法在有客户端接入时自动调用。

void Server::incomingConnection(int sockDesc)
{
    m_socketList.append(sockDesc);

    serverThread *thread = new serverThread(sockDesc);

    m_dialog->showConnection(sockDesc);

    connect(thread, SIGNAL(disconnectTCP(int)), this, SLOT(clientDisconnected(int)));
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

    connect(thread, SIGNAL(dataReady(const QString&, const QByteArray&)),
            m_dialog, SLOT(recvData(const QString&, const QByteArray&)));

    connect(m_dialog, SIGNAL(sendData(int, const QByteArray&)),
            thread, SLOT(sendDataSlot(int, const QByteArray&)));

    thread->start();
}

二、虽然多线程服务器端的例子书上和网上很多(虽然基本一样= =!), 都是简单的时间服务器,只实现简单的发送功能,而且每个客户端发一次就断开了,但是许多时候我们都要使用完整的收发功能。对于发送实现还比较简单只需要根据socketDescriptor和write函数就可以将信息发送到指定的客户端:

void serverThread::sendData(const QString &data, int id)
{
    if (id == socketDescriptor) {
        tso->write(data.toLocal8Bit());
    }
}

接收方面,许多人第一时间就会想到连接readReady()信号,这个时候问题又发生了,经过一番qDebug发现readReady()信号根本就没触发。到这里网上的资料也少了,在许多资料都提到阻塞式接收和waitForReadyRead(),但是具体的都没写了,就一个函数要怎么用啊,多少给个例子呗,然而怎么找都没有。然后我就在Qt文档里找这个函数,居然就发现了一个例子:

int numRead = 0, numReadTotal = 0;
char buffer[50];

forever {
	numRead  = socket.read(buffer, 50);
    // do whatever with array
	numReadTotal += numRead;
    if (numRead == 0 && !socket.waitForReadyRead()) {
    	 break;
    }          
}

果然还是官方的靠谱,赶紧把自己的程序改改,然后就可以接受数据了,然后就没有然后了。

void serverThread::run()
{
    tso = new QTcpSocket;
    if (!tso->setSocketDescriptor(socketDescriptor)) {
        return;
    }

	connect(tso, &QTcpSocket::disconnected, this, &serverThread::disconnectToHost);

    QByteArray data;
    forever {
        data = tso->readAll();
        QString msg = QString::fromLocal8Bit(data);
        if (tso->waitForReadyRead()) {    
            if (msg.length() != 0) {
                msg = tso->peerAddress().toString() + ':'+ msg;
                emit recvData(msg);
            }
        }
    }
}

当然这种方法比较low,而且在实现send的时候会出现一个在线程中新开一个线程的警告,所以为了达到更好的效果,我们可以继承TcpSocket类,在里面实现数据的收发,而且这样也不需要使用到阻塞。

修改后serverThread的部分源码:

void serverThread::run(void)
{
    m_socket = new MySocket(m_sockDesc);

    if (!m_socket->setSocketDescriptor(m_sockDesc)) {
        return ;
    }

    connect(m_socket, &MySocket::disconnected, this, &serverThread::disconnectToHost);
    connect(m_socket, SIGNAL(dataReady(const QString&, const QByteArray&)),
            this, SLOT(recvDataSlot(const QString&, const QByteArray&)));
    connect(this, SIGNAL(sendData(int, const QByteArray&)),
            m_socket, SLOT(sendData(int, const QByteArray&)));

    this->exec();
}

void serverThread::sendDataSlot(int sockDesc, const QByteArray &data)
{
    if (data.isEmpty()) {
        return ;
    }

    emit sendData(sockDesc, data);
}

void serverThread::recvDataSlot(const QString &ip, const QByteArray &data)
{
    emit dataReady(ip, data);
}

这里线程中只是一个信号转发的功能。

在MySocket类中只需要像普通一样实现数据收发就行啦:

MySocket::MySocket(int sockDesc, QObject *parent) :
    QTcpSocket(parent),
    m_sockDesc(sockDesc)
{
    connect(this, SIGNAL(readyRead()), this, SLOT(recvData()));
}

void MySocket::sendData(int id, const QByteArray &data)
{
    if (id == m_sockDesc && !data.isEmpty()) {
        this->write(data);
    }
}

void MySocket::recvData(void)
{
    QString ip = peerAddress().toString().remove(0, 7);
    QByteArray data = readAll();

    emit dataReady(ip, data);
}

程序运行图如下:
这里写图片描述
因为是自己平常调试用,所以端口号是写死了的,需要动态设置端口的同学,就自己多加几个控件,多写几行代码啦。

代码下载,由于CSDN的下载有点坑,请移步GitHub:
https://github.com/DragonPang/QtMultiThreadTcpServer

评论 58
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值