一、TCP\IP协议族简单介绍
可以参考一下几篇博客:
QT中进行TCP/IP网络通信服务器的基础配置与简单理解_qt tcp服务器-CSDN博客
QT 之TCP网络编程(非常值得看的一篇博客!)_ubuntu20 tcp网络编程qt-CSDN博客
《QT编写TCP的网络编程》_qt tcp编程-CSDN博客
TCP:(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
相比而言UDP,就是开放式、无连接、不可靠的传输层通信协议。
二、QT实现服务器端
2.1 服务器的UI界面
2.2 服务器端编程
2.2.1 在 .pro工程文件中的处理
QT提供了QTcpSocket类,该类属于network模块,而qt的工程文件中未加入此模块,所以需手动加入此模块。
在 .pro文件中 添加 QT += network ,其余部分不用动。
做完上面这一步后,记得要构建。
2.2.2 在 .h头文件中的相关处理
1、添加相关头文件
2、声明相关对象
相关代码注释:
QList<QTcpSocket*>tcpClient; //声明一个链表,存储的是指向 QTcpSocket 对象的指针,用于存储多个 TCP 客户端的套接字指针,方便在网络编程中管理和操作多个客户端连接。
2.2.3 在自己创建的类的 .cpp文件中的相关处理
在下面红框中的源文件中编写代码
1、先在构造函数中初始化tcpServer对象(服务器端对象) ,选this(即当前对象)做父对象,就不需要自己去析构了tcpServer对象了。
2.通过右击按钮, 点 “转到槽”(生成相应按钮的槽函数) , 在选择clicked()信号,就可以在点击按钮时,触发槽函数( 函数里面的代码需自己写,用于实现自己想要的现象),如下图所示:
相关代码解析(大致流程):
1、监听按钮:
a.获取自己输入端口号。
b.打开tcp服务器端的监听,传入IP地址信息和端口号。ps: 监听地址中的客户端套接字的QHostAddress和端口号,若不自己指定,则为有线网卡和无线网卡一起监听,端口号自动分配。
c.在点击一次按钮后,将监听按钮设为禁用状态(避免反复连接监听)。将断开按钮设置为允许使用状态。
1、连接 连接信号(QTcpServer::newConnection) 与 槽函数:
当客户端的连接请求到达时,服务器端会发出连接信号(newConnection),使用connect()将连接信号和槽函数连接,当连接信号发出后,就会自动执行对应的槽函数。
ps:此处使用了lambda表达式,可自行去查资料。
a.提取客户端连接请求。调用QTcpServer类的nextPendingConnection()函数。
b.在UI界面中显示连接成功的信息。
c.将tcpSocket添加到存储QTcpSocket*类型的链表中。
这段代码的主要作用是将连接的客户端的 IP 地址和端口号格式化为 IP:端口 的形式,并
将其添加到下拉框中,以便用户可以选择。
2、发送消息按钮:
a. 获取自己在发送窗口中输入的信息。
b. 将服务端要发送的数据写入到对应的客户端,调用QTcpSocket的write()函数。
c. 将服务端发送到客户端的数据在服务端的接收窗口中显示。调用append()函数。
d. 最后将服务端发送数据框中的内容清除。调用clear()函数。
2.接收数据(连接 QTcpSocket::readyRead信号 和 槽函数):
a.调用QTcpSocket类中的readAll()函数。当有数据来时,触发了readyRead信号,就将数据全部都出来。
b.将客户端发送的数据在UI界面中展示出来。
3、断开按钮:
a.关闭套接字的连接。调用QTcpSocket类的disconnectFromHost()函数。
b.将客户端从列表中移除。
c.删除客户端对象,调用deleteLater()函数。
d.关闭服务端的监听。调用QTcpServer的close()函数。
e.将监听按钮设置为允许使用状态,将断开按钮设置为禁止使用。
3.断开连接(连接 QTcpSocket::disconnected信号 和 槽函数):
a.在UI界面中显示断开连接成功的信息。
4、清除按钮:
a.调用相关UI控件的ckear()函数。清除接收窗口中的数据。
2.2.4 运行结果
2.3 相关函数的意思
2.3.1 关于split()函数:
1、tr:这是一个用于国际化的函数,通常在 Qt 框架中使用。它的主要作用是将字符串标记为可翻译的,以便在不同语言环境中进行翻译。 "%1:%2" 是一个格式化字符串,其中 %1 和 %2 是占位符,后面会用实际的值替换它们。 2、arg 是一个用于替换占位符的函数。arg(m_t->peerAddress().toString().split("::ffff:")[1]):获取客户端的 IP 地址,并将其转换为字符串, 3、然后通过 split 方法分割字符串,提取出 IPv4 地址部分。split("::ffff:"):这个方法将字符串分割成一个数组,分隔符是 "::ffff:"。例如,如果IP 地址是 "::ffff:192.168.1.1",使用 split 后会得到一个数组 ["", "192.168.1.1"]。 4、[1]:这是数组的索引,表示取分割后数组的第二个元素。在这个例子中,[1] 代表提取出 "192.168.1.1",即去掉了 IPv6 地址中的映射部分。split("::ffff:"):这是一个字符串方法,用于将字符串分割成数组。这里的目的是将IPv6 地址中的 IPv4 映射地址部分(::ffff:)去掉,只保留实际的 IPv4 地址。 5、 这段代码的主要作用是将连接的客户端的 IP 地址和端口号格式化为 IP:端口 的形式,并将其添加到下拉框中,以便用户可以选择。
2.3.2 关于readyRead()信号:
1. 当tcpSocket准备好读取数据时,会发出readyRead信号
2. readyRead 信号表示这个 TCP 套接字有数据可读
3. 当套接字接收到来自服务器(或客户端)的数据时,这个信号会被触发。此时,可以通过调用 tcpSocket->readAll() 或其他读取函数来读取可用的数据.
4. 接收到数据:当 TCP 套接字(QTcpSocket)从远程端(例如服务器或客户端)接收到了数据时。如果对方发送了数据包,且数据已经可以被读取,
5. readyRead() 信号就会被发出。数据缓冲区可读:如果TCP连接的接收缓冲区中有数据可读,无论数据的大小,都会触发这个信号。
三、QT实现客户端
3.1 客户端的UI界面
3.2 客户端编程
3.2.1 在 .pro工程文件中的处理
注意:这一步与上述服务器端的处理一样,下面有些地方就省略了。请看 2.2.1
3.2.2 在 .h头文件中的相关处理
3.2.3 在自己创建的类的 .cpp文件中的相关处理
1、连接按钮:
a. 获取 IP地址 和 port端口号。
b. 客户端尝试与指定的主机建立 TCP 连接。 调用QTcpSocket的connectToHost()函数。
c. 将相关按钮设置为 禁用 或者 允许使用。
1、连接 连接信号 和 槽函数 ( QTcpSocket::connect() ):
a. 在UI界面中显示连接成功。
2、 发送按钮:
a. 获取客户端要发送的信息。
b. 将要发送的数据,写入到套接字,传给服务器。 调用QTcpSocket类的write()函数。
c. 将发送信息框的内容清空。
2、 连接 准备读信号 和 槽函数(QTcpSocket::readyRead信号):
a. 将传到客户端的数据都读出来。 调用QTcpSocket类的 readAll() 函数。
b. 将传来的数据在UI界面的接收框中显示。
3、断开按钮:
a. 断开与对方主机的连接。调用QTcpSocket类的 disconnectFromHost() 函数。
b. 将相关的按钮状态设置为 禁用 或者 允许使用。
3、连接 断开连接信号 和 槽函数(QTcpSocket::disconnected) :
a. 在UI界面显示断开连接。
4、清空按钮:
a. 将接收框中的数据全部清空。
3.2.4 运行结果
//客户端的 .cpp文件
#include "tcpclientwindow.h"
#include "ui_tcpclientwindow.h"
TcpClientWindow::TcpClientWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::TcpClientWindow)
{
ui->setupUi(this);
tcpSocket = new QTcpSocket(this);
//与服务器连接成功
connect(tcpSocket, &QTcpSocket::connected, [=](){
ui->receice_TextEdit->append("-----客户端与服务器连接成功-----");
});
//接收数据
connect(tcpSocket, &QTcpSocket::readyRead, [=](){
QByteArray data = tcpSocket->readAll();
QString dateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); //获取当前时间
QString text = QString("服务器fa: [%1]:%2").arg(dateTime).arg(QString(data));
ui->receice_TextEdit->append(text);
});
//断开连接
connect(tcpSocket, &QTcpSocket::disconnected, [=](){
ui->receice_TextEdit->append("-----与服务器断开连接-----");
});
}
TcpClientWindow::~TcpClientWindow()
{
delete ui;
}
void TcpClientWindow::on_connect_PushButton_clicked() //连接服务器
{
QString IP = ui->IP_LineEdit->text();
QString port = ui->port_LineEdit->text();
if(IP.isEmpty() | port.isEmpty())
{
return;
}
tcpSocket->connectToHost(IP, port.toUShort());
ui->connect_PushButton->setDisabled(true);
ui->disconnect_PushButton->setDisabled(false);
}
void TcpClientWindow::on_send_PushButton_clicked() //发送数据
{
QString message = ui->send_TextEdit->toPlainText();
QByteArray sendMessage = message.toUtf8();
tcpSocket->write(sendMessage);
ui->receice_TextEdit->append("客户端:" + sendMessage);
ui->send_TextEdit->clear();
return;
}
void TcpClientWindow::on_disconnect_PushButton_clicked() //断开连接
{
tcpSocket->disconnectFromHost(); //优雅地断开与对方主机的连接
ui->connect_PushButton->setDisabled(false);
ui->disconnect_PushButton->setDisabled(true);
}
void TcpClientWindow::on_clear_PushButton_clicked() //清空接收框
{
ui->receice_TextEdit->clear();
}
四、客户端与服务器端通信
注意:端口号 要一致。
五、总结
本文中 服务器端 写的有点乱,有时间的话会重写。以上是我的学习笔记,如有错误,请指正,谢谢。