目录
5、服务器收到客户端链接请求在子线程中创建对应套接字进行连接通信
一、学习资料说明
基础通信部分借鉴了【正点原子】I.MX6U嵌入式Qt开发指南V1.1一文的相关例程
TCP服务器多线程实现核心为重写QTcpServer的incomingConnection虚函数,即当QTcpServer的子类对象监听到连接请求,在子线程中创建一个套接字进行和客户端的通信,借鉴文章链接:http://t.csdnimg.cn/0PsHV
qss加载文件原文链接:http://t.csdnimg.cn/XaNAv
二、TCP服务器&客户端
1、实现过程重要代码
(1)、获取本地ip地址
/**
* @brief 获取本地可监听的所有ip地址并存储及显示在各comboBox中
* @author LiDonglin
* @date 20240730
*/
void Widget::getLocalHostIP()
{
/* 获取所有的网络接口 */
QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();
foreach (QNetworkInterface interface, list) {
/* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
QList<QNetworkAddressEntry> entryList = interface.addressEntries();
/* 遍历entryList */
foreach (QNetworkAddressEntry entry, entryList) {
/* 过滤IPv6地址,只留下IPv4 */
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
ui->comboBox->addItem(entry.ip().toString());
/* 添加到IP列表中 */
IPlist<<entry.ip();
}
}
}
}
(2)、服务器进行监听
/**
* @brief 开启tcp服务器槽
* @author LiDonglin
* @date 20240731
*/
void Widget::on_pushButton_clicked()
{
if (ui->comboBox->currentIndex() != -1) {
ui->pushButton->setEnabled(false);
ui->pushButton_3->setEnabled(true);
ui->comboBox->setEnabled(false);
ui->spinBox->setEnabled(false);
tcpServer->listen(IPList[ui->comboBox->currentIndex()],ui->spinBox->value());
}
}
(3)、客户端向服务器监听的IP地址及端口发送链接请求
/**
* @brief 连接服务器槽
* @author LiDonglin
* @date 20240801
*/
void Widget::on_pushButton_clicked()
{
if (tcpSocket->state() != tcpSocket->ConnectedState)
{
tcpSocket->connectToHost(IPlist[ui->comboBox->currentIndex()]
,ui->spinBox->value());
}
}
(4)、服务器收到客户端链接请求在子线程中创建对应套接字进行连接通信
/**
* @brief 重写服务器处理新连接虚函数函数
* @param 新连接的本地套接字描述符
* @author LiDonglin
* @date 20240731
*/
void incomingConnection(qintptr handle) override
{
/* tcp客户端套接字处理子线程 */
static long long threadID = 0;
ServerConnectionThread* thread = new ServerConnectionThread(this,++threadID,handle);
connect(thread, &ServerConnectionThread::finished,thread, &ServerConnectionThread::deleteLater);
emit NewConnection(thread);
/* 创建并显示对话框 */
ClientDialog * clientDialog = new ClientDialog;
clientDialog->show();
connect(thread, &ServerConnectionThread::finished,clientDialog, &ClientDialog::deleteLater);
connect(thread, &ServerConnectionThread::outTitle,clientDialog, &ClientDialog::setTitle);
connect(thread, &ServerConnectionThread::outReadOfclient,clientDialog, &ClientDialog::receiveMessages);
connect(clientDialog, &ClientDialog::stop,thread, &ServerConnectionThread::quit);
connect(clientDialog, &ClientDialog::writeDate,thread, &ServerConnectionThread::sendMessages);
/* 主窗口控制显示线程 */
emit newConnectOfClient(1);
connect(thread, &ServerConnectionThread::NewConnectionOff,this, [&](){
emit newConnectOfClient(0);
});
/* 开启线程 */
thread->start();
}
(5)、服务器的tcp套接字进行读写及其联接状态改变处理
注:write方法是异步,是先将要发送的数据放到数据缓冲区,最后同一发送,所以不能发到子线程,即不可以用Qt::DirectConnection这种发送方式。
//客户端套接字对象接收到数据将发送给对话框
connect(clientSocket, &QTcpSocket::readyRead,clientSocket,[&]()
{
emit outReadOfclient(clientSocket->readAll());
},Qt::DirectConnection);
//客户端套接字对象将对话框数据发送给客户端
connect(this, &ServerConnectionThread::outWriteOfclient,clientSocket,[&](const QString& dt)
{
if(NULL == clientSocket) return;
if(clientSocket->state() == clientSocket->ConnectedState)
{
clientSocket->write(dt.toUtf8().data());
}
});
//客户端断开连接,结束线程
connect(clientSocket, &QTcpSocket::stateChanged,clientSocket,[&](QAbstractSocket::SocketState state)
{
if(state == QAbstractSocket::UnconnectedState)
{
quit();
}
},Qt::DirectConnection);
(6)、客户端的tcp套接字进行读写及其联接状态改变处理
/**
* @brief 接收信息处理槽
* @author LiDonglin
* @date 20240801
*/
void Widget::receiveMessages()
{
QString messages = tcpSocket->readAll();
ui->textBrowser->append("服务端:" + messages);
}
/**
* @brief 发送数据槽
* @author LiDonglin
* @date 20240801
*/
void Widget::on_pushButton_4_clicked()
{
if(NULL == tcpSocket)
return;
if(tcpSocket->state() == tcpSocket->ConnectedState)
{
QString outDate = ui->textEdit->toPlainText();
ui->textBrowser->append("客户端:" + outDate);
tcpSocket->write(outDate.toUtf8().data());
}
}
/**
* @brief 处理套接字状态改变槽
* @param 套接字当前状态
* @author LiDonglin
* @date 20240730
*/
void Widget::socketStateChange(QAbstractSocket::SocketState state)
{
switch (state)
{
case QAbstractSocket::UnconnectedState:
ui->textBrowser->append("scoket 状态:UnconnectedState");
break;
case QAbstractSocket::ConnectedState:
ui->textBrowser->append("scoket 状态:ConnectedState");
break;
case QAbstractSocket::ConnectingState:
ui->textBrowser->append("scoket 状态:ConnectingState");
break;
case QAbstractSocket::HostLookupState:
ui->textBrowser->append("scoket 状态:HostLookupState");
break;
case QAbstractSocket::ClosingState:
ui->textBrowser->append("scoket 状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
ui->textBrowser->append("scoket 状态:ListeningState");
break;
case QAbstractSocket::BoundState:
ui->textBrowser->append("scoket 状态:BoundState");
break;
}
}
2.视频展示
TCP多线程服务器及客户端展示
三、多功能UDP通信助手
1、单播
单播用于两个主机之间的端对端通信,需要知道对方的 IP 地址与端口。
(1)、实现过程关键代码
①udp套接字绑定目标端口
/**
* @brief udp套接字绑定端口
* @author LiDonglin
* @date 20240812
*/
void MainWindow::on_pushButton_clicked()
{
/* 获取目标端口 */
quint16 port = ui->spinBox_2->value();
/* 绑定端口需要在 socket 的状态为 UnconnectedState */
if (udpSocket->state() != QAbstractSocket::UnconnectedState)
{
udpSocket->close();
}
if (udpSocket->bind(port))
{
ui->textBrowser->append("<font color='green'>已经成功绑定端口:"+ QString::number(port));
/* 控件有效性控制 */
ui->pushButton->setEnabled(false);
ui->pushButton_2->setEnabled(true);
ui->comboBox->setEnabled(false);
ui->spinBox->setEnabled(false);
ui->spinBox_2->setEnabled(false);
}
}
②udp套接字往目标ip地址发送数据
/**
* @brief 单播数据
* @author LiDonglin
* @date 20240812
*/
void MainWindow::on_pushButton_3_clicked()
{
QByteArray data = ui->textEdit->toPlainText().toUtf8();
QHostAddress peerAddr = IPlist[ui->comboBox->currentIndex()];
quint16 peerPort = ui->spinBox->value();
qint64 l = udpSocket->writeDatagram(data, peerAddr, peerPort);
if(l == data.size())
{
ui->textBrowser->append("<font color='green'>单播发送:<font color='white'>"+ ui->textEdit->toPlainText());
}
else
{
ui->textBrowser->append("<font color='red'>单播发送失败");
}
}
③接收数据处理
/**
* @brief udp套接字接收信息显示
* @author LiDonglin
* @date 20240812
*/
void MainWindow::receiveMessages()
{
QHostAddress peerAddr;
quint16 peerPort;
/* 如果有数据已经准备好 */
while (udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
/* 读取数据,并获取发送方的 IP 地址和端口 */
udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
QString str = datagram.data();
ui->textBrowser->append("<font color='green'>接收来自"+ peerAddr.toString()+ ":"+
QString::number(peerPort)+"<font color='yellow'>"+ str);
}
}
④状态改变处理
/**
* @brief udp连接状态改变
* @author LiDonglin
* @date 20240812
*/
void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{
switch (state) {
case QAbstractSocket::UnconnectedState:
ui->textBrowser->append("<font color='red'>scoket 状态:UnconnectedState");
break;
case QAbstractSocket::ConnectedState:
ui->textBrowser->append("<font color='green'>scoket 状态:ConnectedState");
break;
case QAbstractSocket::ConnectingState:
ui->textBrowser->append("<font color='green'>scoket 状态:ConnectingState");
break;
case QAbstractSocket::HostLookupState:
ui->textBrowser->append("<font color='green'>scoket 状态:HostLookupState");
break;
case QAbstractSocket::ClosingState:
ui->textBrowser->append("<font color='green'>scoket 状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
ui->textBrowser->append("<font color='green'>scoket 状态:ListeningState");
break;
case QAbstractSocket::BoundState:
ui->textBrowser->append("<font color='green'>scoket 状态:BoundState");
break;
default:
break;
}
}
(1)、视频展示
UDP单播展示
2、广播
使用广播地址 255.255.255.255,将消息发送到在同一广播(也就是局域网内同一网段)网络上的每个主机。
(1)、实现过程关键代码
①广播数据
/**
* @brief 广播消息
* @author LiDonglin
* @date 20240812
*/
void MainWindow::on_pushButton_4_clicked()
{
QByteArray data = ui->textEdit->toPlainText().toUtf8();
QHostAddress peerAddr = QHostAddress::Broadcast;
quint16 peerPort = ui->spinBox->value();
qint64 l = udpSocket->writeDatagram(data, peerAddr, peerPort);
if(l == data.size())
{
ui->textBrowser->append("<font color='green'>广播发送:<font color='white'>"+ ui->textEdit->toPlainText());
}
else
{
ui->textBrowser->append("<font color='red'>广播发送失败");
}
}
(2)、视频展示
UDP广播展示
3、组播
239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效。
(1)、实现过程关键代码
①加入组播
/**
* @brief 加入组播
* @author LiDonglin
* @date 20240812
*/
void MainWindow::on_pushButton_7_clicked()
{
quint16 port = ui->spinBox_3->value();
QString strOfIp = "239." + QString::number(ui->spinBox_4->value()) + "." + QString::number(ui->spinBox_5->value()) + "."
+ QString::number(ui->spinBox_6->value());
QHostAddress groupAddr = QHostAddress(strOfIp);
if(udpSocketOfMulticast->state() != QAbstractSocket::UnconnectedState)
{
udpSocketOfMulticast->close();
}
if (udpSocketOfMulticast->bind(QHostAddress::AnyIPv4,port, QUdpSocket::ShareAddress))
{
bool ok = udpSocketOfMulticast->joinMulticastGroup(groupAddr);
if(ok)
{
ui->textBrowser->append("<font color='green'>加入组播成功");
ui->textBrowser->append("<font color='green'>组播地址 IP:" + strOfIp);
ui->textBrowser->append("<font color='green'>绑定端口:" + QString::number(port));
ui->spinBox_3->setEnabled(false);
ui->spinBox_4->setEnabled(false);
ui->spinBox_5->setEnabled(false);
ui->spinBox_6->setEnabled(false);
ui->pushButton_7->setEnabled(false);
ui->pushButton_8->setEnabled(true);
}
else
{
ui->textBrowser->append("<font color='red'>加入组播失败");
}
}
}
②目标端口发送数据
/**
* @brief udp套接字组播发送显示数据
* @author LiDonglin
* @date 20240812
*/
void MainWindow::on_pushButton_10_clicked()
{
QByteArray data = ui->textEdit->toPlainText().toUtf8();
QString strOfIp = "239." + QString::number(ui->spinBox_4->value()) + "." + QString::number(ui->spinBox_5->value()) + "."
+ QString::number(ui->spinBox_6->value());
QHostAddress peerAddr = QHostAddress(strOfIp);
quint16 peerPort = ui->spinBox_7->value();
qint64 l = udpSocketOfMulticast->writeDatagram(data, peerAddr, peerPort);
if(l == data.size())
{
ui->textBrowser->append("<font color='green'>组播发送:<font color='white'>"+ ui->textEdit->toPlainText());
}
else
{
ui->textBrowser->append("<font color='red'>组播发送失败");
}
}
(2)、视频展示
UDP组播展示
四、开源说明
gitee链接git@gitee.com:LDLmcu/network-communication-assistant.git
动态库打包发布文件夹:networkApp