相信许多初学Qt的同学都会和我一样遇到这样的问题:
一、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