Qt中的各种技术都是异步的,所以像socket通信的这些玩意都是异步的,不需要自己开线程处理。包括串口,网口通信,只需要将接收数据的函数和信号连接上就可以了,当有数据时,会自动触发执行函数。
这篇文章主要为刚入门的新手做个教程,非常简单的一个教程,也是一个功能比较完整的demo。新建一个工程,这里起名叫service,是一个控制台应用程序,由于不需要界面,所以就直接建控制台程序。创建好之后修改项目文件Service.pro,在"QT += "这一行的后面,空格,添加network,表示该项目中钥匙用网络模块。如下:
QT += core network //这里添加 network表示使用网络模块
QT -= gui
CONFIG += c++11
TARGET = Service
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp \
userclient.cpp \
dataserver.cpp
HEADERS += \
userclient.h \
dataserver.h
1、先建一个类,用来表示一个连接上来的客户端(可以没有,只需要socket描述符创建连接即可,但是抽象成一个客户端用户,更符合面向对象的思想),起个类名叫UserClient,文件名userclient.h和userclient.cpp,注意类名单词首字母大写,文件名自动小写,C++文件名一般都小写。
/*************************************************************************
*文件名:userclent.h
*类名:UserClient
*描述:表示一个客户端连接
*************************************************************************/
#ifndef USERCLIENT_H
#define USERCLIENT_H
#include <QObject>
#include <QTcpSocket>
//该类是客户端的抽象和描述,用来创建客户端连接收发和处理客户端数据
class UserClient : public QObject
{
Q_OBJECT
public:
//构造函数,需要有socket描述符才能创建客户端
explicit UserClient(qintptr des,QObject *parent = 0);
//创建客户端,建立连接,并连接需要的信号和槽
void Start();
private:
//socket客户端
QTcpSocket *client;
//socket描述付符
qintptr socketDescriptor;
//用户客户端名称,不是必须的
QString QClientName;
signals:
//当需要销毁该客户连接时,发送给服务,将其从列表中删除并释放空间
void Del(qintptr desc);
public slots:
//槽函数,用来连接Readyread信号,当客户端有数据发来时,会触发该槽函数
void ReadyRead();
//该槽函数用来给当前客户端发送数据,如果有信号连接该槽,调用信号即可发送给该客户端
void WriteData(QByteArray data);
//该槽函数用来连接socket client的断开信号,如果该客户端断开,则会触发该槽函数
void DisConSoket();
//该槽函数用来连接socketclient的error信号,当连接出现错误时,会触发执行该函数
void Error(QAbstractSocket::SocketError);
};
#endif // USERCLIENT_H
下面是源文件,对应上面的头文件。
//文件名:userclient.cpp
#include "userclient.h"
#include <QAbstractSocket>
UserClient::UserClient(qintptr des, QObject *parent) : QObject(parent)
{
this->socketDescriptor=des;
}
//通过 socket描述符创建连接
void UserClient::Start()
{
//创建客户端
client=new QTcpSocket();
if (!client->setSocketDescriptor(socketDescriptor))
{
qDebug()<<"chuang jian shi bai !!";
return;
}
//连接信号和槽
connect(client,SIGNAL(disconnected()),this,SLOT(DisConSoket()));
connect(client,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(Error(QAbstractSocket::SocketError)));
connect(client,SIGNAL(readyRead()),this,SLOT(ReadyRead()));
//打开客户端
client->open(QIODevice::ReadWrite);
}
//当客户端断开,会触发执行这个槽函数
void UserClient::DisConSoket()
{
qDebug()<<"net work disconnected !!";
Del(this->socketDescriptor);
}
//当连接出现错误,会触发执行这个槽函数
void UserClient::Error(QAbstractSocket::SocketError)
{
qDebug()<<"net work error,closed !!!";
qDebug()<<client->errorString();
Del(this->socketDescriptor);
}
//When there is data to be received, the function will be executed
void UserClient::ReadyRead()
{
//读取数据
QByteArray buff= client->readLine();
QString str(buff);
//在这里处理数据就可以了
qDebug()<<"ke hu duan du qu dao :"+str;
}
//Client Send data
void UserClient::WriteData(QByteArray data)
{
if(client==NULL&&!client->isOpen()&&!client->isWritable())
{
qDebug()<<"Network connection is not available!";
return;
}
//发送数据
client->write(data);
}
2、准备好了上面的客户端类,下面开始正式编写服务端,首先创建一个名叫DataServer的类,文件名dataserver.h、dataserver.cpp ,让该类继承Qt的QTcpServer类。
/*************************************************************************
*文件名:dataserver.h
*类名:DataServer
*描述:表示一个服务器端
*************************************************************************/
#ifndef DATASERVER_H
#define DATASERVER_H
#include <QTcpServer>
#include <userclient.h>
#include <QList>
#include <QTimer>
class DataServer : public QTcpServer
{
Q_OBJECT
public:
//构造函数
explicit DataServer(QObject *parent = 0);
//用来存储连接上来的客户端
QMap<qintptr,UserClient*> *listclient;
private:
//这个Timer用来每秒中给所有客户端发送一条数据
QTimer *tik;
//重写的QTcpServer槽函数,当有客户端连接时会触发该函数
void incomingConnection(qintptr socketDescriptor);
signals:
//这个信号用来给客户端发送数据
void SendDataToClient(QByteArray buff);
public slots:
//删除一个客户端
void DelClient(qintptr socketDes);
//Timer连接的槽函数,每到Timer的触发时,会调用该函数
void timetik();
};
#endif // DATASERVER_H
然后编写源文件,对应上面的头文件。
//文件名:dataserver.cpp
#include "dataserver.h"
DataServer::DataServer(QObject *parent) : QTcpServer(parent)
{
tik=new QTimer();
//1秒中执行一次
tik->setInterval(1000);
//将timeout信号和timetik槽函数连接
connect(tik,SIGNAL(timeout()),this,SLOT(timetik()));
listclient=new QMap<qintptr,UserClient*>;
}
//如果有客户端连接,就会触发该函数执行,并传进来一个客户端的socket描述符
void DataServer::incomingConnection(qintptr socketDescriptor)
{
//UserClient是自定义类型,用来保存用户信息,每当连接上来一个客户端
//会产生一个用户客户端,并将socket描述符传给用户客户端
UserClient *client=new UserClient(socketDescriptor);
//将客户端存起来
this->listclient->insert(socketDescriptor,client);
//连接服务端信号和用户客户端的槽函数,当调用该槽函数,会将数据发送给所有客户端
connect(this,SIGNAL(SendDataToClient(QByteArray)),client,SLOT(WriteData(QByteArray)),Qt::QueuedConnection);
//连接客户端信号和服务端槽,当客户端连接出错时(断开),用户客户端会调用
connect(client,SIGNAL(Del(qintptr)),this,SLOT(DelClient(qintptr)));
client->Start();
}
//删除客户端,在客户端出现错误或者断开时执行
void DataServer::DelClient(qintptr socketDes)
{
//如果客户端存在,删除之
if(listclient->contains(socketDes))
{
//释放内存
delete listclient->value(socketDes);
//删除对象指针
listclient->remove(socketDes);
}
}
//发送数据
void DataServer::timetik()
{
QString str("this is a service !!");
QByteArray arr=str.toUtf8();
emit SendDataToClient(arr);
}
3、然后在main函数中启动DataServer即可:
//文件名:main.cpp
#include <QCoreApplication>
#include <dataserver.h>
#include <QNetworkInterface>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//创建一个服务对象
DataServer *server=new DataServer();
//启动监听
if (!server->listen(QHostAddress::Any,51005))
{
//如果没有启动成功,输出错误
qDebug()<<server->errorString();
}
QString ipAddress;
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
// 使用第一个本地IP地址
for (int i = 0; i < ipAddressesList.size(); ++i)
{
if (ipAddressesList.at(i) != QHostAddress::LocalHost &&
ipAddressesList.at(i).toIPv4Address())
{
ipAddress = ipAddressesList.at(i).toString();
break;
}
}
if (ipAddress.isEmpty())
{
ipAddress = QHostAddress(QHostAddress::LocalHost).toString();
}
//输出连接信息
qDebug()<<"The server is running onIP:"+ ipAddress+" port:"+QString::number(server->serverPort());
return a.exec();
}
从上面的程序看到,Qt所有对象间通信都是用信号和槽,信号和槽是智能连接的,自动选择同步连接还是异步连接,用过C#的或许会觉得,C#的delegate委托真是强大……,当用了Qt的信号和槽之后,你会发现其他语言或者框架的对象间消息通信都是渣渣。在这里拿Qt的信号和槽机制和C#的委托做个比较(水平不高,个人见解)。<br> C#的委托:
- 委托需要需要单独定义,而且delegate定义委托是一种类型,和类一个级别的,使用时需要再实例化,麻烦
- 委托不安全,如果调用了一个没有委托了函数(或已经释放)的委托,会报空引用异常,所以必须try catch
- 委托性能较低,用不好会很慢很慢,尤其是当一个委托+=了好几个函数的时候
- 委托用的最多的时候时是线程间通信(和定义事件),向GUI中发送数据,需要调用Invork或BeginInvork,一个同步,一个异步,而这两个函数是GUI的,需要把GUI控件或窗体穿到发送数据的线程中(双向依赖),增加了模块间的依赖(有解决办法,略麻烦)。<br> Qt的信号和槽
- 使用方便,只需将信号和槽函数用connect函数连接起来即可,并且一个信号可以连接很多槽和其他信号
- 非常安全,连不连接都可以调用,即使连接的函数和对象释放了,也没关系
- 性能非常高(之前被认为性能不高,实际上很高,自从Qt5之后,更是直接和函数指针同样高性能,Qt5基于模板)
- 发射信号调用槽函数直接操作GUI,没任何问题;单方面依赖,模块分离。
- C#中动不动开个线程干活,Qt中根本不需要,Qt的各种异步消息循环基本已经做到完美了。
事实上线程是个很难掌握的技术;在没有GUI的程序中使用线程交互是不明智的行为(除非你能保证线程运行在不同的CPU核上,并能做好同步,当然开发中情况复杂,不可一概而论,新手的话还是别整太复杂的好),GUI中主要是为了能够让界面友好的响应人。