【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(主线程)

  1. 初始化界面

    创建窗口、输入框、按钮等基本UI元素。

  2. 创建服务器对象

    实现 My_tcp_server 并监听客户端连接。

  3. 处理新客户端连接

    当有新客户端连接时,创建新的 Chat_thread 线程来处理通信。

  4. 绑定信号槽

    确保主线程与客户端处理线程间的信号槽连接,使用 Qt::QueuedConnection 处理跨线程通信。

  5. 处理消息传递

    接收和发送消息,并在界面上更新显示。

  6. 服务器启动与关闭

    通过按钮控制服务器的启动和关闭,管理所有客户端线程的安全退出。

(2)详细流程widget.c(主线程)

  1. 创建 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");
    }

  2. 初始化服务器对象 My_tcp_server 并处理客户端连接

    • 创建 tcp_server 对象以处理客户端的连接。

    • 使用 connect 函数连接 tcp_servernew_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();
    });
  3. 管理客户端线程 Chat_thread

    • 每当有新客户端连接时,创建一个 Chat_thread 并启动它处理客户端通信。通过 moveToThreadChat_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();  // 启动线程
  4. 处理启动和关闭服务器的按钮事件

    • 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【状态】服务器已停止监听...");}}
  1. 处理发送消息按钮的点击事件

    • 当点击“发送消息”按钮时,触发 send_request 信号,利用信号槽机制将输入的消息发送给客户端。需要确保主线程和子线程的信号槽通信是异步进行的(通过 Qt::QueuedConnection)。

    void Widget::on_btn_send_clicked()
    {QString data = ui->te_send->toPlainText().toUtf8();emit send_request(data);  // 发出 send_request 信号
    }

  2. 服务器监听客户端的状态和信息传递

    • 服务器通过 recv_infosend_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(处理聊天逻辑线程)

  1. 构造函数

    初始化 socketDescriptor 以供后续线程使用。

  2. 线程启动与套接字初始化

    run() 函数中创建 QTcpSocket,并关联 socketDescriptor

  3. 获取客户端信息

    通过 peerAddress()peerPort() 获取客户端 IP 地址和端口号,并进行错误处理。

  4. 信号槽机制连接

    将套接字状态、接收数据、错误处理等信号连接到相应的槽函数。

  5. 处理连接状态变化

    通过 handler_client_changed() 处理客户端的连接或断开,并发出相应的信号。

  6. 处理接收消息

    receive_message() 函数中处理客户端发送的消息,并发出信号 recv_info

  7. 发送消息

    send_message() 函数中,检查连接状态并发送消息,发出 send_info 信号。

  8. 错误处理

    处理客户端连接中的错误,删除资源并退出线程。

(2)详细流程chat_thread.h(处理聊天逻辑线程)

  1. 构造函数初始化

    • Chat_thread 构造函数接受一个 socketDescriptor 参数,并将其存储为类的成员变量,以供 run() 函数中使用。注意,QTcpSocket 对象将在 run() 函数中创建,以确保在新线程中创建并使用。

    Chat_thread::Chat_thread(qintptr socketDescriptor, QObject *parent): QThread{parent}, socketDescriptor(socketDescriptor)
    {// socketDescriptor 存储为成员变量
    }
  2. 线程启动和套接字初始化

    • 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;  // 如果获取失败,直接返回}
    }
  3. 获取客户端信息

    • 在成功创建套接字后,获取客户端的 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;  // 如果获取失败,直接返回
    }
  4. 信号槽机制的连接

    • 连接套接字的状态改变信号 stateChanged 到槽函数 handler_client_changed,以便监控客户端连接状态的变化。

    • 连接 QTcpSocketreadyRead 信号到 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);

  5. 处理客户端连接状态变化

    • 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;}
    }
  6. 接收消息的处理

    • receive_message() 槽函数中,通过 socket->readAll() 读取客户端发送的所有数据,并发出信号 recv_info 通知上层处理。

    void Chat_thread::receive_message()
    {if (socket){QString data = socket->readAll();  // 读取客户端发送的所有数据emit recv_info(data);  // 发出信号,通知收到消息}
    }
  7. 发送消息

    • 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);  // 发出信号,通知发送消息
    }
  8. 错误处理

    • 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(主线程)

  1. 初始化界面

    设置窗口属性并初始化用户输入的默认值。

  2. 创建线程和通信任务对象

    实现异步通信,使用 QThread 和自定义 Chat_thread 处理服务器交互。

  3. 信号槽机制的建立

    连接 UI 和工作线程之间的信号槽,确保各操作异步处理。

  4. 线程管理

    在析构函数中确保线程安全退出,释放资源。

  5. 处理连接与断开

    通过按钮触发连接和断开操作,并更新 UI 显示。

  6. 消息传递与显示

    处理消息的发送与接收,并在 UI 界面上更新显示结果。

