Erlang 21 - 10. How to Implement a Driver

10 ड्राइवर को कैसे लागू करें




erlang

10 ड्राइवर को कैसे लागू करें

ध्यान दें

यह खंड बहुत पहले लिखा गया था। अधिकांश यह अभी भी मान्य है, क्योंकि यह महत्वपूर्ण अवधारणाओं की व्याख्या करता है, लेकिन यह एक पुराने ड्राइवर इंटरफ़ेस के लिए लिखा गया था ताकि उदाहरण अब और काम न करें। पाठक को erl_driver और driver_entry प्रलेखन पढ़ने के लिए प्रोत्साहित किया जाता है।

10.1 परिचय

यह खंड बताता है कि एरलांग के लिए अपने खुद के ड्राइवर का निर्माण कैसे करें।

एर्लांग में एक ड्राइवर सी में लिखी गई एक लाइब्रेरी है, जो एरलंग एमुलेटर से जुड़ी हुई है और एर्लैंग से कॉल की जाती है। ड्राइवर का उपयोग तब किया जा सकता है जब सी, एरलांग की तुलना में अधिक उपयुक्त हो, चीजों को गति प्रदान करने के लिए, या एरंग से सीधे सुलभ नहीं होने वाले ओएस संसाधनों तक पहुंच प्रदान करने के लिए।

एक चालक को गतिशील रूप से लोड किया जा सकता है, एक साझा पुस्तकालय (विंडोज पर एक डीएलएल के रूप में जाना जाता है), या सांख्यिकीय रूप से लोड किया जाता है, जब इसे संकलित और लिंक किया जाता है। केवल गतिशील रूप से लोड किए गए ड्राइवरों को यहां वर्णित किया गया है, सांख्यिकीय रूप से जुड़े ड्राइवर इस खंड के दायरे से परे हैं।

चेतावनी

जब कोई ड्राइवर लोड होता है तो उसे एमुलेटर के संदर्भ में निष्पादित किया जाता है, उसी मेमोरी और उसी थ्रेड को साझा करता है। इसका मतलब यह है कि चालक के सभी ऑपरेशन गैर-अवरोधक होने चाहिए, और ड्राइवर में कोई भी दुर्घटना पूरे एमुलेटर को नीचे लाती है। संक्षेप में, सावधान रहें।

10.2 नमूना चालक

यह खंड libpq C क्लाइंट लाइब्रेरी का उपयोग करके पोस्टग्रेज डेटाबेस तक पहुंचने के लिए एक साधारण ड्राइवर का वर्णन करता है। पोस्टग्रैस का उपयोग किया जाता है क्योंकि यह स्वतंत्र और खुला स्रोत है। पोस्टग्रेज की जानकारी के लिए, www.postgres.org देखें।

ड्राइवर सिंक्रोनस है, यह क्लाइंट लाइब्रेरी के सिंक्रोनस कॉल का उपयोग करता है। यह केवल सादगी के लिए है, लेकिन अच्छा नहीं है, क्योंकि यह डेटाबेस की प्रतीक्षा करते समय एमुलेटर को रोक देता है। यह एक अतुल्यकालिक नमूना चालक के साथ नीचे सुधार हुआ है।

कोड सीधा है: Erlang और ड्राइवर के बीच सभी संचार port_control/3 साथ किया जाता है, और ड्राइवर rbuf का उपयोग करके डेटा वापस rbuf

एर्लांग चालक केवल एक फ़ंक्शन का निर्यात करता है: ड्राइवर प्रविष्टि फ़ंक्शन। इसे एक मैक्रो, DRIVER_INIT साथ परिभाषित किया गया है, जो पॉइंटर को एक सी struct में लौटाता है जिसमें प्रवेश बिंदु होते हैं जो एमुलेटर से बुलाए जाते हैं। struct उन प्रविष्टियों को परिभाषित करती है जो एमुलेटर ड्राइवर को कॉल करने के लिए कॉल करती हैं, उन प्रविष्टियों के लिए एक NULL पॉइंटर होता है जो ड्राइवर द्वारा परिभाषित और उपयोग नहीं किए जाते हैं।

start एंट्री को तब कहा जाता है जब ड्राइवर को open_port/2 साथ पोर्ट के रूप में खोला जाता है। यहां हम उपयोगकर्ता डेटा संरचना के लिए मेमोरी आवंटित करते हैं। जब भी एमुलेटर हमें कॉल करता है, यह उपयोगकर्ता डेटा पास हो जाता है। पहले हम ड्राइवर हैंडल को स्टोर करते हैं, क्योंकि बाद की कॉल में इसकी आवश्यकता होती है। हम उस कनेक्शन हैंडल के लिए मेमोरी आवंटित करते हैं जिसका उपयोग LibPQ द्वारा किया जाता है। हम पोर्ट PORT_CONTROL_FLAG_BINARY सेट करके, set_port_control_flags कॉल करके, आवंटित किए गए ड्राइवर बायनेरिज़ को वापस करने के लिए पोर्ट भी सेट करते हैं। (ऐसा इसलिए है क्योंकि हम नहीं जानते हैं कि हमारा डेटा control के परिणाम बफर में फिट होगा, जिसमें एक डिफ़ॉल्ट आकार है, 64 बाइट्स, एमुलेटर द्वारा स्थापित किया गया है।)

