【Qt网络编程】Tcp多线程并发服务器和客户端通信
目录
一、编写思路
1、服务器
(1)总体思路widget.c(主线程)
(2)详细流程widget.c(主线程)
(1)总体思路chat_thread.c(处理聊天逻辑线程)
(2)详细流程chat_thread.h(处理聊天逻辑线程)
2、客户端
(1)总体思路widget.c(主线程)
(2)详细思路widget.c(主线程)
(1)总体思路chat_thread.c(处理聊天逻辑线程)
(2)详细流程chat_thread.c(处理聊天逻辑线程)
二、实现效果
1、服务器
2、客户端
完整代码请到指定链接下载:Qt网络编程-Tcp多线程并发服务器和客户端通信: 【Qt网络编程】Tcp多线程并发服务器和客户端通信
一、编写思路
1、服务器
(1)总体思路widget.c
(主线程)
初始化界面
创建窗口、输入框、按钮等基本UI元素。
创建服务器对象
实现
My_tcp_server
并监听客户端连接。处理新客户端连接
当有新客户端连接时,创建新的
Chat_thread
线程来处理通信。绑定信号槽
确保主线程与客户端处理线程间的信号槽连接,使用
Qt::QueuedConnection
处理跨线程通信。处理消息传递
接收和发送消息,并在界面上更新显示。
服务器启动与关闭
通过按钮控制服务器的启动和关闭,管理所有客户端线程的安全退出。
(2)详细流程widget.c
(主线程)
-
创建 Qt 界面及设置窗口属性: 首先通过
ui->setupUi(this);
来初始化用户界面,并设置窗口标题、大小等基本属性。这是 Qt 项目的常见步骤,通过.ui
文件生成的类进行界面管理。Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) // 初始化UI对象 {ui->setupUi(this); // 设置UI界面this->setWindowTitle("--服务器--"); // 设置窗口标题this->resize(1024, 960); // 设置窗口大小 ui->le_ip->setText("127.0.0.1");ui->le_port->setText("9999"); }
-
初始化服务器对象
My_tcp_server
并处理客户端连接:-
创建
tcp_server
对象以处理客户端的连接。 -
使用
connect
函数连接tcp_server
的new_descriptor
信号和匿名槽函数,确保一旦有新客户端连接,便创建一个Chat_thread
来处理该客户端。
this->tcp_server = new My_tcp_server(this); connect(tcp_server, &My_tcp_server::new_descriptor, this, [=](qintptr socketDescriptor){QMessageBox::information(this, "提示", "新的客户端连接!", QMessageBox::Ok, QMessageBox::Information); ui->btn_send->setEnabled(true); // 启用“发送消息”按钮 // 创建新线程处理客户端Chat_thread *chat_thread = new Chat_thread(socketDescriptor);chat_thread->moveToThread(chat_thread); // 将线程和对象绑定到同一线程,防止冲突 thread_list.append(chat_thread);// 启动线程处理客户端通信chat_thread->start(); });
-
-
管理客户端线程
Chat_thread
:-
每当有新客户端连接时,创建一个
Chat_thread
并启动它处理客户端通信。通过moveToThread
将Chat_thread
的执行线程与该对象保持一致,避免跨线程冲突。 -
使用
connect
绑定线程中的信号(如连接断开、接收消息)和主界面槽函数,确保客户端状态能够正确显示。
Chat_thread *chat_thread = new Chat_thread(socketDescriptor); chat_thread->moveToThread(chat_thread); // 将线程与对象绑定在同一线程 thread_list.append(chat_thread); // 连接信号和槽 connect(chat_thread, &Chat_thread::break_connect, this, [=](){ui->te_receive->append(currentTime + "\n【状态】客户端断开连接...");ui->btn_send->setEnabled(false); // 禁用“发送消息”按钮 }); connect(chat_thread, &Chat_thread::recv_info, this, [=](QString data){currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");ui->te_receive->append(currentTime + "form client\n 【数据】 " + data); // 在文本框中显示消息 }); chat_thread->start(); // 启动线程
-
-
处理启动和关闭服务器的按钮事件:
-
on_btn_connect_clicked()
处理连接按钮点击事件,启动或关闭服务器。 -
启动时,检查 IP 地址和端口的有效性,成功后开始监听客户端连接。
-
关闭服务器时,停止监听,并确保所有已连接客户端线程安全退出。
void Widget::on_btn_connect_clicked() {if (!is_server_running){// 启动服务器QString ip_address = ui->le_ip->text().trimmed();QString port_text = ui->le_port->text().trimmed();if (!tcp_server->listen(QHostAddress(ip_address), port_text.toUInt())){QMessageBox::warning(this, "warning", "服务器监听失败");return;}is_server_running = true;ui->btn_connect->setText("关闭服务器");ui->te_receive->append(currentTime + "\n【状态】服务器开始监听...");}else{// 停止服务器并关闭所有客户端线程tcp_server->close();for (Chat_thread *thread : qAsConst(thread_list)){thread->exit();thread->wait();thread->deleteLater();}thread_list.clear();is_server_running = false;ui->btn_connect->setText("创建服务器");ui->te_receive->append(currentTime + "\n【状态】服务器已停止监听...");}}
-
-
处理发送消息按钮的点击事件:
-
当点击“发送消息”按钮时,触发
send_request
信号,利用信号槽机制将输入的消息发送给客户端。需要确保主线程和子线程的信号槽通信是异步进行的(通过Qt::QueuedConnection
)。
void Widget::on_btn_send_clicked() {QString data = ui->te_send->toPlainText().toUtf8();emit send_request(data); // 发出 send_request 信号 }
-
-
服务器监听客户端的状态和信息传递:
-
服务器通过
recv_info
和send_info
信号接收客户端消息并在界面上显示。 -
在客户端连接成功或断开时,更新界面显示状态。
connect(chat_thread, &Chat_thread::recv_info, this, [=](QString data){currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");ui->te_receive->append(currentTime + "form client\n 【数据】 " + data); // 显示接收的客户端数据 }); connect(chat_thread, &Chat_thread::send_info, this, [=](QString data){currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");ui->te_receive->append(currentTime + "to client\n 【数据】 " + data); // 显示发送给客户端的数据 });
-
(1)总体思路chat_thread.c
(处理聊天逻辑线程)
构造函数
初始化
socketDescriptor
以供后续线程使用。线程启动与套接字初始化
在
run()
函数中创建QTcpSocket
,并关联socketDescriptor
。获取客户端信息
通过
peerAddress()
和peerPort()
获取客户端 IP 地址和端口号,并进行错误处理。信号槽机制连接
将套接字状态、接收数据、错误处理等信号连接到相应的槽函数。
处理连接状态变化
通过
handler_client_changed()
处理客户端的连接或断开,并发出相应的信号。处理接收消息
在
receive_message()
函数中处理客户端发送的消息,并发出信号recv_info
。发送消息
在
send_message()
函数中,检查连接状态并发送消息,发出send_info
信号。错误处理
处理客户端连接中的错误,删除资源并退出线程。
(2)详细流程chat_thread.h
(处理聊天逻辑线程)
-
构造函数初始化:
-
Chat_thread
构造函数接受一个socketDescriptor
参数,并将其存储为类的成员变量,以供run()
函数中使用。注意,QTcpSocket
对象将在run()
函数中创建,以确保在新线程中创建并使用。
Chat_thread::Chat_thread(qintptr socketDescriptor, QObject *parent): QThread{parent}, socketDescriptor(socketDescriptor) {// socketDescriptor 存储为成员变量 }
-
-
线程启动和套接字初始化:
-
在
run()
函数中创建QTcpSocket
对象,并通过setSocketDescriptor()
将套接字描述符与QTcpSocket
关联。这允许线程使用此套接字与客户端通信。 -
如果套接字初始化失败,进行错误处理并返回。
void Chat_thread::run() {// 创建 QTcpSocket 对象,用于处理与客户端的通信this->socket = new QTcpSocket(); // 将套接字描述符与 QTcpSocket 关联if (!socket->setSocketDescriptor(socketDescriptor)){qDebug() << "Error: Failed to get new socketDescriptor.";return;} // 错误处理:检查是否成功获取客户端连接if (socket == nullptr){qDebug() << "Error: Failed to get new client connection.";return; // 如果获取失败,直接返回} }
-
-
获取客户端信息:
-
在成功创建套接字后,获取客户端的 IP 地址和端口号。
-
如果获取失败,进行错误处理并断开连接。
// 获取客户端的IP地址和端口号 QString ip_addr = socket->peerAddress().toString(); quint16 port = socket->peerPort(); // 错误处理:检查是否成功获取IP地址和端口号 if (ip_addr.isEmpty() || port == 0) {qDebug() << "Error: Failed to get client's IP address or port.";socket->disconnectFromHost(); // 断开连接socket->deleteLater(); // 删除客户端套接字对象return; // 如果获取失败,直接返回 }
-
-
信号槽机制的连接:
-
连接套接字的状态改变信号
stateChanged
到槽函数handler_client_changed
,以便监控客户端连接状态的变化。 -
连接
QTcpSocket
的readyRead
信号到receive_message
槽函数,用于处理接收数据。 -
处理套接字错误时,连接
errorOccurred
信号到handle_socket_error
槽函数。
// 处理连接状态变化的槽函数 connect(socket, &QTcpSocket::stateChanged, this, &Chat_thread::handler_client_changed); // 错误处理:处理客户端的异常断开情况 connect(socket, &QTcpSocket::errorOccurred, this, &Chat_thread::handle_socket_error); // 处理接收数据的槽函数 connect(socket, &QTcpSocket::readyRead, this, &Chat_thread::receive_message);
-
-
处理客户端连接状态变化:
-
在
handler_client_changed()
槽函数中,根据客户端的连接状态(如断开、已连接)做相应处理并发出信号,通知其他部分更新状态。
void Chat_thread::handler_client_changed(QAbstractSocket::SocketState socket_state) {socket = (QTcpSocket*)sender(); // 获取发信的客户端套接字if(!socket) return; switch (socket_state){case QAbstractSocket::UnconnectedState: // 客户端断开连接emit break_connect();break; case QAbstractSocket::ConnectedState: // 客户端已连接emit complete_connect();break; default:break;} }
-
-
接收消息的处理:
-
在
receive_message()
槽函数中,通过socket->readAll()
读取客户端发送的所有数据,并发出信号recv_info
通知上层处理。
void Chat_thread::receive_message() {if (socket){QString data = socket->readAll(); // 读取客户端发送的所有数据emit recv_info(data); // 发出信号,通知收到消息} }
-
-
发送消息:
-
在
send_message()
函数中,检查客户端是否处于连接状态,如果是则发送消息,否则输出警告信息。 -
发送完成后,发出
send_info
信号。
void Chat_thread::send_message(QString data) {if (socket->state() == QAbstractSocket::ConnectedState){socket->write(data.toUtf8()); // 发送数据}else{qDebug() << "warning: 客户端未连接,无法发送消息"; // 输出警告} emit send_info(data); // 发出信号,通知发送消息 }
-
-
错误处理:
-
在
handle_socket_error()
函数中处理QTcpSocket
的错误。如果出现错误,打印错误信息,并退出线程。 -
删除套接字对象并退出线程事件循环。
void Chat_thread::handle_socket_error(QAbstractSocket::SocketError socketError) {qDebug() << "Client connection error, error code: " << socketError; this->exit(); // 退出线程this->wait(); // 等待线程完全退出socket->deleteLater(); // 删除客户端套接字对象 // 停止线程事件循环quit(); }
-
2、客户端
(1)总体思路widget.c
(主线程)
初始化界面
设置窗口属性并初始化用户输入的默认值。
创建线程和通信任务对象
实现异步通信,使用
QThread
和自定义Chat_thread
处理服务器交互。信号槽机制的建立
连接 UI 和工作线程之间的信号槽,确保各操作异步处理。
线程管理
在析构函数中确保线程安全退出,释放资源。
处理连接与断开
通过按钮触发连接和断开操作,并更新 UI 显示。
消息传递与显示
处理消息的发送与接收,并在 UI 界面上更新显示结果。
(2)详细思路widget.c
(主线程)
-
初始化界面:
-
使用
ui->setupUi(this)
初始化用户界面,并设置窗口标题和窗口大小。 -
初始化 IP 地址和端口号的默认值。
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), is_connected(false) // 初始化连接状态为未连接 {ui->setupUi(this); // 设置UI界面this->setWindowTitle("-客户端-"); // 设置窗口标题this->resize(1024, 960); // 设置窗口大小 ui->le_ip->setText("127.0.0.1"); // 设置默认IP地址ui->le_port->setText("8888"); // 设置默认端口号 }
-
-
创建线程和通信任务对象:
-
创建
QThread
对象以进行异步通信任务。 -
创建
Chat_thread
对象负责与服务器进行通信操作。 -
使用
moveToThread
将通信任务对象移到新的线程中执行,并启动该线程。
// 创建线程对象 thread = new QThread; // 创建任务对象,负责与服务器的通信 Chat_thread *worker = new Chat_thread; worker->moveToThread(thread); // 将任务对象移至线程 thread->start(); // 启动工作线程
-
-
信号槽机制的建立:
-
使用信号槽连接 UI 和工作线程之间的交互。例如,连接服务器、发送消息、断开连接等操作通过信号槽机制进行。
-
信号从 UI 线程发出,工作线程的槽函数接收信号并执行相关操作。
// 信号槽连接:从UI线程发出连接信号,worker线程接收并执行连接操作 connect(this, &Widget::connect_server, worker, &Chat_thread::start_connected); connect(this, &Widget::send_info, worker, &Chat_thread::start_send); connect(this, &Widget::quit_connect, worker, &Chat_thread::break_connected); // 连接断开信号槽,worker线程通知UI线程更新UI connect(worker, &Chat_thread::connect_cancel, this, &Widget::submit_connect_cancel); connect(worker, &Chat_thread::connected, this, &Widget::submit_connect_info); connect(worker, &Chat_thread::transfer_recv_info, this, &Widget::submit_recv_info);
-
-
管理线程的生命周期:
-
在析构函数中,确保工作线程在窗口关闭时被正确停止,并释放相关资源。
-
如果线程正在运行,需要先请求线程退出,然后等待其完全退出后再删除。
Widget::~Widget() {if (thread->isRunning()){thread->quit(); // 请求线程退出thread->wait(); // 等待线程结束}delete worker; // 删除任务对象delete thread; // 删除线程对象delete ui; // 删除UI对象 }
-
-
处理连接成功或断开连接的槽函数:
-
当客户端成功连接到服务器时,工作线程发出
connected
信号,UI 界面通过槽函数submit_connect_info()
来更新显示状态,并启用“发送消息”按钮。 -
断开连接时,UI 界面通过槽函数
submit_connect_cancel()
来禁用“发送消息”按钮,并更新状态显示。
// 连接成功时的槽函数,更新UI显示信息 void Widget::submit_connect_info() {currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");ui->te_receive->append(currentTime + "\n【状态】已成功连接到服务器");ui->btn_connect->setText("断开服务器");ui->btn_send->setEnabled(true); // 启用发送按钮is_connected = true; } // 断开连接时的槽函数,更新UI显示信息 void Widget::submit_connect_cancel() {is_connected = false; }
-
-
处理消息的发送与接收:
-
当用户点击“发送消息”按钮时,获取文本框中的消息,发出
send_info
信号,将消息发送到服务器。 -
当从服务器接收到消息时,工作线程发出
transfer_recv_info
信号,UI 界面更新显示接收到的消息。
// 当用户点击发送按钮时,读取输入框中的内容并发送给服务器 void Widget::on_btn_send_clicked() {currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");QString message = ui->te_send->toPlainText().toUtf8(); // 获取用户输入的消息ui->te_receive->append(currentTime + " to server\n 【数据】" + message + "\n");emit send_info(message); // 发出信号,通知工作线程发送消息 } // 当接收到服务器发送的消息时,更新UI显示接收到的消息 void Widget::submit_recv_info(QString message) {currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");ui->te_receive->append(currentTime + " form server\n 【数据】" + message + "\n"); // 显示服务器的消息 }
-
-
处理连接与断开的按钮事件:
-
当点击“连接”按钮时,获取 IP 地址和端口号,检查输入的有效性后发出
connect_server
信号,通知工作线程与服务器建立连接。 -
当点击“断开服务器”按钮时,发出
quit_connect
信号,通知工作线程断开连接。
// 当用户点击"连接"按钮时触发该槽函数 void Widget::on_btn_connect_clicked() {if (!is_connected){QString ip_address = ui->le_ip->text().trimmed();QString port_text = ui->le_port->text().trimmed(); QHostAddress address;if (!address.setAddress(ip_address)) // 检查IP地址的有效性{QMessageBox::warning(this, "warning", "无效的IP地址,请重新输入!");return;} bool ok;unsigned int port = port_text.toUInt(&ok);if (!ok || port == 0 || port > 65535) // 检查端口号的有效性{QMessageBox::warning(this, "warning", "无效的端口号,请输入1到65535之间的数值!");return;} emit connect_server(ip_address, port); // 发出连接服务器的信号}else{currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");ui->te_receive->append(currentTime + "\n【状态】已断开与服务器的连接");ui->btn_send->setEnabled(false); // 禁用发送按钮is_connected = false;emit quit_connect(); // 发出断开连接的信号} }
-
(1)总体思路chat_thread.c
(处理聊天逻辑线程)
构造函数
初始化
Chat_thread
对象。接收消息
通过
readyRead
信号槽接收服务器发送的数据,并将其转发给主线程。处理连接状态变化
监控与服务器的连接状态,并打印调试信息。
断开连接
关闭套接字连接并释放资源,发出连接断开信号。
启动连接
通过指定的 IP 和端口号连接服务器,并处理连接成功、失败、断开、接收数据等事件。
发送消息
检查连接状态并发送消息。如果未连接,则发出未连接信号。
(2)详细流程chat_thread.c
(处理聊天逻辑线程)
-
构造函数:
-
构造函数
Chat_thread::Chat_thread(QObject *parent)
初始化Chat_thread
对象。在这个阶段不需要任何复杂的逻辑,主要是确保对象正常创建。
Chat_thread::Chat_thread(QObject *parent): QObject{parent} {}
-
-
接收消息处理:
-
receive_message()
是一个槽函数,用于接收从服务器发送的数据。当QTcpSocket
对象有数据可读取时,信号readyRead
会被触发,调用此槽函数。读取数据后,通过transfer_recv_info
信号将接收到的消息发送出去。
void Chat_thread::receive_message() {QString message = socket->readAll(); // 从服务器读取数据emit transfer_recv_info(message); // 发出信号,通知接收到的数据 }
-
-
处理连接状态变化:
-
state_changed()
函数是一个槽函数,用于处理客户端与服务器的连接状态变化。根据不同的QAbstractSocket::SocketState
枚举值,打印调试信息并处理相应状态的变化。
void Chat_thread::state_changed(QAbstractSocket::SocketState socketstate) {QString stateStr; // 用于保存状态的字符串switch (socketstate){case QAbstractSocket::UnconnectedState:qDebug()<< "\n【状态】与服务器断开连接...";stateStr = "UnconnectedState";break; case QAbstractSocket::ConnectedState:stateStr = "ConnectedState";qDebug()<< "【状态】与服务器建立连接...";break; case QAbstractSocket::HostLookupState:stateStr = "HostLookupState";qDebug()<< "【状态】正在查找主机...";break; case QAbstractSocket::ConnectingState:stateStr = "ConnectingState";qDebug()<< "【状态】正在连接服务器...";break; case QAbstractSocket::ClosingState:stateStr = "ClosingState";qDebug()<< "【状态】正在关闭连接...";break; default:stateStr = "UnknownState";qDebug()<< "未知的错误, 当前状态: " + stateStr;break;} }
-
-
断开连接处理:
-
break_connected()
用于处理断开与服务器的连接。当套接字连接断开时,关闭并释放资源,并发出connect_cancel
信号通知主线程。
void Chat_thread::break_connected() {socket->close(); // 关闭套接字socket->deleteLater(); // 延迟删除套接字,释放资源emit connect_cancel(); // 发出连接断开信号 }
-
-
开始连接服务器:
-
start_connected()
用于发起连接服务器的请求。创建QTcpSocket
对象并尝试连接到指定的 IP 和端口。连接成功、失败、断开、接收数据等事件都会通过信号槽机制进行处理。
void Chat_thread::start_connected(QString IP, unsigned short PORT) {socket = new QTcpSocket; // 创建套接字对象 socket->connectToHost(QHostAddress(IP), PORT); // 连接到服务器 // 连接成功时,发送 connected 信号通知主线程上传消息connect(socket, &QTcpSocket::connected, this, &Chat_thread::connected); // 连接失败时处理connect(socket, &QTcpSocket::errorOccurred, this, [=](QAbstractSocket::SocketError socketError){qDebug() << "连接失败";QMessageBox::critical(nullptr, "连接失败", "连接失败,错误代码:" + QString::number(socketError));}); // 连接断开时处理connect(socket, &QTcpSocket::disconnected, this, &Chat_thread::break_connected); // 监听数据接收connect(socket, &QTcpSocket::readyRead, this, &Chat_thread::receive_message); }
-
-
发送消息:
-
start_send()
用于发送消息到服务器。首先检查套接字是否处于连接状态,如果已连接,则发送消息。如果未连接,则发出not_connected
信号。
void Chat_thread::start_send(QString message) {if (socket && socket->state() == QAbstractSocket::ConnectedState){socket->write(message.toUtf8()); // 发送消息}else{emit not_connected(); // 如果未连接,发出未连接信号} }
-
二、实现效果
1、服务器
2、客户端
相关文章:

【Qt网络编程】Tcp多线程并发服务器和客户端通信
目录 一、编写思路 1、服务器 (1)总体思路widget.c(主线程) (2)详细流程widget.c(主线程) (1)总体思路chat_thread.c(处理聊天逻辑线程&…...

SkyWalking 简介
SkyWalking是什么 skywalking是一个国产开源框架,2015年由吴晟开源 , 2017年加入Apache孵化器。skywalking是分布式系统的应用 程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。它是一款优秀的 APM(Application Performance Manag…...

语音合成(自然、非自然)
1.环境 Python 3.10.14 2.完成代码 2.1简陋版 import pyttsx3# 初始化tts引擎 engine pyttsx3.init()# 设置语音速度 rate engine.getProperty(rate) engine.setProperty(rate, rate - 50)# 设置语音音量 volume engine.getProperty(volume) engine.setProperty(volume, …...

redis简单使用与安装
redis redis 是什么 Redis 是一个开源的,使用 C 语言编写的,支持网络交互的,内存中的Key-Value 数据结构存储系统,支持多种语言,它可以用作数据库、缓存和消息中间件。 一、存储系统特性 内存存储与持久化 Redis 主要将数据存储在内存中,这…...

封装 WBXpopup 组件
这是Popup组件基于微博小程序,需要改变标签,以及一写方法 支持四个方向抽屉,以及中间弹出功能 // 用法 <template><wbx-view style"height: 100vh;"><!-- 对话框组件 --><wbx-view><wbx-text click&quo…...

【OJ刷题】双指针问题6
这里是阿川的博客,祝您变得更强 ✨ 个人主页:在线OJ的阿川 💖文章专栏:OJ刷题入门到进阶 🌏代码仓库: 写在开头 现在您看到的是我的结论或想法,但在这背后凝结了大量的思考、经验和讨论 目录 1…...

详解:Tensorflow、Pytorch、Keras(搭建自己的深度学习网络)
这是一个专门对Tensorflow、Pytorch、Keras三个主流DL框架的一个详解和对比分析 一、何为深度学习框架? 你可以理解为一个工具帮你构建一个深度学习网络,调用里面的各种方法就能自行构建任意层,diy你想要的DNN,而且任意指定学习…...

【CSS in Depth 2 精译_035】5.5 Grid 网格布局中的子网格布局(全新内容)
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第一章 层叠、优先级与继承(已完结) 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位(已完结) 2.1 相对…...

Java是怎么处理死锁的
文章目录 避免死锁避免嵌套锁资源进行排序超时锁 检测死锁通过Java提供的API检查死锁情况jStack监控工具 Java 本身没有内置的机制自动处理死锁问题,但可以采取一些策略和技术来检测和避免死锁。 避免死锁 避免嵌套锁 尽可能减少嵌套锁操作,避免在一个…...

Effective Java 学习笔记 方法签名设计
目录 谨慎选择方法名称 不要过于追求提供便利的快捷方法 避免过长的参数列表 对于参数类型优先使用接口而不是类 对于boolean参数,要优先使用两个元素的枚举类型 本文接续前一篇文章聚焦Java方法签名的设计,方法签名包括了方法的输入和输出参数以及…...

毛利超70%、超70+智驾客户,这家AI数据训练服务商刚刚止亏
AI训练数据服务第一股海天瑞声终于迎来了“曙光”。 日前,海天瑞声发布2024年半年报显示,上半年其实现营收9242.63万,同比增长24.13%;实现净利润41.64 万元,不过同比去年同期的亏损1724.14万元,扭亏为盈。…...

本地部署高颜值某抑云音乐播放器Splayer并实现无公网IP远程听歌
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

图像压缩编码(4)--H.26x系列视频压缩编码_2
目录 H.261 视频编码标准 H.261的编码与解码 1) 帧内/帧间编码 2)运动补偿 3)量化 4)环路滤波器 5)缓存器 压缩数据的分层 数据复用结构 H.264的编码与解码 H.261 视频编码标准 实际应用时,要求有…...

JS渲染锻炼输入表单
前言 上篇文章为大家展现了好看的信息窗口,接下来我们跟着流程图看下一步 之前我们的带点击事件已经添加完毕,下一步就是当用户点击的时候,渲染锻炼形式,当然这是一个标签,可以提供给用户输入锻炼形式 实例 ● 我…...

proteus仿真学习(1)
一,创建工程 一般选择默认模式,不配置pcb文件 可以选用芯片型号也可以不选 不选则从零开始布局,没有初始最小系统。选用则有初始最小系统以及基础的main函数 本次学习使用从零开始,不配置固件 二,上手软件 1.在元件…...

决策树+随机森林模型实现足球大小球让球预测软件
文章目录 前言一、决策树是什么?二、数据收集与整理1.数据收集2.数据清洗3.特征选择 三、决策树构建3.1绘制训练数据图像3.2 训练决策树模型3.3 依据模型绘制决策树的决策边界3.4 树模型可视化 四、模型预测五、随机森林模型总结 前言 之前搞足球数据分析的时候&…...

31省市农业地图大数据
1.北京市 谷类作物种植结构(万亩) 农作物种植结构(万亩) 2.天津市 谷类作物种植结构(万亩) 农作物种植结构(万亩) 3.黑龙江省 谷类作物种植结构(万亩) 农作物…...

http请求包含什么
HTTP请求通常包含以下几个主要部分: 请求行(Request Line): 包含请求方法(如 GET、POST、PUT、DELETE 等)、请求的目标 URI 和 HTTP 版本。例如:GET /index.html HTTP/1.1 请求头部(…...

【基础算法总结】模拟篇
目录 一,算法介绍二,算法原理和代码实现1576.替换所有的问号495.提莫攻击6.Z字形变换38.外观数列1419.数青蛙 三,算法总结 一,算法介绍 模拟算法本质就是"依葫芦画瓢",就是在题目中已经告诉了我们该如何操作…...

《深度学习》PyTorch 手写数字识别 案例解析及实现 <下>
目录 一、回顾神经网络框架 1、单层神经网络 2、多层神经网络 二、手写数字识别 1、续接上节课代码,如下所示 2、建立神经网络模型 输出结果: 3、设置训练集 4、设置测试集 5、创建损失函数、优化器 参数解析: 1)para…...

【笔记】材料分析测试:晶体学
晶体与晶体结构Crystal and Crystal Structure 1.晶体主要特征 固态物质可以分为晶态和非晶态两大类,分别称为晶体和非晶体。 晶体和非晶体在微观结构上的区别在于是否具有长程有序。 晶体(长程有序)非晶(短程有序)…...

飞塔Fortigate7.4.4的DNS劫持功能
基础网络配置、上网策略、与Server的VIP配置(略)。 在FortiGate上配置DNS Translation,将DNS请求结果为202.103.12.2的DNS响应报文中的IP地址修改为Server的内网IP 10.10.2.100。 config firewall dnstranslationedit 1set src 2.13.12.2set…...

Unity 设计模式 之 行为型模式 -【状态模式】【观察者模式】【备忘录模式】
Unity 设计模式 之 行为型模式 -【状态模式】【观察者模式】【备忘录模式】 目录 Unity 设计模式 之 行为型模式 -【状态模式】【观察者模式】【备忘录模式】 一、简单介绍 二、状态模式(State Pattern) 1、什么时候使用状态模式 2、使用状态模式的…...

【RabbitMQ】RabbitMQ 的概念以及使用RabbitMQ编写生产者消费者代码
目录 1. RabbitMQ 核心概念 1.1生产者和消费者 1.2 Connection和Channel 1.3 Virtual host 1.4 Queue 1.5 Exchange 1.6 RabbitMO工作流程 2. AMQP 3.RabbitMO快速入门 3.1.引入依赖 3.2.编写生产者代码 3.3.编写消费者代码 4.源码 1. RabbitMQ 核心概念 在安装…...

openmv与stm32通信
控制小车视觉循迹使用 OpenMV 往往是不够的。一般使用 OpenMV 对图像进行处理,将处理过后的数据使用串口发送给STM32,使用STM32控制小车行驶。本文主要讲解 OpenMV 模块与 STM32 间的串口通信以及两种循迹方案,分别是划分检测区域和线性回归。…...

C++ STL全面解析:六大核心组件之一----序列式容器(vector和List)(STL进阶学习)
目录 序列式容器 Vector vector概述 vector的迭代器 vector的数据结构 vector的构造和内存管理 vector的元素操作 List List概述 List的设计结构 List的迭代器 List的数据结构 List的内存构造 List的元素操作 C标准模板库(STL)是一组高效的…...

【c数据结构】OJ练习篇 帮你更深层次理解链表!(相交链表、相交链表、环形链表、环形链表之寻找环形入口点、判断链表是否是回文结构、 随机链表的复制)
目录 一. 相交链表 二. 环形链表 三. 环形链表之寻找环形入口点 四. 判断链表是否是回文结构 五. 随机链表的复制 一. 相交链表 最简单粗暴的思路,遍历两个链表,分别寻找是否有相同的对应的结点。 我们对两个链表的每个对应的节点进行判断比较&…...

微软开源GraphRAG的使用教程(最全,非常详细)
GraphRAG的介绍 目前微软已经开源了GraphRAG的完整项目代码。对于某一些LLM的下游任务则可以使用GraphRAG去增强自己业务的RAG的表现。项目给出了两种使用方式: 在打包好的项目状态下运行,可进行尝试使用。在源码基础上运行,适合为了下游任…...

使用Refine构建项目(1)初始化项目
要初始化一个空的Refine项目,你可以使用Refine提供的CLI工具create-refine-app。以下是初始化步骤: 使用npx命令: 在命令行中运行以下命令来创建一个新的Refine项目: npx create-refine-applatest my-refine-project这将引导你通过…...

【Docker】安装及使用
1. 安装Docker Desktop Docker Desktop是官方提供的桌面版Docker客户端,在Mac上使用Docker需要安装这个工具。 访问 Docker官方页面 并下载Docker Desktop for Mac。打开下载的.dmg文件,并拖动Docker图标到应用程序文件夹。安装完成后,打开…...