unreal engine c++ 创建tcp server, tcp client
TCP客户端
TcpConnect.h
// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "Common/UdpSocketReceiver.h"
#include "GameFramework/Actor.h"DECLARE_DELEGATE_TwoParams(FOnServerResponseReceived, const int32&, bool);class FTcpConnect : public FRunnable
{
public:// Sets default values for this actor's propertiesFTcpConnect();virtual ~FTcpConnect(){if (ReceiveThread != nullptr){ReceiveThread->Kill(true);delete ReceiveThread;}}// virtual FSingleThreadRunnable* GetSingleThreadInterface() override// {// return this;// }
public:// Called every framevoid ConnectToServer(FString ServerAddress, const int32 ServerPort);void SendMessage(const FString& Message);FOnServerResponseReceived& OnDataReceived(){return OnServerResponseReceived;}virtual bool Init() override;virtual void Stop() override;protected:virtual uint32 Run() override;virtual void Exit() override;private:FSocket* Socket;TSharedPtr<FInternetAddr> RemoteAddr;FIPv4Endpoint LocalEndpoint;TArray<uint8> ReceivedData;FUdpSocketReceiver* UDPReceiver;bool bIsReceiving;FRunnableThread* ReceiveThread;int64 StartMs;FOnServerResponseReceived OnServerResponseReceived;
};
TcpConnect.cpp
// Fill out your copyright notice in the Description page of Project Settings.#include "Subsystem/TcpConnect.h"// Sets default values
FTcpConnect::FTcpConnect()
{
}void FTcpConnect::ConnectToServer(FString ServerAddress, const int32 ServerPort)
{// Socket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false);// RemoteAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();bool bValid;RemoteAddr->SetIp(*ServerAddress, bValid);RemoteAddr->SetPort(ServerPort);if (Socket->Connect(*RemoteAddr)){UE_LOG(LogTemp, Display, TEXT("Connected to server"));// bIsReceiving = true;ReceiveThread = FRunnableThread::Create(this, TEXT("ReceiveThread"), 128 * 1024);}else{UE_LOG(LogTemp, Error, TEXT("Failed to connect to server"));}
}void FTcpConnect::SendMessage(const FString& Message)
{if (Socket){const TCHAR* MessageData = *Message;int32 BytesSent = 0;StartMs = FDateTime::Now().ToUnixTimestamp() * 1000.0 + FDateTime::Now().GetMillisecond();UE_LOG(LogTemp, Warning, TEXT(" start ms %d"), StartMs);Socket->Send((uint8*)TCHAR_TO_UTF8(MessageData), FCString::Strlen(MessageData), BytesSent);}
}// FOnServerResponseReceived& FTcpConnect::OnDataReceived()
//
// {
// // check(ReceiveThread == nullptr);
// return OnServerResponseReceived;
// }bool FTcpConnect::Init()
{if (ReceiveThread && Socket){return true;}return false;
}void FTcpConnect::Stop()
{bIsReceiving = false;if (ReceiveThread){ReceiveThread->WaitForCompletion();// ReceiveThread.re}if (Socket){Socket->Close();Socket = nullptr;// Socket.Reset();}
}uint32 FTcpConnect::Run()
{while (bIsReceiving){uint8 Data[1024];int32 BytesReceived = 0;if (Socket->Recv(Data, sizeof(Data), BytesReceived, ESocketReceiveFlags::None)){if (BytesReceived > 0){FString Message = FString(UTF8_TO_TCHAR((const char*)Data));UE_LOG(LogTemp, Warning, TEXT(" message %s"), *Message)int64 EndMs = FDateTime::Now().ToUnixTimestamp() * 1000.0 + FDateTime::Now().GetMillisecond();UE_LOG(LogTemp, Warning, TEXT(" start ms %d %d"), EndMs, EndMs- StartMs);auto r = OnServerResponseReceived.ExecuteIfBound(EndMs - StartMs, true);bIsReceiving = false;}}FPlatformProcess::Sleep(0.05f);}return 0;
}void FTcpConnect::Exit()
{// FRunnable::Exit();
}
tcp server
// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "HAL/Runnable.h"class FSocket;
class FInternetAddr;/*** */
class MYPROJECT2_API FTcpServer : public FRunnable
{
public:FTcpServer();~FTcpServer();// 初始化服务器virtual bool Init() override;// 开始监听连接bool StartListening(FString IpAddress, int32 Port);// 停止监听连接void StopListening();// FRunnable 接口virtual uint32 Run() override;virtual void Stop() override;void HandleTextMessage(const FString& Message);private:// 处理客户端连接void HandleConnection(FSocket* NewClientSocket, const TSharedRef<FInternetAddr>& ClientAddress);FString StringFromBinaryArray(const TArray<uint8>& BinaryArray);class FSocket* serverSocket;FSocket* ListenSocket;FRunnableThread* ServerThread;FThreadSafeBool bIsStopping;TMap<FSocket*, FString> ClientDataMap; // 用于存储每个客户端的数据};
cpp
// Fill out your copyright notice in the Description page of Project Settings.#include "Net/TcpServer.h"#include "Common/TcpSocketBuilder.h"
#include "Interfaces/IPv4/IPv4Endpoint.h"FTcpServer::FTcpServer() : ListenSocket(nullptr), ServerThread(nullptr), bIsStopping(false)
{ServerThread = FRunnableThread::Create(this, TEXT("TcpServerThread"));
}FTcpServer::~FTcpServer()
{StopListening();
}bool FTcpServer::Init()
{// 初始化网络模块// if (!ISocketSubsystem::Init())// {// UE_LOG(LogTemp, Error, TEXT("Failed to initialize socket subsystem."));// return false;// }return true;
}bool FTcpServer::StartListening(FString IpAddress, int32 Port)
{FString ServerIP = IpAddress;FIPv4Address ServerAddr;if (!FIPv4Address::Parse(ServerIP, ServerAddr)){UE_LOG(LogTemp, Error, TEXT("Server Ip %s is illegal"), *ServerIP);}ListenSocket = FTcpSocketBuilder(TEXT("Socket Listener")).AsReusable().AsBlocking().BoundToAddress(ServerAddr).BoundToPort(Port).Listening(8).WithReceiveBufferSize(1024).WithSendBufferSize(1024);if (ListenSocket){UE_LOG(LogTemp, Warning, TEXT("Server Create Success!"), *ServerIP);// SocketCreateDelegate.Broadcast(true);// GetWorld()->GetTimerManager().SetTimer(ConnectCheckHandler, this, &ATCPServer::ConnectCheck, 1, true);return false;}else{UE_LOG(LogTemp, Error, TEXT("Server Create Failed!"));// SocketCreateDelegate.Broadcast(false);}return false;
}void FTcpServer::StopListening()
{if (ListenSocket){ListenSocket->Close();ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenSocket);ListenSocket = nullptr;}if (ServerThread){ServerThread->WaitForCompletion();delete ServerThread;ServerThread = nullptr;}
}uint32 FTcpServer::Run()
{ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);if (!SocketSubsystem){UE_LOG(LogTemp, Error, TEXT("Socket subsystem not available."));return 1;}while (!bIsStopping){if (!ListenSocket) continue;TSharedRef<FInternetAddr> ClientAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();FSocket* NewClientSocket = ListenSocket->Accept(*ClientAddress, TEXT("MyTcpServer Connection"));UE_LOG(LogTemp, Log, TEXT("Run"));if (NewClientSocket){// 新客户端连接处理HandleConnection(NewClientSocket, ClientAddress);}FPlatformProcess::Sleep(0.01);}return 0;
}void FTcpServer::Stop()
{UE_LOG(LogTemp, Warning, TEXT("St opped"));bIsStopping = true;
}void FTcpServer::HandleTextMessage(const FString& Message)
{UE_LOG(LogTemp, Warning, TEXT("Received message: %s"), *Message);
}void FTcpServer::HandleConnection(FSocket* NewClientSocket, const TSharedRef<FInternetAddr>& ClientAddress)
{// 在这里处理新客户端连接的逻辑UE_LOG(LogTemp, Warning, TEXT("Client connected: %s"), *ClientAddress->ToString(true));// 接收和处理消息while (NewClientSocket && !bIsStopping){TArray<uint8> ReceivedData;uint32 Size;// 接收数据while (NewClientSocket->HasPendingData(Size)){ReceivedData.Init(0, FMath::Min(Size, 65507u));int32 Read = 0;NewClientSocket->Recv(ReceivedData.GetData(), ReceivedData.Num(), Read);// 处理接收到的数据(这里假设消息以'\n'分隔)FString ReceivedString = StringFromBinaryArray(ReceivedData);TArray<FString> Messages;ReceivedString.ParseIntoArray(Messages, TEXT("\n"), true);for (const FString& Message : Messages){// 处理文本消息HandleTextMessage(Message);}}// 睡眠一段时间,以避免空循环造成CPU过度使用FPlatformProcess::Sleep(0.01);}// 断开客户端连接// UE_LOG(LogTemp, Warning, TEXT("Client disconnected: %s"), *ClientAddress->ToString(true));// NewClientSocket->Close();// ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(NewClientSocket);}FString FTcpServer::StringFromBinaryArray(const TArray<uint8>& BinaryArray)
{return FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(BinaryArray.GetData())));
}
FTcpServerReceive
h
// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "HAL/Runnable.h"/*** */
class MYPROJECT2_API FTcpServerReceive: public FRunnable
{
public:FTcpServerReceive(FSocket* InSocket);~FTcpServerReceive();
protected:virtual bool Init() override;virtual void Stop() override;FString StringFromBinaryArray(TArray<uint8> Array);void HandleTextMessage(const FString& String);virtual uint32 Run() override;
private:FSocket* ClientSocket;FRunnableThread* ServerReceiveThread;bool bIsStopping;
};
cpp
// Fill out your copyright notice in the Description page of Project Settings.#include "Net/FTcpServerReceive.h"#include "Sockets.h"
#include "SocketSubsystem.h"FTcpServerReceive::FTcpServerReceive(FSocket* InSocket)
{ClientSocket = InSocket;ServerReceiveThread = FRunnableThread::Create(this, TEXT("ServerReceiveThread"));bIsStopping = false;
}FTcpServerReceive::~FTcpServerReceive()
{if (ClientSocket){ClientSocket->Close();ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ClientSocket);ClientSocket = nullptr;}if (ServerReceiveThread){ServerReceiveThread->WaitForCompletion();delete ServerReceiveThread;ServerReceiveThread = nullptr;}
}bool FTcpServerReceive::Init()
{if (ServerReceiveThread && ClientSocket){UE_LOG(LogTemp, Warning, TEXT(" %s start"), *FString(__FUNCTION__));return true;}return false;
}void FTcpServerReceive::Stop()
{bIsStopping = true;
}FString FTcpServerReceive::StringFromBinaryArray(TArray<uint8> BinaryArray)
{return FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(BinaryArray.GetData())));
}void FTcpServerReceive::HandleTextMessage(const FString& Message)
{UE_LOG(LogTemp, Warning, TEXT("Received message: %s"), *Message);
}uint32 FTcpServerReceive::Run()
{while (ClientSocket){TArray<uint8> ReceivedData;uint32 Size;// 接收数据while (ClientSocket->HasPendingData(Size)){ReceivedData.Init(0, FMath::Min(Size, 65507u));int32 Read = 0;ClientSocket->Recv(ReceivedData.GetData(), ReceivedData.Num(), Read);// 处理接收到的数据(这里假设消息以'\n'分隔)FString ReceivedString = StringFromBinaryArray(ReceivedData);TArray<FString> Messages;ReceivedString.ParseIntoArray(Messages, TEXT("\n"), true);for (const FString& Message : Messages){// 处理文本消息HandleTextMessage(Message);}}// 睡眠一段时间,以避免空循环造成CPU过度使用FPlatformProcess::Sleep(0.05);}return 0;
}
使用
在GameInstance 使用
void UMyGameInstance::HandleTextMessage(const FString& Message)
{UE_LOG(LogTemp, Warning, TEXT("Received message: %s"), *Message);
}bool UMyGameInstance::Connected(FSocket* Socket, const FIPv4Endpoint& FiPv4Endpoint)
{UE_LOG(LogTemp, Warning, TEXT("Connected %s"), *FiPv4Endpoint.ToString());FTcpServerReceive* ServerReceive = new FTcpServerReceive(Socket);return true;
}void UMyGameInstance::Init()
{Super::Init();FIPv4Address ServerAddr;FIPv4Address::Parse("0.0.0.0", ServerAddr);FIPv4Endpoint IPv4;IPv4.Address = ServerAddr;IPv4.Port = 6666;#if UE_SERVER/FTcpListener* TcpServer = new FTcpListener(IPv4);TcpServer->OnConnectionAccepted().BindUObject(this, &UMyGameInstance::Connected);#elseFTcpClient* TcpClient = new FTcpClient();TcpClient->ConnectToServer("192.168.1.6", 6666);FTimerHandle h;FTimerDelegate d = FTimerDelegate::CreateLambda([=](){FString sent = "aaaaaa";TcpClient->SendMessage(sent);});// GetWorld()->GetTimerManager().SetTimer(h, d, 1.0f, true, 3.0f);#endif
}FString UMyGameInstance::StringFromBinaryArray(const TArray<uint8>& BinaryArray)
{return FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(BinaryArray.GetData())));
}TSharedPtr<TArray<uint8>, ESPMode::ThreadSafe> UMyGameInstance::StringToByteArray(FString DataString)
{TArray<uint8> DataArray;FTCHARToUTF8 Converter(*DataString);DataArray.Append(reinterpret_cast<const uint8*>(Converter.Get()), Converter.Length());return MakeShared<TArray<uint8>, ESPMode::ThreadSafe>(MoveTemp(DataArray));
}相关文章:
unreal engine c++ 创建tcp server, tcp client
TCP客户端 TcpConnect.h // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Common/UdpSocketReceiver.h" #include "GameFramework/Actor.h"DECLARE_DELEGATE…...
24届华东理工大学近5年自动化考研院校分析
今天给大家带来的是华东理工大学控制考研分析 满满干货~还不快快点赞收藏 一、华东理工大学 学校简介 华东理工大学原名华东化工学院,1956年被定为全国首批招收研究生的学校之一,1960年起被中共中央确定为教育部直属的全国重点大学&#…...
初识集合和背后的数据结构
目录 集合 Java集合框架 数据结构 算法 集合 集合,是用来存放数据的容器。其主要表现为将多个元素置于一个单元中,用于对这些元素进行增删查改。例如,一副扑克牌(一组牌的集合)、一个邮箱(一组邮件的集合)。 Java中有很多种集…...
选择适合你的数据可视化工具:提升洞察力的关键决策
导言: 在当今数据驱动的世界中,数据可视化工具成为了帮助我们理解和传达数据见解的关键工具之一。数据可视化不仅能够将复杂的数据转化为易于理解的可视化形式,还能帮助我们发现数据中的模式、趋势和关联。然而,随着市场上可视化工…...
H5中的draggable
基本语法及事件 draggable 属性规定元素是否可拖动。必须设置,否则没有拖拽效果及事件触发 提示: 链接和图像默认是可拖动的。 提示: draggable 属性经常用于拖放操作 语法 <element draggable"true|false|auto"> 值描…...
搭建SVN服务器
简介 SVN(Subversion)是一种版本控制工具,用于管理和跟踪文件的修改历史。它可以帮助团队协作开发,方便地共享和更新代码,同时也可以提供备份和安全控制功能。 使用SVN,你可以创建中央代码库(…...
OpenCV之信用卡识别实战
文章目录 代码视频讲解模板匹配文件主程序(ocr_template_match.py)myutils.py 代码 链接: https://pan.baidu.com/s/1KjdiqkyYGfHk97wwgF-j3g?pwdhhkf 提取码: hhkf 视频讲解 链接: https://pan.baidu.com/s/1PZ6w5NcSOuKusBTNa3Ng2g?pwd79wr 提取码: 79wr 模板匹配文件 …...
Detector定位算法在FPGA中的实现——section1 原理推导
关于算法在FPGA中的实现,本次利用业余的时间推出一个系列章节,专门记录从算法的推导、Matlab的实现、FPGA的移植开发与仿真做一次完整的FPGA算法开发,在此做一下相关的记录和总结,做到温故知新。 这里以Detector在Global Coordinate System(原点为O)中运动为背景,Detect…...
心电信号去噪:方法与应用
目录 1 去噪技术的发展历程 2 滤波器去噪的应用 3 小波去噪的优势 4 深度学习去噪的前景...
睡眠助手/白噪音/助眠夜曲微信小程序源码下载 附教程
睡眠助手/白噪音/助眠夜曲微信小程序源码 附教程 支持分享海报 支持暗黑模式 包含了音频数据 最近很火的助眠小程序,前端vue,可以打包H5,APP,小程序 后台可以设置流量主广告,非常不错的源码 代码完整 完美运营 搭配无…...
Spring Cloud常见问题处理和代码分析
目录 1. 问题:如何在 Spring Cloud 中实现服务注册和发现?2. 问题:如何在 Spring Cloud 中实现分布式配置?3. 问题:如何在 Spring Cloud 中实现服务间的调用?4. 问题:如何在 Spring Cloud 中实现…...
debian怎么修改man help为中文,wsl怎么修改显示语言为中文
在Debian 12系统中,要将系统语言和Man帮助手册设置为中文,需要执行以下步骤: 安装中文语言包: 首先,更新软件包列表并安装中文语言包。打开终端并运行以下命令: sudo apt update sudo apt install locales配…...
k8s概念-亲和力与反亲和力
回到目录 亲和力 Affinity 对部署调度时的优先选择 分为 节点亲和力 pod亲和力 pod反亲和力 节点亲和力 NodeAffinity 进行 pod 调度时,优先调度到符合条件的亲和力节点上 可配置 硬亲和力和软亲和力 RequiredDuringSchedulingIgnoredDuringExecution 硬…...
【数据结构】实现单链表的增删查
目录 1.定义接口2.无头单链表实现接口2.1 头插addFirst2.2 尾插add2.3 删除元素remove2.4 修改元素set2.5 获取元素get 3.带头单链表实现接口3.1 头插addFirst3.2 尾插add3.3 删除元素remove3.4 判断是否包含元素element 1.定义接口 public interface SeqList<E>{//默认…...
Vue2 第二十节 vue-router (四)
1.全局前置路由和后置路由 2.独享路由守卫 3.组件内路由守卫 4.路由器的两种工作模式 路由 作用:对路由进行权限控制 分类:全局守卫,独享守卫,组件内守卫 一.全局前置路由和后置路由 ① 前置路由守卫:每次路由…...
第三章 图论 No.1单源最短路及其综合应用
文章目录 1129. 热浪1128. 信使1127. 香甜的黄油1126. 最小花费920. 最优乘车903. 昂贵的聘礼1135. 新年好340. 通信线路342. 道路与航线341. 最优贸易 做乘法的最短路时,若权值>0,只能用spfa来做,相等于加法中的负权边 1129. 热浪 1129.…...
❤ npm不是内部或外部命令,也不是可运行的程序 或批处理文件
❤ npm不是内部或外部命令,也不是可运行的程序 或批处理文件 cmd或者终端用nvm 安装提示: npm不是内部或外部命令,也不是可运行的程序或批处理文件 原因(一) 提示这个问题,有可能是Node没有安装,也有可能是没有配置…...
关于Godot游戏引擎制作流水灯
先上核心代码 游戏节点 流水灯的通途可以是 1. 装饰 2. 音乐类多媒体程序(如FL中TB-303的步进灯) FL Studio Transistor Bass...
C语言 函数指针详解
一、函数指针 1.1、概念 函数指针:首先它是一个指针,一个指向函数的指针,在内存空间中存放的是函数的地址; 示例: int Add(int x,int y) {return xy;} int main() {printf("%p\n",&Add);…...
LNMP及论坛搭建
安装 Nginx 服务 systemctl stop firewalld systemctl disable firewalld setenforce 0 1.安装依赖包 #nginx的配置及运行需要pcre、zlib等软件包的支持,因此需要安装这些软件的开发包,以便提供相应的库和头文件。 yum -y install pcre-devel zlib-devel…...
光伏产业发展带动紧固件需求增长 市场趋势与应用分析 上海紧固件专业展
2026第十六届上海紧固件专业展(Fastener Expo Shanghai 2026)将于6月24日至26日在上海国家会展中心举行。随着新能源产业持续升温,光伏行业的快速发展正在显著带动紧固件市场需求增长,成为行业关注的重要方向。在全球能源转型的大…...
别再踩坑了!Django Ckeditor配置全指南:从基础使用到高级定制(2023最新版)
Django Ckeditor实战手册:2023年高效配置与深度定制技巧 如果你正在为Django项目寻找一个功能强大且可定制的富文本编辑器,Ckeditor无疑是最佳选择之一。但配置过程中那些令人头疼的兼容性问题、图片上传失败、工具栏自定义困难,确实让不少开…...
Kettle转换里‘阻塞数据’控件为啥不灵?我用这个真实ETL案例给你讲透
Kettle转换中‘阻塞数据’控件的实战解析:从失效到精准控制 在ETL工具Kettle的实际应用中,数据流的精确控制往往是决定任务成败的关键。许多中高级用户在使用"阻塞数据直到步骤都完成"控件时,都曾遇到过看似配置正确却无法生效的困…...
对比学习演进笔记:从Memory Bank到MoCo的负样本队列设计
1. 对比学习的核心思想与演进背景 对比学习(Contrastive Learning)作为自监督学习的重要分支,其核心思想可以用一句话概括:让相似样本的特征表示尽可能接近,不相似样本的特征表示尽可能远离。这种思想最早可以追溯到20…...
智能影像雅鉴系统:丹青识画在美术馆导览中的落地实操
智能影像雅鉴系统:丹青识画在美术馆导览中的落地实操 1. 艺术与科技的完美融合 1.1 传统导览的痛点与革新 在美术馆参观时,我们常常面临这样的困境:站在一幅名画前,却无法真正理解其深层意境;面对珍贵文物ÿ…...
仙侠H5手游【九州封魔劫代金券内购版】服务端图文搭建教程(含资源下载+部署过程)
游戏截图搭建环境信息 系统:Centos 7.6 内存:4G 处理器:2核 注意事项 复制代码需要通过浏览器打开文章才不会报错 搭建资源获取 百度网盘:https://pan.baidu.com/s/1wmz7RegQGBaNrYYVbuJqgg?pwdkdn4 解压密码:www.won…...
2026学生免费用AI编程神器全攻略——白嫖不要白不要,大学生快来
好的,上一章刚教你用GitHub武装自己,筑起技术护城河,但光会搬砖(敲命令)还不够,你得学会“开高达”——用AI编程助手把效率拉满。 2026年了,如果还纯靠手打for循环和查API文档,那你…...
中山专用展示柜灯具,打造完美商品展示效果
在灯具销售领域,商品展示效果的好坏直接影响着销售业绩。一个好的展示柜不仅能保护灯具,更能通过巧妙的设计和布局,将灯具的优点充分展现出来,吸引顾客的目光。而中山作为中国著名的灯饰之都,其专用展示柜灯具更是有着…...
AIVideo效果对比展示:不同参数下的视频生成质量评测
AIVideo效果对比展示:不同参数下的视频生成质量评测 1. 开场白:参数设置对视频效果的影响 你有没有遇到过这样的情况:用AI生成视频时,明明输入的内容一样,但出来的效果却天差地别?有时候画面模糊不清&…...
太原烘焙培训排名
在太原选择烘焙培训机构时,许多朋友会关注不同机构的教学质量与特色。以下整理了一些选择时可以考虑的方面,供您参考。教学方式与内容部分机构采用以实操为主的教学模式,例如山西旭梦圆食品有限公司的课程安排中,实践操作占较大比…...
