热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

erlang小demo2_gen_server模拟游戏服务器进程管理

题目:在游戏中,有很多需要使用共享资源来处理的功能,这时候就需要单独管理进程来分配,并且按照请求有序执行资源分配,假设当前系统有X个资源,每个请求携带需要占用Y个资源,占用耗时Z秒




题目:在游戏中,有很多需要使用共享资源来处理的功能,这时候就需要单独管理进程来分配,并且按照请求有序执行资源分配,假设当前系统有X个资源,每个请求携带需要占用Y个资源,占用耗时Z秒



题目要求


  • 使用gen_server实现该进程管理
  • 请求资源消息,分配资源,资源不足时,返回失败,耗时结束时释放资源
  • 取消资源消息,立马释放资源

题目解析

首先我们要弄清楚需要实现的需求,有一个共享资源(所有进程都能读取到)为X数量,然后每个进程向服务器请求需要Y个资源,并且每个请求耗时Z秒。假如一个进程A向服务器请求100个资源,且服务器共享资源有1000个,那服务器分配成功后就只剩下900个资源,且下一个进程读取到的是900个资源,注意题目还有一个条件是占用耗时,比如是进程A请求100个单位的资源耗时10s,意思是10s后服务器可用的共享资源要加回这100,且第三个要求说进程可以发送提前取消资源的消息,大概就是这些。


具体实现(客户端)

我们先搭建一个gen_server的框架


整体框架

%%%-------------------------------------------------------------------
%%% @author fengshangjiong
%%% @copyright (C) 2020,
%%% @doc
%%%
%%% @end
%%% Created : 28. 十二月 2020 16:43
%%%-------------------------------------------------------------------
-module(client1).
-author("fengshangjiong").
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Request, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

首先复习一下gen_server的结构,有熟悉的start_link初始化的方法,还有同步/异步接收消息的handle_call和handle_cast方法,以及没有返回值的handle_info,终止terminate和code_change代码热换先不说

我们可以给每个申请进来的进程取个名字,比如test1,test2,需要改一个初始化模块


初始化模块

start(Id) ->
gen_server:start(?MODULE, [Id], []).
stop() ->
gen_server:call(?MODULE,stop).
init([Id]) ->
register(list_to_atom("test" ++ integer_to_list(Id)), self()),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
start_link(Id) ->
gen_server:start(?MODULE, [Id], []).

不用原有的start_link我们传入一个Id,给每个客户端申请一个新的名字

这个时候客户端的创建流程就完成了,然后是逻辑代码的编写,请求资源消息和提前取消资源的消息


业务代码

req_resourse(Req_Resources_Amount, Req_time, Name)->
gen_server:call(server1, {apply_resourse, Req_Resources_Amount, Req_time, Name}).
%% 提前取消
req_cancel(Name) ->
Reply = gen_server:call(server1, {req_cancel_early, Name}),
io:format("~p~n", [Reply]).

这里我给服务器取名就叫server1,在这里直接call就行,然后服务端编写对应的接收代码即可

到这里客户端代码其实已经没什么需要添加的,可以在handle_info里接收一个状态信息

handle_info(Msg, State) ->
io:format("~p~n",[Msg]),
{noreply, State}.

然后看看服务端对应接收


具体实现(服务端)


基本框架+初始化

start() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() ->
gen_server:call(?MODULE,stop).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init([]) ->
{ok, ets:new(?MODULE, [set, named_table])}.

初始化这里我是创建了跟文件名同名的ets,准备是用来存资源分配信息的,然后ets的第一个键我准备存系统的全局资源,这样也好符合共享的条件,所有进程过来都能读取到这个资源

然后是处理客户端的请求


处理请求

handle_call({apply_resourse,Req_Resources_Amount, Req_time, Name}, _From, Tab) ->
%%第一次进来,ets里没存系统资源有多少,就会存一下,后面进来就有系统资源数据数据了 则不存
case ets:member(Tab,admin) of
false ->
ets:insert(Tab,{admin,1000});
_ ->
ok
end,
%%读取系统资源
[{_,Resources}] = ets:lookup(Tab, admin),
Reply = case ets:lookup(Tab, Name) of
[] when Resources >= Req_Resources_Amount ->
ets:insert(Tab, {Name, Req_Resources_Amount}),
NewResources = Resources - Req_Resources_Amount,
%% ets:delete(Tab,admin),
ets:insert(Tab, {admin, NewResources}),
erlang:send_after(Req_time, self(), {realse, Name}),
{distribution_success};
[] ->
{resourse_not_enough};
[_] ->
{already_request}
end,
{reply, Reply, Tab};

