QT入门笔记3
一、前言
本文主要记录使用QT制作一个网络调试助手的主要技能
二、网络助手具体实现
2.1、TCP服务端-QTcpServer
构建一个服务端需要使用网络和QTcpServer类,所以需要包括<QTcpServer>头文件,并且需要:
然后我们就可以去查手册使用起来这个类了。
服务端的工作最主要就是监听有谁连接进来了,而QT中实现这个是非常简单的:
QTcpServer* server;
//为新建的服务端建立一个空间
server = new QTcpServer(this);
//按键按下开始监听槽函数
void Widget::on_pushButton_clicked()
{
int port = 8888;//端口号
QHostAddress addr("192.168.137.1");//IP
if(!server->listen(addr,port))//监听失败
{
qDebug() << "listenError";
return;
}
else//监听成功
{
qDebug() << "listenSucces";
ui->pushButton->setEnabled(false);
}
}
但是我们只知道怎么连接是远远不够的,我们怎么知道有谁连接上了呢?连接上的人的信息?我们就需要去查手册了,然后发现里面有一个信号;
有人连接上了就会触发这个信号: 这样我们就解决了第一个问题。第二个问题的解决就需要用到另一个类-QTcpSocket
我们这里先使用这个信号解决第一个问题,使用信号就必须绑定信号与槽,然后通过查询手册这个QTcpSocket类,使用里面的函数就可以解决第二个问题,下面的解析很清楚,可以自己动手查一下手册,或者使用其他成员函数实现。
//绑定信号与槽 newConnection-有新设备连接信号connect(server,SIGNAL(newConnection()),this,SLOT(on_newClient_connect()));
提示:槽函数记得要在头文件声明
//新设备连接响应槽函数
void Widget::on_newClient_connect()
{
qDebug() << "有客户端接入\n";//打印信息说明有客户端连接了
/* hasPendingConnections():
Returns true if the server has a pending connection; otherwise returns false.
如果服务器有一个挂起的连接,返回true;否则返回false。
*/
if(server->hasPendingConnections())
{
//QTcpSocket *QTcpServer::nextPendingConnection()
//将连接的设备的信息返回到connction
QTcpSocket *connction = server->nextPendingConnection();
/* 打印连接的客户端的信息 */
qDebug() << "client Addr: " << connction->peerAddress().toString() << "port:" << connction->peerPort();
}
}
运行效果:
2.2、查看电脑上所有的IP
实现这个功能需要用到另外两个类-QNetworkInterface,QHostAddress,网络地址分为IPv4。IPv6,还有其它,最主要是IPv4,所以我们需要将它们区分出来:
//查看当前电脑所有的IP号
//QList<QHostAddress> QNetworkInterface::allAddresses()
QList<QHostAddress> addresss = QNetworkInterface::allAddresses();
for(QHostAddress tmp : addresss)
{
//筛选出IPV4的IP进行处理
if(tmp.protocol() == QAbstractSocket::IPv4Protocol)
{
qDebug() << tmp.toString();//把所有的IP打印出来
}
}
运行效果:
2.3、服务端接收信息
实现接收信息也是需要使用到信号与槽,有信息(readyRead())来了,就会有一个信号通知服务端,通知来了,你可以开始接收信息了,那么这个信号到底是谁发送来的呢?肯定是客户端啦,所以信号的发送者就是发送信息的客户端,接受者就不用过多描述了肯定是this了:
//新设备连接响应槽函数
void Widget::on_newClient_connect()
{
qDebug() << "有客户端接入\n";//打印信息说明有客户端连接了
/* hasPendingConnections():
Returns true if the server has a pending connection; otherwise returns false.
如果服务器有一个挂起的连接,返回true;否则返回false。
*/
if(server->hasPendingConnections())
{
//QTcpSocket *QTcpServer::nextPendingConnection()
//将连接的设备的信息返回到connction
QTcpSocket *connction = server->nextPendingConnection();
/* 打印连接的客户端的信息 */
qDebug() << "client Addr: " << connction->peerAddress().toString() << "port:" << connction->peerPort();
//接收信息信号和槽函数绑定,发送者;connction
connect(connction,SIGNAL(readyRead()),this,SLOT(on_readyRead_handler()));
}
}
//接收信息槽函数
void Widget::on_readyRead_handler()
{
//找到是谁(哪个客户端发送的信息)
QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
//读取信息,将读取到的信息返回回来
QByteArray revData = tmpSock->readAll();
//ui->textEditRev->insertPlainText("客户端: " + revData);
qDebug() << revData;
}
2.4、socket连接状态
连接转状态无非就是连接上了,断开了,连接中,断开中。。。。。,我们都需要对这些情况进行处理,或者找出断开的理由,所以我们必须知道并且关注它的状态。所以我们需要使用一个类-QAbstractSocket,QTcpSocket的儿子。QTcpSocket这个类我们实现服务端接收信息的时候已经使用过了。
上面的信号又很多,这里主要介绍的是红圈那个,主要可以检测的信号有:
//新设备连接响应槽函数
void Widget::on_newClient_connect()
{
qDebug() << "有客户端接入\n";//打印信息说明有客户端连接了
/* hasPendingConnections():
Returns true if the server has a pending connection; otherwise returns false.
如果服务器有一个挂起的连接,返回true;否则返回false。
*/
if(server->hasPendingConnections())
{
//QTcpSocket *QTcpServer::nextPendingConnection()
//将连接的设备的信息返回到connction
QTcpSocket *connction = server->nextPendingConnection();
/* 打印连接的客户端的信息 */
qDebug() << "client Addr: " << connction->peerAddress().toString() << "port:" << connction->peerPort();
//接收信息信号和槽函数绑定,发送者;connction
connect(connction,SIGNAL(readyRead()),this,SLOT(on_readyRead_handler()));
//绑定socket状态改变信号与槽
connect(connction,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,SLOT(mstateChanged(QAbstractSocket::SocketState)));
}
}
void Widget::mstateChanged(QAbstractSocket::SocketState socketState)
{
QTcpSocket *tmpSock = qobject_cast<QTcpSocket *>(sender());
qDebug() << "client out In state:" << socketState;
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
qDebug() << "断开了\n";
tmpSock->deleteLater();//删除,释放内存
break;
case QAbstractSocket::ClosingState:
qDebug() << "断开中\n";
break;
case QAbstractSocket::ConnectedState:
qDebug() << "已连接\n";
break;
case QAbstractSocket::ConnectingState:
qDebug() << "连接中\n";
break;
}
}
运行结果;(这里代码是连接上了后才绑定信号与槽,所以检测不到连接中和已连接) 所以也打印不了对应信息:
2.5、服务端发送信息
发送信息也是很简单的,直接调用QTcpSocket中的Write函数即可,这里要实现按键按下发送横条输入条中的字符串给所有的客户端:
//服务端发送信息按键槽函数
void Widget::on_pushButton_Send_clicked()
{
//找到全部的客户端
QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
//发送横条上的字符串
for(QTcpSocket* tmp:tcpSocketClients)
{
tmp->write(ui->lineEdit->text().toStdString().c_str());
}
}
运行结果:(提示:支持客户端多连接)
2.6、选择性通信
我们作为服务器被一个或多个客户端连接上后,需要有选择的进行通信,所以我们需要对所有的客户端进行选择,这里我们使用Qcombobox进行选择,点击后将所有的连接上的客户端的信息刷新在列表上,但是通过查手册,没有发现Qcombobox有类似的信号,所以我们需要使用到事件!
思路:所以我们要新建一个类并且父类为QComboBox类,然后重写鼠标点击函数,并且自定义一个信号量,当鼠标左键点击后,手动释放这个信号,然后在主函数中,自定义一个槽函数,并且将这两个绑定起来,然后在槽函数中找到全部的客户端,然后将其信息全部添加到上面就行了,点击选中后,再使用逻辑实现可选择性通信!
新建类(MyComboBox):
.h文件:
#ifndef MYCOMBOBOX_H
#define MYCOMBOBOX_H
#include <QComboBox>
#include <QWidget>
class MyComboBox : public QComboBox
{
Q_OBJECT//使用信号与槽必须添加
public:
MyComboBox(QWidget *parent);
//鼠标按下函数重写
protected:
void mousePressEvent(QMouseEvent *e) override;
//新的信号量
signals:
void on_ComboBox_clicked();
};
#endif // MYCOMBOBOX_H
.c文件:
#include "mycombobox.h"
/* 添加鼠标事件头文件 */
#include <QMouseEvent>
MyComboBox::MyComboBox(QWidget *parent) : QComboBox(parent)
{
}
void MyComboBox::mousePressEvent(QMouseEvent *e)
{
/* 如果是鼠标左键按下 */
if(e->button() == Qt::LeftButton){
emit on_ComboBox_clicked();//释放信号量
}
QComboBox::mousePressEvent(e);//依旧执行原来动作-下拉列表
}
然后再UI中将我们的QComboBox提升为MyComboBox,然后进行信号与槽的绑定:
connect(ui->comboBox1,&MyComboBox::on_ComboBox_clicked,this,&Widget::mComboBox_refresh);
槽函数中:
void Widget::mComboBox_refresh()
{
qDebug() << "jjj";
ui->comboBox1->clear();//清除全部标题
/* 找到全部连接上的客户端 */
QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
for(QTcpSocket* tmp:tcpSocketClients){
if(tmp!=nullptr)
/* 添加客户端的端口号 */
ui->comboBox1->addItem(QString::number(tmp->peerPort()));
}
ui->comboBox1->addItem("all");
}
运行结果:
然后我们需要根据列表上面的端口号索引找到对应的客户端对其发送信息:所以我们需要根据当前的索引号进行通信,我们可以看看有什么适合使用到信号: 槽函数:
//列表索引变化信号触发槽
void Widget::on_comboBox1_activated(int index)
{
Client_Id = index;//将当前索引返回到全局变量使用
qDebug() << index;
}
发送按键槽槽函数补充:
//服务端发送信息按键槽函数
void Widget::on_pushButton_Send_clicked()
{
//找到全部的客户端
QList<QTcpSocket*> tcpSocketClients = server->findChildren<QTcpSocket*>();
if(ui->comboBox1->currentText() == "all")
{
//发送横条上的字符串
for(QTcpSocket* tmp:tcpSocketClients)
{
tmp->write(ui->lineEdit->text().toStdString().c_str());
}
}
else
{
tcpSocketClients[Client_Id]->write(ui->lineEdit->text().toStdString().c_str());
}
}
2.7、客户端
创建一个客户端:
QTcpSocket *client;//客户端变量声明
/* 为客户端创建一个空间 */
client = new QTcpSocket(this);
2.8、客户端接收信息
/* 绑定信号与槽 */
connect(client,SIGNAL(readyRead()),this,SLOT(mRead_Data_From_Server()));
//读取服务端发送来的信息函数
void Widget::mRead_Data_From_Server()
{
//将读取到的信息显示到屏幕上
ui->textEditRev->insertPlainText(client->readAll());
}
2.9、客户端发送信息
//按键按下发送信息
void Widget::on_btnSend_clicked()
{
client->write(ui->textEditSend->toPlainText().toUtf8());
}
2.10、断开连接
client->close();
2.11、控制发送信息的颜色
我们需要通过查询手册:
//将字符串str染色成color
void Widget::mInserTextByColor(Qt::GlobalColor color,QString str)
{
/* 返回表示当前可见游标的QTextCursor的副本。 */
QTextCursor cursor = ui->textEditRev->textCursor();
//QTextCharFormat类提供格式化服务
QTextCharFormat format;
//将前景画笔设置为指定的画笔。前景刷主要用于渲染文本。
format.setForeground(QBrush(QColor(color)));
//设置可见游标。
cursor.setCharFormat(format);
cursor.insertText(str);
}