ड्राइवर को लोड करने पर एंट्री init को कहा जाता है। हालांकि, हम इसका उपयोग नहीं करते हैं, क्योंकि यह केवल एक बार निष्पादित होता है, और हम ड्राइवर के कई उदाहरणों की संभावना चाहते हैं।

पोर्ट बंद होने पर stop एंट्री को कहा जाता है।

control प्रविष्टि को एमुलेटर से कहा जाता है जब वास्तविक कार्य करने के लिए Erlang कोड port_control/3 कॉल करता है। हमने आदेशों का एक सरल सेट निर्धारित किया है: डेटाबेस में लॉग इन करने के लिए connect करें, लॉग आउट करने के लिए disconnect select और एसक्यूएल-क्वेरी भेजने और परिणाम प्राप्त करने के लिए select । सभी परिणाम rbuf माध्यम से rbuf erl_interface में लाइब्रेरी ei का उपयोग बाइनरी टर्म फॉर्मेट में डेटा को एनकोड करने के लिए किया जाता है। परिणाम को एमुलेटर पर बाइनरी शर्तों के रूप में binary_to_term जाता है, इसलिए परिणाम को टर्म फॉर्म में बदलने के लिए binary_to_term को binary_to_term में बुलाया जाता है।

कोड pg_sync.c के sample निर्देशिका में pg_sync.c में उपलब्ध है।

ड्राइवर प्रविष्टि में फ़ंक्शन होते हैं जिन्हें एमुलेटर द्वारा बुलाया जाएगा। इस उदाहरण में, केवल start , stop और control प्रदान किया गया है:

/* Driver interface declarations */
static ErlDrvData start(ErlDrvPort port, char *command);
static void stop(ErlDrvData drv_data);
static int control(ErlDrvData drv_data, unsigned int command, char *buf, 
                   int len, char **rbuf, int rlen); 

static ErlDrvEntry pq_driver_entry = {
    NULL,                        /* init */
    start,
    stop,
    NULL,                        /* output */
    NULL,                        /* ready_input */
    NULL,                        /* ready_output */
    "pg_sync",                   /* the name of the driver */
    NULL,                        /* finish */
    NULL,                        /* handle */
    control,
    NULL,                        /* timeout */
    NULL,                        /* outputv */
    NULL,                        /* ready_async */
    NULL,                        /* flush */
    NULL,                        /* call */
    NULL                         /* event */
};

हमारे पास ड्राइवर द्वारा आवश्यक राज्य को संग्रहीत करने के लिए एक संरचना है, इस मामले में हमें केवल डेटाबेस कनेक्शन रखने की आवश्यकता है:

typedef struct our_data_s {
    PGconn* conn;
} our_data_t;

नियंत्रण कोड जिसे हमने परिभाषित किया है वह इस प्रकार है:

/* Keep the following definitions in alignment with the
 * defines in erl_pq_sync.erl
 */

#define DRV_CONNECT             'C'
#define DRV_DISCONNECT          'D'
#define DRV_SELECT              'S'

यह ड्राइवर संरचना लौटाता है। मैक्रो DRIVER_INIT केवल निर्यात किए गए फ़ंक्शन को परिभाषित करता है। अन्य सभी कार्य स्थिर हैं, और लाइब्रेरी से निर्यात नहीं किए जाएंगे।

/* INITIALIZATION AFTER LOADING */

/* 
 * This is the init function called after this driver has been loaded.
 * It must *not* be declared static. Must return the address to 
 * the driver entry.
 */

DRIVER_INIT(pq_drv)
{
    return &pq_driver_entry;
}

यहां कुछ इनिशियलाइज़ेशन किया जाता है, start को open_port से बुलाया जाता है। डेटा को control और stop लिए पारित किया जाएगा।

/* DRIVER INTERFACE */
static ErlDrvData start(ErlDrvPort port, char *command)
{ 
    our_data_t* data;

    data = (our_data_t*)driver_alloc(sizeof(our_data_t));
    data->conn = NULL;
    set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
    return (ErlDrvData)data;
}

हम डेटाबेस से लॉग आउट करने के लिए डिस्कनेक्ट कहते हैं। (यह Erlang से किया जाना चाहिए था, लेकिन सिर्फ मामले में।)

static int do_disconnect(our_data_t* data, ei_x_buff* x);

