`
bachmozart
  • 浏览: 111654 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

看gen_tcp driver源码学写erlang driver

阅读更多
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过程中给予了非常大的帮助,呵呵
分享到:
评论
4 楼 mryufeng 2010-05-20  
B的效率高好多 但是是阻塞的,写不好会影响调度器的运作。
现在还可以有NIF可以选择. R14A的crypto库已经由port_control改成了NIF!
3 楼 xiao_maijia 2010-05-19  
请问能否详细介绍一下您上面说的两种方法A,B的方法,并在效率以及它们的异同吗?亟需求解!
2 楼 bachmozart 2009-10-07  
mryufeng 写道
写的很好, 但是有个小问题。 gen_tcp的请求是通过control出去的 但是数据什么的还是通过消息回来的 原因很简单 这样才能避免阻塞。


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





1 楼 mryufeng 2009-10-06  
写的很好, 但是有个小问题。 gen_tcp的请求是通过control出去的 但是数据什么的还是通过消息回来的 原因很简单 这样才能避免阻塞。

相关推荐

    erlang-gen_tcp手册

    erlang-gen_tcp手册,详细完整,网络tcp开发好东东

    gen_tcp的close与delay_send交叉问题

    在Erlang编程环境中,`gen_tcp`是一个用于TCP连接的通用接口,它提供了丰富的功能,如打开、接受、发送、接收和关闭TCP连接。在实际应用中,开发者可能会遇到`gen_tcp`的`close`函数与`delay_send`选项之间的交互...

    基于Erlang的gen_tcp聊天室代码,功能完整

    基于Erlang的gen_tcp库是其强大的网络编程接口,用于实现TCP协议的应用。在这个场景中,我们讨论的是一个使用Erlang和gen_tcp编写的聊天室应用。 **gen_tcp简介** gen_tcp是Erlang OTP(开放电信平台)提供的一种...

    gen_tcp_server:Erlang 应用程序的通用 TCP 服务器

    通用 TCP 服务器 通用 TCP 服务器( gen_tcp_server ) 是一种 Erlang 行为,提供快速简便的方法将 TCP 服务器功能添加到您的应用程序。 它被实现为管理 TCP 连接的主管,因为它是孩子。如何使用它? 运行make来构建。...

    gen_server tasting 之超简单名称服务(续)

    在IT行业中,`gen_server`是Erlang OTP(开放电信平台)框架中的一个核心组件,它提供了一种模式化的方式来实现服务器进程。本篇博客“gen_server tasting 之超简单名称服务(续)”主要探讨了如何使用gen_server来...

    erlang tcp_server

    4. **gen_tcp模块**:这个模块提供了处理TCP连接的函数,如`gen_tcp:connect/3`用于建立客户端连接,`gen_tcp:recv/2,3`用于接收数据,`gen_tcp:send/2`用于发送数据,以及`gen_tcp:close/1`用于关闭连接。...

    erlang 聊天室

    在本文中,我们将深入探讨如何使用Erlang构建一个简易的聊天室,主要涉及的关键技术是gen_tcp和gen_server。Erlang是一种并发性极强、面向进程的编程语言,特别适合于构建高可用性和可扩展性的分布式系统,如聊天室...

    tcp_client:一个Erlang TCP客户端连接管理器

    - 在Erlang中,我们可以使用`gen_tcp`模块来操作TCP连接,包括监听、连接、发送和接收数据,以及关闭连接。 2. **Erlang的并发模型**: - Erlang的进程模型使得每个TCP连接可以作为一个独立的进程运行,这样可以...

    otp_win64_20.3-Erlang.zip

    标题中的"otp_win64_20.3-Erlang.zip"表明这是一个包含Erlang编程语言特定版本的Windows 64位版本的压缩文件。OTP(Open Telecom Platform)是Erlang的主要实现,它是一个用于构建高度并发、容错且可扩展的系统平台...

    erlang_otp_win64_25.0

    在"erlang_otp_win64_25.0"这个标题中,我们可以提取出几个关键点: 1. **Erlang**:这是一种函数式编程语言,以其在处理并发性和容错性方面的强大能力而闻名。Erlang的设计理念是让程序员能够轻松地构建能够并行...

    esl-erlang_23.0_windows_amd64.exe rabbitmq-server-3.8.4.exe

    esl-erlang_23.0和rabbitmq-3.8.4windows版本 直接下载安装就行,可以直接下载就可安装,非常的方便 ,欢迎大家下载 注意事项: 1. Erlang版本和RabbitMQ版本要配套 (Erlang23.0, RabbitMQ3.8.4) 2. amd芯片请乖乖...

    gen_server:Erlang 的 gen_server 的(不完整的)OcamlAsync 实现

    《Erlang gen_server在OcamlAsync中的实现探索》 Erlang的gen_server是其并发模型的核心组件,它提供了一种强大的状态管理和错误处理机制。而在OCaml语言中,尽管有着自己的并发库如Async,但直接移植或模仿Erlang...

    Erlang-Formula.zip_Erlang B _Erlang-B_erlang_erlang B计算_erlang C

    Erlang B 和 Erlang C 是在电信领域中广泛使用的两个数学公式,用于预测和管理电话交换系统的呼叫处理能力。这两个公式由丹麦工程师 Agner Krarup Erlang 在20世纪初开发,对于理解通信系统中的呼叫占用率、阻塞率和...

    gen_smtp:可扩展的Erlang SMTP客户端和服务器库

    gen_smtp Erlang SMTP客户端和服务器库。 使命 提供通用的Erlang SMTP服务器框架,可以通过OTP样式的回调模块进行扩展。 还包括一个纯Erlang SMTP客户端。 目的是使在Erlang中收发电子邮件变得容易,而又省却POP / ...

    gs_tcp:gen_tcp的监听封装

    ##gs_tcp 这是我们游戏服务器,启动监听通用过程,由于项目之间共享通过复制粘贴,很难维护,我独立成一个App,然后添加一行gs_tcp:listen就可以了。主要实现是通过传一个模块,然后里面实现start_reader,然后tcp_...

    erlang OTP Design Principles之Gen中文

    Erlang OTP设计原则中的Gen_Fsm行为是一个关键的概念,用于构建健壮、可扩展的并发应用程序。Gen_Fsm,即通用有限状态机,是一种行为模式,它提供了一种结构化的方法来处理具有多种状态和事件的系统。本文将深入探讨...

    erlang_standard_snippets-源码.rar

    在"erlang_standard_snippets-源码.rar"这个压缩包中,我们可以期待找到一些Erlang编程的代码片段,这些片段可能包含了Erlang语言的标准实践和常见模式。 Erlang的设计理念是基于actor模型,每个进程(process)都...

    gen_server tasting 之超简单名称服务

    首先,我们来看看`gen_server`。它是一个行为(behaviour),定义了一组回调函数,这些函数构成了服务器的基本操作,如初始化、处理呼叫、处理Cast消息、处理定时事件等。通过实现这些回调函数,我们可以创建具有...

    Erlang程序设计_第2版_含中文版英文版_含源码_(Programming_Erlang)_Joe.Armstrong

    《Erlang程序设计》是Joe Armstrong所著的一本经典书籍,主要面向对并发编程和分布式系统感兴趣的读者。这本书的第二版提供了更深入的Erlang语言教程,以及丰富的实践案例,帮助读者理解和掌握Erlang的核心特性。...

    recvbench:gen_tcp

    用于测量Erlang中TCP数据接收性能的源代码组。 各种无组织和技术性的。 建造 $ git clone git://github.com/sile/recvbench.git $ cd recvbench $ ./rebar get-deps compile 使用例 $ erl - pz ebin deps /*/ ebin ...

Global site tag (gtag.js) - Google Analytics