(2)详细思路widget.c(主线程)

  1. 初始化界面

    • 使用 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");  // 设置默认端口号
    }

  2. 创建线程和通信任务对象

    • 创建 QThread 对象以进行异步通信任务。

    • 创建 Chat_thread 对象负责与服务器进行通信操作。

    • 使用 moveToThread 将通信任务对象移到新的线程中执行,并启动该线程。

    // 创建线程对象
    thread = new QThread;
    ​
    // 创建任务对象,负责与服务器的通信
    Chat_thread *worker = new Chat_thread;
    ​
    worker->moveToThread(thread);  // 将任务对象移至线程
    thread->start();  // 启动工作线程

  3. 信号槽机制的建立

    • 使用信号槽连接 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);

  4. 管理线程的生命周期

    • 在析构函数中,确保工作线程在窗口关闭时被正确停止,并释放相关资源。

    • 如果线程正在运行,需要先请求线程退出,然后等待其完全退出后再删除。

    Widget::~Widget()
    {if (thread->isRunning()){thread->quit();  // 请求线程退出thread->wait();  // 等待线程结束}delete worker;  // 删除任务对象delete thread;  // 删除线程对象delete ui;  // 删除UI对象
    }

  5. 处理连接成功或断开连接的槽函数

    • 当客户端成功连接到服务器时,工作线程发出 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;
    }

  6. 处理消息的发送与接收

    • 当用户点击“发送消息”按钮时,获取文本框中的消息,发出 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");  // 显示服务器的消息
    }

  7. 处理连接与断开的按钮事件

    • 当点击“连接”按钮时,获取 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(处理聊天逻辑线程)

  1. 构造函数

    初始化 Chat_thread 对象。

  2. 接收消息

    通过 readyRead 信号槽接收服务器发送的数据,并将其转发给主线程。

  3. 处理连接状态变化

    监控与服务器的连接状态,并打印调试信息。

  4. 断开连接

    关闭套接字连接并释放资源,发出连接断开信号。

  5. 启动连接

    通过指定的 IP 和端口号连接服务器,并处理连接成功、失败、断开、接收数据等事件。

  6. 发送消息

    检查连接状态并发送消息。如果未连接,则发出未连接信号。

(2)详细流程chat_thread.c(处理聊天逻辑线程)

  1. 构造函数

    • 构造函数 Chat_thread::Chat_thread(QObject *parent) 初始化 Chat_thread 对象。在这个阶段不需要任何复杂的逻辑,主要是确保对象正常创建。

    Chat_thread::Chat_thread(QObject *parent): QObject{parent}
    {}

  2. 接收消息处理

    • receive_message() 是一个槽函数,用于接收从服务器发送的数据。当 QTcpSocket 对象有数据可读取时,信号 readyRead 会被触发,调用此槽函数。读取数据后,通过 transfer_recv_info 信号将接收到的消息发送出去。

    void Chat_thread::receive_message()
    {QString message = socket->readAll();  // 从服务器读取数据emit transfer_recv_info(message);  // 发出信号,通知接收到的数据
    }

  3. 处理连接状态变化

    • 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;}
    }

  4. 断开连接处理

    • break_connected() 用于处理断开与服务器的连接。当套接字连接断开时,关闭并释放资源,并发出 connect_cancel 信号通知主线程。

    void Chat_thread::break_connected()
    {socket->close();  // 关闭套接字socket->deleteLater();  // 延迟删除套接字,释放资源emit connect_cancel();  // 发出连接断开信号
    }

  5. 开始连接服务器

    • 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);
    }

  6. 发送消息

    • 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、客户端

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/54293.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Elasticsearch系列十四】Elasticsearch

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

