Qt学习第4天:TCP/UDP通信 【笔记】
发布日期:2021-07-01 04:00:41 浏览次数:2 分类:技术文章

本文共 14247 字,大约阅读时间需要 47 分钟。

文章目录

1.Linux下的TCP通信过程

在这里插入图片描述

2.Qt下的TCP通信过程

在这里插入图片描述

注意,服务器有两个套接字:QTcpServerQTcpSocket,即监听套接字和通信套接字.

3. TCP通信

TCP服务器

TextEdit设置只读:

在这里插入图片描述
serverwidget.h

#ifndef SERVERWIDGET_H#define SERVERWIDGET_H#include 
#include
//监听套接字#include
//通信套接字namespace Ui {
class ServerWidget;}class ServerWidget : public QWidget{
Q_OBJECTpublic: explicit ServerWidget(QWidget *parent = nullptr); ~ServerWidget();private slots: void on_buttonSent_clicked(); void on_buttonClose_clicked();private: Ui::ServerWidget *ui; QTcpServer *tcpServer; //监听套接字 QTcpSocket *tcpSocket; //通信套接字};#endif // SERVERWIDGET_H

serverwidget.cpp

#include "serverwidget.h"#include "ui_serverwidget.h"ServerWidget::ServerWidget(QWidget *parent) :    QWidget(parent),    ui(new Ui::ServerWidget){
ui->setupUi(this); tcpServer = NULL; tcpSocket = NULL; setWindowTitle("服务器:8888"); //监听套接字,指定父对象,让其自动回收空间 tcpServer = new QTcpServer(this); tcpServer->listen(QHostAddress::Any,8888);//绑定网卡所有IP,端口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); } ); } );}ServerWidget::~ServerWidget(){
delete ui;}void ServerWidget::on_buttonSent_clicked(){
if(NULL==tcpSocket) {
return; } //获取编辑区内容 QString str = ui->textEditWrite->toPlainText(); //给对方发送数据,使用套接字tcpSocket tcpSocket->write(str.toUtf8().data());}void ServerWidget::on_buttonClose_clicked(){
if(NULL==tcpSocket) {
return; } //主动和客户端断开连接 tcpSocket->disconnectFromHost(); tcpSocket->close(); tcpSocket = NULL;}

TCP客户端

clientwidget.h

#ifndef CLIENTWIDGET_H#define CLIENTWIDGET_H#include 
#include
//通信套接字namespace Ui {
class ClientWidget;}class ClientWidget : public QWidget{
Q_OBJECTpublic: explicit ClientWidget(QWidget *parent = nullptr); ~ClientWidget();private slots: void on_buttonConnect_clicked(); void on_buttonSend_clicked(); void on_buttonClose_clicked(); private: Ui::ClientWidget *ui; QTcpSocket *tcpSocket;};#endif // CLIENTWIDGET_H

clientwidget.cpp

