TCP通信
1. 先讲解TCP服务端:首先需要 在项目中引入网络模块
QT += core gui network
#include <QTcpServer>
#include <QTcpSocket>
2. 分配 服务器对象和 套接字 对象
/* 实例化 tcp 服务器与 tcp 套接字 */
tcpServer = new QTcpServer(this);
tcpSocket = new QTcpSocket(this);
3. 监听 指定IP 地址 & 端口号
/* 开始监听 */
void MainWindow::startListen()
{
/* 需要判断当前主机是否有 IP 项 */
if (comboBox->currentIndex() != -1) {
qDebug()<<"start listen"<<endl;
tcpServer->listen(IPlist[comboBox->currentIndex()],
spinBox->value());
/* 设置按钮与下拉列表框的状态 */
pushButton[0]->setEnabled(false);
pushButton[1]->setEnabled(true);
comboBox->setEnabled(false);
spinBox->setEnabled(false);
/* 在文本浏览框里显示出服务端 */
textBrowser->append("服务器 IP 地址:"
+ comboBox->currentText());
textBrowser->append("正在监听端口:"
+ spinBox->text());
}
}
4. 新的 客户端连接 处理方法
void MainWindow::clientConnected()
{
/* 获取客户端的套接字 */
tcpSocket = tcpServer->nextPendingConnection();
/* 客户端的 ip 信息 */
QString ip = tcpSocket->peerAddress().toString();
/* 客户端的端口信息 */
quint16 port = tcpSocket->peerPort();
/* 在文本浏览框里显示出客户端的连接信息 */
textBrowser->append("客户端已连接");
textBrowser->append("客户端 ip 地址:" + ip);
textBrowser->append("客户端端口:"+ QString::number(port));
connect(tcpSocket, SIGNAL(readyRead()),this, SLOT(receiveMessages()));
connect(tcpSocket,
SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,
SLOT(socketStateChange(QAbstractSocket::SocketState)));}
5. 向服务器 写 数据
/* 服务端发送消息 */
void MainWindow::sendMessages()
{
if(NULL == tcpSocket)
return;
/* 如果已经连接 */
if(tcpSocket->state() == tcpSocket->ConnectedState) {
/* 发送消息 */
tcpSocket->write(lineEdit->text().toUtf8().data());
/* 在服务端插入发送的消息 */
textBrowser->append("服务端:" + lineEdit->text());
}
}
注意: lineEdit->text().toUtf8().data()
让我们逐步解析这段代码:
- lineEdit->text():lineEdit 是一个 QLineEdit 对象,text() 是 QLineEdit类的成员函数,用于获取文本框中的文本内容。
- lineEdit->text().toUtf8():toUtf8() 是 QString 类的成员函数,用于将 QString对象转换为 UTF-8 编码的字节流 (QByteArray)。
- lineEdit->text().toUtf8().data():data() 是 QByteArray 类的成员函数,用于获取
QByteArray 对象中存储数据的指针。
以上步骤将文本框中的内容转换为 UTF-8 编码的字节流,并获取了该字节流的指针。
6. 向服务器 读 数据
/* 服务端接收消息 */
void MainWindow::receiveMessages()
{
/* 读取接收到的消息 */
QString messages = "客户端:" + tcpSocket->readAll();
textBrowser->append(messages);
}
7. 客户端 与 服务端 的状态改变
/* 服务端状态改变 */
void MainWindow::socketStateChange(QAbstractSocket::SocketState
state)
{
switch (state) {
case QAbstractSocket::UnconnectedState:
textBrowser->append("scoket 状态:UnconnectedState");
break;
case QAbstractSocket::ConnectedState:
textBrowser->append("scoket 状态:ConnectedState");
break;
case QAbstractSocket::ConnectingState:
textBrowser->append("scoket 状态:ConnectingState");
break;
case QAbstractSocket::HostLookupState:
textBrowser->append("scoket 状态:HostLookupState");
break;
case QAbstractSocket::ClosingState:
textBrowser->append("scoket 状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
textBrowser->append("scoket 状态:ListeningState");
break;
case QAbstractSocket::BoundState:
textBrowser->append("scoket 状态:BoundState");
break;
default:
break;
}
}
8. 服务端 主动断开连接
/* 停止监听 */
void MainWindow::stopListen()
{
qDebug()<<"stop listen"<<endl;
/* 停止监听 */
tcpServer->close();
/* 如果是连接上了也应该断开,如果不断开客户端还能继续发送信息,
* 因为 socket 未断开,还在监听上一次端口 */
if (tcpSocket->state() == tcpSocket->ConnectedState)
tcpSocket->disconnectFromHost();
/* 设置按钮与下拉列表框的状态 */
pushButton[1]->setEnabled(false);
pushButton[0]->setEnabled(true);
comboBox->setEnabled(true);
spinBox->setEnabled(true);
/* 将停止监听的信息添加到文本浏览框中 */
textBrowser->append("已停止监听端口:"
+ spinBox->text());
}
1. TCP 客户端
QT += core gui network
#include <QTcpServer>
#include <QTcpSocket>
2. 分配客户端 套接字
/* tcp 套接字 */
tcpSocket = new QTcpSocket(this);
3. 主动连接服务器
void MainWindow::toConnect()
{
/* 如果连接状态还没有连接 */
if (tcpSocket->state() != tcpSocket->ConnectedState) {
/* 指定 IP 地址和端口连接 */
tcpSocket->connectToHost(IPlist[comboBox->currentIndex()],
spinBox->value());
}
}
4. 主动向服务器 写 数据
/* 客户端发送消息 */
void MainWindow::sendMessages()
{
if(NULL == tcpSocket)
return;
if(tcpSocket->state() == tcpSocket->ConnectedState) {
/* 客户端显示发送的消息 */
textBrowser->append("客户端:" + lineEdit->text());
/* 发送消息 */
tcpSocket->write(lineEdit->text().toUtf8().data());
}
}
4. 主动向服务器 读 数据
/* 客户端接收消息 */
void MainWindow::receiveMessages()
{
/* 读取接收到的消息 */
QString messages = tcpSocket->readAll();
textBrowser->append("服务端:" + messages);
}
5. 客户端 与 服务端 的连接 的状态改变
/* 客户端状态改变 */
void MainWindow::socketStateChange(QAbstractSocket::SocketState
state)
{
switch (state) {
case QAbstractSocket::UnconnectedState:
textBrowser->append("scoket 状态:UnconnectedState");
break;
case QAbstractSocket::ConnectedState:
textBrowser->append("scoket 状态:ConnectedState");
break;
case QAbstractSocket::ConnectingState:
textBrowser->append("scoket 状态:ConnectingState");
break;
case QAbstractSocket::HostLookupState:
textBrowser->append("scoket 状态:HostLookupState");
break;
case QAbstractSocket::ClosingState:
textBrowser->append("scoket 状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
textBrowser->append("scoket 状态:ListeningState");
break;
case QAbstractSocket::BoundState:
textBrowser->append("scoket 状态:BoundState");
break;
default:
break;
}
}
5. 客户端 主动 断开连接
void MainWindow::toDisConnect()
{
/* 断开连接 */
tcpSocket->disconnectFromHost();
/* 关闭 socket*/
tcpSocket->close();
}
总结:TCP 通信的大概流程
- 服务器端:
-
创建 QTcpServer 对象,并绑定到特定的 IP 地址和端口号。
-
监听来自客户端的连接请求,当有连接请求时,使用
nextPendingConnection() 函数获取新的 QTcpSocket 对象用于与客户端进行通信。 -
与客户端建立连接后,使用
QTcpSocket 对象的读写函数实现数据交换。 -
关闭连接时,关闭 QTcpSocket 和 QTcpServer。
- 客户端:
- 创建 QTcpSocket 对象,并连接到服务器的 IP 地址和端口号。
- 与服务器建立连接后,使用 QTcpSocket 对象的读写函数实现数据交换。
- 关闭连接时,关闭 QTcpSocket
UDP 通信
1. 先讲解UDP服务端:首先需要 在项目中引入网络模块
QT += core gui network
#include <QtNetwork/QUdpSocket>
2. 创建 QUdpSocket 对象,并绑定到特定的 IP 地址和端口号。
// 创建 UDP Socket
QUdpSocket udpSocket;
// 绑定到本地回环地址和端口号
udpSocket.bind(QHostAddress::LocalHost, 12345);
3. 使用 QUdpSocket 对象的 readyRead 信号连接槽函数,以接收来自客户端的数据报。
// 信号槽连接,当有数据到达时触发 readyRead() 信号
QObject::connect(&udpSocket, &QUdpSocket::readyRead, [&]() {
while (udpSocket.hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(udpSocket.pendingDatagramSize());
QHostAddress senderAddress;
quint16 senderPort;
udpSocket.readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
qDebug() << "Received datagram from" << senderAddress.toString() << "port" << senderPort
<< ":" << datagram;
}
});
4. 当收到数据报时,在槽函数中使用 readDatagram() 函数读取数据报的内容。
// 信号槽连接,当有数据到达时触发 readyRead() 信号
QObject::connect(&udpSocket, &QUdpSocket::readyRead, [&]() {
while (udpSocket.hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(udpSocket.pendingDatagramSize());
QHostAddress senderAddress;
quint16 senderPort;
udpSocket.readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
qDebug() << "Received datagram from" << senderAddress.toString() << "port" << senderPort
<< ":" << datagram;
}
});
注意: udpSocket.readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
- readDatagram() 函数是一个带有输出参数的函数,用于接收数据报并将发送者的地址和端口填充到提供的变量中。当
readDatagram() 函数被调用时,它会将发送者的地址和端口信息填充到 senderAddress 和 senderPort
中,因此你不需要在代码中显式初始化这两个变量。
5. 向 客户端 写数据
void MainWindow::sendBroadcastMessages()
{
/* 局部变量,用于获取发送者的 IP 和端口 */
QHostAddress peerAddr;
quint16 peerPort;
/* 文本浏览框显示发送的信息 */
textBrowser->append("发送:" + lineEdit->text());
/* 要发送的信息,转为 QByteArray 类型字节数组,数据一般少于 512 个字节 */
QByteArray data = lineEdit->text().toUtf8();
/* 广播地址,一般为 255.255.255.255,
* 同一网段内监听目标端口的程序都会接收到消息 */
QHostAddress peerAddr = QHostAddress::Broadcast;
/* 要发送的目标端口号 */
quint16 peerPort = spinBox[1]->text().toInt();
/* 发送消息 */
udpSocket->writeDatagram(data, peerAddr, peerPort);
}
6. 套接字状态发生变化
/* socket 状态改变 */
void MainWindow::socketStateChange(QAbstractSocket::SocketState
state)
{
switch (state) {
case QAbstractSocket::UnconnectedState:
textBrowser->append("scoket 状态:UnconnectedState");
break;
case QAbstractSocket::ConnectedState:
textBrowser->append("scoket 状态:ConnectedState");
break;
case QAbstractSocket::ConnectingState:
textBrowser->append("scoket 状态:ConnectingState");
break;
case QAbstractSocket::HostLookupState:
textBrowser->append("scoket 状态:HostLookupState");
break;
case QAbstractSocket::ClosingState:
textBrowser->append("scoket 状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
textBrowser->append("scoket 状态:ListeningState");
break;
case QAbstractSocket::BoundState:
textBrowser->append("scoket 状态:BoundState");
break;
default:
break;
}
}
7. 关闭连接时,关闭 QUdpSocket。
165 void MainWindow::unbindPort()
166 {
167 /* 解绑,不再监听 */
168 udpSocket->abort();
169
170 /* 设置界面中的元素的可用状态 */
171 pushButton[0]->setEnabled(true);
172 pushButton[1]->setEnabled(false);
173 spinBox[1]->setEnabled(true);
174 }
注意:
QUdpSocket 是 Qt 提供的用于进行 UDP 通信的类,它可以用于在网络上发送和接收 UDP 数据报。abort() 和 close() 都是 QUdpSocket 类的成员函数,用于控制和管理 UDP 套接字的状态和连接。
- abort() 是 QUdpSocket 类的成员函数,用于中止当前的 UDP 套接字操作。当你调用 abort() 函数时,UDP
套接字会立即停止当前正在进行的操作,并且任何待处理的数据包都会被丢弃。这意味着已经发送的但尚未传送到目标主机的数据包和已经接收但尚未被读取的数据包都会被丢弃。该函数将
UDP 套接字恢复到未连接状态。 - QUdpSocket 是 Qt 提供的用于进行 UDP 通信的类,它可以用于在网络上发送和接收 UDP 数据报。abort() 和
close() 都是 QUdpSocket 类的成员函数,用于控制和管理 UDP 套接字的状态和连接。 - abort() 是 QUdpSocket 类的成员函数,用于中止当前的 UDP 套接字操作。当你调用 abort() 函数时,UDP
套接字会立即停止当前正在进行的操作,并且任何待处理的数据包都会被丢弃。这意味着已经发送的但尚未传送到目标主机的数据包和已经接收但尚未被读取的数据包都会被丢弃。该函数将
UDP 套接字恢复到未连接状态。 - close() 也是 QUdpSocket 类的成员函数,用于关闭当前的 UDP 套接字。当你调用 close() 函数时,UDP
套接字会被关闭,并且无法再发送或接收数据。与 abort() 不同,close()
不会立即停止正在进行的操作,而是等待当前操作完成。当关闭后,UDP 套接字将无法再用于网络通信。
客户端:
- 创建 QUdpSocket 对象。
- 使用 QUdpSocket 对象的 writeDatagram() 函数向服务器端发送数据报。
- 关闭连接时,关闭 QUdpSocket
#include <QtNetwork/QUdpSocket>
#include <QCoreApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建 UDP Socket
QUdpSocket udpSocket;
// 发送数据到服务器
QString message = "Hello from UDP Client!";
udpSocket.writeDatagram(message.toUtf8(), QHostAddress::LocalHost, 12345);
qDebug() << "Sent datagram:" << message;
return a.exec();
}