MySQL:事务的ACID特性隔离级别脏读、不可重复读、幻读、Next-Key锁——场景复现

目录 1、什么是事务 2、 事务的ACID特性 2.1 事务的隔离性 3、为什么要使用事务&#xff1f; 4、查看支持事务的存储引擎 5、使用事务 5.1 控制事务 5.1.1 开启事务 5.1.2 关闭事务 5.2 开始一个事务&#xff0c;执行修改后回滚 5.3 开始一个事务&#xff0c;执行修…

使用Addressables+SpriteAtlas打包产生冗余

1&#xff09;使用AddressablesSpriteAtlas打包产生冗余 2&#xff09;使用SBP打AssetBundle脚本引用丢失 3&#xff09;Unity构建后处理&#xff08;IPostprocessBuildWithReport等接口&#xff09;抛出异常后&#xff0c;构建不会停止 4&#xff09;Unity 2022.3.0版本使用Oc…

谷歌论文提前揭示o1模型原理:AI大模型竞争或转向硬件

Open AI最强模型o1的护城河已经没有了&#xff1f;仅在OpenAI发布最新推理模型o1几日之后&#xff0c;海外社交平台 Reddit 上有网友发帖称谷歌Deepmind在 8 月发表的一篇论文内容与o1模型原理几乎一致&#xff0c;OpenAI的护城河不复存在。 谷歌DeepMind团队于今年8月6日发布…

我的AI工具箱Tauri版-VideoClipMixingCut视频批量混剪

本教程基于自研的AI工具箱Tauri版进行VideoClipMixingCut视频批量混剪。 VideoClipMixingCut视频批量混剪 是自研AI工具箱Tauri版中的一款强大工具&#xff0c;专为自动化视频批量混剪设计。该模块通过将预设的解说文稿与视频素材进行自动拼接生成混剪视频&#xff0c;适合需要…

数据结构 ——— 算法的时间复杂度

目录 时间复杂度的概念 时间复杂度函数式 大O的渐进表示法的概念 大O的渐进表示法 时间复杂度的概念 在计算机科学中&#xff0c;算法的时间复杂度是一个函数&#xff08;数学上的函数式&#xff09;&#xff0c;它定量描述了该算法的运行时间&#xff0c;一个算法执行所耗…

java工具安装教程

提示:先安装软件打开后关闭&#xff0c;在执行魔法操作 解压后会多个文件夹&#xff0c;从文件夹打开 要魔法哪款软件就打开对应的魔法脚本 比如&#xff1a;idea就运行idea魔法 点击打开 显示下面弹窗则成功&#xff0c;点击确定即可 打开IDEA查看&#xff1a;

Arthas thread(查看当前JVM的线程堆栈信息)

文章目录 二、命令列表2.1 jvm相关命令2.1.2 thread&#xff08;查看当前JVM的线程堆栈信息&#xff09;举例1&#xff1a;展示[数字]线程的运行堆栈&#xff0c;命令&#xff1a;thread 线程ID举例2&#xff1a;找出当前阻塞其他线程的线程 二、命令列表 2.1 jvm相关命令 2.…

面试题高频之token无感刷新(vue3+node.js)

