Network and File I/O Tutorial

 

Network and file system input and output are common operations in any programming. Luban offers a set of tools for IO purpose. You’ll see how easily it is to persist and recover Luban objects through network or file system. Though they are not part of the Luban programming language, they are so useful that we need to use a chapter to describe them.

 

1           Console, File and Socket

These are the basic data types used to persist information or pass them around network. And they share a more or less common interface. Below listed are their Luban type names and the description of their member functions.

 

Luban type names.

Console:           std::console

File:                  std::file

Socket:             net::socket

 

Member functions that apply to all above data types:

write(obj1,obj2…):

Writes the object in human readable string format to the media.

Return null, or error in case of failure.

writeline(obj1,obj2…):

Same as write, only adds one extra line break at the end of each object string.

Return null, or error in case of failure.

read( int n=1 ):

Reads specified number of characters, or read one character if n not specified.

Returns a string containing all characters read.

readline():

Reads the media to break of line.

Returns a string.

writeobj(obj1,obj2…):

Writes the objects in platform independent binary format from which the object can be restored.

Returns null, or error for failure.

readobj():

            Restore object back from media, likely the object written by writeobj.

            Returns restored object.

 

What need to be mentioned is that writeobj and readobj are designed to be used in pair. Any object serialized and persisted with writeobj can be restored using readobj.

 

2           How To Create, Read and Write File

Below is a simple text file processing example:

 

            linecount = 0;

            wordcount = 0;

txtfile = std::file(“mydatafile”, ‘r’); // open file ”mydatafile”

            alldata = txtfile.readall(); // read ALL content of the file into alldata

            lines = alldata.split(‘\n’); // split alldata into vector lines

            foreach( line in lines )   // Iterate through each line in the vector lines

            {

                        ++linecount;  //increase line count

                        words = line.split();   // Split one line into a vector of words

                        wordcount += words.size(); // increase word count

            }

            //print result

            std::println(obj=”lines: “+string(linecount)+” words: “+string(wordcount));

 

It needs to be mentioned that above code use a new file type member function readall() that reads while content of a file into a string data object. The code then split the whole content into lines and does analysis.

 

The below example script shows how to use file to persist and restore data object using file:

 

            fw = std::fileobjfile”, ‘w’); // open a file for writing

            fw.writeobj([1,2,3]); // write a vector object

            fw.close(); // close the file

            fr = std::fileobjfile”, ‘r’); // open file for reading

            vec = fr.readobj(); // read one object back

            std::println(obj=vec); // print out [1,2,3]

 

There are three modes can be used to open a file. They are read, write and append, and are represented by characters as ‘r’, ‘w’ and ‘a’. Above code shows read and write. While in the write mode opening an existing file will cause it to be truncated, opening the same file in append mode will cause the later writing append to the end the file.

 

3           Socket Routine, Server and Client

The below scripts demonstrate how to use socket, including both client and server side. The server side uses a new data type net::listener that is basically a TCP port listener. It can be created given a port number. Its major member function is accept() that accepts one incoming connection and return a socket object that can talk to the client.

 

Server script:

 

            // This server greets clients and assign each of them a number

            listener = net::listener(6500); // take TCP port 6500

            clientcount=0;

            while( true ) // forever loop

            {

                        socket = listener.accept(); // wait for client connection

name = socket.readobj(); // read client name

                        socket.writeobj(“Hello, ”+name); // say hello

                        ++clientcount; // increase client number

Socket.writeobj(clientcount); // send the number to client

            }

 

Client script:

 

            socket = net::socketlocalhost”, 6500); // connect to server at port 6500

            socket.writeobj(“Chinese”);  // send server its name

            msg = socket.readobj(); // receive greeting

            std::console().writeline(“The server says: “, msg); // print greeting

            mynumber = socket.readobj(); // read assigned number

            std::console().writelineMy number is “, mynumber); // print number

 

In the above, the server script takes TCP port 6500 and listens to incoming connection. For each client connection server says hello then assign a number to the client. Client script simply connect to the server at port 6500, send its name, then receive greeting message and number. You can run both scripts on the same machine. If you want to run them on different machines, you need to change the sever name from “localhost” to the actual server host name.

 

4           Utility Structures: std::println and std::print

Just for convenience, std::println and std::print structures are coded as part of Luban package. The structure print object to console in a simple way. Below is their source code:

 

            namespace std;

 

            struct println( input obj;)

            as process

            {   std::console().writeline(input.obj); }

 

            struct print( input obj;)

            as process

            {   std::console().write(input.obj); }

 

5           Utility Structure std::des

Every Luban object can dump itself into a binary stream. In order to restore one object out of a binary string, the std::des structure is needed, as below sample code shows.

 

            x = {“one”:1, “two”:2};  // construct a map

            xguts = x.serialize();//  serialize it into a string

            xback = std::des(stream=xguts).obj; // call std::des to restore it

            truth = xback == x; // truth = true, compare the restored object and original