#include "clientwidget.h"#include "ui_clientwidget.h"#include 
ClientWidget::ClientWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ClientWidget){
ui->setupUi(this); setWindowTitle("客户端"); tcpSocket = NULL; //分配空间,指定父对象 tcpSocket = new QTcpSocket(this); connect(tcpSocket,&QTcpSocket::connected, [=]() {
ui->textEditRead->setText("成功和服务器建立连接!"); }); connect(tcpSocket,&QTcpSocket::readyRead, [=]() {
//获取对方发送的内容 QByteArray array = tcpSocket->readAll(); //追加到编辑区 ui->textEditRead->append(array); } );}ClientWidget::~ClientWidget(){
delete ui;}void ClientWidget::on_buttonConnect_clicked(){
//获取服务器IP和端口 QString ip = ui->lineEditIp->text(); qint16 port = ui->lineEditPort->text().toInt(); //主动和服务器建立连接 tcpSocket->connectToHost(QHostAddress(ip),port);}void ClientWidget::on_buttonSend_clicked(){
//获取编辑框内容 QString str = ui->textEditWrite->toPlainText(); //发送数据 tcpSocket->write(str.toUtf8().data());}void ClientWidget::on_buttonClose_clicked(){
//主动和对方断开连接 tcpSocket->disconnectFromHost(); tcpSocket->close();}

4. UDP通信

UDP通信过程

Linux下的UDP

在这里插入图片描述

Qt下的UDP

在这里插入图片描述

UDP与TCP的区别

  • UDP像写信,只要知道地址就可以发
  • TCP像打电话,只有两人同时在线才能通信

UDP 文本发送

widget.h

#ifndef WIDGET_H#define WIDGET_H#include 
#include
//UDP套接字namespace Ui {
class Widget;}class Widget : public QWidget{
Q_OBJECTpublic: explicit Widget(QWidget *parent = nullptr); ~Widget(); void dealMsg();//槽函数,处理对方发过来的数据private slots: void on_buttonSend_clicked();private: Ui::Widget *ui; QUdpSocket *udpSocket;//UDP套接字};#endif // WIDGET_H

widget.cpp

#include "widget.h"#include "ui_widget.h"#include 
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget){
ui->setupUi(this); setWindowTitle("服务器端口为:8888"); //分配空间,指定父对象 udpSocket = new QUdpSocket(this); //绑定 udpSocket->bind(8888); //当对方成功发送数据过来 //自动触发 readyRead() connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::dealMsg);}void Widget::dealMsg(){
//读取对方发送的内容 char buf[1024]={
0}; QHostAddress cliAddr;//对方地址 quint16 port;//对方端口 qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port); if(len > 0) {
//格式化 [192.68.2.2:8888]aaaa QString str = QString("[%1:%2] %3") .arg(cliAddr.toString()) .arg(port) .arg(buf); //给编辑区设置内容 ui->textEdit->setText(str); }}Widget::~Widget(){
delete ui;}void Widget::on_buttonSend_clicked(){
//先获取对方的IP和端口 QString ip = ui->lineEditIP->text(); qint16 port = ui->lineEditPort->text().toInt(); //获取编辑区内容 QString str = ui->textEdit->toPlainText(); //给指定IP发送数据 udpSocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);}

UDP多播组播

在这里插入图片描述

widget.cpp

#include "widget.h"#include "ui_widget.h"#include 
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget){
ui->setupUi(this); setWindowTitle("服务器端口为:8888"); //分配空间,指定父对象 udpSocket = new QUdpSocket(this); //绑定 //udpSocket->bind(8888); //注意,群播时绑定的是IPv4 udpSocket->bind(QHostAddress::AnyIPv4,8888); //加入某个组播 //组播地址必须是D类地址 udpSocket->joinMulticastGroup(QHostAddress("224.0.0.2")); //udpSocket->leaveMulticastGroup(); //退出组播 //当对方成功发送数据过来 //自动触发 readyRead() connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::dealMsg);}void Widget::dealMsg(){
//读取对方发送的内容 char buf[1024]={
0}; QHostAddress cliAddr;//对方地址 quint16 port;//对方端口 qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port); if(len > 0) {
//格式化 [192.68.2.2:8888]aaaa QString str = QString("[%1:%2] %3") .arg(cliAddr.toString()) .arg(port) .arg(buf); //给编辑区设置内容 ui->textEdit->setText(str); }}Widget::~Widget(){
delete ui;}void Widget::on_buttonSend_clicked(){
//先获取对方的IP和端口 QString ip = ui->lineEditIP->text(); qint16 port = ui->lineEditPort->text().toInt(); //获取编辑区内容 QString str = ui->textEdit->toPlainText(); //给指定IP发送数据 udpSocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);}

5. QTimer定时器

5.TCP传文件

在这里插入图片描述

务必注意TCP的黏包问题,通常通过发送头部数据+延时来处理!!!

main.cpp

#include "serverwidget.h"#include 
#include "clientwidget.h"int main(int argc, char *argv[]){
QApplication a(argc, argv); ServerWidget w; w.show(); ClientWidget w2; w2.show(); return a.exec();}

服务器

serverwidget.h

#ifndef SERVERWIDGET_H#define SERVERWIDGET_H#include 
#include
//监听套接字#include
//通信套接字#include
#include
namespace Ui {
class ServerWidget;}class ServerWidget : public QWidget{
Q_OBJECTpublic: explicit ServerWidget(QWidget *parent = nullptr); ~ServerWidget(); void sendData();//发送文件数据private slots: void on_buttonFile_clicked(); void on_buttonSend_clicked();private: Ui::ServerWidget *ui; QTcpServer *tcpServer;//监听套接字 QTcpSocket *tcpSocket;//通信套接字 QFile file;//文件对象 QString fileName;//文件名字 qint64 fileSize;//文件大小 qint64 sendSize;//已发送的大小 QTimer timer;//定时器};#endif // SERVERWIDGET_H

serverwidget.cpp

#include "serverwidget.h"#include "ui_serverwidget.h"#include 
#include
#include
ServerWidget::ServerWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ServerWidget){
ui->setupUi(this); //监听套接字 tcpServer = new QTcpServer(this); //监听 tcpServer->listen(QHostAddress::Any,8888); setWindowTitle("服务器端口为:8888"); //两个按钮都不能按 ui->buttonFile->setEnabled(false); ui->buttonSend->setEnabled(false); //如果客户端成功和服务器连接 connect(tcpServer,&QTcpServer::newConnection, [=]() {
//取出建立好连接的套接字 tcpSocket = tcpServer->nextPendingConnection(); //获取对方的IP和端口 QString ip = tcpSocket->peerAddress().toString(); quint16 port = tcpSocket->peerPort(); QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port); ui->textEdit->setText(str);//显示到编辑区 //成功连接后,才能按按钮选择文件 ui->buttonFile->setEnabled(true); }); connect(&timer,&QTimer::timeout, [=]() {
//关闭定时器 timer.stop(); //发送文件 sendData(); } );}ServerWidget::~ServerWidget(){
delete ui;}//选择文件的按钮void ServerWidget::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() << "只读方式打开文件失败"; } //提示打开文件路径 ui->textEdit->append(filePath); ui->buttonFile->setEnabled(false); ui->buttonSend->setEnabled(true); } else {
qDebug() << "选择文件路径出错!"; }}//发送文件按键void ServerWidget::on_buttonSend_clicked(){
//先发送文件头信息 QString head = QString("%1##%2").arg(fileName).arg(fileSize); qint64 len = tcpSocket->write(head.toUtf8()); if(len > 0)//说明头部信息发送成功 {
//发送真正的文件信息 //防止TCP黏包文件 //需要通过定时器延时20ms timer.start(20);//加延时,防止黏包 } else {
qDebug()<<"头部信息发送失败!"; file.close(); ui->buttonFile->setEnabled(true); ui->buttonSend->setEnabled(false); }}void ServerWidget::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);//如果len≤0,发送完毕 //文件是否发送完毕 if(sendSize == fileSize) {
ui->textEdit->append("文件发送完毕!"); file.close(); //把客户端端口关闭 tcpSocket->disconnectFromHost(); tcpSocket->close(); }}