无感刷新的基本原理 使用刷新令牌&#xff08;refresh token&#xff09;&#xff1a; ○ 应用程序在首次登录成功后会获得一个访问令牌&#xff08;access token&#xff09;和一个刷新令牌&#xff08;refresh token&#xff09;。 ○ 访问令牌通常有较短的有效期&#xff0…

人工智能 | 基于ChatGPT开发人工智能服务平台

简介 ChatGPT 在刚问世的时候&#xff0c;其产品形态就是一个问答机器人。而基于ChatGPT的能力还可以对其做一些二次开发和拓展。比如模拟面试功能、或者智能机器人功能。 模拟面试功能包括个性化问题生成、实时反馈、多轮面试模拟、面试报告。 智能机器人功能提供24/7客服支…

学习之使用IDEA集成GIT

一、环境准备 1.1 配置git忽略文件 git.ignore 文件模版内容如下: # Compiled class file *.Class#Log file *.log# BlueJ file *.ctxt# Mobile Tools for Java (J2Me) *.mtj.tmp/# Package File *.jar *.war *.nar *.ear *.zip *.tar.gz *.rar.classpath .project .settings…

简单接口自动化框架实现(Python+requests+pytest)

1、接口自动化流程 1.需求分析2.挑选需要做自动化测试的功能3.设计测试用例4.搭建自动化测试环境[可选]5.设计自动化测试项目的架构[可选]6.编写代码7.执行测试用例8.生成测试报告并分析结果 2、框架结构 --api -->封装请求 --scripts -->编写测试脚本…

Python基础(六)——PyEcharts数据可视化初级版

案例 【前言&#xff1a;为了巩固之前的Python基础知识&#xff08;一&#xff09;到&#xff08;五&#xff09;&#xff0c;并为后续使用Python作为数据处理的好帮手&#xff0c;我们一起来看几个例子】 使用工具&#xff1a;Echarts Echarts 是一个由百度开源的数据可视化…

[2025]医院健康陪诊系统(源码+定制+服务)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

【电路笔记】-运算放大器比较器

运算放大器比较器 文章目录 运算放大器比较器1、概述2、表示2.1 同相比较器2.2 反相比较器3、临界点转换4、施密特触发器4.1 同相触发器4.2 反相触发器4.3 应用5、总结1、概述 在前面的大多数运算放大器文章中,电路都有一个到反相输入的反馈环路。 这种设计是最常见的,因为它…

基于SpringBoot+Vue的企业会议室预定管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

Gin渲染

HTML渲染 【示例1】 首先定义一个存放模板文件的 templates文件夹&#xff0c;然后在其内部按照业务分别定义一个 posts 文件夹和一个 users 文件夹。 posts/index.tmpl {{define "posts/index.tmpl"}} <!DOCTYPE html> <html lang"en">&…

shell指令及笔试题

一&#xff1a;linux基本指令考察 创建文件&#xff0c;直接在本目录的上级目录下创建一个名为dir1的文件夹&#xff0c;并在dir1文件夹下创建一个名为file1的文件 答&#xff1a;本目录的上级目录下创建一个名为dir1的文件:mkdir ../dir1 在dir1文件夹下创建一个名为file1的…

【SQL】百题计划:SQL内置函数“LENGTH“的使用

【SQL】百题计划-20240912 方法一&#xff1a; Select tweet_id from Tweets where LENGTH(content) > 15;– 方法二&#xff1a; Select tweet_id from Tweets where CHAR_LENGTH(content)> 15;

初始c++:入门基础(完结)

打字不易&#xff0c;留个赞再走吧~~~ 目录 一函数重载二引用1 引⽤的概念和定义2引⽤的特性3引⽤的使⽤三inline四nullptr 一函数重载 C⽀持在同⼀作⽤域中出现同名函数&#xff0c;但是要求这些同名函数的形参不同&#xff0c;可以是参数个数不同或者 类型不同。这样C函数调⽤…