diff options
Diffstat (limited to 'lib/stdlib/doc/src/io_protocol.xml')
-rw-r--r-- | lib/stdlib/doc/src/io_protocol.xml | 1172 |
1 files changed, 608 insertions, 564 deletions
diff --git a/lib/stdlib/doc/src/io_protocol.xml b/lib/stdlib/doc/src/io_protocol.xml index f2a669a49a..84b5f62c7f 100644 --- a/lib/stdlib/doc/src/io_protocol.xml +++ b/lib/stdlib/doc/src/io_protocol.xml @@ -23,7 +23,7 @@ </legalnotice> - <title>The Erlang I/O-protocol</title> + <title>The Erlang I/O Protocol</title> <prepared>Patrik Nyblom</prepared> <responsible></responsible> <docno></docno> @@ -34,183 +34,217 @@ <file>io_protocol.xml</file> </header> - -<p>The I/O-protocol in Erlang specifies a way for a client to communicate -with an I/O server and vice versa. The I/O server is a process that handles -the requests and performs the requested task on e.g. an IO device. The -client is any Erlang process wishing to read or write data from/to the -IO device.</p> - -<p>The common I/O-protocol has been present in OTP since the -beginning, but has been fairly undocumented and has also somewhat -evolved over the years. In an addendum to Robert Virdings rationale -the original I/O-protocol is described. This document describes the -current I/O-protocol.</p> - -<p>The original I/O-protocol was simple and flexible. Demands for spacial -and execution time efficiency has triggered extensions to the protocol -over the years, making the protocol larger and somewhat less easy to -implement than the original. It can certainly be argued that the -current protocol is too complex, but this text describes how it looks -today, not how it should have looked.</p> - -<p>The basic ideas from the original protocol still hold. The I/O server -and client communicate with one single, rather simplistic protocol and -no server state is ever present in the client. Any I/O server can be -used together with any client code and client code need not be aware -of the actual IO device the I/O server communicates with.</p> - -<section> -<title>Protocol Basics</title> - -<p>As described in Robert's paper, I/O servers and clients communicate using -<c>io_request</c>/<c>io_reply</c> tuples as follows:</p> - -<p><em>{io_request, From, ReplyAs, Request}</em><br/> -<em>{io_reply, ReplyAs, Reply}</em></p> - -<p>The client sends an <c>io_request</c> tuple to the I/O server and -the server eventually sends a corresponding <c>io_reply</c> tuple.</p> - -<list type="bulleted"> -<item><c>From</c> is the <c>pid()</c> of the client, the process which -the I/O server sends the IO reply to.</item> - -<item><c>ReplyAs</c> can be any datum and is returned in the corresponding -<c>io_reply</c>. The <seealso marker="stdlib:io">io</seealso> module monitors -the I/O server, and uses the monitor reference as the <c>ReplyAs</c> datum. -A more complicated client -could have several outstanding I/O requests to the same I/O server and -would then use different references (or something else) to differentiate among -the incoming IO replies. The <c>ReplyAs</c> element should be considered -opaque by the I/O server. Note that the <c>pid()</c> of the I/O server is not -explicitly present in the <c>io_reply</c> tuple. The reply can be sent from any -process, not necessarily the actual I/O server. The <c>ReplyAs</c> element is -the only thing that connects one I/O request with an I/O-reply.</item> - -<item><c>Request</c> and <c>Reply</c> are described below.</item> -</list> - -<p>When an I/O server receives an <c>io_request</c> tuple, it acts upon the actual -<c>Request</c> part and eventually sends an <c>io_reply</c> tuple with the corresponding -<c>Reply</c> part.</p> -</section> -<section> -<title>Output Requests</title> - -<p>To output characters on an IO device, the following <c>Request</c>s exist:</p> - -<p> -<em>{put_chars, Encoding, Characters}</em><br/> -<em>{put_chars, Encoding, Module, Function, Args}</em> -</p> -<list type="bulleted"> -<item><c>Encoding</c> is either <c>unicode</c> or <c>latin1</c>, meaning that the - characters are (in case of binaries) encoded as either UTF-8 or - ISO-latin-1 (pure bytes). A well behaved I/O server should also - return error if list elements contain integers > 255 when - <c>Encoding</c> is set to <c>latin1</c>. Note that this does not in any way tell - how characters should be put on the actual IO device or how the - I/O server should handle them. Different I/O servers may handle the - characters however they want, this simply tells the I/O server which - format the data is expected to have. In the <c>Module</c>/<c>Function</c>/<c>Args</c> - case, <c>Encoding</c> tells which format the designated function - produces. Note that byte-oriented data is simplest sent using the ISO-latin-1 - encoding.</item> - -<item>Characters are the data to be put on the IO device. If <c>Encoding</c> is - <c>latin1</c>, this is an <c>iolist()</c>. If <c>Encoding</c> is <c>unicode</c>, this is an - Erlang standard mixed Unicode list (one integer in a list per - character, characters in binaries represented as UTF-8).</item> - -<item><c>Module</c>, <c>Function</c>, and <c>Args</c> denote a function which will be called to - produce the data (like <c>io_lib:format/2</c>). <c>Args</c> is a list of arguments - to the function. The function should produce data in the given - <c>Encoding</c>. The I/O server should call the function as - <c>apply(Mod, Func, Args)</c> and will put the returned data on the IO device as if it was sent - in a <c>{put_chars, Encoding, Characters}</c> request. If the function - returns anything else than a binary or list or throws an exception, - an error should be sent back to the client.</item> -</list> - -<p>The I/O server replies to the client with an <c>io_reply</c> tuple where the <c>Reply</c> -element is one of:</p> -<p> -<em>ok</em><br/> -<em>{error, Error}</em> -</p> - -<list type="bulleted"> -<item><c>Error</c> describes the error to the client, which may do whatever - it wants with it. The Erlang <seealso marker="stdlib:io">io</seealso> - module typically returns it as is.</item> -</list> - -<p>For backward compatibility the following <c>Request</c>s should also be -handled by an I/O server (these requests should not be present after -R15B of OTP):</p> -<p> -<em>{put_chars, Characters}</em><br/> -<em>{put_chars, Module, Function, Args}</em> -</p> - -<p>These should behave as <c>{put_chars, latin1, Characters}</c> and -<c>{put_chars, latin1, Module, Function, Args}</c> respectively. </p> -</section> -<section> -<title>Input Requests</title> - -<p>To read characters from an IO device, the following <c>Request</c>s exist:</p> - -<p><em>{get_until, Encoding, Prompt, Module, Function, ExtraArgs}</em></p> - -<list type="bulleted"> -<item><c>Encoding</c> denotes how data is to be sent back to the client and - what data is sent to the function denoted by - <c>Module</c>/<c>Function</c>/<c>ExtraArgs</c>. If the function supplied returns data as a - list, the data is converted to this encoding. If however the - function supplied returns data in some other format, no conversion - can be done and it is up to the client supplied function to return - data in a proper way. If <c>Encoding</c> is <c>latin1</c>, lists of integers - 0..255 or binaries containing plain bytes are sent back to the - client when possible; if <c>Encoding</c> is <c>unicode</c>, lists with integers in - the whole Unicode range or binaries encoded in UTF-8 are sent to the - client. The user supplied function will always see lists of integers, never - binaries, but the list may contain numbers > 255 if the <c>Encoding</c> is - <c>unicode</c>.</item> - -<item><c>Prompt</c> is a list of characters (not mixed, no binaries) or an atom - to be output as a prompt for input on the IO device. <c>Prompt</c> is - often ignored by the I/O server and if set to <c>''</c> it should always - be ignored (and result in nothing being written to the IO device).</item> - -<item><p><c>Module</c>, <c>Function</c>, and <c>ExtraArgs</c> denote a function and arguments to - determine when enough data is written. The function should take two - additional arguments, the last state, and a list of characters. The - function should return one of:</p> -<p> -<em>{done, Result, RestChars}</em><br/> -<em>{more, Continuation}</em> -</p> - <p>The <c>Result</c> can be any Erlang term, but if it is a <c>list()</c>, the - I/O server may convert it to a <c>binary()</c> of appropriate format before - returning it to the client, if the I/O server is set in binary mode (see - below).</p> - - <p>The function will be called with the data the I/O server finds on - its IO device, returning <c>{done, Result, RestChars}</c> when enough data is - read (in which case <c>Result</c> is sent to the client and <c>RestChars</c> is - kept in the I/O server as a buffer for subsequent input) or - <c>{more, Continuation}</c>, indicating that more characters are needed to - complete the request. The <c>Continuation</c> will be sent as the state in - subsequent calls to the function when more characters are - available. When no more characters are available, the function - shall return <c>{done, eof, Rest}</c>. - The initial state is the empty list and the data when an - end of file is reached on the IO device is the atom <c>eof</c>. An emulation - of the <c>get_line</c> request could be (inefficiently) implemented using - the following functions:</p> -<code> + <p>The I/O protocol in Erlang enables bi-directional communication between + clients and servers.</p> + + <list type="bulleted"> + <item> + <p>The I/O server is a process that handles the requests and performs + the requested task on, for example, an I/O device.</p> + </item> + <item> + <p>The client is any Erlang process wishing to read or write data from/to + the I/O device.</p> + </item> + </list> + + <p>The common I/O protocol has been present in OTP since the beginning, but + has been undocumented and has also evolved over the years. In an + addendum to Robert Virding's rationale, the original I/O protocol is + described. This section describes the current I/O protocol.</p> + + <p>The original I/O protocol was simple and flexible. Demands for memory + efficiency and execution time efficiency have triggered extensions + to the protocol over the years, making the protocol larger and somewhat + less easy to implement than the original. It can certainly be argued that + the current protocol is too complex, but this section describes how it + looks today, not how it should have looked.</p> + + <p>The basic ideas from the original protocol still hold. The I/O server + and client communicate with one single, rather simplistic protocol and no + server state is ever present in the client. Any I/O server can be used + together with any client code, and the client code does not need to be + aware of the I/O device that the I/O server communicates with.</p> + + <section> + <title>Protocol Basics</title> + <p>As described in Robert's paper, I/O servers and clients communicate + using <c>io_request</c>/<c>io_reply</c> tuples as follows:</p> + + <pre> +{io_request, From, ReplyAs, Request} +{io_reply, ReplyAs, Reply}</pre> + + <p>The client sends an <c>io_request</c> tuple to the I/O server and the + server eventually sends a corresponding <c>io_reply</c> tuple.</p> + + <list type="bulleted"> + <item> + <p><c>From</c> is the <c>pid()</c> of the client, the process which + the I/O server sends the I/O reply to.</p> + </item> + <item> + <p><c>ReplyAs</c> can be any datum and is returned in the + corresponding <c>io_reply</c>. The + <seealso marker="stdlib:io"><c>io</c></seealso> module monitors the + the I/O server and uses the monitor reference as the <c>ReplyAs</c> + datum. A more complicated client can have many outstanding I/O + requests to the same I/O server and can use different references (or + something else) to differentiate among the incoming I/O replies. + Element <c>ReplyAs</c> is to be considered opaque by the I/O + server.</p> + <p>Notice that the <c>pid()</c> of the I/O server is not explicitly + present in tuple <c>io_reply</c>. The reply can be sent from any + process, not necessarily the actual I/O server.</p> + </item> + <item> + <p><c>Request</c> and <c>Reply</c> are described below.</p> + </item> + </list> + + <p>When an I/O server receives an <c>io_request</c> tuple, it acts upon the + <c>Request</c> part and eventually sends an <c>io_reply</c> tuple with + the corresponding <c>Reply</c> part.</p> + </section> + + <section> + <title>Output Requests</title> + <p>To output characters on an I/O device, the following <c>Request</c>s + exist:</p> + + <pre> +{put_chars, Encoding, Characters} +{put_chars, Encoding, Module, Function, Args}</pre> + + <list type="bulleted"> + <item> + <p><c>Encoding</c> is <c>unicode</c> or <c>latin1</c>, meaning that the + characters are (in case of binaries) encoded as UTF-8 or ISO Latin-1 + (pure bytes). A well-behaved I/O server is also to return an error + indication if list elements contain integers > 255 + when <c>Encoding</c> is set to <c>latin1</c>.</p> + <p>Notice that this does not in any way tell how characters are to be + put on the I/O device or handled by the I/O server. Different I/O + servers can handle the characters however they want, this only tells + the I/O server which format the data is expected to have. In the + <c>Module</c>/<c>Function</c>/<c>Args</c> case, <c>Encoding</c> tells + which format the designated function produces.</p> + <p>Notice also that byte-oriented data is simplest sent using the ISO + Latin-1 encoding.</p> + </item> + <item> + <p><c>Characters</c> are the data to be put on the I/O device. If + <c>Encoding</c> is <c>latin1</c>, this is an <c>iolist()</c>. If + <c>Encoding</c> is <c>unicode</c>, this is an Erlang standard mixed + Unicode list (one integer in a list per character, characters in + binaries represented as UTF-8).</p> + </item> + <item> + <p><c>Module</c>, <c>Function</c>, and <c>Args</c> denote a function + that is called to produce the data (like + <seealso marker="stdlib:io_lib#format/2"><c>io_lib:format/2</c></seealso>). + </p> + <p><c>Args</c> is a list of arguments to the function. The function is + to produce data in the specified <c>Encoding</c>. The I/O server is + to call the function as <c>apply(Mod, Func, Args)</c> and put the + returned data on the I/O device as if it was sent in a + <c>{put_chars, Encoding, Characters}</c> request. If the function + returns anything else than a binary or list, or throws an exception, + an error is to be sent back to the client.</p> + </item> + </list> + + <p>The I/O server replies to the client with an <c>io_reply</c> tuple, where + element <c>Reply</c> is one of:</p> + + <pre> +ok +{error, Error}</pre> + + <list type="bulleted"> + <item><c>Error</c> describes the error to the client, which can do + whatever it wants with it. The + <seealso marker="stdlib:io"><c>io</c></seealso> module typically + returns it "as is".</item> + </list> + + <p>For backward compatibility, the following <c>Request</c>s are also to be + handled by an I/O server (they are not to be present after + Erlang/OTP R15B):</p> + + <pre> +{put_chars, Characters} +{put_chars, Module, Function, Args}</pre> + + <p>These are to behave as <c>{put_chars, latin1, Characters}</c> and + <c>{put_chars, latin1, Module, Function, Args}</c>, respectively.</p> + </section> + + <section> + <title>Input Requests</title> + <p>To read characters from an I/O device, the following <c>Request</c>s + exist:</p> + + <pre> +{get_until, Encoding, Prompt, Module, Function, ExtraArgs}</pre> + + <list type="bulleted"> + <item> + <p><c>Encoding</c> denotes how data is to be sent back to the client + and what data is sent to the function denoted by + <c>Module</c>/<c>Function</c>/<c>ExtraArgs</c>. If the function + supplied returns data as a list, the data is converted to this + encoding. If the function supplied returns data in some other format, + no conversion can be done, and it is up to the client-supplied + function to return data in a proper way.</p> + <p>If <c>Encoding</c> is <c>latin1</c>, lists of integers <c>0..255</c> + or binaries containing plain bytes are sent back to the client when + possible. If <c>Encoding</c> is <c>unicode</c>, lists with integers + in the whole Unicode range or binaries encoded in UTF-8 are sent to + the client. The user-supplied function always sees lists of + integers, never binaries, but the list can contain numbers > 255 + if <c>Encoding</c> is <c>unicode</c>.</p> + </item> + <item> + <p><c>Prompt</c> is a list of characters (not mixed, no binaries) or an + atom to be output as a prompt for input on the I/O device. + <c>Prompt</c> is often ignored by the I/O server; if set to <c>''</c>, + it is always to be ignored (and results in nothing being written to + the I/O device).</p> + </item> + <item> + <p><c>Module</c>, <c>Function</c>, and <c>ExtraArgs</c> denote a + function and arguments to determine when enough data is written. The + function is to take two more arguments, the last state, and a list of + characters. The function is to return one of:</p> + <pre> +{done, Result, RestChars} +{more, Continuation}</pre> + <p><c>Result</c> can be any Erlang term, but if it is a <c>list()</c>, + the I/O server can convert it to a <c>binary()</c> of appropriate + format before returning it to the client, if the I/O server is set in + binary mode (see below).</p> + <p>The function is called with the data the I/O server finds on its I/O + device, returning one of:</p> + <list type="bulleted"> + <item> + <p><c>{done, Result, RestChars}</c> when enough data is read. In + this case <c>Result</c> is sent to the client and <c>RestChars</c> + is kept in the I/O server as a buffer for later input.</p> + </item> + <item> + <p><c>{more, Continuation}</c>, which indicates that more + characters are needed to complete the request.</p> + </item> + </list> + <p><c>Continuation</c> is sent as the state in later calls to the + function when more characters are available. When no more characters + are available, the function must return <c>{done, eof, Rest}</c>. The + initial state is the empty list. The data when an end of file is + reached on the IO device is the atom <c>eof</c>.</p> + <p>An emulation of the <c>get_line</c> request can be (inefficiently) + implemented using the following functions:</p> + <code> -module(demo). -export([until_newline/3, get_line/1]). @@ -234,226 +268,253 @@ get_line(IoServer) -> receive {io_reply, IoServer, Data} -> Data - end. -</code> - <p>Note especially that the last element in the <c>Request</c> tuple (<c>[$\n]</c>) - is appended to the argument list when the function is called. The - function should be called like - <c>apply(Module, Function, [ State, Data | ExtraArgs ])</c> by the I/O server</p> -</item> -</list> - -<p>A fixed number of characters is requested using this <c>Request</c>:</p> -<p> -<em>{get_chars, Encoding, Prompt, N}</em> -</p> - -<list type="bulleted"> -<item><c>Encoding</c> and <c>Prompt</c> as for <c>get_until</c>.</item> - -<item><c>N</c> is the number of characters to be read from the IO device.</item> -</list> - -<p>A single line (like in the example above) is requested with this <c>Request</c>:</p> -<p> -<em>{get_line, Encoding, Prompt}</em> -</p> - -<list type="bulleted"> -<item><c>Encoding</c> and <c>Prompt</c> as above.</item> -</list> - -<p>Obviously, the <c>get_chars</c> and <c>get_line</c> could be implemented with the -<c>get_until</c> request (and indeed they were originally), but demands for -efficiency has made these additions necessary.</p> - -<p>The I/O server replies to the client with an <c>io_reply</c> tuple where the <c>Reply</c> -element is one of:</p> -<p> -<em>Data</em><br/> -<em>eof</em><br/> -<em>{error, Error}</em> -</p> - -<list type="bulleted"> -<item><c>Data</c> is the characters read, in either list or binary form - (depending on the I/O server mode, see below).</item> -<item><c>Error</c> describes the error to the client, which may do whatever it - wants with it. The Erlang <seealso marker="stdlib:io">io</seealso> - module typically returns it as is.</item> -<item><c>eof</c> is returned when input end is reached and no more data is -available to the client process.</item> -</list> - -<p>For backward compatibility the following <c>Request</c>s should also be -handled by an I/O server (these reqeusts should not be present after -R15B of OTP):</p> - -<p> -<em>{get_until, Prompt, Module, Function, ExtraArgs}</em><br/> -<em>{get_chars, Prompt, N}</em><br/> -<em>{get_line, Prompt}</em><br/> -</p> - -<p>These should behave as <c>{get_until, latin1, Prompt, Module, Function, -ExtraArgs}</c>, <c>{get_chars, latin1, Prompt, N}</c> and <c>{get_line, latin1, -Prompt}</c> respectively.</p> -</section> -<section> -<title>I/O-server Modes</title> - -<p>Demands for efficiency when reading data from an I/O server has not -only lead to the addition of the <c>get_line</c> and <c>get_chars</c> requests, but -has also added the concept of I/O server options. No options are -mandatory to implement, but all I/O servers in the Erlang standard -libraries honor the <c>binary</c> option, which allows the <c>Data</c> element of the -<c>io_reply</c> tuple to be a binary instead of a list <em>when possible</em>. -If the data is sent as a binary, Unicode data will be sent in the -standard Erlang Unicode -format, i.e. UTF-8 (note that the function of the <c>get_until</c> request still gets -list data regardless of the I/O server mode).</p> - -<p>Note that i.e. the <c>get_until</c> request allows for a function with the data specified as always being a list. Also the return value data from such a function can be of any type (as is indeed the case when an <c>io:fread</c> request is sent to an I/O server). The client has to be prepared for data received as answers to those requests to be in a variety of forms, but the I/O server should convert the results to binaries whenever possible (i.e. when the function supplied to <c>get_until</c> actually returns a list). The example shown later in this text does just that.</p> - -<p>An I/O-server in binary mode will affect the data sent to the client, -so that it has to be able to handle binary data. For convenience, it -is possible to set and retrieve the modes of an I/O server using the -following I/O requests:</p> - -<p> -<em>{setopts, Opts}</em> -</p> - - -<list type="bulleted"> -<item><c>Opts</c> is a list of options in the format recognized by <seealso marker="stdlib:proplists">proplists</seealso> (and - of course by the I/O server itself).</item> -</list> -<p>As an example, the I/O server for the interactive shell (in <c>group.erl</c>) -understands the following options:</p> -<p> -<em>{binary, boolean()}</em> (or <em>binary</em>/<em>list</em>)<br/> -<em>{echo, boolean()}</em><br/> -<em>{expand_fun, fun()}</em><br/> -<em>{encoding, unicode/latin1}</em> (or <em>unicode</em>/<em>latin1</em>) -</p> - -<p>- of which the <c>binary</c> and <c>encoding</c> options are common for all -I/O servers in OTP, while <c>echo</c> and <c>expand</c> are valid only for this -I/O server. It is worth noting that the <c>unicode</c> option notifies how -characters are actually put on the physical IO device, i.e. if the -terminal per se is Unicode aware, it does not affect how characters -are sent in the I/O-protocol, where each request contains encoding -information for the provided or returned data.</p> - -<p>The I/O server should send one of the following as <c>Reply</c>:</p> -<p> -<em>ok</em><br/> -<em>{error, Error}</em> -</p> - -<p>An error (preferably <c>enotsup</c>) is to be expected if the option is -not supported by the I/O server (like if an <c>echo</c> option is sent in a -<c>setopts</c> request to a plain file).</p> - -<p>To retrieve options, this request is used:</p> -<p> -<em>getopts</em> -</p> - -<p>The <c>getopts</c> request asks for a complete list of all options -supported by the I/O server as well as their current values.</p> - -<p>The I/O server replies:</p> -<p> -<em>OptList</em><br/> -<em>{error, Error}</em> -</p> - -<list type="bulleted"> -<item><c>OptList</c> is a list of tuples <c>{Option, Value}</c> where <c>Option</c> is always - an atom.</item> -</list> -</section> -<section> -<title>Multiple I/O Requests</title> - -<p>The <c>Request</c> element can in itself contain several <c>Request</c>s by using -the following format:</p> -<p> -<em>{requests, Requests}</em> -</p> -<list type="bulleted"> -<item><c>Requests</c> is a list of valid <c>io_request</c> tuples for the protocol, they - shall be executed in the order in which they appear in the list and - the execution should continue until one of the requests result in an - error or the list is consumed. The result of the last request is - sent back to the client.</item> -</list> - -<p>The I/O server can for a list of requests send any of the valid results in -the reply:</p> - -<p> -<em>ok</em><br/> -<em>{ok, Data}</em><br/> -<em>{ok, Options}</em><br/> -<em>{error, Error}</em> -</p> -<p>- depending on the actual requests in the list.</p> -</section> -<section> -<title>Optional I/O Requests</title> - -<p>The following I/O request is optional to implement and a client -should be prepared for an error return:</p> -<p> -<em>{get_geometry, Geometry}</em> -</p> -<list type="bulleted"> -<item><c>Geometry</c> is either the atom <c>rows</c> or the atom <c>columns</c>.</item> -</list> -<p>The I/O server should send the <c>Reply</c> as:</p> -<p> -<em>{ok, N}</em><br/> -<em>{error, Error}</em> -</p> - -<list type="bulleted"> -<item><c>N</c> is the number of character rows or columns the IO device has, if - applicable to the IO device the I/O server handles, otherwise <c>{error, - enotsup}</c> is a good answer.</item> -</list> -</section> -<section> -<title>Unimplemented Request Types</title> - -<p>If an I/O server encounters a request it does not recognize (i.e. the -<c>io_request</c> tuple is in the expected format, but the actual <c>Request</c> is -unknown), the I/O server should send a valid reply with the error tuple:</p> -<p> -<em>{error, request}</em> -</p> - -<p>This makes it possible to extend the protocol with optional requests -and for the clients to be somewhat backwards compatible.</p> -</section> -<section> -<title>An Annotated and Working Example I/O Server</title> - -<p>An I/O server is any process capable of handling the I/O protocol. There is -no generic I/O server behavior, but could well be. The framework is -simple enough, a process handling incoming requests, usually both -I/O-requests and other IO device-specific requests (for i.e. positioning, -closing etc.).</p> - -<p>Our example I/O server stores characters in an ETS table, making up a -fairly crude ram-file (it is probably not useful, but working).</p> - -<p>The module begins with the usual directives, a function to start the -I/O server and a main loop handling the requests:</p> - -<code> + end.</code> + <p>Notice that the last element in the <c>Request</c> tuple + (<c>[$\n]</c>) is appended to the argument list when the function is + called. The function is to be called like + <c>apply(Module, Function, [ State, Data | ExtraArgs ])</c> by the + I/O server.</p> + </item> + </list> + + <p>A fixed number of characters is requested using the following + <c>Request</c>:</p> + + <pre> +{get_chars, Encoding, Prompt, N}</pre> + + <list type="bulleted"> + <item> + <p><c>Encoding</c> and <c>Prompt</c> as for <c>get_until</c>.</p> + </item> + <item> + <p><c>N</c> is the number of characters to be read from the I/O + device.</p> + </item> + </list> + + <p>A single line (as in former example) is requested with the + following <c>Request</c>:</p> + + <pre> +{get_line, Encoding, Prompt}</pre> + + <list type="bulleted"> + <item><c>Encoding</c> and <c>Prompt</c> as for <c>get_until</c>.</item> + </list> + + <p>Clearly, <c>get_chars</c> and <c>get_line</c> could be implemented with + the <c>get_until</c> request (and indeed they were originally), but + demands for efficiency have made these additions necessary.</p> + + <p>The I/O server replies to the client with an <c>io_reply</c> tuple, where + element <c>Reply</c> is one of:</p> + + <pre> +Data +eof +{error, Error}</pre> + + <list type="bulleted"> + <item> + <p><c>Data</c> is the characters read, in list or binary form + (depending on the I/O server mode, see the next section).</p> + </item> + <item> + <p><c>eof</c> is returned when input end is reached and no more data is + available to the client process.</p> + </item> + <item> + <p><c>Error</c> describes the error to the client, which can do + whatever it wants with it. The + <seealso marker="stdlib:io"><c>io</c></seealso> module typically + returns it as is.</p> + </item> + </list> + + <p>For backward compatibility, the following <c>Request</c>s are also to be + handled by an I/O server (they are not to be present after + Erlang/OTP R15B):</p> + + <pre> +{get_until, Prompt, Module, Function, ExtraArgs} +{get_chars, Prompt, N} +{get_line, Prompt}</pre> + + <p>These are to behave as + <c>{get_until, latin1, Prompt, Module, Function, ExtraArgs}</c>, + <c>{get_chars, latin1, Prompt, N}</c>, and + <c>{get_line, latin1, Prompt}</c>, respectively.</p> + </section> + + <section> + <title>I/O Server Modes</title> + <p>Demands for efficiency when reading data from an I/O server has not only + lead to the addition of the <c>get_line</c> and <c>get_chars</c> requests, + but has also added the concept of I/O server options. No options are + mandatory to implement, but all I/O servers in the Erlang standard + libraries honor the <c>binary</c> option, which allows element + <c>Data</c> of the <c>io_reply</c> tuple to be a binary instead of a list + <em>when possible</em>. If the data is sent as a binary, Unicode data is + sent in the standard Erlang Unicode format, that is, UTF-8 (notice that + the function of the <c>get_until</c> request still gets list data + regardless of the I/O server mode).</p> + + <p>Notice that the <c>get_until</c> request allows for a function with the + data specified as always being a list. Also, the return value data from + such a function can be of any type (as is indeed the case when an + <seealso marker="stdlib:io#fread/2"><c>io:fread/2,3</c></seealso> + request is sent to an I/O server). + The client must be prepared for data received as + answers to those requests to be in various forms. However, the I/O + server is to convert the results to binaries whenever possible (that is, + when the function supplied to <c>get_until</c> returns a list). This is + done in the example in section + <seealso marker="#example_io_server">An Annotated and Working Example I/O Server</seealso>. + </p> + + <p>An I/O server in binary mode affects the data sent to the client, so that + it must be able to handle binary data. For convenience, the modes of an + I/O server can be set and retrieved using the following I/O requests:</p> + + <pre> +{setopts, Opts}</pre> + + <list type="bulleted"> + <item><c>Opts</c> is a list of options in the format recognized by the + <seealso marker="stdlib:proplists"><c>proplists</c></seealso> module + (and by the I/O server).</item> + </list> + + <p>As an example, the I/O server for the interactive shell (in + <c>group.erl</c>) understands the following options:</p> + + <pre> +{binary, boolean()} (or binary/list) +{echo, boolean()} +{expand_fun, fun()} +{encoding, unicode/latin1} (or unicode/latin1)</pre> + + <p>Options <c>binary</c> and <c>encoding</c> are common for all I/O servers + in OTP, while <c>echo</c> and <c>expand</c> are valid only for this I/O + server. Option <c>unicode</c> notifies how characters are put on the + physical I/O device, that is, if the terminal itself is Unicode-aware. + It does not affect how characters are sent in the I/O protocol, where + each request contains encoding information for the provided or returned + data.</p> + + <p>The I/O server is to send one of the following as <c>Reply</c>:</p> + + <pre> +ok +{error, Error}</pre> + + <p>An error (preferably <c>enotsup</c>) is to be expected if the option is + not supported by the I/O server (like if an <c>echo</c> option is sent in + a <c>setopts</c> request to a plain file).</p> + + <p>To retrieve options, the following request is used:</p> + + <pre> +getopts</pre> + + <p>This request asks for a complete list of all options supported by the + I/O server as well as their current values.</p> + + <p>The I/O server replies:</p> + + <pre> +OptList +{error, Error}</pre> + + <list type="bulleted"> + <item><c>OptList</c> is a list of tuples <c>{Option, Value}</c>, where + <c>Option</c> always is an atom.</item> + </list> + </section> + + <section> + <title>Multiple I/O Requests</title> + <p>The <c>Request</c> element can in itself contain many <c>Request</c>s + by using the following format:</p> + + <pre> +{requests, Requests}</pre> + + <list type="bulleted"> + <item><c>Requests</c> is a list of valid <c>io_request</c> tuples for the + protocol. They must be executed in the order that they appear in + the list. The execution is to continue until one of the requests results + in an error or the list is consumed. The result of the last request is + sent back to the client.</item> + </list> + + <p>The I/O server can, for a list of requests, send any of the following + valid results in the reply, depending on the requests in the list:</p> + + <pre> +ok +{ok, Data} +{ok, Options} +{error, Error}</pre> + </section> + + <section> + <title>Optional I/O Request</title> + <p>The following I/O request is optional to implement and a client is to + be prepared for an error return:</p> + + <pre> +{get_geometry, Geometry}</pre> + + <list type="bulleted"> + <item><c>Geometry</c> is the atom <c>rows</c> or the atom + <c>columns</c>.</item> + </list> + + <p>The I/O server is to send the <c>Reply</c> as:</p> + + <pre> +{ok, N} +{error, Error}</pre> + + <list type="bulleted"> + <item><c>N</c> is the number of character rows or columns that the I/O + device has, if applicable to the I/O device handled by the I/O server, + otherwise <c>{error, enotsup}</c> is a good answer.</item> + </list> + </section> + + <section> + <title>Unimplemented Request Types</title> + <p>If an I/O server encounters a request that it does not recognize (that + is, the <c>io_request</c> tuple has the expected format, but the + <c>Request</c> is unknown), the I/O server is to send a valid reply with + the error tuple:</p> + + <pre> +{error, request}</pre> + + <p>This makes it possible to extend the protocol with optional requests + and for the clients to be somewhat backward compatible.</p> + </section> + + <section> + <title>An Annotated and Working Example I/O Server</title> + <marker id="example_io_server"/> + <p>An I/O server is any process capable of handling the I/O protocol. There + is no generic I/O server behavior, but could well be. The framework is + simple, a process handling incoming requests, usually both I/O-requests + and other I/O device-specific requests (positioning, closing, and so on). + </p> + + <p>The example I/O server stores characters in an ETS table, making + up a fairly crude RAM file.</p> + + <p>The module begins with the usual directives, a function to start the + I/O server and a main loop handling the requests:</p> + + <code> -module(ets_io_server). -export([start_link/0, init/0, loop/1, until_newline/3, until_enough/3]). @@ -490,39 +551,34 @@ loop(State) -> ?MODULE:loop(State#state{position = 0}); _Unknown -> ?MODULE:loop(State) - end. -</code> - -<p>The main loop receives messages from the client (which might be using -the <seealso marker="stdlib:io">io</seealso> module to send requests). -For each request the function -<c>request/2</c> is called and a reply is eventually sent using the <c>reply/3</c> -function.</p> + end.</code> -<p>The "private" message <c>{From, rewind}</c> results in the -current position in the pseudo-file to be reset to 0 (the beginning of -the "file"). This is a typical example of IO device-specific -messages not being part of the I/O-protocol. It is usually a bad idea -to embed such private messages in <c>io_request</c> tuples, as that might be -confusing to the reader.</p> + <p>The main loop receives messages from the client (which can use the + the <seealso marker="stdlib:io"><c>io</c></seealso> module to send + requests). For each request, the function <c>request/2</c> is called and a + reply is eventually sent using function <c>reply/3</c>.</p> -<p>Let us look at the reply function first...</p> + <p>The "private" message <c>{From, rewind}</c> results in the + current position in the pseudo-file to be reset to <c>0</c> (the beginning + of the "file"). This is a typical example of I/O device-specific + messages not being part of the I/O protocol. It is usually a bad idea to + embed such private messages in <c>io_request</c> tuples, as that can + confuse the reader.</p> -<code> + <p>First, we examine the reply function:</p> + <code> reply(From, ReplyAs, Reply) -> - From ! {io_reply, ReplyAs, Reply}. + From ! {io_reply, ReplyAs, Reply}.</code> -</code> + <p>It sends the <c>io_reply</c> tuple back to the client, providing element + <c>ReplyAs</c> received in the request along with the result of the + request, as described earlier.</p> -<p>Simple enough, it sends the <c>io_reply</c> tuple back to the client, -providing the <c>ReplyAs</c> element received in the request along with the -result of the request, as described above.</p> + <p>We need to handle some requests. First the requests for writing + characters:</p> -<p>Now look at the different requests we need to handle. First the -requests for writing characters:</p> - -<code> + <code> request({put_chars, Encoding, Chars}, State) -> put_chars(unicode:characters_to_list(Chars,Encoding),State); request({put_chars, Encoding, Module, Function, Args}, State) -> @@ -531,23 +587,22 @@ request({put_chars, Encoding, Module, Function, Args}, State) -> catch _:_ -> {error, {error,Function}, State} - end; -</code> + end;</code> -<p>The <c>Encoding</c> tells us how the characters in the request are -represented. We want to store the characters as lists in the -ETS table, so we convert them to lists using the -<seealso marker="stdlib:unicode#characters_to_list/2"><c>unicode:characters_to_list/2</c></seealso> function. The conversion function -conveniently accepts the encoding types <c>unicode</c> or <c>latin1</c>, so we can -use <c>Encoding</c> directly.</p> + <p>The <c>Encoding</c> says how the characters in the request are + represented. We want to store the characters as lists in the ETS + table, so we convert them to lists using function + <seealso marker="stdlib:unicode#characters_to_list/2"><c>unicode:characters_to_list/2</c></seealso>. + The conversion function conveniently accepts the encoding types + <c>unicode</c> and <c>latin1</c>, so we can use <c>Encoding</c> directly.</p> -<p>When <c>Module</c>, <c>Function</c> and <c>Arguments</c> are provided, we simply apply it -and do the same thing with the result as if the data was provided -directly.</p> + <p>When <c>Module</c>, <c>Function</c>, and <c>Arguments</c> are provided, + we apply it and do the same with the result as if the data was provided + directly.</p> -<p>Let us handle the requests for retrieving data too:</p> + <p>We handle the requests for retrieving data:</p> -<code> + <code> request({get_until, Encoding, _Prompt, M, F, As}, State) -> get_until(Encoding, M, F, As, State); request({get_chars, Encoding, _Prompt, N}, State) -> @@ -555,17 +610,16 @@ request({get_chars, Encoding, _Prompt, N}, State) -> get_until(Encoding, ?MODULE, until_enough, [N], State); request({get_line, Encoding, _Prompt}, State) -> %% To simplify the code, get_line is implemented using get_until - get_until(Encoding, ?MODULE, until_newline, [$\n], State); -</code> + get_until(Encoding, ?MODULE, until_newline, [$\n], State);</code> -<p>Here we have cheated a little by more or less only implementing -<c>get_until</c> and using internal helpers to implement <c>get_chars</c> and -<c>get_line</c>. In production code, this might be too inefficient, but that -of course depends on the frequency of the different requests. Before -we start actually implementing the functions <c>put_chars/2</c> and -<c>get_until/5</c>, let us look into the few remaining requests:</p> + <p>Here we have cheated a little by more or less only implementing + <c>get_until</c> and using internal helpers to implement <c>get_chars</c> + and <c>get_line</c>. In production code, this can be inefficient, but + that depends on the frequency of the different requests. Before we start + implementing functions <c>put_chars/2</c> and <c>get_until/5</c>, we + examine the few remaining requests:</p> -<code> + <code> request({get_geometry,_}, State) -> {error, {error,enotsup}, State}; request({setopts, Opts}, State) -> @@ -573,23 +627,23 @@ request({setopts, Opts}, State) -> request(getopts, State) -> getopts(State); request({requests, Reqs}, State) -> - multi_request(Reqs, {ok, ok, State}); -</code> + multi_request(Reqs, {ok, ok, State});</code> -<p>The <c>get_geometry</c> request has no meaning for this I/O server, so the -reply will be <c>{error, enotsup}</c>. The only option we handle is the -<c>binary</c>/<c>list</c> option, which is done in separate functions.</p> + <p>Request <c>get_geometry</c> has no meaning for this I/O server, so the + reply is <c>{error, enotsup}</c>. The only option we handle is + <c>binary</c>/<c>list</c>, which is done in separate functions.</p> -<p>The multi-request tag (<c>requests</c>) is handled in a separate loop -function applying the requests in the list one after another, -returning the last result.</p> + <p>The multi-request tag (<c>requests</c>) is handled in a separate loop + function applying the requests in the list one after another, returning + the last result.</p> -<p>What is left is to handle backward compatibility and the <seealso marker="kernel:file">file</seealso> module -(which uses the old requests until backward compatibility with pre-R13 -nodes is no longer needed). Note that the I/O server will not work with -a simple <c>file:write/2</c> if these are not added:</p> + <p>We need to handle backward compatibility and the + <seealso marker="kernel:file"><c>file</c></seealso> module (which + uses the old requests until backward compatibility with pre-R13 nodes is + no longer needed). Notice that the I/O server does not work with a simple + <c>file:write/2</c> if these are not added:</p> -<code> + <code> request({put_chars,Chars}, State) -> request({put_chars,latin1,Chars}, State); request({put_chars,M,F,As}, State) -> @@ -599,38 +653,35 @@ request({get_chars,Prompt,N}, State) -> request({get_line,Prompt}, State) -> request({get_line,latin1,Prompt}, State); request({get_until, Prompt,M,F,As}, State) -> - request({get_until,latin1,Prompt,M,F,As}, State); -</code> + request({get_until,latin1,Prompt,M,F,As}, State);</code> -<p>OK, what is left now is to return <c>{error, request}</c> if the request is -not recognized:</p> + <p><c>{error, request}</c> must be returned if the request is not + recognized:</p> -<code> + <code> request(_Other, State) -> - {error, {error, request}, State}. -</code> + {error, {error, request}, State}.</code> -<p>Let us move further and actually handle the different requests, first -the fairly generic multi-request type:</p> + <p>Next we handle the different requests, first the fairly generic + multi-request type:</p> -<code> + <code> multi_request([R|Rs], {ok, _Res, State}) -> multi_request(Rs, request(R, State)); multi_request([_|_], Error) -> Error; multi_request([], Result) -> - Result. -</code> + Result.</code> -<p>We loop through the requests one at the time, stopping when we either -encounter an error or the list is exhausted. The last return value is -sent back to the client (it is first returned to the main loop and then -sent back by the function <c>io_reply</c>).</p> + <p>We loop through the requests one at the time, stopping when we either + encounter an error or the list is exhausted. The last return value is + sent back to the client (it is first returned to the main loop and then + sent back by function <c>io_reply</c>).</p> -<p>The <c>getopts</c> and <c>setopts</c> requests are also simple to handle, we just -change or read our state record:</p> + <p>Requests <c>getopts</c> and <c>setopts</c> are also simple to handle. + We only change or read the state record:</p> -<code> + <code> setopts(Opts0,State) -> Opts = proplists:unfold( proplists:substitute_negations( @@ -662,46 +713,44 @@ getopts(#state{mode=M} = S) -> true; _ -> false - end}],S}. -</code> + end}],S}.</code> -<p>As a convention, all I/O servers handle both <c>{setopts, [binary]}</c>, -<c>{setopts, [list]}</c> and <c>{setopts,[{binary, boolean()}]}</c>, hence the trick -with <c>proplists:substitute_negations/2</c> and <c>proplists:unfold/1</c>. If -invalid options are sent to us, we send <c>{error, enotsup}</c> back to the -client.</p> + <p>As a convention, all I/O servers handle both <c>{setopts, [binary]}</c>, + <c>{setopts, [list]}</c>, and <c>{setopts,[{binary, boolean()}]}</c>, + hence the trick with <c>proplists:substitute_negations/2</c> and + <c>proplists:unfold/1</c>. If invalid options are sent to us, we send + <c>{error, enotsup}</c> back to the client.</p> -<p>The <c>getopts</c> request should return a list of <c>{Option, Value}</c> tuples, -which has the twofold function of providing both the current values -and the available options of this I/O server. We have only one option, -and hence return that.</p> + <p>Request <c>getopts</c> is to return a list of <c>{Option, Value}</c> + tuples. This has the twofold function of providing both the current values + and the available options of this I/O server. We have only one option, and + hence return that.</p> -<p>So far our I/O server has been fairly generic (except for the <c>rewind</c> -request handled in the main loop and the creation of an ETS table). -Most I/O servers contain code similar to the one above.</p> + <p>So far this I/O server is fairly generic (except for request + <c>rewind</c> handled in the main loop and the creation of an ETS + table). Most I/O servers contain code similar to this one.</p> -<p>To make the example runnable, we now start implementing the actual -reading and writing of the data to/from the ETS table. First the -<c>put_chars/3</c> function:</p> + <p>To make the example runnable, we start implementing the reading and + writing of the data to/from the ETS table. First function + <c>put_chars/3</c>:</p> -<code> + <code> put_chars(Chars, #state{table = T, position = P} = State) -> R = P div ?CHARS_PER_REC, C = P rem ?CHARS_PER_REC, [ apply_update(T,U) || U <- split_data(Chars, R, C) ], - {ok, ok, State#state{position = (P + length(Chars))}}. -</code> + {ok, ok, State#state{position = (P + length(Chars))}}.</code> -<p>We already have the data as (Unicode) lists and therefore just split -the list in runs of a predefined size and put each run in the -table at the current position (and forward). The functions -<c>split_data/3</c> and <c>apply_update/2</c> are implemented below.</p> + <p>We already have the data as (Unicode) lists and therefore only split + the list in runs of a predefined size and put each run in the table at + the current position (and forward). Functions <c>split_data/3</c> and + <c>apply_update/2</c> are implemented below.</p> -<p>Now we want to read data from the table. The <c>get_until/5</c> function reads -data and applies the function until it says it is done. The result is -sent back to the client:</p> + <p>Now we want to read data from the table. Function <c>get_until/5</c> + reads data and applies the function until it says that it is done. The + result is sent back to the client:</p> -<code> + <code> get_until(Encoding, Mod, Func, As, #state{position = P, mode = M, table = T} = State) -> case get_loop(Mod,Func,As,T,P,[]) of @@ -737,34 +786,34 @@ get_loop(M,F,A,T,P,C) -> get_loop(M,F,A,T,NewP,NewC); _ -> {error,F} - end. -</code> - -<p>Here we also handle the mode (<c>binary</c> or <c>list</c>) that can be set by -the <c>setopts</c> request. By default, all OTP I/O servers send data back to -the client as lists, but switching mode to <c>binary</c> might increase -efficiency if the I/O server handles it in an appropriate way. The -implementation of <c>get_until</c> is hard to get efficient as the supplied -function is defined to take lists as arguments, but <c>get_chars</c> and -<c>get_line</c> can be optimized for binary mode. This example does not -optimize anything however. It is important though that the returned -data is of the right type depending on the options set, so we convert -the lists to binaries in the correct encoding <em>if possible</em> -before returning. The function supplied in the <c>get_until</c> request tuple may, -as its final result return anything, so only functions actually -returning lists can get them converted to binaries. If the request -contained the encoding tag <c>unicode</c>, the lists can contain all Unicode -codepoints and the binaries should be in UTF-8, if the encoding tag -was <c>latin1</c>, the client should only get characters in the range -0..255. The function <c>check/2</c> takes care of not returning arbitrary -Unicode codepoints in lists if the encoding was given as <c>latin1</c>. If -the function did not return a list, the check cannot be performed and -the result will be that of the supplied function untouched.</p> - -<p>Now we are more or less done. We implement the utility functions below -to actually manipulate the table:</p> - -<code> + end.</code> + + <p>Here we also handle the mode (<c>binary</c> or <c>list</c>) that can be + set by request <c>setopts</c>. By default, all OTP I/O servers send data + back to the client as lists, but switching mode to <c>binary</c> can + increase efficiency if the I/O server handles it in an appropriate way. + The implementation of <c>get_until</c> is difficult to get efficient, as + the supplied function is defined to take lists as arguments, but + <c>get_chars</c> and <c>get_line</c> can be optimized for binary mode. + However, this example does not optimize anything.</p> + + <p>It is important though that the returned data is of the correct type + depending on the options set. We therefore convert the lists to binaries + in the correct encoding <em>if possible</em> before returning. The + function supplied in the <c>get_until</c> request tuple can, as its final + result return anything, so only functions returning lists can get them + converted to binaries. If the request contains encoding tag + <c>unicode</c>, the lists can contain all Unicode code points and the + binaries are to be in UTF-8. If the encoding tag is <c>latin1</c>, the + client is only to get characters in the range <c>0..255</c>. Function + <c>check/2</c> takes care of not returning arbitrary Unicode code points + in lists if the encoding was specified as <c>latin1</c>. If the function + does not return a list, the check cannot be performed and the result is + that of the supplied function untouched.</p> + + <p>To manipulate the table we implement the following utility functions:</p> + + <code> check(unicode, List) -> List; check(latin1, List) -> @@ -775,18 +824,16 @@ check(latin1, List) -> catch throw:_ -> {error,{cannot_convert, unicode, latin1}} - end. -</code> + end.</code> -<p>The function check takes care of providing an error tuple if Unicode -codepoints above 255 is to be returned if the client requested -latin1.</p> + <p>The function check provides an error tuple if Unicode code points > + 255 are to be returned if the client requested <c>latin1</c>.</p> -<p>The two functions <c>until_newline/3</c> and <c>until_enough/3</c> are helpers used -together with the <c>get_until/5</c> function to implement <c>get_chars</c> and -<c>get_line</c> (inefficiently):</p> - -<code> + <p>The two functions <c>until_newline/3</c> and <c>until_enough/3</c> are + helpers used together with function <c>get_until/5</c> to implement + <c>get_chars</c> and <c>get_line</c> (inefficiently):</p> + + <code> until_newline([],eof,_MyStopCharacter) -> {done,eof,[]}; until_newline(ThisFar,eof,_MyStopCharacter) -> @@ -810,16 +857,15 @@ until_enough(ThisFar,CharList,N) {Res,Rest} = my_split(N,ThisFar ++ CharList, []), {done,Res,Rest}; until_enough(ThisFar,CharList,_N) -> - {more,ThisFar++CharList}. -</code> + {more,ThisFar++CharList}.</code> -<p>As can be seen, the functions above are just the type of functions -that should be provided in <c>get_until</c> requests.</p> + <p>As can be seen, the functions above are just the type of functions that + are to be provided in <c>get_until</c> requests.</p> -<p>Now we only need to read and write the table in an appropriate way to -complete the I/O server:</p> + <p>To complete the I/O server, we only need to read and write the table in + an appropriate way:</p> -<code> + <code> get(P,Tab) -> R = P div ?CHARS_PER_REC, C = P rem ?CHARS_PER_REC, @@ -856,18 +902,16 @@ apply_update(Table, {Row, Col, List}) -> {Part1,_} = my_split(Col,OldData,[]), {_,Part2} = my_split(Col+length(List),OldData,[]), ets:insert(Table,{Row, Part1 ++ List ++ Part2}) - end. -</code> - -<p>The table is read or written in chunks of <c>?CHARS_PER_REC</c>, overwriting -when necessary. The implementation is obviously not efficient, it is -just working.</p> - -<p>This concludes the example. It is fully runnable and you can read or -write to the I/O server by using i.e. the <seealso marker="stdlib:io">io</seealso> module or even the <seealso marker="kernel:file">file</seealso> -module. It is as simple as that to implement a fully fledged I/O server -in Erlang.</p> -</section> + end.</code> + + <p>The table is read or written in chunks of <c>?CHARS_PER_REC</c>, + overwriting when necessary. The implementation is clearly not efficient, + it is just working.</p> + + <p>This concludes the example. It is fully runnable and you can read or + write to the I/O server by using, for example, the + <seealso marker="stdlib:io"><c>io</c></seealso> module or even the + <seealso marker="kernel:file"><c>file</c></seealso> module. It is + as simple as that to implement a fully fledged I/O server in Erlang.</p> + </section> </chapter> - - |