1 效果图

image.png

2 Server端

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. #include <QDebug>
  4. #include <QHostAddress>
  5. #include <QFileDialog>
  6. #include <QDataStream>
  7. #include <QDateTime>
  8. Widget::Widget(QWidget *parent) :
  9. QWidget(parent),
  10. ui(new Ui::Widget)
  11. {
  12. ui->setupUi(this);
  13. ui->splitter->setStretchFactor(0,2); // 上面占2份
  14. ui->splitter->setStretchFactor(1,1); // 下面占1份
  15. connect(&server,SIGNAL(newConnection()),
  16. this,SLOT(onNewConnection()));
  17. // 服务端开始监听所有IPv4地址
  18. bool isOk = server.listen(QHostAddress::AnyIPv4,5485);
  19. qDebug() << "监听结果:" << isOk;
  20. imageIndex = 0;
  21. LastPacksize = 0;
  22. }
  23. Widget::~Widget()
  24. {
  25. delete ui;
  26. }
  27. void Widget::onNewConnection()
  28. {
  29. QTcpSocket* socket = server.nextPendingConnection();
  30. clients.append(socket);
  31. connect(socket,SIGNAL(connected()),
  32. this,SLOT(onConnected()));
  33. connect(socket,SIGNAL(disconnected()),
  34. this,SLOT(onDisconnected()));
  35. connect(socket,SIGNAL(error(QAbstractSocket::SocketError)),
  36. this,SLOT(onError(QAbstractSocket::SocketError)));
  37. connect(socket,SIGNAL(readyRead()),
  38. this,SLOT(onReadyRead()));
  39. }
  40. void Widget::onConnected() // 连接成功
  41. {
  42. qDebug() << "connected";
  43. }
  44. void Widget::onDisconnected() // 断开
  45. {
  46. QObject* obj = this->sender();
  47. QTcpSocket* socket = qobject_cast<QTcpSocket*>(obj);
  48. clients.removeAll(socket);
  49. socket->deleteLater();
  50. qDebug() << "disconnected";
  51. }
  52. void Widget::onError(QAbstractSocket::SocketError socketError) // 出错
  53. {
  54. qDebug() << "error: " << socketError;
  55. }
  56. void Widget::onReadyRead() // 读取数据
  57. {
  58. QObject* obj = this->sender();
  59. QTcpSocket* socket = qobject_cast<QTcpSocket*>(obj);
  60. quint64 sizeNow = 0;
  61. QDataStream stream(socket);
  62. do
  63. {
  64. // 当前缓冲区里的数据大小
  65. sizeNow = socket->bytesAvailable(); // 获取等待读取的传入字节数。
  66. if(LastPacksize == 0) // 第一次要先读取包大小
  67. {
  68. // 第一次要先判断传入字节数是否小于4字节
  69. if(sizeNow < sizeof(quint32)) return;
  70. stream >> LastPacksize;
  71. }
  72. qDebug() << "sizeNow:"<< sizeNow;
  73. qDebug() << "sizePack:"<< LastPacksize;
  74. // 包不完整返回,防止出现半包
  75. if(sizeNow < LastPacksize - 4) return;
  76. // 包已经完整,数据读取完毕
  77. qDebug() << "数据包读取完毕";
  78. LastPacksize = 0;
  79. // 设置输出框内容
  80. QString dateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
  81. QByteArray dataFull;
  82. stream >> dataFull; // 读取完整数据
  83. QString prefix = dataFull.mid(0,4);
  84. if(prefix == "TXT:")
  85. {
  86. ui->tedtOutput->append(dateTime);
  87. QString textContent = dataFull.mid(4);
  88. ui->tedtOutput->append("<p>" + textContent + "</p>");
  89. }
  90. else if(prefix == "IMG:")
  91. {
  92. ui->tedtOutput->append(dateTime);
  93. QString index = QString::number(imageIndex);
  94. QFile file(index + ".png");
  95. file.open(QIODevice::WriteOnly);
  96. file.write(dataFull.mid(4));
  97. file.close();
  98. QString htmlTag = QString("<img src=\"%1\"></img>").arg(index + ".png");
  99. ui->tedtOutput->append(htmlTag);
  100. imageIndex++;
  101. }
  102. // 判断剩下的字节数,是否会有粘包的情况
  103. sizeNow = socket->bytesAvailable();
  104. qDebug() << "剩余等待读取的传入字节数 sizeNow=" << sizeNow;
  105. }while(sizeNow > 0);
  106. }
  107. void Widget::on_btnSend_clicked()
  108. {
  109. QString qstrSend = ui->tedtInput->toPlainText();
  110. if(qstrSend.isEmpty()) return;
  111. QList<QTcpSocket*>::iterator it;
  112. // 把服务端的文字发给所有连接的客户端
  113. for(it = clients.begin(); it != clients.end(); ++it)
  114. {
  115. QString input = "TXT:"+ qstrSend;
  116. QByteArray data = input.toLocal8Bit();
  117. // 封装数据包,加上包头
  118. QByteArray dataSend; // 封装的数据包
  119. QDataStream stream(&dataSend,QIODevice::WriteOnly); // 定义一个数据流
  120. // 写入数据到数据流,格式为:数据包大小(32bit)+数据
  121. stream << (quint32)0 << data;
  122. // 移到流索引为0处, 用数据大小覆盖
  123. stream.device()->seek(0);
  124. stream << dataSend.size();
  125. (*it)->write(dataSend);
  126. }
  127. ui->tedtInput->clear();
  128. }
  129. void Widget::on_btnImage_clicked()
  130. {
  131. QString imagePath = QFileDialog::getOpenFileName(this,"请选择一张图片",".","Images (*.png *.xpm *.jpg)");
  132. if(imagePath.isEmpty())
  133. return;
  134. QFile file(imagePath);
  135. file.open(QIODevice::ReadOnly);
  136. QByteArray data = "IMG:" + file.readAll();
  137. file.close();
  138. // 封装数据包,加上包头
  139. QByteArray dataSend; // 封装的数据包
  140. QDataStream stream(&dataSend,QIODevice::WriteOnly); // 定义一个数据流
  141. // 写入数据到数据流,格式为:数据包大小(32bit)+数据
  142. stream << (quint32)0 << data;
  143. // 移到流索引为0处, 用数据大小覆盖
  144. stream.device()->seek(0);
  145. stream << dataSend.size();
  146. QList<QTcpSocket*>::iterator it;
  147. // 把服务端的图片发给所有连接的客户端
  148. for(it = clients.begin(); it != clients.end(); ++it)
  149. {
  150. (*it)->write(dataSend);
  151. }
  152. }