这里是第一个请求处理,就是请求资源的处理,包括几种情况,在ets查找该进程是否有请求的记录,如果有就不给请求了,如果没有,还要看请求的资源是否大于当前系统的剩余资源,都符合才给请求,每个case最后一句都是返回给终端的打印消息,类似于日志,最后按照gen_server中的handle_call返回标准消息即可

注意其中有一句

erlang:send_after(Req_time, self(), {realse, Name}),

这里设置了一个定时器,是用来实现请求时间到期后,服务端释放资源的方法,时间到了服务端给自己发送一个信号,给这哥们释放了不让他继续用了,然后下面是释放资源的处理函数

handle_call({realse,Name}, _From, Tab) ->
Reply = do(Tab, Name),
Name ! {realse_success},
{reply, Reply, Tab};

以及客户端提前取消资源的函数

handle_call({req_cancel_early, Pid}, _From, Tab) ->
%% io:format("ccccccccc"),
Reply = do(Tab, Pid),
%% io:format("xxxxxxxxxxxx"),
{reply, Reply, Tab};
handle_call(stop, _From, Tab) ->
{stop, normal, stopped, Tab}.

这里的do是处理消息释放的函数,我单独抽取出来封装了,因为在第二个handle_call中调用了自己,当然不能再handle_call中调用handle_call的方法,你不死锁谁死锁,要在handle_info里处理服务端自己发送释放的消息(其实我感觉这里可以在客户端做,但是不知道咋实现)

handle_info({realse,Name}, Tab) ->
Reply = do(Tab, Name),
Name ! {realse_success},
{noreply, Tab}.

释放请求的代码

%%%===================================================================
%%% Internal functions
%%%===================================================================
do(Tab, Pid) ->
case ets:lookup(Tab, Pid) of
[_] ->
[{_,Resources}] = ets:lookup(Tab, admin),
[{_,Release_Resources}] = ets:lookup(Tab, Pid),
NewResources = Resources + Release_Resources,
ets:delete(Tab, Pid),
%% ets:delete(Tab,admin),
ets:insert(Tab, {admin, NewResources}),
{realse_success};
[] ->
%%可能被客户端提前取消,即使定时器未关闭,
%% 等定时器到点后也会走这里在ets中找不到数据返回
{already_realse}
end.

我们到这里差不多了,运行下看看,代码可能写的比较累赘,但是逻辑还是很清晰的,如果没看懂,后面有完整的代码,先看运行结果


运行结果

服务端启动->客户端启动->客户端申请资源->正常结束释放->客户端再申请资源->客户端发送提前释放信号->释放成功

GIF 2020-12-31 14-51-29

我们也可以用observer工具观察ets中的存储情况

GIF 2020-12-31 14-57-25

可以明显发现,ets中有读取和释放操作


完整代码


client1.erl

