Qt网络服务端程序
Qt网络服务端程序
小果汁儿 发表于1年前
Qt网络服务端程序
  • 发表于 1年前
  • 阅读 57
  • 收藏 1
  • 点赞 0
  • 评论 0

腾讯云 新注册用户 域名抢购1元起>>>   

摘要: 写给新手入门的教程,高手略过吧,中间有蹩脚英文,也略过吧哈哈。文中Qt信号和槽连接的写法是按旧的方式写的,Qt5中有新的更科学的写法。

  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中主要是为了能够让界面友好的响应人。

标签: c++ Qt
共有 人打赏支持
粉丝 8
博文 7
码字总数 6789
作品 1
×
小果汁儿
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: