C++没有封装专门的网络套接字的类,因此C++只能调用C对应的API,而在Linux和Windows环境下的API都是不一样的
Qt作为一个C++框架提供了相关封装好的套接字通信类
在Qt中需要用到两个类,两个类都属于network且都是属于IO操作,只不过这两个类是对网络传过来数据进行IO操作
使用前需要再.pro文件里添加 += network
QTcpServer 服务器类,用于监听客户端连接和与客户端创建连接
QTcpSocket 通信的套接字类,服务器和客户端都要使用
QTcpServer常用API
常用函数
构造函数
参数指定父对象,目的是利用Qt对象树机制
cpp
QTcpServer::QTcpServer(QObject *parent = nullptr)
给套接字设置监听
cpp
//第一个参数,绑定本地地址,默认是任意一个地址,使用时建议用默认值,代表自动绑定
//第二个参数是端口号
bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
//判断当前对象是否在监听,是返回true,否返回false
bool QTcpServer::isListening() const
//如果当前对象正在监听,返回监听的服务器地址信息否则返回QHostAddress::Null
QHostAddress QTcpServer::serverAddress() const
//如果当前对象正在监听,返回监听的服务器端口号,否则返回0
quint16 QTcpServer::serverPort() const
注:
1.listen函数的两个参数都有默认值,但是端口号必须由程序员指定,否则系统会随机绑定一个端口,这样就无法连接了。建议使用5000以上的端口
获取通信套接字
此函数会获得通信使用的套接字对象,这个对象是QTcpServer的子对象,当父对象被析构时,子对象也会被析构
cpp
QTcpSocket *QTcpServer::nextPendingConnection()
本函数是一个阻塞函数。当启动服务器线程后调用这个函数后就会阻塞服务器并等待客户端连接,直到客户端连接后解除阻塞,但是不推荐使用,建议使用信号
cpp
bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = nullptr)
- 第一个参数设置最大阻塞时间,单位毫秒
- 第二个参数是个传出参数true为超时解除阻塞,false为非超时解除阻塞
信号
cpp
[signal] void QTcpServer::newConnection()
每次有新连接时都会发出newConnection信号
cpp
[signal, since 5.0] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError)
当接受新连接导致错误时,会发出acceptError信号,socketError参数描述了错误信息
QTcpSocket常用API
Qt读写网络上传过来的数据,本质上是对本地的数据进行读写,因为Qt会对接收的数据放入一块分配好的内存,然后对这块内存进行读写
常用函数
构造函数
cpp
QTcpSocket::QTcpSocket(QObject *parent = nullptr)
连接服务器,指定端口和IP地址等需要的信息
第一个参数是服务器地址(IP地址),第二个参数是服务器端口,服务器绑定了哪个端口就连接哪个端口,第三个参数是打开方式,第四个参数一般不做修改
cpp
[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, QIODeviceBase::OpenMode openMode = ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = AnyIPProtocol)
void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, QIODeviceBase::OpenMode openMode = ReadWrite)
通信流程
网络通信发送文件与进度条处理
服务端
子线程文件
cpp
#include "recvfile.h"
#include <QFile>
RecvFile::RecvFile(QTcpSocket* tcp,QObject *parent)
: QThread{parent}
{
m_tcp = tcp;
}
void RecvFile::run()
{
QFile *file = new QFile("recv.txt");
file->open(QFile::WriteOnly);
//接受数据
connect(m_tcp,&QTcpSocket::readyRead,this,[=]()
{
static int count = 0;
static int total = 0;
if (count == 0)
{
m_tcp->read((char*)&total,4);
}
//读出剩余的数据
QByteArray all = m_tcp->readAll();
count += all.size();
file->write(all);
if (count == total)
{
m_tcp->close();
m_tcp->deleteLater();
file->close();
file->deleteLater();
emit over();
}
});
//进入事件循环,要一直等有没有文件发过来
exec();
}
主线程文件
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "recvfile.h"
#include <QMessageBox>
#include <QTcpSocket>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
qDebug() << "服务器主线程:" << QThread::currentThread();
m_s = new QTcpServer(this);
//有连接的信号槽处理
connect(m_s,&QTcpServer::newConnection,this,[=](){
QTcpSocket* m_tcp = m_s->nextPendingConnection();
//创建子线程对象
RecvFile* subThread = new RecvFile(m_tcp);
subThread->start();
connect(subThread,&RecvFile::over,this,[=](){
subThread->quit();
subThread->wait();
subThread->deleteLater();
QMessageBox::information(this,"信息","文件传输完毕");
});
});
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_setListen_clicked()
{
unsigned short port = ui->port->text().toUShort();
m_s->listen(QHostAddress::Any,port);
}
客户端
工作类
cpp
#include "sendfile.h"
#include <QFile>
#include <QFileInfo>
#include <QHostAddress>
SendFile::SendFile(QObject *parent)
: QObject{parent}
{}
void SendFile::connectToServer(unsigned short port, QString ip)
{
m_socket = new QTcpSocket;
//连接服务器
m_socket->connectToHost(QHostAddress(ip),port);
//检测服务器和客户端是否连接成功
connect(m_socket,&QTcpSocket::connected,this,&SendFile::connetOK);
//断开连接操作
connect(m_socket,&QTcpSocket::disconnected,this,[=](){
m_socket->close();
m_socket->deleteLater();
emit fileFinish();
});
}
void SendFile::sendFile(QString path)
{
//打开文件
QFile file(path);
//第一次发送时要获取文件大小
QFileInfo info(path);
//文件大小
int fileSize = info.size();
file.open(QFile::ReadOnly);
//文件没读完就一直读
while (!file.atEnd())
{
//第一次循环就发送文件大小
static int num = 0;
if (num == 0)
{
m_socket->write((char*)&fileSize,4);
}
QByteArray line = file.readLine();
num += line.size();
//计算当前的发送百分比
int percent = num * 100 / fileSize;
//发送对应用于进度条维护的信号
emit curPercent(percent);
//发送信息
m_socket->write(line);
}
}
主窗口
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include "sendfile.h"
#include <QMessageBox>
#include <QFileDialog>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//初始化端口,ip和进度条
ui->ip->setText("127.0.0.1");
ui->port->setText("8989");
ui->progressBar->setRange(0,100);
ui->progressBar->setValue(0);
//创建线程对象
QThread* t = new QThread;
//创建任务对象
SendFile* worker = new SendFile;
//将任务移动到线程里
worker->moveToThread(t);
//发送信号告诉子线程什么时候连接服务器,什么时候发送文件
connect(this,&MainWindow::startConnect,worker,&SendFile::connectToServer);
connect(this,&MainWindow::sendFile,worker,&SendFile::sendFile);
//处理子线程发出来的信号
connect(worker,&SendFile::connetOK,this,[=](){
QMessageBox::information(this,"连接服务器","服务器连接成功!");
});
//更新进度条处理
connect(worker,&SendFile::curPercent,ui->progressBar,&QProgressBar::setValue);
//文件发完了就释放资源
connect(worker,&SendFile::fileFinish,this,[=](){
//资源释放
t->quit();
t->wait();
worker->deleteLater();
t->deleteLater();
});
t->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_connect_clicked()
{
QString ip = ui->ip->text();
unsigned short port = ui -> port->text().toUShort();
emit startConnect(port,ip);
}
void MainWindow::on_selFile_clicked()
{
QString path = QFileDialog::getOpenFileName();
if (path.isEmpty())
{
QMessageBox::warning(this,"警告","文件路径不能为空!");
return;
}
ui->filePath->setText(path);
}
void MainWindow::on_sendFile_clicked()
{
emit sendFile(ui->filePath->text());
}