文章目录
- 一、TCP/IP 通信过程简介
- 1. Socket 通信
- 2. Linux 下的 TCP/IP 通信过程
- 3. QT 下的 TCP/IP 通信过程
- 3.1 在 QT 中实现 TCP/IP 服务器端通信的流程
- 3.2 在 QT 中实现 TCP/IP 客户端通信的流程
- 二、TCP/IP 通信过程操作实现
- 1. 服务器端
- 2. 客户端
- 三、服务器端和客户端实现代码
- 1. 主函数 main.c
- 2. 服务器端头文件 widget.h
- 3. 服务器端源文件 widget.cpp
- 4. 客户端头文件 clientwidget.h
- 5. 客户端源文件 clientwidget.cpp
由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见
QT 学习笔记(一)
提示:具体项目准备工作和细节讲解见
QT 学习笔记(二)
一、TCP/IP 通信过程简介
1. Socket 通信
- QT 中提供的所有的 Socket 类都是非阻塞的。
- QT 中常用的用于 socket 通信的套接字类:
- QTcpServer:用于 TCP/IP 通信,作为服务器端套接字使用。
- QTcpSocket:用于 TCP/IP 通信,作为客户端套接字使用。
- QUdpSocket:用于 UDP 通信,服务器,客户端均使用此套接字。
2. Linux 下的 TCP/IP 通信过程
3. QT 下的 TCP/IP 通信过程
3.1 在 QT 中实现 TCP/IP 服务器端通信的流程
- (1) 创建套接字。
- (2) 将套接字设置为监听模式。
- (3) 等待并接受客户端请求:可以通过 QTcpServer 提供的 void newConnection() 信号来检测是否有连接请求,如果有可以在对应的槽函数中调用 nextPendingConnection 函数获取到客户端的 Socket 信息(返回值为 QTcpSocket* 类型指针),通过此套接字与客户端之间进行通信。
- (4) 接收或者向客户端发送数据,包括如下两种:
- 接收数据:使用 read() 或者 readAll() 函数。
- 发送数据:使用 write() 函数。
3.2 在 QT 中实现 TCP/IP 客户端通信的流程
- (1) 创建套接字。
- (2) 连接服务器:可以使用 QTcpSocket 类的 connectToHost() 函数来连接服务器。
- (3) 向服务器发送或者接受数据。
二、TCP/IP 通信过程操作实现
- 生成一个新的项目,具体步骤过程见提示。
- 在生成新项目的过程中,我们选择基类为 QWidget ,这是因为 QWidget 当中比较干净,不存在别的东西,而 QMainWindow 虽然也可以实现,但其中存在工具栏,菜单栏,核心控件等东西,比较复杂。
1. 服务器端
- 首先,我们在 ui 界面布置出服务器端的窗口界面,包含两个按钮(发送 send 和 关闭 close)和两个文本编辑区(分别用来输入和显示),具体界面布局如下图所示。
- 同时,我们将上面的文本编辑区设置为 readOnly(只读)。这里需要注意的是只修改上面的文本编辑区,下面的是用来输入信息的。
- 我们在 TCP.pro 当中加入 QT += network 代码,以便后续工作可以顺利开展。
- 通过 QT 提供的 QTcpServer 类实现服务器端的 socket 通信。
- 只有服务器端需要两个套接字,分别是监听套接字 QTcpServer 和通信套接字 QTcpSocket。
- 这里要使用 Lambda 表达式,需要提前在 TCP.pro 当中加入 CONFIG += C++11 代码。
- 我们还需要对两个按钮在 ui 界面进行转到槽函数的操作,并进行代码的编写,由于这里需要服务器端和客户端相结合才可以看到现象,故在此处不过多展示实现现象(主要在客户端展示)。
- 当我们完成基础代码的编写后,运行程序,直接点击 send 按钮,会产生程序异常结束的现象,具体实现现象如下图所示。
- 这是因为当我们按下按钮时 tcpsocket 并没有内容,因此,我们需要在代码当中添加 if 判断,tcpsocket 是否为空,若为空,就直接返回,不进入按钮操作。
- 同时,当我们断开与客户端的连接后,也需要将 tcpsocket 置为空。
2. 客户端
- 客户端通过使用 QT 提供的 QTcpSocket 类可以方便的实现与服务器端的通信。
- 在完成服务器端后,我们进行客户端的编写,客户端只有一个套接字就是通信套接字 tcpsocket。
- 这里我们不重新开一个项目,而是将两部分放在一块。但是,服务器端和客户端的 ui 界面并不相同,因此,我们需要在添加一个 QT 设计师界面类,对客户端的 ui 界面进行设计。
- 界面模板默认 Widget 即可。
- 随后,我们在 ui 界面布置出客户端的窗口界面,包含两个标签(分别是服务器端口和服务器 IP )以及他们对应的行编辑区,三个按钮(分别是发送 send ,关闭 close 和连接 connect)和两个文本编辑区(分别用来输入和显示)。
- 同时,我们将上面的文本编辑区设置为 readOnly(只读)。这里需要注意的是只修改上面的文本编辑区,下面的是用来输入信息的。
- 其中的服务器端口的行编辑区直接默认值是 8888(与服务器端相对应),服务器 IP 的行编辑区直接默认值是 127.0.0.1(本地连接),具体界面布局如下图所示。
- 在完成 ui 界面的设计后,我们先编写按钮 connect ,并测试是否可以与服务器端连接,具体实现现象如下图所示。
- 但是,这里存在一个问题,就是客户端自己本身并不知道是否连接成功,对此进行优化,使客户端显示成功和服务器端建立好连接,得到如下的实现现象。
- 在确保可以完成服务器端和客户端的连接之后,我们进行信息收发功能的实现(主要包括客户端发送信息和服务器端获取客户端编辑区信息并展现),具体实现现象如下图所示。
- 最后,实现断开服务器端和客户端连接的功能,就是无法继续发送信息,但是窗口仍然保存,不会关闭,可再次通信发送。
三、服务器端和客户端实现代码
1. 主函数 main.c
#include "widget.h"
#include <QApplication>
#include "clientwidget.h"int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();CLientWidget w2;w2.show();return a.exec();
}
2. 服务器端头文件 widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTcpServer>//监听套接字
#include <QTcpSocket>//通信套接字namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = nullptr);~Widget();private slots:void on_buttonsend_clicked();void on_pushButton_2_clicked();private:Ui::Widget *ui;QTcpServer *tcpserver;//监听套接字QTcpSocket *tcpsocket;//通信套接字
};#endif // WIDGET_H
3. 服务器端源文件 widget.cpp
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{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);});});
}Widget::~Widget()
{delete ui;
}void Widget::on_buttonsend_clicked()
{if(NULL == tcpsocket){return;}//获取编辑区内容QString str = ui->textEditread->toPlainText();//给对方发送数据,使用套接字是 tcpsockettcpsocket->write(str.toUtf8().data());
}void Widget::on_pushButton_2_clicked()
{if(NULL == tcpsocket){return;}//主动和客户端断开连接tcpsocket->disconnectFromHost();tcpsocket->close();tcpsocket = NULL;
}
4. 客户端头文件 clientwidget.h
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H#include <QWidget>
#include <QTcpSocket>//通信套接字namespace Ui {
class CLientWidget;
}class CLientWidget : public QWidget
{Q_OBJECTpublic:explicit CLientWidget(QWidget *parent = nullptr);~CLientWidget();private slots:void on_buttonconnect_clicked();void on_pushButtonsend_clicked();void on_pushButtonclose_clicked();private:Ui::CLientWidget *ui;QTcpSocket *tcpsocket;//通信套接字
};#endif // CLIENTWIDGET_H
5. 客户端源文件 clientwidget.cpp
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QHostAddress>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_pushButtonsend_clicked()
{//获取编辑框内容QString str = ui->textEditwrite->toPlainText();//发送数据tcpsocket->write(str.toUtf8().data());
}void CLientWidget::on_pushButtonclose_clicked()
{//主动和对方断开连接tcpsocket->disconnectFromHost();tcpsocket->close();
}