linux下QT网络聊天室

一、server

QT中的TCPserver:

在这里插入图片描述

步骤:

在这里插入图片描述

自行定义槽函数:
在这里插入图片描述

接收消息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NOvSwwlG-1678021194248)(C:\Users\cww\AppData\Roaming\Typora\typora-user-images\image-20230219102840122.png)]

二、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

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值