目录

1.libwebsockets库中回调函数里resons部分宏

2. API解释

3.实现代码


1.libwebsockets库中回调函数里resons部分宏

关于libwebsockets库中回调函数里resons部分宏的解释参考:https://blog.csdn.net/qifengzou/article/details/50281545

回调函数原型:

ws_service_callback(strcut lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)

本人腾讯翻译(reason):

LWS_CALLBACK_CLIENT_ESTABLISHED

after your client connection completed,a handshake with the remote server.

客户端连接完成后,与远程服务器进行握手。

LWS_CALLBACK_ESTABLISHED

after the server completes a handshake with an incoming client.If you built the library with ssl support,in is a pointer to the ssl struct associated with the connection or NULL.

服务器完成与传入客户端的握手后,如果构建具有SSL支持的库,*in是指向与连接关联的SSL结构的指针,或者为NULL。

LWS_CALLBACK_CLOSED

when the websocket session ends.

当Websocket会话结束时。

LWS_CALLBACK_CLOSED_HTTP

when a HTTP(non-websocket) session ends

HTTP(非Websocket)会话结束时

LWS_CALLBACK_CLIENT_CONNECTION_ERROR

the request client connection has been unable to complete a handshake with the remote sever.If in is non-NULL,you can find an error string if length len where it point to

