FACE tutorial

Scenario

Server starts up and listen on port 9527, client will connect to server and send predefined RPC 'ping' with parameters: client version(int) and client name(char array), server will reply server version.

Steps

0. Install FACE

face2cpp

Face protocol 'compiler', require lex&yacc support. For Linux, with these 2 tools installed, go to /face2cpp directory:

somebody@localhost theface/face2cpp$ make 

This will produce the utility 'face2cpp' Win32 version for face2cpp is not provided yet, but could be walk around easily with flex and bison windows version.

libface

Linux: Type 'make' in libface directory, be sure the libevent is installed into system.

somebody@localhost theface/face2cpp$ make 

Windows: At 2008-01-16, the latest libevent port for WIN32 is version 1.1a, the Visual C++ library "libevent.lib" and "event.h" are both packed in the libface directory. Just use 'face.sln' to compile under Visual C++, this will produce face.lib for WIN32 environment.

1. Define the RPC

According to the scenario above, we wrote the following RPC description. Let's call it 'face protocol'. Resvered words are marked blue.

rpcset Sample{
  c ping(int client_version, char client_name[16])
  s pong(int server_version)
}   

Save it to as "sample.face"

2. Compile the protocol

Since we have the face 'compiler': face2cpp at step 0, now we are able to 'compile' the RPC definition into C++ source:

somebody@localhost theface/sample/protocol$ ../../face2cpp/face2cpp -f sample.face   

This will produce C++ source file:

sample_srv.h
sample_srv.cpp
sample_cli.h
sample_cli.cpp   

Files above contain 2 class: SampleS and SampleC, those are RPC protocol described in C++, to be separately apply for server and client.

3. Make server

server.cpp is very simple:

Face<SampleS> f;  // Declare what protocol to use

int main(int argc, char **argv){
    if(2 != argc){
        usage();
        exit(0);
    }

    f.listen_to(atoi(argv[1]),  // port
                10,             // max_connection
                1024            // buffer
        );

    while(1)
        f.smile();

    return 0;
}

4. Make client

Client contains a little more:

int main(int argc, char **argv){
    if(3 != argc){
        usage();
        exit(0);
    }

    Face<SampleC> f; // Here is the client logic

    f.set_connect_param(10,             // max_connection
                        1024            // buffer
        );

    Connection *c = f.connect_to(argv[1], atoi(argv[2]));

    char client_name[] = "NO 1 client";

    f._logic->ping(c, 1, client_name, sizeof(client_name)); // send ping to server

    while(1)
        f.smile(); // smile(), aka heartbeat(), flushing network I/O buffers

    return 0;
}   

5. Fill in the logic

The files we generated above:

sample_srv.h
sample_srv.cpp   

Will be compiled with server.cpp. Header files contain all the dull work we don't wanna do like setup the socket and handle the buffer, so we leave them alone. In .cpp file, let's fill in the logic we want our program to behave.

server: what to do when ping() arrived from client, face2cpp already generated the function for us with the name: ping_on_receive(), All we need to do is reply pong() with server version, here we assume server version is 2008:

sample_srv.cpp:
int SampleS::ping_on_receive(Connection *c,
                             int client_version,
                             char *client_name, 
                             int client_name_len){
    f._logic->pong(c, 2008);                          
    return 0;
};

All client need to do is display the server version when server replies client's ping() with pong():

sample_cli.cpp:
int SampleS::pong_on_receive(Connection *c, int server_version){
    cout<<"server version: "<<server_version<<endl;
    return 0;
};

6. Build!

Here is server side Makefile, client is similar:

FACEPROTOCOL=../protocol/sample.face
CXX=g++
FACECC=../../face2cpp/face2cpp
FACESRC=sample_srv.cpp
FACEHEADER=sample_srv.h
SRC=$(wildcard *.cpp)
OBJ=$(patsubst %.cpp,%.o,$(SRC))
LIBS=-ll -levent -L../../libface
CXXFLAGS=-g -I../../libface 
FACEFLAGS=-s
FACELIB=../../libface/libface.a
PROG=sample_srv

all: $(PROG)

$(PROG): $(OBJ)
	$(CXX) $(OBJ) $(LIBS) $(FACELIB) $(CXXFLAGS) -o $(PROG)

p: $(FACEPROTOCOL)
	$(FACECC) $(FACEFLAGS) -f $(FACEPROTOCOL)

clean:
	rm -f *.o
	rm -f *~
	rm -f $(FACESRC) $(FACEHEADER)
	rm -f $(PROG)

Steps 0-5 are used to demonstrate what FACE workflow is. Actual work is simple with this Makefile:

somebody@localhost theface/sample/server$ make p
somebody@localhost theface/sample/server$ make

'make p' will generate the relevant C++ source file, and make will do the rest.

7. Test drive

Start server at port 9527:

somebody@localhost theface/sample/server$ ./simple_srv 9527

connect to server:

somebody@localhost theface/sample/client$ ./simple_cli 192.168.1.1 9527
Have fun! Return to top