一、server
QT中的TCPserver:
步骤:
自行定义槽函数:
接收消息:
二、Client
接收消息:
一、单线程网络聊天
要实现的效果:
多个客户端连接上服务器后,每个客户端发送任何消息都会显示在聊天框中,任何客户端上线与下线在聊天框都会有对应的提示。并且每个客户端都可以设置自己的昵称。
如何实现:
server:
首先由于是单线程,我们直接使用QT自带的QTcpServer类就行了。
要知道客户端与服务端是如何进行通信的,我们必须知道,QTcpServer类与客户端的QTcpSocket类之间产生的信号关系。
1.newConnection信号
这个信号是当有客户端连接上来就会发送给服务端的信号。我们在服务端必须建立相应的槽函数与其连接。
2.readyRead信号
这个信号是当客户端有消息来时,发送给服务端的信号。服务端建立相应的槽函数与这个连接,在槽函数中就可以实现发送信号给客户端.
Client:
1.connected信号
当客户端与服务器连接成功时,服务端会发送这个信号。
2.readyRead信号
当服务端给客户端发送数据时,会发送这个信号过来。
3.disconnected信号
当客户端断开与服务端连接时,服务端会发送这个信号。
4.error(QAbstractSocket::SocketError)信号
当客户端连接服务端出错时,服务端会发送这个数据给客户端。
细节:
因为在服务端每产生一个客户端连接,就会多一个QTcpSocket对象,而我们要如何存这些对象呢?
我们使用:
QList<QTcpSocket*> tcpClientList;//保存与server通信的套接字
而我们在发送数据给客户端的时候我们就遍历整个list发送消息。
优化:
由于到后面list里的客户端对象越来越多,而其中又有些会随时断开,或者已经断开的了,那我们必须将这些断开的从list中去除。
我们使用定时器,在定时器定时遍历这些list,如果有断开的就去除。
//定时器槽函数(删除那些断开连接的套接字)
void ServerDialog::onTimeout(void)
{
for(int i=0;i<tcpClientList.size();++i)
{
if(tcpClientList.at(i)->state()==QAbstractSocket::UnconnectedState)
{
tcpClientList.removeAt(i);
--i;//removeAT移除后后面的元素会向前移动
}
}
}
二、多线程网络聊天室:(重写QThread的run方法)
QT的TCP连接想要实现多线程是一件挺抽象的事情。因为我们不能直接使用QTcpserver类了,
我们必须继承QTcpserver实现一个自己的server类,
以及继承QThread实现一个自己的Thread类,
以及继承QTcpSocket实现一个自己的Socket类,
以及将我们的主线程与最底层Socket之间的脉络打通(这个就是靠每一层自己的信号与槽的连接了)。
最终实现的效果就是mainwindow->MyServer->MyThread->MySocket。其中MyServer之后就是多线程了,而MyServer及其之前还是单一的主线程
如何连接主线程与Socket:
1.主线程与Mythread建立连接
(这些都是在MyServer重写的incomingConnection中执行的)
//从底层发送上来的客户端断开连接信号,发送给主线程,用于显示断开连接
connect(thread, SIGNAL(disconnectTCP(int)), m_dialog, SLOT(clientDisconnected(int)));
//将底层接受数据信号发送给上层主线程
connect(thread, SIGNAL(dataReady(QByteArray)),
m_dialog,SLOT(readData(QByteArray)));
//将主线程发送数据信号发送给底层socket发送函数
connect(m_dialog, SIGNAL(sendData(int, QByteArray)),
thread, SLOT(sendDataSlot(int,QByteArray)));
2.MyThread与MySocket建立连接
(这些是在MyThread的run中执行的)
//猜是用于终止线程用的
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
//将底层断开连接信号发送到上层
connect(m_socket, SIGNAL(disconnected()), this, SLOT(disconnectToHost()));
//建立从socket发送到thread
connect(m_socket, SIGNAL(dataReady(QByteArray)),
this, SLOT(recvDataSlot(QByteArray)));
//建立从thread发送到socket
connect(this, SIGNAL(sendData(int, QByteArray)),
m_socket, SLOT(sendData(int, QByteArray)));
3.最终到最底层的MySocket的发送与接收客户端消息,而我们要实现这些函数
//来自最底层的接受信息信号
connect(this, SIGNAL(readyRead()), this, SLOT(recvData()));
多线程建立的步骤:
1.首先有客户的连接到来,执行我们重写的MyServer中的incomingConnection。
1.创建MyThread线程对象
2.建立main与下一级的连接
3.开启线程
void MyServer::incomingConnection(int sockDesc)
{
//将连接标识符加入list
m_dialog->ClientList.append(sockDesc);
//创建线程
MyThread *thread = new MyThread(sockDesc);
//从底层发送上来的客户端断开连接信号,发送给主线程,用于显示断开连接
connect(thread, SIGNAL(disconnectTCP(int)), m_dialog, SLOT(clientDisconnected(int)));
//将底层接受数据信号发送给上层主线程
connect(thread, SIGNAL(dataReady(QByteArray)),
m_dialog,SLOT(readData(QByteArray)));
//将主线程发送数据信号发送给底层socket发送函数
connect(m_dialog, SIGNAL(sendData(int, QByteArray)),
thread, SLOT(sendDataSlot(int,QByteArray)));
//开启线程
thread->start();
}
2.执行我们MThread重写的run
1.创建MySocket对象
2.建立上一层与下一层的连接
3.开启线程的事件循环
void MyThread::run(void)
{
m_socket = new MySocket(m_sockDesc);
if (!m_socket->setSocketDescriptor(m_sockDesc)) {//如果不是对于这个线程的客户端
return ;
}
//猜是用于终止线程用的
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
//将底层断开连接信号发送到上层
connect(m_socket, SIGNAL(disconnected()), this, SLOT(disconnectToHost()));
//建立从socket发送到thread
connect(m_socket, SIGNAL(dataReady(QByteArray)),
this, SLOT(recvDataSlot(QByteArray)));
//建立从thread发送到socket
connect(this, SIGNAL(sendData(int, QByteArray)),
m_socket, SLOT(sendData(int, QByteArray)));
//开启线程的事件循环
this->exec();
}
3.此时mian与MySocket的信号连接已经建立好了,如果一旦有main或者客户端发送信号,就会按照建立好的连接进行触发信号传输数据。
三、多线程细节:
1.记得在接收到客户端断开连接的消息时利用this.quit将线程杀死。
void MyThread::disconnectToHost(void)
{
emit disconnectTCP(m_sockDesc);
//现在断开与客户端连接
m_socket->disconnectFromHost();
//终止线程,可能触发finished信号
this->quit();
}
2.利用QListClientList;保存与服务端建立连接的标识符,而不用QList<TcpSocket*>ClientList,原因在于重写Server的incomingConnection函数后,会传入对应连接的标识符,主线程通过标识符去寻找对应的线程。
3.要让其它类中能够写主线程类中的ClientList要在主线程类中表明该类为它的友元函数。
4.线程exec与quit
exec()方法是QThread类的一个虚函数,它会运行线程的事件循环,直到线程结束或者调用quit()方法。quit()方法会结束线程的事件循环,使线程结束。
源码地址:(在gittee下载)
单线程:
https://gitee.com/hktk-cww/qt-tcp-chat.git
多线程
https://gitee.com/hktk-cww/qt-tcp-chat-multithread.git
该类为它的友元函数。
4.线程exec与quit
exec()方法是QThread类的一个虚函数,它会运行线程的事件循环,直到线程结束或者调用quit()方法。quit()方法会结束线程的事件循环,使线程结束。
源码地址:(在gittee下载)
单线程:
https://gitee.com/hktk-cww/qt-tcp-chat.git
多线程
https://gitee.com/hktk-cww/qt-tcp-chat-multithread.git