请求客户端连接无法完成与远程服务器的握手。如果in为非空,则可以找到错误字符串(如果其指向的位置为

LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPODRTED

when a client connection is being prepared to start a handshake to a server,each supported extension is checked with protocols[0] callback with this reason,giving the user code a chance to suppress the claim to support that extension by returning non-zero.If unhandled,by default 0 will be returned and the extension support include in the header to the server.Notice this callback comes to protocols[0].

当客户端连接准备开始与服务器握手时,每个支持的扩展将根据protocols[0]回调函数中的resaon检查,从而使用户代码有机会通过返回非零来抑制声明以支持该扩展。如果未处理,默认情况下,将返回0,并且扩展支持包含在服务器的报头中。注意,这个回调到达protocols[0]。

LWS_CALLBACK_CLIENT_RECEIVE

data has appeared from the server for the client connection,it is can be found at *in and is len bytes long.

数据出现在客户端连接的服务器上时,可以在通过*in处找到,长度为len字节。

LWS_CALLBACK_RECEIVE

data has appeared for this server endpoint from  a remote client,it is can be found at *in and is len bytes long.

此服务器端点的数据来自远程客户端,可以在*in处找到,长度为len字节。

LWS_CALLBACK_CLIENT_WRITEABLE

If you call lws_callback_on_writbable() on a connection,you will get one of these callbacks coming when the connection socket is able to accecpt another write packet without blocking.If it already was able to  take another packet without blocking,you'll get this callback at the next call to the service loop function.
Notice that CLIENTs get LWS_CALLBACK_CLIENT_WRITEABLE and servers get LWS_CALLBACK_SERVER_WRITEABLE

如果在连接时调用lws_Callback_on_write(),那么当连接套接字能够不阻塞地访问另一个写数据包时,您将得到其中一个回调。如果它已经能够不阻塞地接收另一个数据包,那么您将在下一次调用服务循环函数时得到这个回调。
请注意,客户端获得LWS_CALLBACK_CLIENT_WRITEABLE,而服务器获得LWS_CALLBACK_SERVER_WRITEABLE

LWS_CALLBACK_SERVER_WRITEABLE

同上

LWS_CALLBACK_CLIENT_HTTP_WRITEABLE

When  doing an HTTP type client connection,you can call
lws_clinet_http_body_pending(wsi,i) from
LWS_CALLBACK_CLINET_APPEND_HANDSHAKE_HEADER to get these callbackes sending the HTTP headers.

在进行HTTP类型的客户端连接时,您可以调用:
Lws_Clinet_http_BODY_PINING(wsiI,i)来自
lws_Callback_Clinet_append_handsh_Header以获取这些发送HTTP标头的回调。

From this callback,when you have sen everything,you should let lws  know by calling lws_clinet_http_body_pending(wsi,0)

在这个回调中,当您已经完成所有操作时,您应该通过调用lws_clinet_http_body_pending(wsi,0)

LWS_CALLBACK_COMPLETED_CLIENT_HTTP

没有解释

LWS_CALLBACK_ESTBLISHED_CLIENT_HTTP

没有解释

LWS_CALLBACK_RECEIVE_CLIENT_HTTP

没有解释

LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ

没有解释

LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER

this callback happens when a client handshake is being compiled. user is NULL, in is a char **,it's pointing to a char * which holds the next location in the header buffer where you can add headers,and len is the remaining sapce in the header buffer,which is typically some hundreds of bytes.So, to add a canned cookie,your handler code might look similar to:

此回调发生在编译客户端握手时。user为null,in是char **,它指向一个char*,其中包含头缓冲区中可以添加头文件的下一个位置,len是头文件缓冲区中的剩余部分,通常为数百个字节,因此,要添加罐装cookie,处理程序代码可能类似于:

Notice if you add anything,you just have to take care about the CRLF in the line you add.Obviously this callback is optional,if you don't handle it everything is fine.

注意,如果您添加任何内容,您只需注意您添加的行中的CRLF。显然,这个回调是可选的,如果您不处理它,一切都很好。

Notice the callback is coming to protocols[0] all the time,because there is no specific protocol negotiated yet.

请注意,回调一直指向 protocols[0],因为还没有经过协商的特定协议。


2. API解释

本人腾讯翻译(API解释):

void lws_set_log_level(int level, void (*log_emit_function)(int level, const char *line))

lws_set_log_level() - Set the logging bitfield(位域)

lws_set_log_level()-设置日志位字段(位域)。
param level: OR together the LLL_ debug contexts you want output from

参数 level:或者一起使用要从其中输出的LLL_DEBUG上下文。
param log_emit_function: NULL to leave it as it is, or a user-supplied function to perform log string emission instead of the default stderr(标准输出) one.
参数 LOG_EEMIT_Function:如果保持不变,则为NULL,或者使用用户提供的函数来执行日志字符串发射,而不是默认的stderr(标准输出)函数。

log level defaults to "err", "warn" and "notice" contexts enabled and emission on stderr. If stderr is a tty (according to isatty()) then the output is coloured according to the log level using ANSI escapes.(分级显示)

日志级别默认为“ERR”、“WARN”和“NOTER”上下文已启用,并在stderr上发出。如果stderr是tty(根据isatty(),则根据日志级别使用ANSI转义对输出进行着色。(分级显示)

struct lws_context *lws_create_context(struct lws_context_creation_info *info);

lws_create_context() - Create the websocket handler(管理者)

lws_create_context() - 创建WebSocket处理程序(管理者)

param info: pointer to struct with parameters

参数info:指向带参数的结构的指针

This function creates the listening socket (if serving) and takes care of all initialization in one step(创建socket并完成一系列初始化工作)

此函数创建侦听套接字(如果提供服务),并在这一步中处理所有初始化(创建套接字并完成一系列初始化工作)。

If option LWS_SERVER_OPTION_EXPLICIT_VHOSTS is given, no vhost is created; you're expected to create your own vhosts afterwards using lws_create_vhost(). Otherwise a vhost named "default" is also createdusing the information in the vhost-related members, for compatibility(创建vhost,特殊情况调用lws_create_vhost())

如果给出了选项LWS_SERVER_OPTION_EXPLICIT_VHOSTS,则不会创建vhost;之后需要使用lws_create_vhost()创建自己的vhost。否则,为了兼容性,还会使用vhost相关成员中的信息创建名为“default”的vhost(创建vhost,特殊情况调用lws_create_vhost()

After initialization, it returns a struct lws_context * that represents this server.

初始化后,它返回表示此服务器的struct lws_context *(比如叫context )。

After calling, user code needs to take care of calling lws_service() with the context pointer to get the server's sockets serviced.

调用之后,用户代码需要使用上下文指针context调用lws_service(),以获得服务于服务器的套接字。

This must be done in the same process context as the initialization call.(初始化以后,循环调用lws_service()来获得服务)

这必须在初始化调用所在的进程上下文中完成。(初始化以后,循环调用lws_service()来获得服务)

The protocol callback functions are called for a handful of events including http requests coming in, websocket connections becoming established, and data arriving; it's also called periodically to allow async transmission.(连接,请求发送,接受消息都会调用协议回调函数,回调函数是被周期性调用的,以便达到异步传输的效果)

协议回调函数被调用来处理一些事件,包括http请求的传入、WebSocket连接的建立和数据的到达;它也被定期调用以允许异步传输。

HTTP requests are sent always to the FIRST protocol in protocol, since at that time websocket protocol has not been negotiated. Other protocols after the first one never see any HTTP callback activity.(HTTP请求只在第一次交互时才会有,这个时候ws还未达成协议,之后其它协议就不需要HTTP头了)

HTTP请求总是发送到协议中的第一个协议,因为当时还没有协商WebSocket协议。第一个协议之后的其他协议再不看到任何HTTP回调活动。

The server created is a simple http server by default; part of the websocket standard is upgrading this http connection to a websocket one.(WS是HTTP服务的一个浓缩)

默认情况下,创建的服务器是简单的HTTP服务器;WebSocket标准的一部分是将此HTTP连接升级到WebSocket连接。(WS是HTTP服务的一个浓缩)。

This allows the same server to provide files like scripts and favicon images or whatever over http and dynamic data over websockets all inone place; they're all handled in the user callback. (在用户回调里面,提供脚本语言,图片和其它数据)

这允许相同的服务器通过http提供诸如脚本和图标图像之类的文件,并在WebSocket上提供动态数据;它们都是在用户回调中处理的。(在用户回调里面,提供脚本语言,图片和其它数据)

int lws_service(struct lws_context *context, int timeout_ms)

polllws_service() - Service any pending(即将发生的) websocket activity

Pollws_service()-服务任何挂起的(即将发生的)WebSocket活动

param context: Websocket context(上下文)

参数context:WebSocket上下文(上下文)

param  timeout_ms: 0 means return immediately if nothing needed service otherwise block and service immediately, returning after the timeout if nothing needed service.

参数Timeout_ms:0表示在没有任何需要的情况下立即返回服务,否则立即阻止服务,如果没有任何需要的服务,则在超时后返回。

This function deals with any pending websocket traffic(交互), for three kinds of event. It handles these events on both server and client types of connection the same.

此函数处理任何挂起的WebSocket通信量(交互),用于三种事件。它处理服务器和客户端连接类型相同的这些事件。

1) Accept new connections to our context's server(接受连接)

接受到上下文服务器的新连接(接受连接)

2) Call the receive callback for incoming frame data received by server or client connections.(消息回调)

对服务器或客户端连接接收的传入帧数据调用接收回调。(消息回调)

You need to call this service function periodically to all the above functions to happen; if your application is single-threaded you can just call it in your main event loop. (循环调用该函数)

您需要定期对所有上述函数调用该服务函数;如果您的应用程序是单线程的,您只需在主事件循环中调用它。(循环调用该函数)

Alternatively you can fork a new process that asynchronously handles calling this service in a loop. In that case you are happy if this call blocks your thread until it needs to take care of something and would call it with a large nonzero timeout. Your loop then takes no CPU while there is nothing happening.(最好重开线程调用该函数)

或者,您可以派生一个新进程,该进程在循环中异步处理调用此服务。在这种情况下,如果这个调用阻塞了您的线程,直到它需要处理一些事情,并且会使用一个很大的非零超时来调用它,那么您会很高兴。当什么都没有发生的时候,你的循环就不需要CPU了。(最好重开线程调用该函数)

If you are calling it in a single-threaded app, you don't want it to wait around blocking other things in your loop from happening, so you would call it with a timeout_ms of 0, so it returns immediately if nothing is pending, or as soon as it services whatever was pending.(超时最好设为0)

如果您在单线程应用程序中调用它,您不希望它等待阻止您循环中的其他事情发生,因此您应该使用Timeout_ms为0调用它,因此如果没有挂起,它就会立即返回,或者在它为任何挂起的东西提供服务时立即返回。(超时最好设为0)

struct lws * lws_client_connect_via_info(struct lws_client_connect_info * ccinfo);

lws_client_connect_via_info() - Connect to another websocket server

lws_client_connect_via_info() - 连接到另一台websocket服务器

param ccinfo: pointer to lws_client_connect_info struct

参数ccinfo:指向lws_client_connect_info结构的指针

This function creates a connection to a remote server using the information provided in ccinfo.

此函数使用ccinfo中提供的信息创建到远程服务器的连接。

void lws_context_destroy(struct lws_context *context);

lws_context_destroy() - Destroy the websocket context

lws_context_destroy() - 销毁WebSocket上下文

param context: Websocket context

参数context:Websocket上下文

This function closes any active connections and then frees the context. After calling this, any further use of the context is undefined

此函数将关闭所有活动连接,然后释放上下文。调用它之后,上下文的任何进一步使用都是未定义的。

struct lws_client_connect_info

struct lws_client_connect_info {
struct lws_context *context;/**< lws context to create connection in */要在其中创建连接的LWS上下文
const char *address;/**< remote address to connect to */要连接到的远程地址
int port;/**< remote port to connect to */要连接到的远程端口
int ssl_connection;/**< nonzero for ssl */SSL非零
const char *path; /**< uri path */URI路径
const char *host; /**< content of host header */主机标头的内容
const char *origin;/**< content of origin header */原始标题的内容
const char *protocol;/**< list of ws protocols we could accept */可以接受的ws协议列表
int ietf_version_or_minus_one; /**< deprecated: currently leave at 0 or -1 */已弃用:当前为0或-1
void *userdata;/**< if non-NULL, use this as wsi user_data instead of malloc it */如果不为NULL,则将其用作wsi user_data而不是malloc
const void *client_exts;/**< UNUSED... provide in info.extensions at context creation time */没用过.。在创建上下文时在info.Extension中提供
const char *method;
/**< if non-NULL, do this http method instead of ws[s] upgrade.如果不为空,请执行此http方法,而不是%ws[s]升级。
* use "GET" to be a simple http client connection */使用“get”作为简单的http客户端连接
struct lws *parent_wsi;
/**< if another wsi is responsible for this connection, give it here.如果另一个WSI负责此连接,请在此处给出。
* this is used to make sure if the parent closes so do any  这用于确保如果父连接关闭,则先执行子连接
* child connections first. */
const char *uri_replace_from;
/**< if non-NULL, when this string is found in URIs in   如果非NULL,则当在text/html内容编码中的URI中找到此字符串时,
* text/html content-encoding, it's replaced with uri_replace_to */ 它将被替换为uri_place_to const char*uri_place_to;
const char *uri_replace_to;
/**< see uri_replace_from */参见uri替换自
struct lws_vhost *vhost;
/**< vhost to bind to (used to determine related SSL_CTX) */ 要绑定到的vhost(用于确定相关的SSL_CTX)
struct lws **pwsi;
/**< if not NULL, store the new wsi here early in the connection
* process. Although we return the new wsi, the call to create the
* client connection does progress the connection somewhat and may
* meet an error that will result in the connection being scrubbed and
* NULL returned. While the wsi exists though, he may process a
* callback like CLIENT_CONNECTION_ERROR with his wsi: this gives the
* user callback a way to identify which wsi it is that faced the error
* even before the new wsi is returned and even if ultimately no wsi
* is returned.
*/

如果不为NULL,则在连接过程的早期将新的WSI存储在此处。尽管我们返回了新的WSI,但是创建客户机连接的调用确实在一定程度上推进了连接,并且可能会遇到一个错误,该错误将导致连接被擦除并返回NULL。但是,当WSI存在时,他可以使用WSI处理类似Client_Connection_Error的回调:这为用户回调提供了一种方法,以确定即使在返回新的WSI之前(即使最终没有返回任何ws)面临错误的WSI是哪个WSI。
const char *iface;
/**< NULL to allow routing on any interface, or interface name or IP
* to bind the socket to */
/* Add new things just above here ---^
* This is part of the ABI, don't needlessly break compatibility
*
* The below is to ensure later library versions with new
* members added above will see 0 (default) even if the app
* was not built against the newer headers.

*/

空,允许在任何接口上进行路由,或接口名称或ip将套接字绑定到。
添加新的东西就在这里这是ABI的一部分,不要不必要地破坏兼容性下面是为了确保以后的库版本与上面添加的新成员将看到0(默认),即使应用程序不是针对较新的标题构建的。

void *_unused[4];

};


3.实现代码

struct session_data {int fd;
};
static int ws_service_callback(struct lws *wsi,enum lws_callback_reasons reason, void *user,void *in, size_t len)
{switch (reason) {case LWS_CALLBACK_CLIENT_ESTABLISHED:printf("[Main Service] Connect with server success.\n");connection_flag = 1;break;case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:printf("[Main Service] Connect with server error.\n");destroy_flag = 1;connection_flag = 0;break;case LWS_CALLBACK_CLOSED:printf("[Main Service] LWS_CALLBACK_CLOSED\n");destroy_flag = 1;connection_flag = 0;break;case LWS_CALLBACK_CLIENT_RECEIVE:printf("[Main Service] Client recvived:%s\n", (char *)in);if (writeable_flag)destroy_flag = 1;break;case LWS_CALLBACK_CLIENT_WRITEABLE:printf("[Main Service] On writeable is called. send byebye message\n");//websocket_write_back(wsi, "Byebye! See you later", -1);writeable_flag = 1;break;default:break;}return 0;
}
static lws_context* s_ptContext;
static struct lws_protocols protocols[] = {{ "lws-mirror-protocol",ws_service_callback, 0, 1024 * 32 },{ NULL, NULL, 0, 0 } /* end */
};
#define PORT_LEN 8
#define SESSION_LEN 24
string CombineIPAndPort(std::string strIP, int wPort, int dwType)
{std::string strSession(strIP);char achPort[PORT_LEN] = { 0 };sprintf_s(achPort, PORT_LEN, ":%d|%d", wPort, dwType);strSession.append(achPort);return strSession;
}
static lws_context* CreateContext()
{lws_set_log_level(0xFF, NULL);lws_context* plcContext = NULL;lws_context_creation_info tCreateinfo;memset(&tCreateinfo, 0, sizeof tCreateinfo);tCreateinfo.port = CONTEXT_PORT_NO_LISTEN;tCreateinfo.protocols = protocols;plcContext = lws_create_context(&tCreateinfo);return plcContext;
}
static void Connect(string url, string &strServerIP, int wPort, int dwType)
{struct lws_client_connect_info tConnectInfo;memset(&tConnectInfo, 0, sizeof(tConnectInfo));tConnectInfo.context = s_ptContext;tConnectInfo.address = "172.22.3.25";tConnectInfo.port = 90;tConnectInfo.path = url.c_str();tConnectInfo.protocol = protocols->name;tConnectInfo.host = "172.22.3.25";// char *pchSession = new char[SESSION_LEN];// if (pchSession)// {// memset(pchSession, 0, SESSION_LEN);// memcpy(pchSession, CombineIPAndPort(strServerIP, wPort, dwType).c_str(), SESSION_LEN);// }// tConnectInfo.userdata = pchSession;lws_client_connect_via_info(&tConnectInfo);
}
DWORD WINAPI lws_ServiceThread(LPVOID lpParameter)
{s_ptContext = CreateContext();while (TRUE){lws_service(s_ptContext, 50);if (false){break;}}lws_context_destroy(s_ptContext);return 0;
}
int initLibWebSocket(string url)
{HANDLE hThread = CreateThread(0, 0, lws_ServiceThread, (LPVOID)10, CREATE_SUSPENDED, 0);ResumeThread(hThread);Sleep(3000);string ipTemp = "127.0.0.1";Connect(url, ipTemp, 80, 0);return 0;
}

真正的实例,待验证:https://blog.csdn.net/yuanwei1314/article/details/76228495

会报段错误,原因不知,但知道在哪里报

server端:lws_service(context, 50);报段错误

client端:wsi = lws_client_connect(.......)报段错误

20190506(libwebsockets库使用学习)相关推荐

  1. 【C++】libwebsockets库的简易教程

    说在前面 最近很长一段时间,都有在嵌入式上进行websocket通信的需求. 查了很多资料,现在C++可用的ws第三方库不多,尤其是在较老的嵌入式开发环境中,既要支持C99和SSL,又需要轻量级不依赖 ...

  2. 20190503(cmake安装,利用libwebsockets库去实现http服务器,websocket服务器,虚拟机安装)

    目录 1.libwebsockets简介安装 2.libwebsockets实现简易http服务器 3.实现简易websocket服务器 4.websocket介绍 5.虚拟机安装 1.libwebs ...

  3. python内置库之学习configparser库(一)

    python内置库之学习configparser库(一) 1.引言 ini文件简介 [节] 键=值 注:节不能重复出现 2.自己封装了一个增删改查的类,可以参考一下 import configpars ...

  4. go标准库的学习-crypto/sha1

    参考:https://studygolang.com/pkgdoc 导入方式: import "crypto/sha1" sha1包实现了SHA1哈希算法,参见RFC 3174. ...

  5. 日志库EasyLogging++学习系列(10)—— 日志文件滚动

    在很多应用场合,我们是需要实现日志文件滚动的,特别是在一些长期运行的服务器程序中,如果把所有的日志都记录在一个文件之中,势必会造成日志文件越来越大.当日志内容很多的时候,万一哪天突然需要查询某个日志信 ...

  6. 日志库EasyLogging++学习系列(7)—— 记录方式详解

    在前面所列文章的演示代码中,其实已经展示了一部分记录日志的方式.为了使用方便,在 Easylogging++ 中,通过使用宏的集合来完成日志记录. 普通日志记录 对于普通的日志记录,我们可以选择以下两 ...

  7. 日志库EasyLogging++学习系列(6)—— 日志记录器

    所有的日志都是由日志记录器完成的,日志记录器使用唯一的 ID(大小写敏感)来标识.在 Easylogging++ 中默认了三个现有的日志记录器: 默认日志记录器,其 ID 为:default 性能日志 ...

  8. 日志库EasyLogging++学习系列(5)—— 辅助配置功能

    正如前面<日志库EasyLogging++学习系列(3)-- 配置功能>文中最后提到的,在某些应用场景下,我们还需要通过其他的一些配置手段来辅助我们完成某些特殊功能,这些辅助配置手段包括设 ...

  9. 日志库EasyLogging++学习系列(3)—— 配置功能

    在前面的文章 <日志库Easylogging++学习系列(1) -- 简要介绍 >中,我们已经初步见识到了 Easylogging++ 日志库强大的配置功能.那么配置文件中各个字段的意义是 ...

最新文章

  1. Domino URL Command 详解
  2. Spring 数据访问那些事儿(二)Spring + JdbcTemplate
  3. 量子叠加态系数_1.2 量子比特
  4. php 文件目录操作相关函数file_exists mkdir chmod touch
  5. SpringCloud Gateway堆外内存溢出排查
  6. 卷积和反卷积输出的计算公式
  7. MATLAB--基本操作与矩阵输入
  8. 【DEBUG】mxs-auart mxs-auart.0: Unhandled status 520080
  9. idea2021.3.x激活教程
  10. IDEA中java文件出现小黄色的J
  11. 金融衍生品PK:期权和权证俩兄弟
  12. 《数据结构与算法:Python语言描述》一1.3算法和算法分析
  13. Elmedia Player - Mac 上最好用的音视屏媒体播放器
  14. 【C语言】基础知识梳理总结(超详细!!!!
  15. 高校学生“在家实践”计划 免费领取12个月阿里云ECS云服务器
  16. C盘里的每个文件夹都是干什么用的
  17. 在centos中安装rar压缩工具
  18. mysql配置服务器失败怎么办_安装用友MSDE2000数据库提示:安装程序配置服务器失败怎么解决?...
  19. 滴答验箱视频智能验箱演示,司机随时随地验箱区分好坏箱,箱信息+箱况报备,套箱云堆场,智慧航运智能化船公司智能化数字化集装箱管控
  20. 迈普光彩北区销售部签订辽宁某会议室55寸LCD液晶拼接显示屏项目

热门文章

  1. ZooKeeper未授权访问漏洞确认与修复
  2. 微信小程序python解析获取用户手机号_微信小程序获取用户手机号
  3. 影响亚马逊review排名的因素有哪些?
  4. 仙人掌相关问题的解法(1)-DFS树解决仙人掌DP问题,圆方树
  5. Viola-Jones算法 Rapid Object Detection using a Boosted Cascade of Simple Features
  6. css 去掉i标签默认斜体样式
  7. VMtools的安装
  8. 大一寒假周总结(一)
  9. 传输原理及网管培训教材
  10. 高级网管网上培训班即将开课