客户端

clientwidget.h

#ifndef CLIENTWIDGET_H#define CLIENTWIDGET_H#include 
#include
#include
namespace Ui {
class ClientWidget;}class ClientWidget : public QWidget{
Q_OBJECTpublic: explicit ClientWidget(QWidget *parent = nullptr); ~ClientWidget();private slots: void on_buttonConnect_clicked();private: Ui::ClientWidget *ui; QTcpSocket *tcpSocket; QFile file;//文件对象 QString fileName;//文件名字 qint64 fileSize;//文件大小 qint64 recvSize;//已接收文件的大小 bool isStart;};#endif // CLIENTWIDGET_H

clientwidget.cpp

#include "clientwidget.h"#include "ui_clientwidget.h"#include 
#include
#include
ClientWidget::ClientWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ClientWidget){
ui->setupUi(this); ui->progressBar->setValue(0);//设置进度条当前值为0 tcpSocket = new QTcpSocket(this); isStart = true; connect(tcpSocket,&QTcpSocket::readyRead, [=]() {
//取出接收的内容 QByteArray buf = tcpSocket->readAll(); if(true == isStart)//接收头 {
isStart = false; //解析头部信息 QString buf = "hello##1024" //QString str = "hello##1024#mike"; //取hello: str.section("##",0,0) //取1024:str.section("##",1,1).toInt() //取mike:str.section("##",2,2) //初始化 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!!!"; tcpSocket->disconnectFromHost(); tcpSocket->close();//关闭套接字 return ;//如果打开文件失败,终端函数 } //弹出对话框,显示接收文件的信息 QString str = QString("接收的文件: [%1: %2kb]").arg(fileName).arg(fileSize/1024); QMessageBox::information(this,"文件信息",str); //设置进度条 ui->progressBar->setMinimum(0); //最小值 ui->progressBar->setMaximum(fileSize/1024);//设置最大值 ui->progressBar->setValue(0);//当前值 } else //文件信息 {
qint64 len = file.write(buf); if(len > 0)//接收数据大于0 {
recvSize += len;//累计接收大小 QString str = QString::number(recvSize); tcpSocket->write(str.toUtf8().data()); qDebug()<<"str = "<
progressBar->setValue(recvSize/1024); if(recvSize == fileSize)//文件接收完毕 {
//先给服务器发送(接收文件完成的信息) tcpSocket->write("file done"); file.close(); //关闭文件 QMessageBox::information(this,"完成","文件接收完成!"); //断开连接 tcpSocket->disconnectFromHost(); tcpSocket->close(); } } });}ClientWidget::~ClientWidget(){
delete ui;}void ClientWidget::on_buttonConnect_clicked(){
//获取服务器IP和端口 QString ip = ui->lineEditIP->text(); quint16 port = ui->lineEditPort->text().toInt(); tcpSocket->connectToHost(QHostAddress(ip),port);}

小技巧:

  • 有时加载完模块不会立即生效,这时可以点下锤子,只编译不运行!或者重新打开项目即可~

转载地址:https://miracle.blog.csdn.net/article/details/105899437 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Qt Creator经常"未响应"问题的解决
下一篇:git本地与云端不一致时文件的提交

发表评论

最新留言

做的很好,不错不错
[***.243.131.199]2024年05月01日 14时08分27秒