%%%-------------------------------------------------------------------
%%% @author fengshangjiong
%%% @copyright (C) 2020,
%%% @doc
%%%
%%% @end
%%% Created : 29. 十二月 2020 9:02
%%%-------------------------------------------------------------------
-module(client1).
-author("fengshangjiong").
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-export([
start/1,
start_link/1,
stop/0,
req_resourse/3,
req_cancel/1
]).
-define(SERVER, ?MODULE).
-record(state, {}).
%%题目:在游戏中,有很多需要使用共享资源来处理的功能,这时候就需要单独管理进程来分配,
%% 并且按照请求有序执行资源分配,假设当前系统有X个资源,每个请求携带需要占用Y个资源,占用耗时Z秒
%% 1) 使用gen_server实现该管理进程
%% 2) 请求资源消息, 分配资源, 资源不足时, 返回失败, 耗时结束时释放资源
%% 3) 取消资源消息, 立马释放资源
%%%===================================================================
%%% API
%%%===================================================================
start(Id) ->
gen_server:start(?MODULE, [Id], []).
stop() ->
gen_server:call(?MODULE,stop).
req_resourse(Req_Resources_Amount, Req_time, Name)->
gen_server:call(server1, {apply_resourse, Req_Resources_Amount, Req_time, Name}).
%% 提前取消
req_cancel(Name) ->
{Reply} = gen_server:call(server1, {req_cancel_early, Name}),
io:format("~p~n", [Reply]).
%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
start_link(Id) ->
gen_server:start(?MODULE, [Id], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([Id]) ->
register(list_to_atom("test" ++ integer_to_list(Id)), self()),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call(_Request, _From, State) ->
{reply, ok, State};
handle_call(stop, _From, State) ->
{stop, normal, stopped, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(_Request, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info(Msg, State) ->
io:format("~p~n",[Msg]),
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

server1.erl

%%%-------------------------------------------------------------------
%%% @author fengshangjiong
%%% @copyright (C) 2020,
%%% @doc
%%%
%%% @end
%%% Created : 29. 十二月 2020 9:04
%%%-------------------------------------------------------------------
-module(server1).
-author("fengshangjiong").
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-export([
start/0,
stop/0
]).
-define(SERVER, ?MODULE).
-record(state, {}).
%%题目:在游戏中,有很多需要使用共享资源来处理的功能,这时候就需要单独管理进程来分配,
%% 并且按照请求有序执行资源分配,假设当前系统有X个资源,每个请求携带需要占用Y个资源,占用耗时Z秒
%% 1) 使用gen_server实现该管理进程
%% 2) 请求资源消息, 分配资源, 资源不足时, 返回失败, 耗时结束时释放资源
%% 3) 取消资源消息, 立马释放资源
%%%===================================================================
%%% API
%%%===================================================================
start() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() ->
gen_server:call(?MODULE,stop).

%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([]) ->
{ok, ets:new(?MODULE, [set, named_table])}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
handle_call({apply_resourse,Req_Resources_Amount, Req_time, Name}, _From, Tab) ->
%%第一次进来,ets里没存系统资源有多少,就会存一下,后面进来就有系统资源数据数据了 则不存
case ets:member(Tab,admin) of
false ->
ets:insert(Tab,{admin,1000});
_ ->
ok
end,
%%读取系统资源
[{_,Resources}] = ets:lookup(Tab, admin),
Reply = case ets:lookup(Tab, Name) of
[] when Resources >= Req_Resources_Amount ->
ets:insert(Tab, {Name, Req_Resources_Amount}),
NewResources = Resources - Req_Resources_Amount,
%% ets:delete(Tab,admin),
ets:insert(Tab, {admin, NewResources}),
erlang:send_after(Req_time, self(), {realse, Name}),
{distribution_success};
[] ->
{resourse_not_enough};
[_] ->
{already_request}
end,
{reply, Reply, Tab};
handle_call({realse,Name}, _From, Tab) ->
Reply = do(Tab, Name),
Name ! {realse_success},
{reply, Reply, Tab};
handle_call({req_cancel_early, Pid}, _From, Tab) ->
%% io:format("ccccccccc"),
Reply = do(Tab, Pid),
%% io:format("xxxxxxxxxxxx"),
{reply, Reply, Tab};
handle_call(stop, _From, Tab) ->
{stop, normal, stopped, Tab}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
handle_cast(_Request, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_info({realse,Name}, Tab) ->
Reply = do(Tab, Name),
Name ! {realse_success},
{noreply, Tab}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, Tab) ->
io:format("im over ~p~n",[_Reason]),
ok.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
do(Tab, Pid) ->
case ets:lookup(Tab, Pid) of
[_] ->
[{_,Resources}] = ets:lookup(Tab, admin),
[{_,Release_Resources}] = ets:lookup(Tab, Pid),
NewResources = Resources + Release_Resources,
ets:delete(Tab, Pid),
%% ets:delete(Tab,admin),
ets:insert(Tab, {admin, NewResources}),
{realse_success};
[] ->
%%可能被客户端提前取消,即使定时器未关闭,
%% 等定时器到点后也会走这里在ets中找不到数据返回
{already_realse}
end.


推荐阅读
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
author-avatar
mobiledu2502861417
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有