static void stop(ErlDrvData drv_data)
{
    our_data_t* data = (our_data_t*)drv_data;

    do_disconnect(data, NULL);
    driver_free(data);
}

हम एमुलेटर पर डेटा वापस करने के लिए केवल द्विआधारी प्रारूप का उपयोग करते हैं; इनपुट डेटा connect और select लिए एक स्ट्रिंग पैरामीटर है। लौटाए गए डेटा में Erlang शब्द शामिल हैं।

फ़ंक्शन get_s और ei_x_to_new_binary उपयोगिताओं हैं जो कोड को छोटा बनाने के लिए उपयोग किया जाता है। get_s स्ट्रिंग को डुप्लिकेट करता है और शून्य-टर्मिनेट करता है, क्योंकि पोस्टग्रैज क्लाइंट लाइब्रेरी यही चाहती है। ei_x_to_new_binary एक ei_x_buff बफर लेता है, एक बाइनरी आवंटित करता है, और वहां डेटा की प्रतिलिपि बनाता है। इस बाइनरी को *rbuf में लौटाया गया है। (ध्यान दें कि इस बाइनरी को एमुलेटर द्वारा मुक्त किया गया है, हमारे द्वारा नहीं।)

static char* get_s(const char* buf, int len);
static int do_connect(const char *s, our_data_t* data, ei_x_buff* x);
static int do_select(const char* s, our_data_t* data, ei_x_buff* x);

/* As we are operating in binary mode, the return value from control
 * is irrelevant, as long as it is not negative.
 */
static int control(ErlDrvData drv_data, unsigned int command, char *buf, 
                   int len, char **rbuf, int rlen)
{
    int r;
    ei_x_buff x;
    our_data_t* data = (our_data_t*)drv_data;
    char* s = get_s(buf, len);
    ei_x_new_with_version(&x);
    switch (command) {
        case DRV_CONNECT:    r = do_connect(s, data, &x);  break;
        case DRV_DISCONNECT: r = do_disconnect(data, &x);  break;
        case DRV_SELECT:     r = do_select(s, data, &x);   break;
        default:             r = -1;        break;
    }
    *rbuf = (char*)ei_x_to_new_binary(&x);
    ei_x_free(&x);
    driver_free(s);
    return r;
}

do_connect वह जगह है जहां हम डेटाबेस में लॉग इन करते हैं। यदि कनेक्शन सफल था, तो हम ड्राइवर डेटा में कनेक्शन हैंडल को स्टोर करते हैं, और 'ok' लौटाते हैं। अन्यथा, हम पोस्टग्रेज से त्रुटि संदेश लौटाते हैं और चालक डेटा में NULL को संग्रहीत करते हैं।

static int do_connect(const char *s, our_data_t* data, ei_x_buff* x)
{
    PGconn* conn = PQconnectdb(s);
    if (PQstatus(conn) != CONNECTION_OK) {
        encode_error(x, conn);
        PQfinish(conn);
        conn = NULL;
    } else {
        encode_ok(x);
    }
    data->conn = conn;
    return 0;
}

यदि हम जुड़े हुए हैं (और यदि कनेक्शन हैंडल NULL नहीं NULL ), तो हम डेटाबेस से लॉग आउट करते हैं। हमें यह जांचने की आवश्यकता है कि क्या हमें 'ok' एनकोड करना चाहिए, क्योंकि हम फंक्शन stop से यहां पहुंच सकते हैं, जो एमुलेटर पर डेटा वापस नहीं करता है:

static int do_disconnect(our_data_t* data, ei_x_buff* x)
{
    if (data->conn == NULL)
        return 0;
    PQfinish(data->conn);
    data->conn = NULL;
    if (x != NULL)
        encode_ok(x);
    return 0;
}

हम एक क्वेरी निष्पादित करते हैं और परिणाम को एनकोड करते हैं। एन्कोडिंग एक और सी मॉड्यूल, pg_encode.c में किया जाता है, जो नमूना कोड के रूप में भी प्रदान किया जाता है।

static int do_select(const char* s, our_data_t* data, ei_x_buff* x)
{
   PGresult* res = PQexec(data->conn, s);
    encode_result(x, res, data->conn);
    PQclear(res);
    return 0;
}

यहां हम पोस्टग्रेज से रिजल्ट चेक करते हैं। यदि यह डेटा है, तो हम इसे कॉलम डेटा वाली सूचियों की सूची के रूप में एन्कोड करते हैं। पोस्टग्रेज से सब कुछ C स्ट्रिंग्स है, इसलिए हम परिणाम के लिए ei_x_encode_string के रूप में भेजने के लिए ei_x_encode_string का उपयोग करते हैं। (सूची के प्रमुख में कॉलम नाम शामिल हैं।)

