相信许多初学Qt的同学都会和我一样遇到这样的问题:
一、Qt TCP通信在使用nextPendingConnect后,服务器端就只会与最后接入的客户端通信,这个时候就会考虑继承QThread实现多线程,从而实现多个客户端与服务器端通信,每当一个新的客户端连接时,通过标识码socketDescriptor,实现与对应的客户端通信。
void server::incomingConnection(int socketDescriptor)
{
socketList.append(socketDescriptor);
serverThread *thread = new serverThread(socketDescriptor, 0);
connect(thread, SIGNAL(started()), dlg, SLOT(showConnection()));
connect(thread, SIGNAL(disconnectTCP(int)), dlg, SLOT(showDisconnection(int)));
connect(thread, SIGNAL(revData(QString)), dlg, SLOT(revData(QString)));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
connect(dlg, SIGNAL(sendData(QString, int)), thread, SLOT(sendData(QString, int)));
thread->start();
}
二、虽然多线程服务器端的例子书上和网上很多(虽然基本一样= =!), 都是简单的时间服务器,只实现简单的发送功能,而且每个客户端发一次就断开了,但是许多时候我们都要使用完整的收发功能。对于发送实现还比较简单只需要根据socketDescriptor和write函数就可以将信息发送到指定的客户端:
void serverThread::sendData(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 revData(msg);
}
}
}
}
当然这种方法比较low,而且在实现send的时候会出现一个在线程中新开一个线程的警告,所以为了达到更好的效果,我们可以继承TcpSocket类,在里面实现数据的收发,而且这样也不需要使用到阻塞。
修改后serverThread的部分源码:
void serverThread::run()
{
sock = new MySocket(socketDescriptor, 0);
if (!sock->setSocketDescriptor(socketDescriptor))
return ;
connect(sock, &MySocket::disconnected, this, &serverThread::disconnectToHost);
connect(sock, SIGNAL(revData(QString)), this, SLOT(recvData(QString)));
connect(this, SIGNAL(sendDat(QString,int)), sock, SLOT(sendMsg(QString,int)));
exec();
}
void serverThread::sendData(QString data, int id)
{
if (data == "")
return ;
emit sendDat(data, id);
}
void serverThread::recvData(QString msg)
{
emit revData(msg);
}
这里线程中只是一个信号转发的功能。
在MySocket类中只需要像普通一样实现数据收发就行啦:
MySocket::MySocket(int socketDescriptor, QObject *parent)
: QTcpSocket(parent), socketDescriptor(socketDescriptor)
{
connect(this, SIGNAL(readyRead()),
this, SLOT(recvData()));
}
void MySocket::sendMsg(QString msg, int id)
{
if (id == socketDescriptor)
{
write(msg.toLocal8Bit());
}
}
void MySocket::recvData()
{
QByteArray data;
data = readAll();
QString msg = peerAddress().toString() + ':'+ QString::fromLocal8Bit(data);
emit revData(msg);
}
程序运行图如下:
因为是自己平常调试用,所以端口号是写死了的,需要动态设置端口的同学,就自己多加几个控件,多写几行代码啦。
代码下载,由于CSDN的下载有点坑,请移步GitHub:
https://github.com/DragonPang/QtMultiThreadTcpServer