基于TCP Qt的网络通信
在标准 C++ 没有提供专门用于套接字通信的类,所以只能使用操作系统提供的基于 C 的 API 函数,基于这些 C 的 API 函数我们也可以封装自己的 C++ 类 , 但是Qt 提供了封装好的套接字通信类:
QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接 QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接
这两个套接字通信类都属于网络模块 network ,使用前切记在项目的 .pro文件中添加 network 模块
1、QTcpSever
QTcpServer 类用于监听客户端连接以及和客户端建立连接,在使用之前先介绍一下这个类提供的一些常用 API 函数:
1.1 成员函数
QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);
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
参数:
- address: 通过类 QHostAddress 可以封装 IPv4 、 IPv6 格式的 IP地址,QHostAddress :: Any 表示自动绑定;
- port: 端口号 , 指定为 0 表示随机绑定一个可用端口;
- 返回值: 绑定失败返回 false , 成功 true;
- 得到和客户端建立连接之后用于通信的
QTcpSocket 套接字对象,它是 QTcpServer 的一个子对象,当 QTcpServer 对象析构的时候会自动析构这个子对象,当然也可自己手动析构,建议用完之后自己手动析构这个通信的 QTcpSocket 对象。
QTcpSocket *QTcpServer::nextPendingConnection();
- 阻塞等待客户端发起的连接请求,
不推荐在单线程程序中使用,建议使用非阻塞方式处理新连接,即使用信号 newConnection() 。
bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR);
参数:
- mes: 指定阻塞的最大时间 , 单位毫秒;
- timeout: 传出参数,如果操作超时 timeout 为 true,没有超时 timeout 为 false;
1.2 信号
- 当接受新连接导致错误时,将发射如下信号。socketError 参数描述了发生的错误相关的信息;
[signal] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError);
- 每次有新连接可用时都会发出 newConnection () 信号。
[signal] void QTcpServer::newConnection();
2、 QTcpSocket
QTcpSocket 是一个套接字通信类,不管是客户端还是服务器端都需要使用。在 Qt 中发送和接收数据也属于 IO 操作(网络 IO),先来看一下这个类的继承关系:
QTcpSocket 继承于 QAbstractSocket 继承于 QIODevice
2.1 成员函数
QTcpSocket::QTcpSocket(QObject *parent = Q_NULLPTR);
- 连接服务器,
需要指定服务器端绑定的IP和端口信息。
[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
[virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite);
在 Qt 中不管调用读操作函数接收数据,还是调用写函数发送数据,操作的对象都是本地的由 Qt 框架维护的一块内存。因此,调用了发送函数数据不一定会马上被发送到网络中,调用了接收函数也不是直接从网络中接收数据,关于底层的相关操作是不需要使用者来维护的
// 指定可接收的最大字节数 maxSize 的数据到指针 data 指向的内存中
qint64 QIODevice::read(char *data, qint64 maxSize);
// 指定可接收的最大字节数 maxSize,返回接收的字符串
QByteArray QIODevice::read(qint64 maxSize);
// 将当前可用操作数据全部读出,通过返回值返回读出的字符串
QByteArray QIODevice::readAll();
// 发送指针 data 指向的内存中的 maxSize 个字节的数据
qint64 QIODevice::write(const char *data, qint64 maxSize);
// 发送指针 data 指向的内存中的数据,字符串以 \0 作为结束标记
qint64 QIODevice::write(const char *data);
// 发送参数指定的字符串
qint64 QIODevice::write(const QByteArray &byteArray);
2.2 信号
在使用 QTcpSocket 进行套接字通信的过程中,如果该类对象发射出 readyRead() 信号,说明对端发送的数据达到了,之后就可以调用 read 函数 接收数据了
[signal] void QIODevice::readyRead();
- 调用
connectToHost() 函数并成功建立连接之后发出 connected() 信号
[signal] void QAbstractSocket::connected();
- 在套接字断开连接时发出
disconnected() 信号
[signal] void QAbstractSocket::disconnected();
3、 通信流程
代码百度连接,自行提取
链接:https://pan.baidu.com/s/1W-ixxxt66IsmDLXUTD0PsA 提取码:1234
3.1 服务器端
- 创建套接字服务器
QTcpServer 对象 - 通过
QTcpServer 对象设置监听,即:QTcpServer::listen() - 基于
QTcpServer::newConnection() 信号检测是否有新的客户端连接 - 如果有新的客户端连接调用
QTcpSocket *QTcpServer::nextPendingConnection() 得到通信的套接字对象 - 使用通信的套接字对象
QTcpSocket 和客户端进行通信
头文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QString>
#include <QLabel>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_setlisten_clicked();
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
QTcpServer* m_s;
QTcpSocket* m_tcp;
QLabel* m_status;
};
#endif // MAINWINDOW_H
源文件:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("服务器");
ui->pushButton->setDisabled(true); // 初始化连接前,不能按发送按钮
m_s = new QTcpServer(this);// 创建监听的套接字
ui->port->setText("8899"); // 默认资源
connect(m_s ,&QTcpServer::newConnection ,this,[=](){
// 创建用于通信的套接字
m_tcp = m_s->nextPendingConnection();
m_status->setPixmap(QPixmap(":/green.png").scaled(20,20));// 切换状态栏图片
// 检测是否可以接受数据
connect(m_tcp,&QTcpSocket::readyRead,this,[=](){
QByteArray data = m_tcp->readAll();
ui->record->append("客户端 : " + data);
});
// 断开连接
connect(m_tcp,&QTcpSocket::disconnected ,this,[=](){
m_tcp->close();
m_tcp->deleteLater(); // 封装 delete
m_status->setPixmap(QPixmap(":/red.png").scaled(20,20));
ui->pushButton->setDisabled(true);
});
// 切换发送按钮状态
ui->pushButton->setDisabled(false);
});
// 状态栏
m_status = new QLabel;
m_status->setPixmap(QPixmap(":/red.png").scaled(20,20));
ui->statusBar->addWidget(new QLabel("连接状态: "));
ui->statusBar->addWidget(m_status);
}
MainWindow::~MainWindow()
{
delete ui;
}
// 启动监听服务按钮
void MainWindow::on_setlisten_clicked()
{
unsigned short port = ui->port->text().toUShort();
m_s->listen(QHostAddress::Any ,port); // 设置监听
ui->setlisten->setDisabled(true);
}
// 发送数据按钮
void MainWindow::on_pushButton_clicked()
{
QString msg = ui->msg->toPlainText();
m_tcp->write(msg.toUtf8()); // 输入客户端
ui->record->append("服务器 : " + msg);
}
3.2 客户端
- 创建通信的套接字类
QTcpSocket 对象 - 使用服务器端绑定的 IP 和端口连接服务器
QAbstractSocket::connectToHost() - 使用
QTcpSocket 对象和服务器进行通信
头文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpSocket>
#include <QString>
#include <QLabel>
#include <QHostAddress>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void readData();
private slots:
void on_pushButton_clicked();
void on_connect_clicked();
void on_disconnect_clicked();
private:
Ui::MainWindow *ui;
QTcpSocket* m_tcp;
QLabel* m_status;
};
#endif // MAINWINDOW_H
源文件:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowIcon(QIcon(":/m4.ico"));
setWindowTitle("客户端");
ui->disconnect->setDisabled(true); // 初始时 ,断开连接按钮不可用
ui->port->setText("30003"); // 默认端口
ui->ip->setText("192.168.79.129"); // 默认ip
ui->msg->setPlaceholderText("请输入指令");
// 创建通信的套接字
m_tcp =new QTcpSocket(this);
// 接受到服务端消息
connect(m_tcp,&QTcpSocket::readyRead,this,[=](){
QByteArray data = m_tcp->readAll();
// ui->record->append("服务器 : " + data);
// readData();
});
// 服务器端断开连接
connect(m_tcp,&QTcpSocket::disconnected ,this,[=](){
m_tcp->close();
//m_tcp->deleteLater(); // 封装 delete
m_status->setPixmap(QPixmap(":/red.png").scaled(20,20));
ui->record->append("已经和服务器断开连接....");
ui->connect->setDisabled(false);// 设置连接按钮不可用
ui->disconnect->setDisabled(true);
});
// 检测连接状态
connect(m_tcp,&QTcpSocket::connected ,this,[this](){
m_status->setPixmap(QPixmap(":/green.png").scaled(20,20));
ui->record->append("连接服务器成功...");
ui->connect->setDisabled(true);
ui->disconnect->setDisabled(false);
});
// 状态栏
m_status = new QLabel;
m_status->setPixmap(QPixmap(":/red.png").scaled(20,20));
ui->statusBar->addWidget(new QLabel("连接状态: "));
ui->statusBar->addWidget(m_status);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::readData()
{
QByteArray byteArray;//临时变量,用来存储每次从机械臂读进来的数据
byteArray.resize(this->m_tcp->bytesAvailable());//先设置每次读取的大小为可接收缓存的大小
int byteRead = this->m_tcp->read(byteArray.data(), byteArray.size());//把缓存区所有数据读取回来,可以用readAll替代,但是现在用的函数可以判断读取结果
if (byteRead == -1) {//读取错误
ui->record->append("read data from robot error!") ;
return;
} else if (byteRead == 0) {//没读到东西
ui->record->append("read data from robot error!") ;
return;
}
}
// 发送数据按钮
void MainWindow::on_pushButton_clicked()
{
QString msg = ui->msg->toPlainText();
m_tcp->write(msg.toUtf8()+"\n"); // 输入客户端
ui->record->append("客户端 : " + msg);
ui->msg->clear();
}
// 连接服务器按钮
void MainWindow::on_connect_clicked()
{
QString ip = ui->ip->text();
unsigned short port = ui->port->text().toUShort();
m_tcp->connectToHost(QHostAddress(ip) , port);
}
// 断开连接
void MainWindow::on_disconnect_clicked()
{
m_tcp->close();
ui->disconnect->setDisabled(true);
ui->connect->setDisabled(false);
}
|