void encode_result(ei_x_buff* x, PGresult* res, PGconn* conn)
{
    int row, n_rows, col, n_cols;
    switch (PQresultStatus(res)) {
    case PGRES_TUPLES_OK: 
        n_rows = PQntuples(res); 
        n_cols = PQnfields(res); 
        ei_x_encode_tuple_header(x, 2);
        encode_ok(x);
        ei_x_encode_list_header(x, n_rows+1);
        ei_x_encode_list_header(x, n_cols);
        for (col = 0; col < n_cols; ++col) {
            ei_x_encode_string(x, PQfname(res, col));
        }
        ei_x_encode_empty_list(x); 
        for (row = 0; row < n_rows; ++row) {
            ei_x_encode_list_header(x, n_cols);
            for (col = 0; col < n_cols; ++col) {
                ei_x_encode_string(x, PQgetvalue(res, row, col));
            }
            ei_x_encode_empty_list(x);
        }
        ei_x_encode_empty_list(x); 
        break; 
    case PGRES_COMMAND_OK:
        ei_x_encode_tuple_header(x, 2);
        encode_ok(x);
        ei_x_encode_string(x, PQcmdTuples(res));
        break;
    default:
        encode_error(x, conn);
        break;
    }
}

10.3 नमूना चालक को संकलित करना और जोड़ना

ड्राइवर को संकलित किया जाना है और एक साझा पुस्तकालय (विंडोज़ पर DLL) से जोड़ा जाना है। -fpic साथ, यह लिंक फ्लैग -fpic और -fpic साथ किया जाता है। जैसा कि हम ei लाइब्रेरी का उपयोग करते हैं, हमें इसे भी शामिल करना चाहिए। डीबग या गैर-डीबग और बहु-थ्रेडेड या एकल-थ्रेडेड के लिए संकलित ei कई संस्करण हैं। नमूने के लिए obj , obj डायरेक्टरी का उपयोग ei लाइब्रेरी के लिए किया जाता है, जिसका अर्थ है कि हम गैर-डिबग, एकल-थ्रेडेड संस्करण का उपयोग करते हैं।

10.4 एर्लांग में एक ड्राइवर को पोर्ट के रूप में कॉल करना

इससे पहले कि किसी ड्राइवर को एरलैंग से बुलाया जा सके, उसे लोड करके खोला जाना चाहिए। लोडिंग erl_ddll मॉड्यूल ( erl_ddll ड्राइवर जो डायनेमिक ड्राइवर लोड करता है, वास्तव में स्वयं ड्राइवर है) का उपयोग करके किया जाता है। यदि लोडिंग सफल है, तो पोर्ट को open_port/2 खोला जा सकता है। पोर्ट नाम साझा लाइब्रेरी के नाम और ड्राइवर प्रविष्टि संरचना में नाम से मेल खाना चाहिए।

जब पोर्ट खोला गया है, तो ड्राइवर को बुलाया जा सकता है। pg_sync उदाहरण में, हमारे पास पोर्ट से कोई डेटा नहीं है, केवल port_control से रिटर्न वैल्यू है।

निम्नलिखित कोड सिंक्रोनस पोस्टग्रेज ड्राइवर, pg_sync.erl :

-module(pg_sync).

-define(DRV_CONNECT, 1).
-define(DRV_DISCONNECT, 2).
-define(DRV_SELECT, 3).

-export([connect/1, disconnect/1, select/2]).

connect(ConnectStr) ->
    case erl_ddll:load_driver(".", "pg_sync") of
        ok -> ok;
        {error, already_loaded} -> ok;
        E -> exit({error, E})
    end,
    Port = open_port({spawn, ?MODULE}, []),
    case binary_to_term(port_control(Port, ?DRV_CONNECT, ConnectStr)) of
        ok -> {ok, Port};
        Error -> Error
    end.

disconnect(Port) ->
    R = binary_to_term(port_control(Port, ?DRV_DISCONNECT, "")),
    port_close(Port),
    R.

select(Port, Query) ->
    binary_to_term(port_control(Port, ?DRV_SELECT, Query)).

एपीआई सरल है:

  • connect/1 ड्राइवर को लोड करता है, इसे खोलता है, और डेटाबेस में लॉग ऑन करता है, यदि सफल हो तो एर्लैंग पोर्ट लौटाता है।

  • select/2 ड्राइवर को एक क्वेरी भेजता है और परिणाम देता है।

  • disconnect/1 डेटाबेस कनेक्शन और ड्राइवर को बंद कर देता है। (हालांकि, यह इसे अनलोड नहीं करता है।)

कनेक्शन स्ट्रिंग पोस्टग्रेज के लिए कनेक्शन स्ट्रिंग होना है।

ड्राइवर को erl_ddll:load_driver/2 साथ लोड किया गया है। यदि यह सफल है, या यदि यह पहले से लोड है, तो इसे खोला जाता है। इसे ड्राइवर में start फंक्शन कहेंगे।

