看gen_tcp driver源码学写erlang driver

转载:http://bachmozart.iteye.com/blog/482268


erlang 与外部通信有2种方式: 

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方式调用的 

Java代码   收藏代码
  1. %% Control command  
  2. ctl_cmd(Port, Cmd, Args) ->  
  3.     ?DBG_FORMAT("prim_inet:ctl_cmd(~p, ~p, ~p)~n", [Port,Cmd,Args]),  
  4.     Result =  
  5.     try erlang:port_control(Port, Cmd, Args) of  
  6.         [?INET_REP_OK|Reply]  -> {ok,Reply};  
  7.         [?INET_REP_SCTP]  -> {error,sctp_reply};  
  8.         [?INET_REP_ERROR|Err] -> {error,list_to_atom(Err)}  
  9.     catch  
  10.         error:_               -> {error,einval}  
  11.     end,  
  12.         ?DBG_FORMAT("prim_inet:ctl_cmd() -> ~p~n", [Result]),  
  13.     Result.  


这段方法就是prim_inet里封装port调用及返回值的代码,调用driver里对应的是 
C代码   收藏代码
  1. /* TCP requests from Erlang */  
  2. static int tcp_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, int len,  
  3.             char** rbuf, int rsize)  
  4. {  
  5.     tcp_descriptor* desc = (tcp_descriptor*)e;  
  6.     switch(cmd) {  
  7.     case INET_REQ_OPEN:   
  8.         。。。  
  9.     }  
  10. }  

能够看到driver通过erlang传过来的cmd 来区分不同的请求 

这个方法的参数含义具体如下: 

cmd  就是 erlang 调用port_control的第2个参数cmd 
buf  是erlang 调用port_control的第3个参数args 
len  是erlang传递args的长度 
rbuf 是返回值buf 
rsize 是这个用于返回的buf的初始大小 

driver里对返回值处理也是通用的一个函数 
C代码   收藏代码
  1. /* general control reply function */  
  2. static int ctl_reply(int rep, char* buf, int len, char** rbuf, int rsize)  
  3. {  
  4.     char* ptr;  
  5.   
  6.     if ((len+1) > rsize) {  
  7.     ptr = ALLOC(len+1);  
  8.     *rbuf = ptr;  
  9.     }  
  10.     else  
  11.     ptr = *rbuf;  
  12.     *ptr++ = rep;  
  13.     memcpy(ptr, buf, len);  
  14.     return len+1;  
  15. }  

也就是说,返回格式就是  resp code(1字节) + resp msg(len字节)的格式 
然后拷贝buf到返回值rbuf中,rsize会提示当前返回值rbuf的初始大小,driver根据需要来重新alloc这个rbuf 

写个简单的例子,印证下 
example_drv.c 
C代码   收藏代码
  1. #include <stdio.h>  
  2. #include <strings.h>  
  3. #include <stdarg.h>  
  4. #include <time.h>  
  5. #include <stdlib.h>  
  6. #include "erl_driver.h"  
  7. #include <ei.h>  
  8.   
  9. typedef struct {  
  10.     ErlDrvPort port;  
  11. } example_data;  
  12.   
  13. static void logmsg(char *fmt, ...){  
  14.   va_list args;  
  15.   FILE *logfile=fopen("./driver.log","a+");  
  16.   if(logfile!=NULL){  
  17.     time_t now=time(NULL);  
  18.     struct tm *now_tm=localtime(&now);  
  19.     char date[80];  
  20.     strftime(date,sizeof(date),"%D %H:%M:%S",now_tm);  
  21.     fprintf(logfile,"[%s] ",date);  
  22.     va_start(args,fmt);  
  23.     vfprintf(logfile,fmt,args);  
  24.     va_end(args);  
  25.     fclose(logfile);  
  26.   }  
  27. }  
  28.   
  29. static ErlDrvData example_drv_start(ErlDrvPort port, char *buff)  
  30. {  
  31.     example_data* d = (example_data*)driver_alloc(sizeof(example_data));  
  32.     d->port = port;  
  33.     return (ErlDrvData)d;  
  34. }  
  35.   
  36. static void example_drv_stop(ErlDrvData handle)  
  37. {  
  38.     driver_free((char*)handle);  
  39. }  
  40.   
  41. static void example_drv_output(ErlDrvData handle, char *buff, int bufflen)  
  42. {  
  43.         char *str;  
  44.         str=(char *)malloc(sizeof(*str)*bufflen+1);  
  45.         strcpy(str,buff);  
  46.         ei_x_buff x;    
  47.         ei_x_new_with_version(&x);  
  48.         ei_x_encode_tuple_header(&x, 2);   
  49.         ei_x_encode_atom(&x, "echoback");            
  50.   
  51.         ei_x_encode_string(&x, str);    
  52.         example_data* d = (example_data*)handle;  
  53.         logmsg("output successful\n");  
  54.         driver_output(d->port, x.buff,x.index );  
  55.   
  56.   
  57. }  
  58. static int port_ctl(ErlDrvData handle, unsigned int cmd, char* buf, int len,  
  59.                         char** rbuf, int rsize){  
  60.         logmsg("request cmd=%d,buf=%s,len=%d,rsize=%d\n",cmd,buf,len,rsize);  
  61.         char *ptr=*rbuf;  
  62.         char *s="port ctl back";  
  63.         strcpy(ptr,s);  
  64.         return strlen(s);  
  65.   
  66. }  
  67. ErlDrvEntry example_driver_entry = {  
  68.     NULL,                         
  69.     example_drv_start,             
  70.     example_drv_stop,            
  71.     example_drv_output,          
  72.     NULL,                        
  73.     NULL,                        
  74.     "example_drv",               
  75.     NULL,  
  76.     NULL,                        
  77.     port_ctl,                
  78.     NULL,                    
  79.     NULL                     
  80. };  
  81.   
  82. DRIVER_INIT(example_drv) /* must match name in driver_entry */  
  83. {  
  84.     return &example_driver_entry;  
  85. }  


logmsg用于打印在driver中的日志 

test_port.erl 
Java代码   收藏代码
  1. -module(test_port).  
  2. -export([start/1, init/1,test/1,ctl/2]).  
  3.   
  4. start(SharedLib) ->  
  5.     case erl_ddll:load_driver(".", SharedLib) of  
  6.         ok -> ok;  
  7.         {error, already_loaded} -> ok;  
  8.         R ->  
  9.                         io:format("could not load driver ~p~n",[R]),  
  10.                         exit({error, could_not_load_driver})  
  11.     end,  
  12.     spawn(?MODULE, init, [SharedLib]).  
  13.   
  14. init(SharedLib) ->  
  15.     register(complex, self()),  
  16.     Port = open_port({spawn, SharedLib}, [binary]),  
  17.     loop(Port).  
  18.   
  19. test(Msg) ->  
  20.     complex ! {call, Msg}.  
  21.   
  22. ctl(Cmd,Msg)->  
  23.         complex ! {ctl,Cmd,Msg}.  
  24.   
  25. loop(Port) ->  
  26.     receive  
  27.         {call, Msg} ->  
  28.             Port ! {self(), {command, Msg}},  
  29.             receive  
  30.                 {Port, {data, Data}} ->  
  31.                 io:format("recv port data ~p~n",[Data])  
  32.             end,  
  33.             loop(Port);  
  34.          {ctl,Cmd,Msg}->  
  35.                 Resp=erlang:port_control(Port, Cmd, Msg),  
  36.                 io:format("recv ctl data ~p~n",[Resp]),  
  37.                 loop(Port);  
  38.         {'EXIT', Port, Reason} ->  
  39.             io:format("~p ~n", [Reason]),  
  40.             exit(port_terminated)  
  41.     end.  


编译driver 
Java代码   收藏代码
  1. 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过程中给予了非常大的帮助,呵呵

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值