浏览 4705 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-10-06
A. 外部port,外部程序通过stdin,stdout与erlang交互 B. linkin driver 是erlang 直接调用的方式 从效率上看,当然是B的效率最好,一直觉得这个东西是非常有用的,以前也有些idea,请教过yufeng老大,结果被教育了,所以一直不敢乱用,打算从gen_tcp开始学习driver的编写 从源码上看,gen_tcp 模块在erlang这边只是很薄的一层API,最终都是通过prim_inet.erl调用的inet_drv.c (我只关心了IPv4的实现) erlang调用 driver大体也有2种方法 A. port_command 或者是 port ! message的方式 这种方式的调用,返回值需要通过receive方法得到 B. port_control 这种方式的调用,可以直接得到返回值,manual上讲这种方式也是最快的 gen_tcp里大部分API是通过B方式调用的 %% Control command ctl_cmd(Port, Cmd, Args) -> ?DBG_FORMAT("prim_inet:ctl_cmd(~p, ~p, ~p)~n", [Port,Cmd,Args]), Result = try erlang:port_control(Port, Cmd, Args) of [?INET_REP_OK|Reply] -> {ok,Reply}; [?INET_REP_SCTP] -> {error,sctp_reply}; [?INET_REP_ERROR|Err] -> {error,list_to_atom(Err)} catch error:_ -> {error,einval} end, ?DBG_FORMAT("prim_inet:ctl_cmd() -> ~p~n", [Result]), Result. 这段方法就是prim_inet里封装port调用及返回值的代码,调用driver里对应的是 /* TCP requests from Erlang */ static int tcp_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, int len, char** rbuf, int rsize) { tcp_descriptor* desc = (tcp_descriptor*)e; switch(cmd) { case INET_REQ_OPEN: 。。。 } } 能够看到driver通过erlang传过来的cmd 来区分不同的请求 这个方法的参数含义具体如下: cmd 就是 erlang 调用port_control的第2个参数cmd buf 是erlang 调用port_control的第3个参数args len 是erlang传递args的长度 rbuf 是返回值buf rsize 是这个用于返回的buf的初始大小 driver里对返回值处理也是通用的一个函数 /* general control reply function */ static int ctl_reply(int rep, char* buf, int len, char** rbuf, int rsize) { char* ptr; if ((len+1) > rsize) { ptr = ALLOC(len+1); *rbuf = ptr; } else ptr = *rbuf; *ptr++ = rep; memcpy(ptr, buf, len); return len+1; } 也就是说,返回格式就是 resp code(1字节) + resp msg(len字节)的格式 然后拷贝buf到返回值rbuf中,rsize会提示当前返回值rbuf的初始大小,driver根据需要来重新alloc这个rbuf 写个简单的例子,印证下 example_drv.c #include <stdio.h> #include <strings.h> #include <stdarg.h> #include <time.h> #include <stdlib.h> #include "erl_driver.h" #include <ei.h> typedef struct { ErlDrvPort port; } example_data; static void logmsg(char *fmt, ...){ va_list args; FILE *logfile=fopen("./driver.log","a+"); if(logfile!=NULL){ time_t now=time(NULL); struct tm *now_tm=localtime(&now); char date[80]; strftime(date,sizeof(date),"%D %H:%M:%S",now_tm); fprintf(logfile,"[%s] ",date); va_start(args,fmt); vfprintf(logfile,fmt,args); va_end(args); fclose(logfile); } } static ErlDrvData example_drv_start(ErlDrvPort port, char *buff) { example_data* d = (example_data*)driver_alloc(sizeof(example_data)); d->port = port; return (ErlDrvData)d; } static void example_drv_stop(ErlDrvData handle) { driver_free((char*)handle); } static void example_drv_output(ErlDrvData handle, char *buff, int bufflen) { char *str; str=(char *)malloc(sizeof(*str)*bufflen+1); strcpy(str,buff); ei_x_buff x; ei_x_new_with_version(&x); ei_x_encode_tuple_header(&x, 2); ei_x_encode_atom(&x, "echoback"); ei_x_encode_string(&x, str); example_data* d = (example_data*)handle; logmsg("output successful\n"); driver_output(d->port, x.buff,x.index ); } static int port_ctl(ErlDrvData handle, unsigned int cmd, char* buf, int len, char** rbuf, int rsize){ logmsg("request cmd=%d,buf=%s,len=%d,rsize=%d\n",cmd,buf,len,rsize); char *ptr=*rbuf; char *s="port ctl back"; strcpy(ptr,s); return strlen(s); } ErlDrvEntry example_driver_entry = { NULL, example_drv_start, example_drv_stop, example_drv_output, NULL, NULL, "example_drv", NULL, NULL, port_ctl, NULL, NULL }; DRIVER_INIT(example_drv) /* must match name in driver_entry */ { return &example_driver_entry; } logmsg用于打印在driver中的日志 test_port.erl -module(test_port). -export([start/1, init/1,test/1,ctl/2]). start(SharedLib) -> case erl_ddll:load_driver(".", SharedLib) of ok -> ok; {error, already_loaded} -> ok; R -> io:format("could not load driver ~p~n",[R]), exit({error, could_not_load_driver}) end, spawn(?MODULE, init, [SharedLib]). init(SharedLib) -> register(complex, self()), Port = open_port({spawn, SharedLib}, [binary]), loop(Port). test(Msg) -> complex ! {call, Msg}. ctl(Cmd,Msg)-> complex ! {ctl,Cmd,Msg}. loop(Port) -> receive {call, Msg} -> Port ! {self(), {command, Msg}}, receive {Port, {data, Data}} -> io:format("recv port data ~p~n",[Data]) end, loop(Port); {ctl,Cmd,Msg}-> Resp=erlang:port_control(Port, Cmd, Msg), io:format("recv ctl data ~p~n",[Resp]), loop(Port); {'EXIT', Port, Reason} -> io:format("~p ~n", [Reason]), exit(port_terminated) end. 编译driver gcc -I/usr/local/erlang/lib/erlang/lib/erl_interface-3.5.9/include/ -I/usr/local/erlang/lib/erlang/erts-5.6.5/include/ -o example_drv.so -fpic -shared -L/usr/local/erlang/lib/erlang/lib/erl_interface-3.5.9/lib example_drv.c -lei -lerl_interface 调用 test_port:start("example_drv"). test_port:ctl(1,"hi port control"). driver打印日志: [10/06/09 14:43:50] request cmd=1,buf=hi port control,len=15,rsize=64 并且shell返回消息正常 今天就先学这些,计划每天都看一点,另外很感谢mryufeng老大总是有问必答,在我学习erlang过程中给予了非常大的帮助,呵呵 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-10-06
写的很好, 但是有个小问题。 gen_tcp的请求是通过control出去的 但是数据什么的还是通过消息回来的 原因很简单 这样才能避免阻塞。
|
|
返回顶楼 | |
发表时间:2009-10-07
mryufeng 写道 写的很好, 但是有个小问题。 gen_tcp的请求是通过control出去的 但是数据什么的还是通过消息回来的 原因很简单 这样才能避免阻塞。
谢谢老大指点,呵呵 我看driver里面 创建完socket后就马上设置Nonblocking 了,之后的accept,read等都不会在driver里阻塞了 然后通过事件机制通知的driver,然后返回给prim_inet,这段还没弄明白,我研究下先,有不明白的再向老大请教 |
|
返回顶楼 | |
发表时间:2010-05-19
请问能否详细介绍一下您上面说的两种方法A,B的方法,并在效率以及它们的异同吗?亟需求解!
|
|
返回顶楼 | |
发表时间:2010-05-20
B的效率高好多 但是是阻塞的,写不好会影响调度器的运作。
现在还可以有NIF可以选择. R14A的crypto库已经由port_control改成了NIF! |
|
返回顶楼 | |