हम ड्राइवर में सभी कॉल के लिए port_control/3 फ़ंक्शन का उपयोग करते हैं। ड्राइवर से परिणाम तुरंत लौटाया जाता है और binary_to_term/1 कॉल करके शर्तों में बदल दिया जाता है। (हमें भरोसा है कि ड्राइवर से लौटी शर्तें अच्छी तरह से बनाई गई हैं, अन्यथा binary_to_term कॉल एक catch में निहित हो सकती हैं।)

10.5 नमूना अतुल्यकालिक चालक

कभी-कभी डेटाबेस क्वेरीज़ को पूरा होने में लंबा समय लग सकता है, हमारे pg_sync ड्राइवर में, एमुलेटर pg_sync जब ड्राइवर अपना काम कर रहा होता है। यह अक्सर स्वीकार्य नहीं है, क्योंकि किसी भी अन्य एरलंग प्रक्रिया को कुछ भी करने का मौका नहीं मिलता है। अपने पोस्टग्रेज ड्राइवर पर सुधार करने के लिए, हम इसे LibPQ में एसिंक्रोनस कॉल का उपयोग करके फिर से लागू करते हैं।

ड्राइवर का अतुल्यकालिक संस्करण नमूना फाइलों pg_async.c और pg_asyng.erl

/* Driver interface declarations */
static ErlDrvData start(ErlDrvPort port, char *command);
static void stop(ErlDrvData drv_data);
static int control(ErlDrvData drv_data, unsigned int command, char *buf, 
                   int len, char **rbuf, int rlen); 
static void ready_io(ErlDrvData drv_data, ErlDrvEvent event);

static ErlDrvEntry pq_driver_entry = {
    NULL,                     /* init */
    start, 
    stop, 
    NULL,                     /* output */
    ready_io,                 /* ready_input */
    ready_io,                 /* ready_output */ 
    "pg_async",               /* the name of the driver */
    NULL,                     /* finish */
    NULL,                     /* handle */
    control, 
    NULL,                     /* timeout */
    NULL,                     /* outputv */
    NULL,                     /* ready_async */
    NULL,                     /* flush */
    NULL,                     /* call */
    NULL                      /* event */
};

typedef struct our_data_t {
    PGconn* conn;
    ErlDrvPort port;
    int socket;
    int connecting;
} our_data_t;

pg_sync.c से कुछ चीजें बदल गई हैं: हम ready_input और ready_io लिए प्रविष्टि ready_io उपयोग करते हैं, जिसे केवल सॉकेट से पढ़ने के लिए इनपुट होने पर एमुलेटर से बुलाया जाता है। (वास्तव में, सॉकेट का उपयोग एमुलेटर के अंदर एक select फ़ंक्शन में किया जाता है, और जब सॉकेट को सिग्नल किया जाता है, तो यह दर्शाता है कि पढ़ने के लिए डेटा है, ready_input प्रविष्टि को कहा जाता है। इसके बारे में और नीचे देखें।)

हमारा ड्राइवर डेटा भी बढ़ाया गया है, हम पोस्टग्रेज के साथ संचार के लिए उपयोग किए जाने वाले सॉकेट का ट्रैक रखते हैं, और पोर्ट भी, जिसकी आवश्यकता तब होती है जब हम driver_output साथ पोर्ट पर डेटा भेजते हैं। हमारे पास यह बताने के लिए एक ध्वज connecting है कि क्या चालक कनेक्शन के लिए इंतजार कर रहा है या क्वेरी के परिणाम की प्रतीक्षा कर रहा है। (यह आवश्यक है, क्योंकि प्रविष्टि के लिए ready_io को कनेक्ट करते समय और जब कोई क्वेरी परिणाम होता है, दोनों कहा जाता है।)

static int do_connect(const char *s, our_data_t* data)
{
    PGconn* conn = PQconnectStart(s);
    if (PQstatus(conn) == CONNECTION_BAD) {
        ei_x_buff x;
        ei_x_new_with_version(&x);
        encode_error(&x, conn);
        PQfinish(conn);
        conn = NULL;
        driver_output(data->port, x.buff, x.index);
        ei_x_free(&x);
    }
    PQconnectPoll(conn);
    int socket = PQsocket(conn);
    data->socket = socket;
    driver_select(data->port, (ErlDrvEvent)socket, DO_READ, 1);
    driver_select(data->port, (ErlDrvEvent)socket, DO_WRITE, 1);
    data->conn = conn;
    data->connecting = 1;
    return 0;
}

