论坛首页 综合技术论坛

看gen_tcp driver源码学写erlang driver

浏览 4705 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-10-06  
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方式调用的

%% 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过程中给予了非常大的帮助,呵呵
   发表时间:2009-10-06  
写的很好, 但是有个小问题。 gen_tcp的请求是通过control出去的 但是数据什么的还是通过消息回来的 原因很简单 这样才能避免阻塞。
0 请登录后投票
   发表时间:2009-10-07  
mryufeng 写道
写的很好, 但是有个小问题。 gen_tcp的请求是通过control出去的 但是数据什么的还是通过消息回来的 原因很简单 这样才能避免阻塞。


谢谢老大指点,呵呵
我看driver里面 创建完socket后就马上设置Nonblocking 了,之后的accept,read等都不会在driver里阻塞了
然后通过事件机制通知的driver,然后返回给prim_inet,这段还没弄明白,我研究下先,有不明白的再向老大请教





0 请登录后投票
   发表时间:2010-05-19  
请问能否详细介绍一下您上面说的两种方法A,B的方法,并在效率以及它们的异同吗?亟需求解!
0 请登录后投票
   发表时间:2010-05-20  
B的效率高好多 但是是阻塞的,写不好会影响调度器的运作。
现在还可以有NIF可以选择. R14A的crypto库已经由port_control改成了NIF!
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics