目录
Socket通信基础知识
前言
TCP通信
下面这个是服务端与客户端的通信流程。基本编程也是按照这个顺序进行编程。
服务端在Qt中实现TCP/IP服务器端通信的流程:
- 创建套接字
- 将套接字设置为监听模式
- 等待并接受客户端请求
可以通过QTcpServer提供的void newConnection()信号来检测是否有连接请求,如果有可以在对应的槽函数中调用nextPendingConnection函数获取到客户端的Socket信息(返回值为QTcpSocket*类型指针),通过此套接字与客户端之间进行通信。- 接收或者向客户端发送数据
- 接收数据:使用read()或者readAll()函数
- 发送数据:使用write()函数
客户端通信流程:
- 创建套接字
- 连接服务器
可以使用QTcpSocket类的connectToHost()函数来连接服务器。- 向服务器发送或者接受数据
这里要注意,一般是客户端去连接服务端的,所以,服务端要多一个tcpServer 进行监听的。
TCP通信小知识
-
在.pro文件中要添加QT += network,否则无法使用Qt的网络功能。类似于下面这张图的这个位置。
-
监听套接字与已连接套接字:参考于监听套接字与已连接套接字
内核会认为socket函数创建的套接字是主动套接字(active socket),它存在于一个连接的客户端。而服务器调用listen函数告诉内核,该套接字是被服务器而不是客户端使用的,即listen函数将一个主动套接字转化为监听套接字。监听套接字可以接受来自客户端的连接请求。
TCP通信实例一之字符通信
运行效果
主要代码讲解
- 这里,首先设置好监听套接字和连接套接字,将服务器设置为监听状态,为客服端发送信息给服务端做准备,当客户端信息发送到服务端时,二者将建立连接套接字,从而建立连接。接下来,便借助于信号槽,当套接字已准备好readRead时,发出信号,便可以将套接字中的内容取出。
另外三个槽函数,主要是connect,send,close三个函数。
//serverwidget.cpp
ui->setupUi(this);
tcpServer = NULL;
tcpSocket = NULL;
//监听套接字,指定父对象,让其自动回收空间
tcpServer = new QTcpServer(this);
tcpServer->listen(QHostAddress::Any, 8888);
setWindowTitle("服务器: 8888");
connect(tcpServer, &QTcpServer::newConnection,
[=]()
{
//取出建立好连接的套接字
tcpSocket = tcpServer->nextPendingConnection();
//获取对方的IP和端口
QString ip = tcpSocket->peerAddress().toString();
qint16 port = tcpSocket->peerPort();
QString temp = QString("[%1:%2]:成功连接").arg(ip).arg(port);
ui->textEditRead->setText(temp);
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//从通信套接字中取出内容
QByteArray array = tcpSocket->readAll();
ui->textEditRead->append(array);
}
);
}
);
clientwidget.cpp
:在与服务端建立好连接后,使用这里的槽函数,主要是使用connect和readyRead的信号进行使用,进行信息的通信。
ui->setupUi(this);
tcpSocket = NULL;
//分配空间,指定父对象
tcpSocket = new QTcpSocket(this);
setWindowTitle("客户端");
connect(tcpSocket, &QTcpSocket::connected,
[=]()
{
ui->textEditRead->setText("成功和服务器建立好连接");
}
);
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//获取对方发送的内容
QByteArray array = tcpSocket->readAll();
//追加到编辑区中
ui->textEditRead->append(array);
}
);
下面这个是客户端主动和服务器建立连接的语句。
void ClientWidget::on_buttonConnect_clicked()
{
//获取服务器ip和端口
QString ip = ui->lineEditIP->text();
qint16 port = ui->lineEditPort->text().toInt();
//主动和服务器建立连接
tcpSocket->connectToHost(QHostAddress(ip), port);
}
代码地址
https://download.csdn.net/download/weixin_38809485/12623790里面的01_TCP
,若急需,但却没有积分,可评论下留言。
TCP通信实例二之文件通信
运行效果
主要代码讲解
ServerWidget.h
#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H
#include <QWidget>
#include <QtWidgets>
#include <QTcpServer> //监听套接字
#include <QTcpSocket> //通信套接字
#include <QFile>
#include <QTimer>
class CServerWidget : public QWidget
{
Q_OBJECT
public:
CServerWidget(QWidget *parent = 0);
~CServerWidget();
void sendData(); //发送文件数据
protected:
void createUi();
// QPushButton *buttonFile;
// QPushButton *buttonSend;
private:
QWidget *ServerWidget;
QGridLayout *gridLayout;
QSpacerItem *horizontalSpacer;
QLabel *label;
QSpacerItem *horizontalSpacer_2;
QTextEdit *textEdit;
QPushButton *buttonFile;
QSpacerItem *horizontalSpacer_3;
QPushButton *buttonSend;
private slots:
void on_buttonFile_clicked();
void on_buttonSend_clicked();
private:
QTcpServer *tcpServer; //监听套接字
QTcpSocket *tcpSocket; //通信套接字
QFile file; //文件对象
QString fileName; //文件名字
qint64 fileSize; //文件大小
qint64 sendSize; //已经发送文件的大小
QTimer timer; //定时器
};
#endif // SERVERWIDGET_H
ClientWidget.h
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H
#include <QWidget>
#include <QtWidgets>
#include <QTcpServer> //监听套接字
#include <QTcpSocket> //通信套接字
#include <QFile>
#include <QTimer>
class CClientWidget : public QWidget
{
Q_OBJECT
public:
explicit CClientWidget(QWidget *parent = nullptr);
~CClientWidget();
signals:
private slots:
void on_buttonConnect_clicked();
void readFile();
protected:
void createUi();
// QPushButton *buttonFile;
// QPushButton *buttonSend;
public:
QWidget *ClientWidget;
QGridLayout *gridLayout_2;
QWidget *widget_2;
QHBoxLayout *horizontalLayout;
QSpacerItem *horizontalSpacer;
QLabel *label_3;
QSpacerItem *horizontalSpacer_2;
QWidget *widget;
QGridLayout *gridLayout;
QLabel *label;
QLineEdit *lineEditPort;
QLineEdit *lineEditIP;
QLabel *label_2;
QPushButton *buttonConnect;
QProgressBar *progressBar;
QTextEdit *textEdit;
private:
QTcpSocket *tcpSocket;
QFile file; //文件对象
QString fileName; //文件名字
qint64 fileSize; //文件大小
qint64 recvSize; //已经接收文件的大小
bool isStart; //标志位,是否为头部信息
};
#endif // CLIENTWIDGET_H
ServerWidget.cpp
该文件主要提供的是监听客户端发来的请求,并与客户端建立连接,在建立连接后,选择文件并使用tcpSocket发送数据。
#include "ServerWidget.h"
CServerWidget::CServerWidget(QWidget *parent)
: QWidget(parent)
{
createUi();
//监听套接字
tcpServer = new QTcpServer(this);
//监听
tcpServer->listen(QHostAddress::Any, 8888);
setWindowTitle("Server Port:8888");
//两个按钮都不能按
buttonFile->setEnabled(false);
buttonSend->setEnabled(false);
//如果客户端成功和服务器连接
//tcpServer会自动触发 newConnection()
connect(tcpServer, &QTcpServer::newConnection,
[=]()
{
qDebug()<<"has a new Connnection";
//取出建立好连接的套接字
tcpSocket = tcpServer->nextPendingConnection();
//获取对方的ip和端口
QString ip = tcpSocket->peerAddress().toString();
quint16 port = tcpSocket->peerPort();
QString str = QString("[%1:%2] success connect").arg(ip).arg(port);
textEdit->setText(str); //显示到编辑区
//成功连接后,才能按选择文件
buttonFile->setEnabled(true);
connect(buttonFile,SIGNAL(pressed()), this, SLOT(on_buttonFile_clicked()));
connect(buttonSend,SIGNAL(pressed()), this, SLOT(on_buttonSend_clicked()));
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//取客户端的信息
QByteArray buf = tcpSocket->readAll();
if(QString(buf) == "file done")
{//文件接收完毕
textEdit->append("File send over");
file.close();
//断开客户端端口
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
);
}
);
connect(&timer, &QTimer::timeout,
[=]()
{
//关闭定时器
timer.stop();
//发送文件
sendData();
}
);
}
//选择文件的按钮
void CServerWidget::on_buttonFile_clicked()
{
QString filePath = QFileDialog::getOpenFileName(this, "open", "../");
if(false == filePath.isEmpty()) //如果选择文件路径有效
{
fileName.clear();
fileSize = 0;
//获取文件信息
QFileInfo info(filePath);
fileName = info.fileName(); //获取文件名字
fileSize = info.size(); //获取文件大小
sendSize = 0; //发送文件的大小
//只读方式打开文件
//指定文件的名字
file.setFileName(filePath);
//打开文件
bool isOk = file.open(QIODevice::ReadOnly);
if(false == isOk)
{
qDebug() << "open file fail with readonly 106";
}
//提示打开文件的路径
textEdit->append(filePath);
buttonFile->setEnabled(false);
buttonSend->setEnabled(true);
}
else
{
qDebug() << "Chose file path is wrong! 118";
}
}
//发送文件按钮
void CServerWidget::on_buttonSend_clicked()
{
buttonSend->setEnabled(false);
qDebug()<<"buttonSend";
//先发送文件头信息 文件名##文件大小
QString head = QString("%1##%2").arg(fileName).arg(fileSize);
//发送头部信息
qint64 len = tcpSocket->write( head.toUtf8() );
if(len > 0)//头部信息发送成功
{
//发送真正的文件信息
//防止TCP黏包
//需要通过定时器延时 20 ms
timer.start(20);
}
else
{
qDebug() << "The head message send fail 142";
file.close();
buttonFile->setEnabled(true);
buttonSend->setEnabled(false);
}
}
void CServerWidget::sendData()
{
textEdit->append("is sending");
qDebug()<<"exec server sendData";
qint64 len = 0;
do
{
//每次发送数据的大小
char buf[4*1024] = {0};
len = 0;
//往文件中读数据
len = file.read(buf, sizeof(buf));
//发送数据,读多少,发多少
len = tcpSocket->write(buf, len);
//发送的数据需要累积
sendSize += len;
}while(len > 0);
// //是否发送文件完毕
// if(sendSize == fileSize)
// {
// ui->textEdit->append("文件发送完毕");
// file.close();
// //把客户端端口
// tcpSocket->disconnectFromHost();
// tcpSocket->close();
// }
}
CServerWidget::~CServerWidget()
{
}
void CServerWidget::createUi()
{
ServerWidget = new QWidget(this);
ServerWidget->resize(574, 422);
buttonFile = new QPushButton();
buttonSend = new QPushButton();
ServerWidget->resize(574, 422);
gridLayout = new QGridLayout(ServerWidget);
gridLayout->setSpacing(6);
gridLayout->setContentsMargins(11, 11, 11, 11);
gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
horizontalSpacer = new QSpacerItem(130, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
gridLayout->addItem(horizontalSpacer, 0, 0, 1, 2);
label = new QLabel(ServerWidget);
label->setObjectName(QString::fromUtf8("label"));
QFont font;
font.setFamily(QString::fromUtf8("\346\245\267\344\275\223"));
font.setPointSize(24);
label->setFont(font);
label->setText("Server");
label->setAlignment(Qt::AlignCenter);
gridLayout->addWidget(label, 0, 2, 1, 1);
horizontalSpacer_2 = new QSpacerItem(138, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
gridLayout->addItem(horizontalSpacer_2, 0, 3, 1, 2);
textEdit = new QTextEdit(ServerWidget);
textEdit->setObjectName(QString::fromUtf8("textEdit"));
textEdit->setReadOnly(true);
gridLayout->addWidget(textEdit, 1, 0, 1, 5);
buttonFile = new QPushButton(ServerWidget);
buttonFile->setText("Chose File");
buttonFile->setObjectName(QString::fromUtf8("buttonFile"));
gridLayout->addWidget(buttonFile, 2, 0, 1, 1);
horizontalSpacer_3 = new QSpacerItem(217, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
gridLayout->addItem(horizontalSpacer_3, 2, 1, 1, 3);
buttonSend = new QPushButton(ServerWidget);
buttonSend->setText("Send File");
buttonSend->setObjectName(QString::fromUtf8("buttonSend"));
gridLayout->addWidget(buttonSend, 2, 4, 1, 1);
ServerWidget->setLayout(gridLayout);
ServerWidget->setWindowTitle(QApplication::translate("ServerWidget", "ServerWidget", nullptr));
label->setText(QApplication::translate("ServerWidget", "\346\234\215\345\212\241\345\231\250", nullptr));
buttonFile->setText(QApplication::translate("ServerWidget", "\351\200\211\346\213\251\346\226\207\344\273\266", nullptr));
buttonSend->setText(QApplication::translate("ServerWidget", "\345\217\221\351\200\201\346\226\207\344\273\266", nullptr));
}
ClientWidget.cpp
本文件完成是发起连接请求,并接受服务端发来的文件,将文件接受进度已ProgressBar的形式显示在客户端。
#include "ClientWidget.h"
CClientWidget::CClientWidget(QWidget *parent) : QWidget(parent)
{
createUi();
connect(buttonConnect,SIGNAL(clicked()), this, SLOT(on_buttonConnect_clicked()));
tcpSocket = new QTcpSocket(this);
isStart = true;
progressBar->setValue(0); //当前值
setWindowTitle("Server");
connect(tcpSocket, &QTcpSocket::connected,
[=]()
{
//提示连接成功
textEdit->clear();
textEdit->append("connect success with server,waiting for sending……");
}
);
connect(tcpSocket, SIGNAL(readyRead()),this,SLOT(readFile()));
}
//本小类用于当Socket中readyRead()时,执行读取socket中文件
void CClientWidget::readFile()
{
//取出接收的内容
QByteArray buf = tcpSocket->readAll();
if(true == isStart)
{//接收头
isStart = false;
//解析头部信息 QString buf = "hello##1024"
// QString str = "hello##1024#mike";
// str.section("##", 0, 0)
//初始化
//文件名
fileName = QString(buf).section("##", 0, 0);
//文件大小
fileSize = QString(buf).section("##", 1, 1).toInt();
recvSize = 0; //已经接收文件大小
//打开文件
//关联文件名字
file.setFileName(fileName);
//只写方式方式,打开文件
bool isOk = file.open(QIODevice::WriteOnly);
if(false == isOk)
{
qDebug() << "WriteOnly error 49";
tcpSocket->disconnectFromHost(); //断开连接
tcpSocket->close(); //关闭套接字
return; //如果打开文件失败,中断函数
}
//弹出对话框,显示接收文件的信息
qDebug()<<"filesize"<<fileSize;
QString str = QString("Receive file: [%1: %2kb]").arg(fileName).arg(fileSize/1024);
//QMessageBox::information(this, "文件信息", str);
textEdit->append(str);
textEdit->append("is receiving..");
//设置进度条
progressBar->setMinimum(0); //最小值
progressBar->setMaximum(fileSize/1024); //最大值
progressBar->setValue(0); //当前值
}
else //文件信息
{
qint64 len = file.write(buf);
if(len >0) //接收数据大于0
{
recvSize += len; //累计接收大小
qDebug() << len;
}
//更新进度条
qDebug()<<"client progressbar";
progressBar->setValue(recvSize/1024);
if(recvSize == fileSize) //文件接收完毕
{
//先给服务发送(接收文件完成的信息)
tcpSocket->write("file done");
textEdit->append("file receive over");
QMessageBox::information(this, "over", "file receive over");
file.close(); //关闭文件
//断开连接
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
}
void CClientWidget::on_buttonConnect_clicked()
{
qDebug()<<"buttonConnect";
//获取服务器的ip和端口
QString ip = lineEditIP->text();
quint16 port = lineEditPort->text().toInt();
//主动和服务器连接
tcpSocket->connectToHost(QHostAddress(ip), port);
isStart = true;
//设置进度条
progressBar->setValue(0);
}
CClientWidget::~CClientWidget()
{
}
void CClientWidget::createUi()
{
ClientWidget = new QWidget(this);
ClientWidget->resize(400, 300);
if (ClientWidget->objectName().isEmpty())
ClientWidget->setObjectName(QString::fromUtf8("ClientWidget"));
ClientWidget->resize(400, 300);
gridLayout_2 = new QGridLayout(ClientWidget);
gridLayout_2->setObjectName(QString::fromUtf8("gridLayout_2"));
widget_2 = new QWidget(ClientWidget);
widget_2->setObjectName(QString::fromUtf8("widget_2"));
horizontalLayout = new QHBoxLayout(widget_2);
horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
horizontalSpacer = new QSpacerItem(125, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addItem(horizontalSpacer);
label_3 = new QLabel(widget_2);
label_3->setObjectName(QString::fromUtf8("label_3"));
QFont font;
font.setFamily(QString::fromUtf8("\346\245\267\344\275\223"));
font.setPointSize(24);
label_3->setFont(font);
label_3->setAlignment(Qt::AlignCenter);
horizontalLayout->addWidget(label_3);
horizontalSpacer_2 = new QSpacerItem(125, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addItem(horizontalSpacer_2);
gridLayout_2->addWidget(widget_2, 0, 0, 1, 1);
widget = new QWidget(ClientWidget);
widget->setObjectName(QString::fromUtf8("widget"));
gridLayout = new QGridLayout(widget);
gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
label = new QLabel(widget);
label->setObjectName(QString::fromUtf8("label"));
gridLayout->addWidget(label, 0, 0, 1, 1);
lineEditPort = new QLineEdit(widget);
lineEditPort->setObjectName(QString::fromUtf8("lineEditPort"));
gridLayout->addWidget(lineEditPort, 2, 1, 1, 1);
lineEditIP = new QLineEdit(widget);
lineEditIP->setObjectName(QString::fromUtf8("lineEditIP"));
gridLayout->addWidget(lineEditIP, 0, 1, 1, 1);
label_2 = new QLabel(widget);
label_2->setObjectName(QString::fromUtf8("label_2"));
gridLayout->addWidget(label_2, 1, 0, 2, 1);
buttonConnect = new QPushButton(widget);
buttonConnect->setObjectName(QString::fromUtf8("buttonConnect"));
gridLayout->addWidget(buttonConnect, 0, 2, 3, 1);
gridLayout_2->addWidget(widget, 1, 0, 1, 1);
progressBar = new QProgressBar(ClientWidget);
progressBar->setObjectName(QString::fromUtf8("progressBar"));
progressBar->setValue(24);
gridLayout_2->addWidget(progressBar, 2, 0, 1, 1);
textEdit = new QTextEdit(ClientWidget);
textEdit->setObjectName(QString::fromUtf8("textEdit"));
textEdit->setReadOnly(true);
gridLayout_2->addWidget(textEdit, 3, 0, 1, 1);
// retranslateUi(ClientWidget);
QMetaObject::connectSlotsByName(ClientWidget);
ClientWidget->setWindowTitle(QApplication::translate("ClientWidget", "Form", nullptr));
label_3->setText(QApplication::translate("ClientWidget", "\345\256\242\346\210\267\347\253\257", nullptr));
label->setText(QApplication::translate("ClientWidget", "\346\234\215\345\212\241\345\231\250\347\232\204IP:", nullptr));
lineEditPort->setText(QApplication::translate("ClientWidget", "8888", nullptr));
lineEditIP->setText(QApplication::translate("ClientWidget", "127.0.0.1", nullptr));
label_2->setText(QApplication::translate("ClientWidget", "\346\234\215\345\212\241\345\231\250\347\232\204\347\253\257\345\217\243:", nullptr));
buttonConnect->setText(QApplication::translate("ClientWidget", "connect", nullptr));
}
代码地址
https://download.csdn.net/download/weixin_38809485/12623790里面的MyTcpFile
TCP黏包问题
前言
黏包,通俗来讲就是按照使用数据报的形式使用了数据流。网络通信方式主要有两种:TCP与UDP。 UDP是基于报文传输的,发送几次Write(),接收端就会用几次Read(),每次读取一个报文,报文间不合并,多余缓冲区的报文会丢弃。TCP是基于数据流传输的,Write()和Read()的次数不固定,报文间会以随机的方式合并,这就需要在接收时处理粘包了。通过上面的分析,我们可以发现,粘包只可能出现在流式传输中。 其粘包原因可能是下面两种情况:
- 发送端需要等缓冲区才能发送数据,造成发送时就粘包;
- 接收端未及时接收缓冲区的数据,多包一起接收,造成粘包;
粘包的解决方法
为了避免粘包,我们一般可以采取以下三种措施:
- 发送端粘包,可以通过程序设置push指令,不等缓冲区满就立即发送数据(默认情况是等缓冲区满后再发送)。这种方法对于通信的传输效率会降低,有时也不是百分百能可靠。
- 接收端粘包,可以通过优化程序、精简进程工作量、提高进程优先级等措施,使其及时接收数据。这种方法,你可以发现,有时候也是无法优化的,实现起来会比较难。
- 采用自定义包头结构,人为控制多次合并,来避免粘包问题。这是常用的做法。
一般选择用拆包放入buffer的方式,来读取socket数据。
拆包过程总结:
TCP传输大文件的问题
首先,应该明白整个socket传输的机制,我们可以了解到,每次服务端发送给客户端的都是一部分数据,从来都不可能直接把整个文件一次性发过去,这个发的操作也就是tcpSocket->write(buf, len);
这样的操作。然后,客户端使用tcp->readall
进行读取socket里面的全部字节,但你可千万别被all
这个关键词给麻痹了。这个tcp->readall
是在每次socket每次有内容,就会调用的,也就是当客户端有一个函数叫readyread
这个触发后,就会执行这样的操作。
所以,我在处理这个事情遇到的两个问题是:
- 如何存储多次传输过来的字节流。使用字节数组allByteArray,但每次都被自动刷新了。
- 传输结束后,是否能够只获取数据的那一部分,用于读取,并显示成一个xml文件。
解决办法:
1.关于第一个的问题出在我重复new了对象,所以,在平常写bug的过程中,要尽量在构造函数去new对象,尽量不要在程序的运行过程中去new对象,除非你保证你能够hold的住。这里也在讲述一个找问题的比较好的方法:
关于你要查找的东西,右键点击这个,在该文件中所存在的这个东西都会被列出来,那个时候,你就知道是否,你有做了什么奇怪的事情没有,对于某个东西。
2. 第二个问题的话,还是比较简单的,直接在后面用一个if判断是文件头信息还是文件数据信息,若是文件数据则将所有的字节流进行累加,就可以得到所以的数据字节流了。
readyRead的注意事项
readyRead的解释是:每一次,在当前用来存放读数据的位置中(也就是可读缓冲区),有可读数据可用,都会发送这个信号。当新的数据到来的时候,这个信号还会再发送且仅发送一次。比如:新的网络数据到达你的网络socket,或者新的数据块添加到你的设备。
但需要注意的点是:当新的数据到来的时候,这个信号还会再发送且仅发送一次。”很多朋友对新的数据到来的错误理解是,发送端对应的QTcpSokcet写一次,也就是write一次,那么接收方就会有新的数据到达,于是readyread信号被触发一次。这个理解是错误的。经过测试,我们发现事实是这样的:发送和接收是没有一一对应关系的。发送端write函数调用一次,假如这一次write了2M的数据,那么接收方的readyread信号就往往会触发两次以上。反过来,如过发送端write函数被调用两次或两次以上,那么接收方的readyread信号也有可能只触发一次。参考:https://blog.csdn.net/whiskey_wei/article/details/80790428
UDP通信
前言
使用Qt提供的QUdpSocket进行UDP通信。在UDP方式下,客户端并不与服务器建立连接,它只负责调用发送函数向服务器发送数据。类似的服务器也不从客户端接收连接,只负责调用接收函数,等待来自客户端的数据的到达。
在UDP通信中,服务器端和客户端的概念已经显得有些淡化,两部分做的工作都大致相同:
- 创建套接字
- 绑定套接字
在UDP中如果需要接收数据则需要对套接字进行绑定,只发送数据则不需要对套接字进行绑定。
通过调用bind()函数将套接字绑定到指定端口上。 - 接收或者发送数据
- 接收数据:使用readDatagram()接收数据,函数声明如下:
qint64 readDatagram(char * data, qint64 maxSize,
QHostAddress * address = 0, quint16 * port = 0)
参数:
5. data: 接收数据的缓存地址
6. maxSize: 缓存接收的最大字节数
7. address: 数据发送方的地址(一般使用提供的默认值)
8. port: 数据发送方的端口号(一般使用提供的默认值)
使用pendingDatagramSize()可以获取到将要接收的数据的大小,根据该函数返回值来准备对应大小的内存空间存放将要接收的数据。
发送数据: 使用writeDatagram()函数发送数据,函数声明如下:
qint64 writeDatagram(const QByteArray & datagram,
const QHostAddress & host, quint16 port)
参数:
9. datagram:要发送的字符串
10. host:数据接收方的地址
11. port:数据接收方的端口号
广播
在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为广播地址:QHostAddress::Broadcast此设置相当于QHostAddress(“255.255.255.255”)
使用UDP广播的的特点:
- 使用UDP进行广播,局域网内的其他的UDP用户全部可以收到广播的消息。
- UDP广播只能在局域网范围内使用。
组播
我们再使用广播发送消息的时候会发送给所有用户,但是有些用户是不想接受消息的,这时候我们就应该使用组播,接收方只有先注册到组播地址中才能收到组播消息,否则则接受不到消息。另外组播是可以在Internet中使用的。
在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为组播地址,关于组播地址的分类:
224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
注册加入到组播地址需要使用QUdpSocket类的成员函数:
bool joinMulticastGroup(const QHostAddress & groupAddress)
UDP通信小知识
- 在UDP中如果需要接收数据则需要对套接字进行绑定,只发送数据则不需要对套接字进行绑定。通过调用bind()函数将套接字绑定到指定端口上。
- 使用UDP广播的的特点:
1.使用UDP进行广播,局域网内的其他的UDP用户全部可以收到广播的消息.
2.UDP广播只能在局域网范围内使用
3. 接收方只有先注册到组播地址中才能收到组播消息,否则则接受不到消息。
参考文献
P.S:如有错误,欢迎指正~