connect फ़ंक्शन थोड़ा अलग भी दिखता है। हम एसिंक्रोनस PQconnectStart फ़ंक्शन का उपयोग करके कनेक्ट करते हैं। कनेक्शन शुरू होने के बाद, हम PQsocket साथ कनेक्शन के लिए सॉकेट को पुनः प्राप्त करते हैं। कनेक्शन के लिए प्रतीक्षा करने के लिए इस सॉकेट का उपयोग driver_select फ़ंक्शन के साथ किया जाता है। जब सॉकेट इनपुट के लिए या आउटपुट के लिए तैयार होता है, तो ready_io फ़ंक्शन को कहा जाता है।

ध्यान दें कि हम केवल डेटा ( driver_output साथ) वापस करते हैं यदि यहां कोई त्रुटि है, अन्यथा हम कनेक्शन पूरा होने की प्रतीक्षा करते हैं, जिस स्थिति में हमारा ready_io फ़ंक्शन कहा जाता है।

static int do_select(const char* s, our_data_t* data)
{
    data->connecting = 0;
    PGconn* conn = data->conn;
    /* if there's an error return it now */
    if (PQsendQuery(conn, s) == 0) {
        ei_x_buff x;
        ei_x_new_with_version(&x);
        encode_error(&x, conn);
        driver_output(data->port, x.buff, x.index);
        ei_x_free(&x);
    }
    /* else wait for ready_output to get results */
    return 0;
}

do_select फ़ंक्शन एक चयन आरंभ करता है, और यदि कोई तत्काल त्रुटि नहीं है, तो वापस लौटाता है। परिणाम तैयार होने पर लौटाया जाता है।

static void ready_io(ErlDrvData drv_data, ErlDrvEvent event)
{
    PGresult* res = NULL;
    our_data_t* data = (our_data_t*)drv_data;
    PGconn* conn = data->conn;
    ei_x_buff x;
    ei_x_new_with_version(&x);
    if (data->connecting) {
        ConnStatusType status;
        PQconnectPoll(conn);
        status = PQstatus(conn);
        if (status == CONNECTION_OK)
            encode_ok(&x);
        else if (status == CONNECTION_BAD)
            encode_error(&x, conn);
    } else {
        PQconsumeInput(conn);
        if (PQisBusy(conn))
            return;
        res = PQgetResult(conn);
        encode_result(&x, res, conn);
        PQclear(res);
        for (;;) {
            res = PQgetResult(conn);
            if (res == NULL)
                break;
            PQclear(res);
        }
    }
    if (x.index > 1) {
        driver_output(data->port, x.buff, x.index);
        if (data->connecting) 
            driver_select(data->port, (ErlDrvEvent)data->socket, DO_WRITE, 0);
    }
    ei_x_free(&x);
}

ready_io फंक्शन को कहा जाता है, जब हमें पोस्टग्रेट्स से जो सॉकेट मिला है वह इनपुट या आउटपुट के लिए तैयार है। यहां हम पहले चेक करते हैं कि क्या हम डेटाबेस से कनेक्ट कर रहे हैं। उस स्थिति में, हम कनेक्शन की स्थिति की जांच करते हैं और यदि कनेक्शन सफल है, या त्रुटि है तो ठीक है। यदि कनेक्शन अभी तक स्थापित नहीं है, तो हम बस वापस आ जाते हैं; ready_io को फिर से बुलाया जाता है।

यदि हमारे पास x बफर में डेटा होने से इंगित एक कनेक्ट से परिणाम होता है, तो हमें अब आउटपुट ( ready_output ) पर चयन करने की आवश्यकता नहीं है, इसलिए हम driver_select को कॉल करके इसे हटा देते हैं।

यदि हम कनेक्ट नहीं कर रहे हैं, तो हम एक PQsendQuery से परिणाम की प्रतीक्षा करते हैं, इसलिए हमें परिणाम मिलता है और इसे वापस लौटाता है। एन्कोडिंग उसी कार्य के साथ किया जाता है जैसे कि पहले उदाहरण में।

त्रुटि हैंडलिंग को यहां जोड़ा जाना है, उदाहरण के लिए, यह जांचना कि सॉकेट अभी भी खुला है, लेकिन यह केवल एक सरल उदाहरण है।

एसिंक्रोनस ड्राइवर के pg_async.erl भाग में नमूना फ़ाइल pg_async.erl

-module(pg_async).

-define(DRV_CONNECT, $C).
-define(DRV_DISCONNECT, $D).
-define(DRV_SELECT, $S).

-export([connect/1, disconnect/1, select/2]).

connect(ConnectStr) ->
    case erl_ddll:load_driver(".", "pg_async") of
        ok -> ok;
        {error, already_loaded} -> ok;
        _ -> exit({error, could_not_load_driver})
    end,
    Port = open_port({spawn, ?MODULE}, [binary]),
    port_control(Port, ?DRV_CONNECT, ConnectStr),
    case return_port_data(Port) of
        ok -> 
            {ok, Port};
        Error ->
            Error
    end.    

disconnect(Port) ->
    port_control(Port, ?DRV_DISCONNECT, ""),
    R = return_port_data(Port),
    port_close(Port),
    R.

select(Port, Query) ->
    port_control(Port, ?DRV_SELECT, Query),
    return_port_data(Port).

return_port_data(Port) ->
    receive
        {Port, {data, Data}} ->
            binary_to_term(Data)
    end.

Erlang कोड थोड़ा अलग है, क्योंकि हम परिणाम को port_control से समकालिक रूप से वापस नहीं करते हैं, इसके बजाय हम इसे driver_output कतार में डेटा के रूप में driver_output से प्राप्त करते हैं। फ़ंक्शन return_port_data ऊपर पोर्ट से डेटा प्राप्त करता है। जैसा कि डेटा बाइनरी प्रारूप में है, हम इसे Erlang शब्द में बदलने के लिए binary_to_term/1 का उपयोग करते हैं। ध्यान दें कि ड्राइवर को बाइनरी मोड में खोला गया है ( open_port/2 को विकल्प [binary] ) के साथ कहा जाता है। इसका मतलब है कि ड्राइवर से एमुलेटर पर भेजे गए डेटा को बायनेरिज़ के रूप में भेजा जाता है। विकल्प binary बिना, वे पूर्णांकों की सूची में होंगे।

10.6 एक एसिंक्रोनस चालक Driver_async का उपयोग कर रहा है

अंतिम उदाहरण के रूप में हम driver_async का उपयोग प्रदर्शित करते हैं। हम ड्राइवर शब्द इंटरफ़ेस का भी उपयोग करते हैं। ड्राइवर C ++ में लिखा गया है। यह हमें STL से एक एल्गोरिथ्म का उपयोग करने में सक्षम बनाता है। हम पूर्णांक की सूची का अगला क्रमांकन प्राप्त करने के लिए next_permutation एल्गोरिथ्म का उपयोग करते हैं। बड़ी सूचियों (> 100,000 तत्वों) के लिए, इसमें कुछ समय लगता है, इसलिए हम इसे एक अतुल्यकालिक कार्य के रूप में निष्पादित करते हैं।

ड्राइवरों के लिए अतुल्यकालिक एपीआई जटिल है। सबसे पहले, काम तैयार किया जाना चाहिए। उदाहरण में, यह output में किया जाता है। हम control उपयोग कर सकते थे, लेकिन हम उदाहरणों में कुछ बदलाव चाहते हैं। हमारे ड्राइवर में, हम एक संरचना आवंटित करते हैं जिसमें कुछ भी होता है जो काम करने के लिए अतुल्यकालिक कार्य के लिए आवश्यक होता है। यह मुख्य एमुलेटर थ्रेड में किया जाता है। फिर एसिंक्रोनस फ़ंक्शन को ड्राइवर थ्रेड से बुलाया जाता है, मुख्य एमुलेटर थ्रेड से अलग होता है। ध्यान दें कि ड्राइवर फ़ंक्शंस फिर से प्रवेश नहीं करते हैं, इसलिए उनका उपयोग नहीं किया जाना है। अंत में, फ़ंक्शन पूरा होने के बाद, ड्राइवर कॉलबैक ready_async को मुख्य एमुलेटर थ्रेड से बुलाया जाता है, यह वह जगह है जहां हम परिणाम को ready_async को वापस कर देते हैं। (हम अतुल्यकालिक फ़ंक्शन के भीतर से परिणाम नहीं लौटा सकते, क्योंकि हम ड्राइवर फ़ंक्शन को कॉल नहीं कर सकते हैं।)

निम्न कोड नमूना फ़ाइल next_perm.cc । ड्राइवर प्रविष्टि पहले की तरह दिखती है, लेकिन इसमें कॉलबैक ready_async भी शामिल है।

static ErlDrvEntry next_perm_driver_entry = {
    NULL,                        /* init */
    start,
    NULL,                        /* stop */
    output,
    NULL,                        /* ready_input */
    NULL,                        /* ready_output */ 
    "next_perm",                 /* the name of the driver */
    NULL,                        /* finish */
    NULL,                        /* handle */
    NULL,                        /* control */
    NULL,                        /* timeout */
    NULL,                        /* outputv */
    ready_async,
    NULL,                        /* flush */
    NULL,                        /* call */
    NULL                         /* event */
};

output फ़ंक्शन एसिंक्रोनस फ़ंक्शन के कार्य क्षेत्र को आवंटित करता है। जैसा कि हम C ++ का उपयोग करते हैं, हम एक संरचना का उपयोग करते हैं, और इसमें डेटा को भरते हैं। हमें मूल डेटा को कॉपी करना चाहिए, output फ़ंक्शन से वापस आने के बाद यह मान्य नहीं है, और do_perm फ़ंक्शन को बाद में, और दूसरे थ्रेड से कहा जाता है। हम यहां कोई डेटा नहीं लौटाते हैं, इसके बजाय इसे बाद में ready_async कॉलबैक से भेजा जाता है।

async_data do_perm फ़ंक्शन को पास किया do_perm है। हम async_free फ़ंक्शन ( async_free लिए अंतिम तर्क) का उपयोग नहीं करते हैं, इसका उपयोग केवल तभी किया जाता है यदि कार्य को प्रोग्रामेटिक रूप से रद्द कर दिया गया हो।

struct our_async_data {
    bool prev;
    vector<int> data;
    our_async_data(ErlDrvPort p, int command, const char* buf, int len);
};

our_async_data::our_async_data(ErlDrvPort p, int command,
                               const char* buf, int len)
    : prev(command == 2),
      data((int*)buf, (int*)buf + len / sizeof(int))
{
}

static void do_perm(void* async_data);

static void output(ErlDrvData drv_data, char *buf, int len)
{
    if (*buf < 1 || *buf > 2) return;
    ErlDrvPort port = reinterpret_cast<ErlDrvPort>(drv_data);
    void* async_data = new our_async_data(port, *buf, buf+1, len);
    driver_async(port, NULL, do_perm, async_data, do_free);
}

do_perm हम वह कार्य करते हैं, जो उस संरचना पर काम करता है जिसे output में आवंटित किया गया था।

static void do_perm(void* async_data)
{
    our_async_data* d = reinterpret_cast<our_async_data*>(async_data);
    if (d->prev)
        prev_permutation(d->data.begin(), d->data.end());
    else
        next_permutation(d->data.begin(), d->data.end());
}

ready_async फ़ंक्शन में आउटपुट को एमुलेटर पर वापस भेजा जाता है। हम ei बजाय ड्राइवर शब्द प्रारूप का उपयोग करते हैं। binary_to_term/1 पर कॉल करने के लिए binary_to_term/1 कोड न होने पर, सीधे एक ड्राइवर को binary_to_term/1 शब्द भेजने का एकमात्र तरीका है। सरल उदाहरण में यह अच्छी तरह से काम करता है, और हमें द्विआधारी शब्द प्रारूप को संभालने के लिए ei का उपयोग करने की आवश्यकता नहीं है।

जब डेटा वापस आ जाता है, तो हम अपना डेटा डील करते हैं।

static void ready_async(ErlDrvData drv_data, ErlDrvThreadData async_data)
{
    ErlDrvPort port = reinterpret_cast<ErlDrvPort>(drv_data);
    our_async_data* d = reinterpret_cast<our_async_data*>(async_data);
    int n = d->data.size(), result_n = n*2 + 3;
    ErlDrvTermData *result = new ErlDrvTermData[result_n], *rp = result;
    for (vector<int>::iterator i = d->data.begin();
         i != d->data.end(); ++i) {
        *rp++ = ERL_DRV_INT;
        *rp++ = *i;
    }
    *rp++ = ERL_DRV_NIL;
    *rp++ = ERL_DRV_LIST;
    *rp++ = n+1;
    driver_output_term(port, result, result_n);    
    delete[] result;
    delete d;
}

इस ड्राइवर को एर्लैंग के अन्य लोगों की तरह कहा जाता है। हालाँकि, जैसा कि हम driver_output_term उपयोग driver_output_term , driver_output_term को कॉल करने की कोई आवश्यकता नहीं है। Erlang कोड नमूना फ़ाइल next_perm.erl

इनपुट को पूर्णांकों की सूची में बदल दिया जाता है और ड्राइवर को भेज दिया जाता है।

-module(next_perm).

-export([next_perm/1, prev_perm/1, load/0, all_perm/1]).

load() ->
    case whereis(next_perm) of
        undefined ->
            case erl_ddll:load_driver(".", "next_perm") of
                ok -> ok;
                {error, already_loaded} -> ok;
                E -> exit(E)
            end,
            Port = open_port({spawn, "next_perm"}, []),
            register(next_perm, Port);
        _ ->
            ok
    end.

list_to_integer_binaries(L) ->
    [<<I:32/integer-native>> || I <- L].

next_perm(L) ->
    next_perm(L, 1).

prev_perm(L) ->
    next_perm(L, 2).

next_perm(L, Nxt) ->
    load(),
    B = list_to_integer_binaries(L),
    port_control(next_perm, Nxt, B),
    receive
        Result ->
            Result
    end.

all_perm(L) ->
    New = prev_perm(L),
    all_perm(New, L, [New]).

all_perm(L, L, Acc) ->
    Acc;
all_perm(L, Orig, Acc) ->
    New = prev_perm(L),
    all_perm(New, Orig, [New | Acc]).