目录
一 前言
二 TCP/IP协议架构和通信原理
三 TCP/IP的连接与断开过程
四 Qt中开发TCP/IP原理概述
五 完整实例代码示范
在软件开发中,常用的技术体系里面网络通信属于最重要的 “联通” 技术,是必须要掌握的技术。
那为什么网络通信如此重要呢,我想大概有以下几点:
以上是日常生活中需要使用网络的例子,另外无线通信技术、导航等底层都使用了网络通信技术,只是其底层稍有差异,根据硬件、功能、可靠性的不同而不同。
Qt框架也封装非常多的网络通信相关API供开发者使用,常用的有基于TCP/IP的Socket通信、基于UDP的Socket通信、http通信等。
本篇博客重点介绍基于TCP/IP的Socket通信,从TCP/IP基本原理出发,结合Qt的网络通信框架进行演示和讲解,最后附上完整的代码实例。
TCP/IP是Transmission Control Protocol / Internet Protocol(传输控制协议/互联网络协议)的缩写。TCP和IP只是其中的2个协议,也是很重要的2个协议.
TCP/IP协议族是一组协议的集合,也叫互联网协议族,用来实现互联网上主机之间的相互通信。
TCP/IP四层模型如下:
网络接口层
用于协作IP数据在已有网络介质上传输的协议。实际上TCP/IP标准并不定义与ISO数据链路层和物理层相对应的功能。它提供TCP/IP协议的数据结构和实际物理硬件之间的接口。
网络层
本层包含IP协议、RIP协议(Routing Information Protocol,路由信息协议),负责数据的分片、寻址和路由。同时还包含网间控制报文协议(Internet Control Message Protocol,ICMP)用来提供网络诊断信息。
传输层
传输层有时候也称为主机到主机层,提供两种端到端的通信服务。
1)TCP协议(Transmission Control Protocol)提供可靠的数据流运输服务。
2)UDP协议(Use Datagram Protocol)提供不可靠的用户数据报服务。
应用层
应用层对应于OSI参考模型的应用层、表示层、会话层,为用户提供所需要的各种服务,例如:HTTP、TFTP、FTP、NFS、SMTP等。应用层是协议栈与主机上应用程序或进程接口的地方,因此,也被称为处理层。
连接时三次握手
第一次握手:
建立连接之前客户端要保证服务器已经开始监听,然后客户端选择一个随机的端口开始和服务器进行socket通信。等待服务器确认;
第二次握手:
服务器收到该报文段后,向客户端发送确认;
第三次握手:
客户端收到服务器的确认后还要向服务器给一个确认,服务器收到客户端的确认后也进入建立连接状态。
断开时四次挥手
第一次挥手:
客户端先发送一个连接释放的报文并停止发送数据。等待服务器的确认。
第二次分手:
服务器收到该报文段后就发出确认,
第三次分手:
客户端收到服务器的确认后,等待客户端的确认。
第四次分手 :
客户端收到连接释放报文段后,给服务器一个确认结束的报文段,客户端也进入关闭。
Qt中的TCP/IP通信原理图如下:
对于服务器端来说:
需要两个套接字类,其中一个负责监听(QTcpServer),另外一个负责通信(QTcpSocket),大概步骤如下:
QTcpServer对象负责监听是否有客户端连接此服务器。调用监听函数并设置服务器端的监听IP地址和端口,
tcpserver->listen(QHostAddress::Any, 8888);
当监听到客户端连接过来时,服务器端会触发newConnection这个信号,接着创建一个槽函数并和newConnection绑定,以实现和客户端的连接之后的操作 。
tcpserver = new QTcpServer(this);
tcpserver->listen(QHostAddress::Any, 8888);
connect(tcpserver, &QTcpServer::newConnection, this, &Widget::ConnectToClient);
1 //取出建立好的套接字
2 tcpsocket = tcpserver->nextPendingConnection();
3 //获取对方的端口号和ip地址,并且显示在文本编辑框中。
4 QString ip = tcpsocket->peerAddress().toString();
5 qint16 port = tcpsocket->peerPort();
6
7 ui->textEditRead->setText(QString("[%1:%2]连接成功").arg(ip).arg(port));
接着,服务器端便可以和客户端开始通信了:
QString str = ui->textEditWrite->toPlainText();
tcpsocket->write(str.toUtf8().data());
对于客户端来说:
首先初始化QTcpSocket对象,然后主动和服务器端连接,
QTcpSocket *tcpsocket=new QTcpSocket(this);
//主动和服务器进行连接: 设定和服务器端指定的IP、port
tcpsocket->connectToHost((QHostAddress)ip, port);
连接成功后,客户端会触发conected信号,我们定义一个槽函数用于与信号进行绑定来处理连接成功后的处理逻辑,
connect(tcpsocket, &QTcpSocket::connected, this, &Widget::connectToServer);
void Widget::connectToServer()
{
ui->textEditRead->setText("成功和服务器进行连接");
}
当服务器端有发送消息给客户端时,客户端会触发readyRead信号,我们自定义一个槽函数与之绑定,进行连接后的逻辑处理,
connect(tcpsocket, &QTcpSocket::readyRead, this, &Widget::ReadInformation);
void Widget::ReadInformation()
{
//获取套接字中的内容
QByteArray temp = tcpsocket->readAll();
ui->textEditRead->append(temp);
}
至此,关于服务器端与客户端的通信过程就说完了,其实许多复杂的通信都是基于上述的过程的,比如多个客户端连接同一个服务器端、服务器端不间断接收服务器端信息等。
本例将实现客户端与服务端的消息通信,可以相互发送文字、图片、视频、文件,同时具有断开连接的功能。
服务端端代码:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QTcpServer *tcpserver;
QTcpSocket *tcpsocket;
private slots:
void connectSlot();
void readData();
void disConnectSlot();
void on_send_clicked();
void StateChanged(QAbstractSocket::SocketState socketState);
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("服务器端");
resize(800,600);
//初始化QTcpServer对象
tcpserver=new QTcpServer(this);
//设置监听IP和端口,这里的any指本机上面的所有网卡地址,listen方法一经调用就会开始循环监听客户端的连接了,
tcpserver->listen(QHostAddress::Any,8899);
//当有客户端连接过来时,就会触发newConnect信号,我们把信号绑定到槽函数上面去,便于实现连接后的逻辑
connect(tcpserver,SIGNAL(newConnection()),this,SLOT(connectSlot()));
//点击按钮与客户端断开连接
connect(ui->dis_client,SIGNAL(clicked(bool)),this,SLOT(disConnectSlot()));
//监测连接状态变化
//connect(tcpsocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(StateChanged(QAbstractSocket::SocketState)));
ui->send->setEnabled(false); //程序启动禁止点击发送消息按钮
ui->dis_client->setEnabled(false); //程序启动禁用于断开连接按钮
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::connectSlot()
{
//获取socket对象
tcpsocket=tcpserver->nextPendingConnection();
//把对方的IP地址和端口显示出来
auto ip = tcpsocket->peerAddress().toString();
auto port = tcpsocket->peerPort();
ui->look_message->append("与客户端建立连接成功!IP地址为:"+ip+",端口号为:"+QString::number(port));
connect(tcpsocket,SIGNAL(readyRead()),this,SLOT(readData()));
ui->send->setEnabled(true); //重新建立客户端连接后,启用点击发送消息按钮
ui->dis_client->setEnabled(true);//重新建立客户端连接后,启用断开连接按钮
}
void MainWindow::readData()
{
ui->look_message->append("客户端说:"+tcpsocket->readAll());
}
void MainWindow::disConnectSlot()
{
tcpsocket->disconnectFromHost();
tcpsocket->close();
tcpsocket=NULL;
ui->look_message->append("与客户端断开连接成功!");
ui->send->setEnabled(false); //断开客户端连接后,禁止点击发送消息按钮
ui->dis_client->setEnabled(false); //点击一次断开客户端连接后,禁用于断开连接按钮
}
void MainWindow::on_send_clicked()
{
//获取需要发送的文本
QString str=ui->input_message->toPlainText().trimmed();
if(str.length()>0 ){
tcpsocket->write(str.toUtf8().data());
ui->look_message->append("我给客户端说:"+str);
}else {
ui->statusbar->showMessage("提示:不能发送空消息哦!");
}
}
void MainWindow::StateChanged(QAbstractSocket::SocketState socketState)
{
// QAbstractSocket::UnconnectedState 0 The socket is not connected.
// QAbstractSocket::HostLookupState 1 The socket is performing a host name lookup.
// QAbstractSocket::ConnectingState 2 The socket has started establishing a connection.
// QAbstractSocket::ConnectedState 3 A connection is established.
// QAbstractSocket::BoundState 4 The socket is bound to an address and port.
// QAbstractSocket::ClosingState 6 The socket is about to close (data may still be waiting to be written).
// QAbstractSocket::ListeningState 5 For internal use only.
switch (socketState) {
case QAbstractSocket::UnconnectedState:
ui->statusbar->showMessage("The socket is not connected");
break;
case QAbstractSocket::HostLookupState:
ui->statusbar->showMessage("The socket is performing a host name lookup");
break;
case QAbstractSocket::ConnectingState:
ui->statusbar->showMessage("The socket has started establishing a connection.");
break;
case QAbstractSocket::ConnectedState :
ui->statusbar->showMessage("A connection is established");
break;
case QAbstractSocket::BoundState :
ui->statusbar->showMessage("The socket is bound to an address and port.");
break;
case QAbstractSocket::ClosingState:
ui->statusbar->showMessage("The socket is about to close (data may still be waiting to be written).");
break;
case QAbstractSocket::ListeningState :
ui->statusbar->showMessage("For internal use only");
break;
}
}
main.cpp
#include "mainwindow.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
客户端代码:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void StartConnect();
void connectSucc();
void ReadData();
void disConnect();
void sendMessage();
void on_send_message_clicked();
private:
Ui::MainWindow *ui;
QTcpSocket *tcpsocket;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("客户端");
resize(800,700);
//设置默认的IP地址和端口
ui->ip->setText("127.0.0.1");
ui->port->setText("8899");
tcpsocket=new QTcpSocket(this);
//开始连接
connect(ui->connect_bt,SIGNAL(clicked(bool)),this,SLOT(StartConnect()));
//绑定客户端的连接信号与槽,便于在槽函数中书写逻辑
connect(tcpsocket,SIGNAL(connected()),this,SLOT(connectSucc()));
//当服务器端发送消息过来时,客户端接收并出来消息
connect(tcpsocket,SIGNAL(readyRead()),this,SLOT(ReadData()));
//断开连接时处理业务
connect(ui->disconnect_bt,SIGNAL(clicked(bool)),this,SLOT(disConnect()));
ui->send_message->setEnabled(false);
ui->disconnect_bt->setEnabled(false);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::StartConnect()
{
//主动连接服务器
tcpsocket->connectToHost(ui->ip->text().trimmed(),ui->port->text().toInt());
ui->display_message->append("开始连接服务器");
}
void MainWindow::connectSucc()
{
//if(tcpsocket->state()==QAbstractSocket::HostLookupState){}
ui->display_message->append("服务器连接成功!");
ui->display_message->append("现在可以开始互相发送消息啦!");
ui->connect_bt->setEnabled(false);
ui->send_message->setEnabled(true);
ui->disconnect_bt->setEnabled(true);
}
void MainWindow::ReadData()
{
//获取服务器端发送过来的内容
auto data = tcpsocket->readAll();
ui->display_message->append("服务器端说:"+data);
}
void MainWindow::disConnect()
{
tcpsocket->disconnectFromHost();
tcpsocket->close();
tcpsocket=NULL;
ui->display_message->append("客户端与服务器端断开成功!");
ui->connect_bt->setEnabled(true);
}
void MainWindow::on_send_message_clicked()
{
//获取文本框的输入,开始发送消息
QString str=ui->text_input->text().trimmed();
if(str.isEmpty()){
ui->statusbar->showMessage("不能发送空消息哦!");
}else {
tcpsocket->write(str.toUtf8().data());
ui->display_message->append("我跟服务器端说:"+str);
}
}
main.cpp
#include "mainwindow.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
运行效果:
下一篇博客:
Qt6教程之三(14) 串口通信_折腾猿王申兵的博客-CSDN博客本章主要介绍Qt的串口通信!https://blog.csdn.net/XiaoWang_csdn/article/details/129768835?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129768835%22%2C%22source%22%3A%22XiaoWang_csdn%22%7D
上一篇博客:
Qt6教程之三(12) 文件管理_折腾猿王申兵的博客-CSDN博客本篇博客主要介绍文件管理,同时实现一个模仿window文件管理的小程序,支持文件的打开浏览及读写!https://blog.csdn.net/XiaoWang_csdn/article/details/129751599