设计一个客户端从服务器端获取时间的程序:
服务器端使用多线程的方式,当有客户端请求到达时,服务器将启动一个新线程为它返回当前的时间,服务完后线程自动销毁,服务器端会显示连接的次数。
客户端比较简单,先搭建客户端。
客户端的搭建 :
创建一个项目:名为:TCP_Client
pro文件添加
QT +=netwowork
ui界面添加以下控件:
dialog.h文件:
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include<QTcpSocket>
#include<QAbstractSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Dialog; }
QT_END_NAMESPACE
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = nullptr);
~Dialog();
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
void enableBth();//设置是否可以点击获取时间
void error_string(QAbstractSocket::SocketError);//输出错误信息
private:
Ui::Dialog *ui;
QTcpSocket *socket;//套接字
uint timer;//时间
};
#endif // DIALOG_H
dialog.cpp文件:
#include "dialog.h"
#include "ui_dialog.h"
#include <QDateTime>
#include<QMessageBox>
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::Dialog)
{
ui->setupUi(this);
setWindowTitle("客户端");
socket=new QTcpSocket(this);
connect(ui->lineEdit,&QLineEdit::textChanged,this,&Dialog::enableBth);//当服务器名输入框内容改变时,执行该函数
connect(ui->lineEdit_2,&QLineEdit::textChanged,this,&Dialog::enableBth);//当端口输入框内容改变时,执行该函数
connect(socket,&QTcpSocket::readyRead,[=]()//当有可读数据时
{
QDataStream in(socket);//创建流
in.setVersion(QDataStream::Qt_5_9);
if(timer==0)
{
if(socket->bytesAvailable()<(int)sizeof(uint))//如果过数据太小,不接收
{
return;
}
in>>timer;//读取数据
}
ui->dateTimeEdit->setDateTime(QDateTime::fromTime_t(timer));//显示获取的时间
ui->pushButton->setEnabled(true);//将获取时间的按键设为可点击
});
//当触发error信号时,执行 connect(socket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(error_string(QAbstractSocket::SocketError)));
ui->lineEdit_2->setFocus();//获取焦点
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::on_pushButton_clicked()//获取时间
{
ui->lineEdit->setEnabled(false);
timer=0;
socket->abort();//断开已有连接
socket->connectToHost(ui->lineEdit->text(),ui->lineEdit_2->text().toInt());//创建连接
}
void Dialog::on_pushButton_2_clicked()//退出
{
close();//关闭界面
}
void Dialog::enableBth()
{
ui->pushButton->setEnabled(!ui->lineEdit->text().isEmpty()&&!ui->lineEdit_2->text().isEmpty());//端口和主机不能为空
}
void Dialog::error_string(QAbstractSocket::SocketError socketerror)//输出错误信息
{
switch(socketerror)
{
case QAbstractSocket::RemoteHostClosedError:
break;
case QAbstractSocket::HostNotFoundError:
QMessageBox::information(this,"错误信息",tr("主机不可达"));
break;
case QAbstractSocket::ConnectionRefusedError:
QMessageBox::information(this,"错误信息",tr("连接被拒绝"));
default:
QMessageBox::information(this,"错误信息",tr("%1").arg(socket->errorString()));//获取错误信息
}
ui->pushButton->setEnabled(true);
}
服务器的搭建:
难点在于如何搭建多线程服务器。
创建一个新项目:Server_thread
服务器的搭建需要创建3个类:
- 主界面类:接受和显示连接次数的类:tcp_server
- 线程类:用来通过套接字发送时间的类:timethread类
- 服务器类:用来建立连接的类:timeserver
搭建的关键:服务器有连接时,使用线程进行执行,(所以要重写有连接时的函数)线程执行完发送信号给主类,显示数据。
每个类的搭建过程:
主页面类:
服务器类:
线程类:
目录:
1.创建项目时已经创建了tcp_server类 (主界面)
pro文件中添加:
QT +=network
2.创建 线程类:继承自QThread
timeThread.h文件
#ifndef TIMETHREAD_H
#define TIMETHREAD_H
#include <QThread>
#include<QTcpServer>
#include<QTcpSocket>
#include<QDataStream>
class timeThread : public QThread//继承自QThread
{
Q_OBJECT
public:
explicit timeThread(qintptr socketDescriptor,QObject *parent=nullptr);//初始时时需要传入套接字描述
void run();//重写run函数
signals:
void error(QTcpSocket::SocketError socketError);//出错信号
private:
qintptr socketDescriptor; //套接字描述
};
#endif // TIMETHREAD_H
timethread.cpp文件
#include "timethread.h"
#include <QDateTime>
timeThread::timeThread(qintptr socketDescriptor,QObject *parent)
: QThread(parent),socketDescriptor(socketDescriptor)//初始化套接字描述
{
}
void timeThread::run()//重写run函数,服务器有连接使,创建套接字来传输数据
{
QTcpSocket tcpSocker;
if(!tcpSocker.setSocketDescriptor(socketDescriptor))
{
emit error(tcpSocker.error());//触发错误信号
return;
}
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_9);//设置版本好
uint tim2u=QDateTime::currentDateTime().toTime_t();//获取时间,转化为标准格式
out<<tim2u;
tcpSocker.write(block);//传回客户端
tcpSocker.disconnectFromHost();//断开连接
tcpSocker.waitForDisconnected();//等待返回
}
3.创建服务器类:timeserver 继承自QTcpServer
timeserver.h文件:
#ifndef TCP_SERVER_H
#define TCP_SERVER_H
#include "timeserver.h"
#include <QDialog>
#include<QTcpServer>
#include<QTcpSocket>
#include<QLabel>
#include<QPushButton>
QT_BEGIN_NAMESPACE
namespace Ui { class TCP_server; }
QT_END_NAMESPACE
class timeserver;
class TCP_server : public QDialog
{
Q_OBJECT
public:
TCP_server(QWidget *parent = nullptr);
~TCP_server();
private slots:
void slotShow();//用来显示界面上的请求次数
private:
Ui::TCP_server *ui;
QLabel *lab1;//显示端口号
QLabel *lab2;//显示已连接的个数
QPushButton *pbt;//退出按键
timeserver *server;//服务器端
int count;//统计个数
};
#endif // TCP_SERVER_H
timeserver.cpp文件:
#include "timeserver.h"
#include<timethread.h>
timeserver::timeserver(QObject *parent) : QTcpServer(parent)
{
tcp_Dia=(TCP_server *)parent;//获取创建这个timeserver对象的父类
}
void timeserver::incomingConnection(qintptr socketDescriptor)
{
timeThread *thread=new timeThread(socketDescriptor,0);//创建一个工作线程
connect(thread,SIGNAL(finished()),tcp_Dia,SLOT(slotShow()),Qt::QueuedConnection);//使用排队连接方式
connect(thread,SIGNAL(finished()),thread,SLOT(deleteLater()),Qt::DirectConnection);//当线程结束时,销毁线程
thread->start();//开启线程
}
4.Tcp_Server(主界面)
ui界面中添加以下控件: QLabel QLabel QPushbutton
tcp_server.h文件:
#ifndef TCP_SERVER_H
#define TCP_SERVER_H
#include "timeserver.h"
#include <QDialog>
#include<QTcpServer>
#include<QTcpSocket>
#include<QLabel>
#include<QPushButton>
QT_BEGIN_NAMESPACE
namespace Ui { class TCP_server; }
QT_END_NAMESPACE
class timeserver;//服务器端的前置声明
class TCP_server : public QDialog
{
Q_OBJECT
public:
TCP_server(QWidget *parent = nullptr);
~TCP_server();
private slots:
void slotShow();//用来显示界面上的请求次数
void on_pushButton_clicked();//退出
private:
Ui::TCP_server *ui;
timeserver *server;//服务器端
int count;//统计个数
};
#endif // TCP_SERVER_H
tcp_server.cpp文件:
#include "tcp_server.h"
#include "ui_tcp_server.h"
#include <QMessageBox>
#include<QHBoxLayout>
#include<QVBoxLayout>
TCP_server::TCP_server(QWidget *parent)
: QDialog(parent)
, ui(new Ui::TCP_server)
{
ui->setupUi(this);
//控件初始化
setWindowTitle(tr("多线程时间服务器"));
lab1=new QLabel("服务器端口");
lab2=new QLabel;
pbt=new QPushButton(tr("退出"));
QHBoxLayout *hlay=new QHBoxLayout;
hlay->addStretch(1);
hlay->addWidget(pbt);
hlay->addStretch(1);
QVBoxLayout *vlay=new QVBoxLayout(this);
vlay->addWidget(lab1);
vlay->addWidget(lab2);
vlay->addLayout(hlay);
connect(pbt,&QPushButton::clicked,this,&TCP_server::close);//点击退出关闭窗口
count=0;
server=new timeserver(this);
if(!server->listen())
{
QMessageBox::information(this,"提示信息",tr("无法启动服务器:%1").arg(server->errorString()));
return;
}
lab1->setText(tr("服务器端口:%1").arg(server->serverPort()));
}
TCP_server::~TCP_server()
{
delete ui;
}
void TCP_server::slotShow()//用来显示界面上的请求次数
{
lab2->setText(tr("第%1次请求完毕").arg(++count));
}
运行效果: