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过程中给予了非常大的帮助,呵呵