一个简单的双向的网络连接和信息发送。效果如下图所示:
只需要配置这个主机的IP和端口号,由客户端发送链接请求。即可进行连接。
QT的network模块是一个用于网络编程的模块,它提供了一系列的类和函数,可以让您使用TCP/IP协议来创建客户端和服务端的应用程序。QT的network模块有以下几个特点:
QT的network模块主要包括以下几类:
这里是官方文档的链接地址:Qt Network 6.5.2
首先要在pro文件里面包含下面的network,不然会报错
QT += core gui network
然后再包含相关的头文件,比如:#include
咱们首先放上,这个客户端的头文件的代码:
#ifndef WIDGET_H
#define WIDGET_H
#include
#include //包含头文件
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
QTcpSocket *tcpSocket;
QFile *localFile; //要发送的文件
qint64 totalBytes; //数据总大小
qint64 bytesWritten; //已经发送数据大小
qint64 bytesToWrite; //剩余数据大小
qint64 loadSize; //每次发送数据的大小
QString fileName; //保存文件路径
QByteArray outBlock; //数据缓冲区,即存放每次要发送的数据
QByteArray inBlock; //数据缓冲区,接收
qint64 bytesReceived; //已收到数据的大小
qint64 fileNameSize; //文件名的大小信息
private slots:
void newConnect(); //连接服务器
void readData(); //接收数据
void sendData();//发送数据
void continueSend(qint64 numBytes); //更新发送进度条
void on_conBtn_clicked();
void on_sendbtn_clicked();
};
#endif // WIDGET_H
然后再放上客户端的.cpp文件,代码部分如下:
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
//#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
//#endif
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);//建立UI界面
tcpSocket = new QTcpSocket(this);
//对变量参数进行初始化。
loadSize = 4*1024;//每次发送数据大小
totalBytes = 0;//总数据大小
bytesWritten = 0;
bytesToWrite = 0;
//接收
bytesReceived = 0;//已收到数据大小信息
fileNameSize = 0;//文件大小信息
//当有数据发送成功时,继续发送
connect(tcpSocket,SIGNAL(bytesWritten(qint64)),this,
SLOT(continueSend(qint64)));
//接收数据
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readData()));
ui->sendbtn->setEnabled(false);
}
Widget::~Widget()
{
delete ui;
}
void Widget::newConnect()
{
// blockSize = 0; //初始化其为0
tcpSocket->abort(); //取消已有的连接
//连接到主机,这里从界面获取主机地址和端口号
tcpSocket->connectToHost(ui->hostEdit->text(), ui->portEdit->text().toInt());
ui->sendbtn->setEnabled(true);//允许button可以进行点击
}
void Widget::readData()
{
QDataStream in(tcpSocket);//定义发送方
in.setVersion(QDataStream::Qt_5_8);
if(bytesReceived <= sizeof(qint64)*2)
{ //如果接收到的数据小于16个字节,那么是刚开始接收数据,我们保存到//来的头文件信息
if((tcpSocket->bytesAvailable() >= sizeof(qint64)*2)
&& (fileNameSize == 0))
{ //接收数据总大小信息和文件名大小信息
in >> totalBytes >> fileNameSize;
bytesReceived += sizeof(qint64) * 2;
}
if((tcpSocket->bytesAvailable() >= fileNameSize)
&& (fileNameSize != 0))
{ //接收文件名,并建立文件
in >> fileName;
ui->statuslab->setText(tr("接收文件 %1 ...").arg(fileName));
bytesReceived += fileNameSize;
localFile= new QFile(fileName);
if(!localFile->open(QFile::WriteOnly))
{
qDebug() << "open file error!";
return;
}
}
else return;
}
if(bytesReceived < totalBytes)
{ //如果接收的数据小于总数据,那么写入文件
bytesReceived += tcpSocket->bytesAvailable();
inBlock+= tcpSocket->readAll();
}
//更新进度条
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesReceived);
if(bytesReceived == totalBytes)
{ //接收数据完成时
//接收显示
QBuffer buffer(&inBlock);
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer,"jpg");
QImage image = reader.read();
if(!image.isNull())
{
image=image.scaled(ui->recLab->size());
ui->recLab->setPixmap(QPixmap::fromImage(image));
}
localFile->write(inBlock);
localFile->close();
inBlock.resize(0);
//重新置0 准备下次接收
totalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
ui->statuslab->setText(tr("接收文件 %1 成功!").arg(fileName));
}
}
void Widget::sendData()
{
bytesWritten = 0;
fileName = QFileDialog::getOpenFileName(this);
if(!fileName.isEmpty())
{
localFile = new QFile(fileName);
if(!localFile->open(QFile::ReadOnly))
{
qDebug() << "open file error!";
return;
}
//文件总大小
totalBytes = localFile->size();
QDataStream sendOut(&outBlock,QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_8);
QString currentFileName = fileName.right(fileName.size()
- fileName.lastIndexOf('/')-1);
//依次写入总大小信息空间,文件名大小信息空间,文件名
sendOut << qint64(0) << qint64(0) << currentFileName;
//这里的总大小是文件名大小等信息和实际文件大小的总和
totalBytes += outBlock.size();
sendOut.device()->seek(0);
//返回outBolock的开始,用实际的大小信息代替两个qint64(0)空间
sendOut<write(outBlock);
ui->statuslab->setText(tr("开始发送"));
outBlock.resize(0);
}
}
void Widget::continueSend(qint64 numBytes)
{
//已经发送数据的大小
bytesWritten += (int)numBytes;
if(bytesToWrite > 0) //如果已经发送了数据
{
//每次发送loadSize大小的数据,这里设置为4KB,如果剩余的数据不足4KB,
//就发送剩余数据的大小
outBlock = localFile->read(qMin(bytesToWrite,loadSize));
//发送完一次数据后还剩余数据的大小
bytesToWrite -= (int)tcpSocket->write(outBlock);
//清空发送缓冲区
outBlock.resize(0);
} else {
localFile->close(); //如果没有发送任何数据,则关闭文件
}
//更新进度条
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesWritten);
if(bytesWritten == totalBytes) //发送完毕
{
ui->statuslab->setText(tr("传送文件 %1 成功").arg(fileName));
localFile->close();
}
}
void Widget::on_conBtn_clicked()
{
newConnect();
}
void Widget::on_sendbtn_clicked()
{
sendData();
}
然后就是服务端的代码,具体代码实现部分如下:
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
QTcpServer *tcpServer;
QTcpSocket *currentClient;
qint64 totalBytes; //存放总大小信息
qint64 bytesReceived; //已收到数据的大小
qint64 fileNameSize; //文件名的大小信息
QString fileName; //存放文件名
QFile *localFile; //本地文件
QByteArray inBlock; //数据缓冲区
qint64 bytesWritten; //已经发送数据大小
qint64 bytesToWrite; //剩余数据大小
qint64 loadSize; //每次发送数据的大小
QByteArray outBlock; //数据缓冲区,即存放每次要发送的数据
private slots:
void NewConnection();
void recMessage();
void sendMessage();
void disconnect();
void continueSend(qint64);
void on_sendButton_clicked();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
#include
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
totalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
tcpServer = new QTcpServer(this);
if(!tcpServer->listen(QHostAddress::Any,6666))
{ //**本地主机的6666端口,如果出错就输出错误信息,并关闭
ui->plainTextEdit->appendPlainText(tcpServer->errorString());
close();
}
//连接信号和相应槽函数,有新的连接进入是需处理
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(NewConnection()));
ui->sendButton->setEnabled(false);
ui->plainTextEdit->appendPlainText(tcpServer->serverAddress().toString());
ui->plainTextEdit->appendPlainText(QString::number (tcpServer->serverPort()));
// QList nets = QNetworkInterface::allInterfaces();
// int count=nets.count(); //接口的数量
// int i = 0;
// foreach(QNetworkInterface netinterface,nets)
// {
// qDebug()<plainTextEdit->appendPlainText(netinterface.humanReadableName());
// //判断该接口是否是环回口
// QNetworkInterface::InterfaceFlags flags = nets[i].flags();
// if(flags.testFlag(QNetworkInterface::IsLoopBack)){
// qDebug()<<"This is loopback";
// }
// else {
// qDebug()<<"This is not loopback";
// }
// i++;
// }
// QList list = QNetworkInterface::allAddresses();
// for (int i = 0; i < list.size(); i++){
// qDebug()<plainTextEdit->appendPlainText(list.at(i).toString());
// }
}
Widget::~Widget()
{
delete ui;
}
void Widget::NewConnection()
{
//初始化为0;
//blockSize=0;
// inBlock.resize(0);
//新连接进入的显示处理
currentClient = tcpServer->nextPendingConnection();
ui->statuslab->setText(tr("%1:%2").arg(currentClient->peerAddress().toString().split("::ffff:")[1])\
.arg(currentClient->peerPort()));
connect(currentClient, SIGNAL(readyRead()), this, SLOT(recMessage()));
connect(currentClient, SIGNAL(disconnected()), this, SLOT(disconnect()));
//当有数据发送成功时,继续发送
connect(currentClient,SIGNAL(bytesWritten(qint64)),this, SLOT(continueSend(qint64)));
ui->sendButton->setEnabled(true);
/*调试***************************/
//接收客户端的链接请求
QTcpSocket *client = tcpServer->nextPendingConnection();
//获取对方的IP和端口
QString ip = currentClient->peerAddress().toString();
quint16 port = currentClient->peerPort();
//打印或处理IP和端口
qDebug() << "Client IP:" << ip << "Port:" << port;
}
void Widget::recMessage()
{
QDataStream in(currentClient);
in.setVersion(QDataStream::Qt_5_8);
if(bytesReceived <= sizeof(qint64)*2)
{ //如果接收到的数据小于16个字节,那么是刚开始接收数据,我们保存到//来的头文件信息
if((currentClient->bytesAvailable() >= sizeof(qint64)*2)
&& (fileNameSize == 0))
{ //接收数据总大小信息和文件名大小信息
in >> totalBytes >> fileNameSize;
bytesReceived += sizeof(qint64) * 2;
}
if((currentClient->bytesAvailable() >= fileNameSize)
&& (fileNameSize != 0))
{ //接收文件名,并建立文件
in >> fileName;
ui->statuslab->setText(tr("接收文件 %1 ...").arg(fileName));
bytesReceived += fileNameSize;
ui->statuslab->setText(fileName);
localFile= new QFile(fileName);
if(!localFile->open(QFile::WriteOnly))
{
qDebug() << "open file error!";
return;
}
}
else return;
}
if(bytesReceived < totalBytes)
{ //如果接收的数据小于总数据,那么写入文件
bytesReceived += currentClient->bytesAvailable();
inBlock+= currentClient->readAll();
}
//更新进度条
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesReceived);
if(bytesReceived == totalBytes)
{ //接收数据完成时
//接收显示
QBuffer buffer(&inBlock);
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer,"jpg");
QImage image = reader.read();
if(!image.isNull())
{
image=image.scaled(ui->recLab->size());
ui->recLab->setPixmap(QPixmap::fromImage(image));
}
localFile->write(inBlock);
localFile->close();
inBlock.resize(0);
//重新置0 准备下次接收
totalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
ui->statuslab->setText(tr("接收文件 %1 成功!").arg(fileName));
}
}
void Widget::sendMessage()
{
bytesWritten = 0;
fileName = QFileDialog::getOpenFileName(this);
if(!fileName.isEmpty())
{
localFile = new QFile(fileName);
if(!localFile->open(QFile::ReadOnly))
{
qDebug() << "open file error!";
return;
}
//文件总大小
totalBytes = localFile->size();
QDataStream sendOut(&outBlock,QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_5_8);
QString currentFileName = fileName.right(fileName.size()
- fileName.lastIndexOf('/')-1);
//依次写入总大小信息空间,文件名大小信息空间,文件名
sendOut << qint64(0) << qint64(0) << currentFileName;
//这里的总大小是文件名大小等信息和实际文件大小的总和
totalBytes += outBlock.size();
sendOut.device()->seek(0);
//返回outBolock的开始,用实际的大小信息代替两个qint64(0)空间
sendOut<write(outBlock);
ui->statuslab->setText(tr("开始发送"));
outBlock.resize(0);
}
}
void Widget::continueSend(qint64 numBytes)
{
//已经发送数据的大小
bytesWritten += (int)numBytes;
if(bytesToWrite > 0) //如果已经发送了数据
{
//每次发送loadSize大小的数据,这里设置为4KB,如果剩余的数据不足4KB,
//就发送剩余数据的大小
outBlock = localFile->read(qMin(bytesToWrite,loadSize));
//发送完一次数据后还剩余数据的大小
bytesToWrite -= (int)currentClient->write(outBlock);
//清空发送缓冲区
outBlock.resize(0);
} else {
localFile->close(); //如果没有发送任何数据,则关闭文件
}
//更新进度条
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesWritten);
if(bytesWritten == totalBytes) //发送完毕
{
ui->statuslab->setText(tr("传送文件 %1 成功").arg(fileName));
localFile->close();
}
}
void Widget::disconnect()
{
}
void Widget::on_sendButton_clicked()
{
//发送数据
sendMessage();
}
**************************************************************************************************************
下面我们用另外一个例子来简单的演示如何使用QT的network模块做一个简单的服务端和客户端的开发。用于实现一个简单的聊天程序。你可以参考以下的代码和注释来了解其原理和步骤:
#include
#include
#include
#include
// 定义一个服务端类,继承自QTcpServer
class ChatServer : public QTcpServer
{
Q_OBJECT
public:
// 构造函数,接受一个端口号作为参数
ChatServer(quint16 port)
{
// 监听指定的端口号,等待客户端的连接请求
if (!listen(QHostAddress::Any, port))
{
qFatal("Failed to listen on port %d", port);
}
// 打印监听成功的信息
qDebug() << "Chat server started on port" << port;
}
protected:
// 重写虚函数incomingConnection,处理客户端的连接请求
void incomingConnection(qintptr socketDescriptor) override
{
// 创建一个QTcpSocket对象,用于和客户端通信
QTcpSocket *socket = new QTcpSocket(this);
// 将QTcpSocket对象与指定的套接字描述符关联
socket->setSocketDescriptor(socketDescriptor);
// 将QTcpSocket对象添加到客户端列表中
clients.append(socket);
// 打印客户端连接成功的信息
qDebug() << "Client connected:" << socket->peerAddress().toString();
// 连接QTcpSocket对象的readyRead信号和服务器的sendData槽函数,用于接收和发送数据
connect(socket, &QTcpSocket::readyRead, this, &ChatServer::sendData);
// 连接QTcpSocket对象的disconnected信号和服务器的removeClient槽函数,用于处理客户端断开连接
connect(socket, &QTcpSocket::disconnected, this, &ChatServer::removeClient);
}
private slots:
// 定义一个槽函数,用于接收客户端发送的数据,并转发给其他客户端
void sendData()
{
// 获取发送数据的客户端对象
QTcpSocket *sender = qobject_cast(QObject::sender());
// 如果客户端对象为空,返回
if (!sender) return;
// 读取客户端发送的数据,并转换为文本格式
QTextStream stream(sender);
QString data = stream.readAll();
// 打印接收到的数据
qDebug() << "Received data:" << data;
// 遍历客户端列表,将数据转发给其他客户端
for (QTcpSocket *client : clients)
{
if (client != sender)
{
QTextStream stream(client);
stream << data;
stream.flush();
}
}
}
// 定义一个槽函数,用于处理客户端断开连接,并从客户端列表中移除
void removeClient()
{
// 获取断开连接的客户端对象
QTcpSocket *sender = qobject_cast(QObject::sender());
// 如果客户端对象为空,返回
if (!sender) return;
// 打印客户端断开连接的信息
qDebug() << "Client disconnected:" << sender->peerAddress().toString();
// 从客户端列表中移除客户端对象
clients.removeOne(sender);
// 删除客户端对象
sender->deleteLater();
}
private:
QList clients; // 存储客户端对象的列表
};
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
ChatServer server(1234); // 创建一个服务端对象,监听1234端口
return app.exec();
}
#include "moc_chatserver.cpp"
客户端代码:
#include
#include
#include
// 定义一个全局变量,存储客户端的昵称
QString g_name;
// 定义一个函数,用于从标准输入读取一行文本,并发送给服务端
void sendToServer(QTcpSocket *socket)
{
// 从标准输入读取一行文本
QTextStream input(stdin);
QString line = input.readLine();
// 如果文本为空,返回
if (line.isEmpty()) return;
// 在文本前加上客户端的昵称
line = g_name + ": " + line;
// 创建一个文本流,关联到套接字对象
QTextStream output(socket);
// 将文本写入到套接字对象,并刷新
output << line << endl;
output.flush();
}
// 定义一个函数,用于从服务端接收一行文本,并打印到标准输出
void receiveFromServer(QTcpSocket *socket)
{
// 创建一个文本流,关联到套接字对象
QTextStream input(socket);
// 读取一行文本
QString line = input.readLine();
// 如果文本为空,返回
if (line.isEmpty()) return;
// 将文本打印到标准输出
QTextStream output(stdout);
output << line << endl;
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 创建一个QTcpSocket对象,用于和服务端通信
QTcpSocket socket;
// 连接QTcpSocket对象的connected信号和QCoreApplication的quit槽函数,用于在连接成功后退出事件循环
QObject::connect(&socket, &QTcpSocket::connected, &app, &QCoreApplication::quit);
// 尝试连接到服务端,指定地址和端口号
socket.connectToHost("127.0.0.1", 1234);
// 进入事件循环,等待连接成功或超时
app.exec();
// 检查连接状态,如果连接失败,打印错误信息并退出程序
if (socket.state() != QAbstractSocket::ConnectedState)
{
qCritical("Failed to connect to server");
return -1;
}
// 打印连接成功的信息
qDebug() << "Connected to server";
// 从标准输入读取客户端的昵称,并存储在全局变量中
QTextStream input(stdin);
qDebug() << "Enter your name:";
g_name = input.readLine();
// 连接QTcpSocket对象的readyRead信号和receiveFromServer函数,用于接收服务端发送的数据
QObject::connect(&socket, &QTcpSocket::readyRead, &{ receiveFromServer(&socket); });
// 进入一个无限循环,不断从标准输入读取数据,并发送给服务端
while (true)
{
sendToServer(&socket);
app.processEvents();
Sleep(100);
app.processEvents();
Sleep(100);
app.processEvents();
Sleep(100);
app.processEvents();
Sleep(100);
app.processEvents();
Sleep(100);
app.processEvents();
Sleep(100);
app.processEvents();
Sleep(100);
app.processEvents();
Sleep(100);
app.processEvents();
Sleep(100);
app.processEvents();
Sleep(100);
}
}
这个代码的目的是使用QT的network模块做一个简单的服务端和客户端的开发,实现一个简单的聊天程序。具体的步骤和原理如下: