前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >简易版QQ?Qt也可以实现!(一)

简易版QQ?Qt也可以实现!(一)

作者头像
用户6557940
发布2022-07-24 15:22:51
7610
发布2022-07-24 15:22:51
举报
文章被收录于专栏:Jungle笔记Jungle笔记

引言

客户端与服务器之间的数据传送在很多案例场景里都会有应用。这里Jungle用Qt来简单设计实现一个场景,即:

①两端:服务器QtServer和客户端QtClient

②功能:服务端连接客户端,两者能够互相发送消息,传送文件,并且显示文件传送进度。

环境:VS2008+Qt4.8.6+Qt设计师

01

基本概念

客户端与服务器的基本概念不说了,关于TCP通信的三次握手等等,在经典教材谢希仁的《计算机网络》里都有详细介绍。这里说下两者是如何建立起通信连接的。

①IP地址:首先服务器和每一个客户端都有一个地址,即IP地址。对于服务器来说,客户端的数量及地址是未知的,除非建立了连接。但是对于客户端来说,必须知道服务器的地址,因为两者之间的连接是由客户端主动发起的。

②端口号:软件层面的端口号,指的是“应用层的各种协议进程与运输实体进行层间交互的一种地址”。简而言之,每一个TCP连接都是一个进程,操作系统需要为每个进程分配一个协议端口(即每一个客户端与服务端的连接,不是两台主机的连接,而是两个端口的连接)。但一台主机通常会有很多服务,很多进程,单靠一个IP地址不能标识某个具体的进程或者连接。所以用端口号来标识访问的目标服务器以及服务器的目标服务类型。端口号也有分类,但这不是本文的重点,详见教材。

③TCP连接:总的来说,TCP的连接管理分为单个阶段:建立连接->数据传送->连接释放。在②里说到,每个TCP连接的是具体IP地址的主机的两个端口,即TCP连接的两个端点由IP地址和端口号组成,这即是**套接字(socket)**的概念:

套接字socket=IP:端口号

因此,我们要通过建立套接字来建立服务端与客户端的通信连接。

02

Qt相关类

  • QTcpSocket:提供套接字
  • QTcpServer:提供基于TCP的服务端,看官方文档的解释如下:

This class makes it possible to accept incoming TCP connections. You can specify the port or have QTcpServer pick one automatically. You can listen on a specific address or on all the machine’s addresses.

这个解释里面提到两点:

  • 指定端口:即开通哪一个端口用于建立TCP连接;
  • 监听:监听指定端口是否有连接的请求。

03

UI设计

客户端:

服务端:

04

客户端实现

类设计如下:

代码语言:javascript
复制
class QtClient : public QWidget
{
  Q_OBJECT
public:
  QtClient(QWidget *parent = 0, Qt::WFlags flags = 0);
  ~QtClient();

  void initTCP();
  void newConnect();

  private slots:
    ////连接服务器
    void connectServer();
    ////与服务器断开连接
    void disconnectServer();
    ////接收服务器发送的数据
    void receiveData();  
    ////向服务器发送数据
    void sendData();

    ////浏览文件
    void selectFile();
    ////发送文件
    void sendFile();
    ////更新文件发送进度
    void updateFileProgress(qint64);
    ////更新文件接收进度
    void updateFileProgress();

private:
  Ui::QtClientClass ui;
  QTcpSocket *tcpSocket;
  QTcpSocket *fileSocket;

  ///文件传送
  QFile *localFile;
  ///文件大小
  qint64 totalBytes;      //文件总字节数
  qint64 bytesWritten;    //已发送的字节数
  qint64 bytestoWrite;    //尚未发送的字节数
  qint64 filenameSize;    //文件名字的字节数
  qint64 bytesReceived;   //接收的字节数
  ///每次发送数据大小
  qint64 perDataSize;
  QString filename;
  ///数据缓冲区
  QByteArray inBlock;
  QByteArray outBlock;

  ////系统时间
  QDateTime current_date_time;
  QString str_date_time;
};

类实现如下:

代码语言:javascript
复制
#include "qtclient.h"

QtClient::QtClient(QWidget *parent, Qt::WFlags flags)
  : QWidget(parent, flags)
{
  ui.setupUi(this);
  this->initTCP();

  /////文件传送相关变量初始化
  ///每次发送数据大小为64kb
  perDataSize = 64*1024;
  totalBytes = 0;
  bytestoWrite = 0;
  bytesWritten = 0;
  bytesReceived = 0;
  filenameSize = 0;

  connect(this->ui.pushButton_openFile,SIGNAL(clicked()),this,SLOT(selectFile()));
  connect(this->ui.pushButton_sendFile,SIGNAL(clicked()),this,SLOT(sendFile()));
}

QtClient::~QtClient()
{
}

void QtClient::initTCP()
{
  this->tcpSocket = new QTcpSocket(this);
  connect(ui.pushButton_connect,SIGNAL(clicked()),this,SLOT(connectServer()));
  connect(ui.pushButton_disconnect,SIGNAL(clicked()),this,SLOT(disconnectServer()));
  connect(ui.pushButton_send,SIGNAL(clicked()),this,SLOT(sendData()));
}

void QtClient::connectServer()
{
  tcpSocket->abort();
  tcpSocket->connectToHost("127.0.0.1",6666);
  connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(receiveData()));
}

这里说明一下两个方法:

  • abort():官方文档给出了说明: Aborts the current connection and resets the socket. Unlike disconnectFromHost(), this function immediately closes the socket, discarding any pending data in the write buffer. 即终止之前的连接,重置套接字。
  • connectToHost():给定IP地址和端口号,连接服务器。这里我们给127.0.0.1,即本机地址,端口号随便给了个,一般来说介于49152~65535之间的都行。
代码语言:javascript
复制
void QtClient::disconnectServer()
{
  //这里不做实现了,大家自己定义吧O(∩_∩)O哈哈~
}

void QtClient::receiveData()
{
  /////获取当前时间
  current_date_time = QDateTime::currentDateTime();
  str_date_time = current_date_time.toString("yyyy-MM-dd hh:mm:ss")+"\n";
  ////接收数据
  QString str = tcpSocket->readAll();
  ////显示
  str = "Server "+str_date_time+str;
  this->ui.textEdit->append(str);
}

void QtClient::sendData()
{
  ////发送数据
  QString str = ui.lineEdit->text();
  this->tcpSocket->write(ui.lineEdit->text().toLatin1());
  ////显示
  current_date_time = QDateTime::currentDateTime();
  str_date_time = current_date_time.toString("yyyy-MM-dd hh:mm:ss");
  str = "You "+str_date_time+"\n"+str;
  ui.textEdit->append(str);
}

这里说明QTCPSocket的两个方法:

  • readAll():如果把一个socket比作一个通讯管道,那么这个方法的作用是读取该管道里的所有数据(格式为QByteArray);
  • write():同上面的比喻,这个方法的作用是向管道里塞数据
代码语言:javascript
复制
void QtClient::selectFile()
{
  this->fileSocket = new QTcpSocket(this);
  fileSocket->abort();
  fileSocket->connectToHost("127.0.0.1",8888);
  ////文件传送进度更新
  connect(fileSocket,SIGNAL(bytesWritten(qint64)),this,SLOT(updateFileProgress(qint64)));
  connect(fileSocket,SIGNAL(readyRead()),this,SLOT(updateFileProgress()));

  this->ui.progressBar->setValue(0);
  this->filename = QFileDialog::getOpenFileName(this,"Open a file","/","files (*)");
  ui.lineEdit_filename->setText(filename);
}

从上面那段代码可以看出,Jungle设计了两个socket,一个用于发送字符数据,另一个套接字用于传送文件。两个socket分别使用两个不同的端口。在服务端里也是这样,待会儿不再解释了。

客户端的剩余部分设计见下一篇文章

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Jungle笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档