3 客户端

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. #include <QDebug>
  4. #include <QFile>
  5. #include <QFileDialog>
  6. #include <QDateTime>
  7. Widget::Widget(QWidget *parent) :
  8. QWidget(parent),
  9. ui(new Ui::Widget)
  10. {
  11. ui->setupUi(this);
  12. ui->splitter->setStretchFactor(0,2); // 上面占2份
  13. ui->splitter->setStretchFactor(1,1); // 下面占1份
  14. connect(&tcpSocket,SIGNAL(connected()),
  15. this,SLOT(onConnected()));
  16. connect(&tcpSocket,SIGNAL(disconnected()),
  17. this,SLOT(onDisconnected()));
  18. connect(&tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),
  19. this,SLOT(onError(QAbstractSocket::SocketError)));
  20. connect(&tcpSocket,SIGNAL(readyRead()),
  21. this,SLOT(onReadyRead()));
  22. // 客户端主动连接服务端
  23. tcpSocket.connectToHost("127.0.0.1",5485); // host地址为本机,端口号为5485
  24. imageIndex = 0;
  25. LastPacksize = 0;
  26. }
  27. Widget::~Widget()
  28. {
  29. delete ui;
  30. }
  31. void Widget::onConnected()
  32. {
  33. qDebug() << "connected";
  34. }
  35. void Widget::onDisconnected()
  36. {
  37. qDebug() << "disconnected";
  38. QObject* obj = this->sender();
  39. QTcpSocket* socket = qobject_cast<QTcpSocket*>(obj);
  40. if(socket == 0) return; // 说明是客户端先关闭,不用做其他操作
  41. socket->close(); // 如果是服务端先关闭,就关闭这个socket
  42. }
  43. void Widget::onError(QAbstractSocket::SocketError socketError)
  44. {
  45. qDebug() << "error: " << socketError;
  46. }
  47. void Widget::onReadyRead()
  48. {
  49. QObject* obj = this->sender();
  50. QTcpSocket* socket = qobject_cast<QTcpSocket*>(obj);
  51. quint64 sizeNow = 0;
  52. QDataStream stream(socket);
  53. do
  54. {
  55. // 当前缓冲区里的数据大小
  56. sizeNow = socket->bytesAvailable(); // 获取等待读取的传入字节数。
  57. if(LastPacksize == 0) // 第一次要先读取包大小
  58. {
  59. // 第一次要先判断传入字节数是否小于4字节
  60. if(sizeNow < sizeof(quint32)) return;
  61. stream >> LastPacksize;
  62. }
  63. qDebug() << "sizeNow:"<< sizeNow;
  64. qDebug() << "sizePack:"<< LastPacksize;
  65. // 包不完整返回,防止出现半包
  66. if(sizeNow < LastPacksize - 4) return;
  67. // 包已经完整,数据读取完毕
  68. qDebug() << "包已经完整,数据读取完毕";
  69. LastPacksize = 0;
  70. // 设置输出框内容
  71. QString dateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
  72. QByteArray dataFull;
  73. stream >> dataFull; // 读取完整数据
  74. QString prefix = dataFull.mid(0,4);
  75. if(prefix == "TXT:")
  76. {
  77. ui->tedtOutput->append(dateTime);
  78. QString textContent = dataFull.mid(4);
  79. ui->tedtOutput->append("<p>" + textContent + "</p>");
  80. }
  81. else if(prefix == "IMG:")
  82. {
  83. ui->tedtOutput->append(dateTime);
  84. QString index = QString::number(imageIndex);
  85. QFile file(index + ".png");
  86. file.open(QIODevice::WriteOnly);
  87. file.write(dataFull.mid(4));
  88. file.close();
  89. QString htmlTag = QString("<img src=\"%1\"></img>").arg(index + ".png");
  90. ui->tedtOutput->append(htmlTag);
  91. imageIndex++;
  92. }
  93. // 判断剩下的字节数,是否会有粘包的情况
  94. sizeNow = socket->bytesAvailable();
  95. qDebug() << "剩余等待读取的传入字节数 sizeNow=" << sizeNow;
  96. }while(sizeNow > 0);
  97. }
  98. void Widget::on_btnSend_clicked()
  99. {
  100. QString qstrSend = ui->tedtInput->toPlainText();
  101. if(qstrSend.isEmpty()) return;
  102. QString input = "TXT:"+ qstrSend;
  103. QByteArray data = input.toLocal8Bit();
  104. // 封装数据包,加上包头
  105. QByteArray dataSend; // 封装的数据包
  106. QDataStream stream(&dataSend,QIODevice::WriteOnly); // 定义一个数据流
  107. // 写入数据到数据流,格式为:数据包大小(32bit)+数据
  108. stream << (quint32)0 << data;
  109. // 移到流索引为0处, 用数据大小覆盖
  110. stream.device()->seek(0);
  111. stream << dataSend.size();
  112. tcpSocket.write(dataSend);
  113. ui->tedtInput->clear();
  114. }
  115. void Widget::on_btnImage_clicked()
  116. {
  117. QString imagePath = QFileDialog::getOpenFileName(this,"请选择一张图片",".","Images (*.png *.xpm *.jpg)");
  118. if(imagePath.isEmpty()) return;
  119. QFile file(imagePath);
  120. file.open(QIODevice::ReadOnly);
  121. QByteArray data = "IMG:" + file.readAll();
  122. file.close();
  123. // 封装数据包,加上包头
  124. QByteArray dataSend; // 封装的数据包
  125. QDataStream stream(&dataSend,QIODevice::WriteOnly); // 定义一个数据流
  126. // 写入数据到数据流,格式为:数据包大小(32bit)+数据
  127. stream << (quint32)0 << data;
  128. // 移到流索引为0处, 用数据大小覆盖
  129. stream.device()->seek(0);
  130. stream << dataSend.size();
  131. tcpSocket.write(dataSend);
  132. }