CD database program
Now we have learned how to use the famous Pipeline to implement a simple client/server system. We can re-view our CD database program and modify it accordingly. We also combined some signal processing to allow some cleanup actions when the process is interrupted. We will use the DBM version with a command line interface before, so that we can directly view the code.
Before we discuss the new version of code in more detail, let's compile this program first. If we have the source code obtained by the web site, we can use makefile to compile and generate the server and client programs.
Enter server-I to initialize a new CD database.
The client does not run until the server is started and running. The following is the MAKEFILE file, showing how the program is combined.
ALL: Server Client
Cc = cc
Cflags =-pedantic-wall
# For debugging un-comment the next line
# Dflags =-ddebug_trace = 1-G
# Where, and which version, of DBM are we using.
# This assumes gdbm is pre-installed in a standard place, but we are
# Going to use the gdbm compatibility routines, that make it emulate ndbm.
# We do this because ndbm is the 'most standard' of the DBM versions.
# Depending on your distribution, these may need changing.
Dbm_inc_path =/usr/include/gdbm
Dbm_lib_path =/usr/lib
Dbm_lib_file = gdbm
. C. O:
$ (CC) $ (cflags)-I $ (dbm_inc_path) $ (dflags)-C $ <
App_ui.o: app_ui.c cd_data.h
Cd_dbm.o: cd_dbm.c cd_data.h
Client_f.o: clientif. c cd_data.h cliserv. h
Pipe_imp.o: pipe_imp.c cd_data.h cliserv. h
Server. O: Server. c cd_data.h cliserv. h
Client: app_ui.o clientif. O pipe_imp.o
$ (CC)-O client $ (dflags) app_ui.o clientif. O pipe_imp.o
Server: Server. O cd_dbm.o pipe_imp.o
$ (CC)-O Server-L $ (dbm_lib_path) $ (dflags) server. O cd_dbm.o pipe_imp.o-
L $ (dbm_lib_file)
Clean:
Rm-F server client_app *. O *~
Target
Our goal is to separate the processing database in the program from the processing user interface. We want to run a server process at the same time, but allow multiple concurrent clients. At the same time, we want to make the minimum Modification on the existing code. If possible, we will keep the existing code unchanged.
In order to make things simple, we also hope to create and delete pipelines in the program, so we do not need the system administrator to create a famous Pipeline before we can use these programs.
In addition, it is important to ensure that we will never "wait" to waste CPU time on one thing. As we can see, Linux allows us to block and wait for events instead of using important resources. We should use the blocking feature of pipelines to ensure that we can use CPU more effectively. In theory, the server should be able to wait several hours for a request to arrive.
Implementation
In the previous process version in Chapter 7th, we used a data access function set for data operations. They are:
Int database_initialize (const int new_database );
Void database_close (void );
Cdc_entry get_cdc_entry (const char * cd_catalog_ptr );
Cdt_entry get_cdt_entry (const char * cd_catalog_ptr, const int track_no );
Int add_cdc_entry (const cdc_entry entry_to_add );
Int add_cdt_entry (const cdt_entry entry_to_add );
Int del_cdc_entry (const char * cd_catalog_ptr );
Int del_cdt_entry (const char * cd_catalog_ptr, const int track_no );
Cdc_entry search_cdc_entry (const char * cd_catalog_ptr,
Int * first_call_ptr );
These functions provide a suitable place to differentiate between the client and the server.
In the implementation of a single process, we can think of this program as two parts, although they are compiled together as a program, as shown in 13-6.
In the implementation of client-server, we hope to insert some reasonable famous pipelines between the two main parts of the program and provide code. Figure 13-7 shows the structure we need.
In our implementation, we choose to place the client and server interface routines in the same file, pipe_imp.c. This puts all code that depends on the famous pipeline used for client/server implementation in one file. The formatting and packaging of the data to be transferred are separated from the routine for implementing the famous Pipeline. The call structure in the program is 13-8.
The file app_ui.c, client_if.c and pipe_imp.c are compiled and linked together to provide a client program. The files cd_dbm.c, server. C and pipe_imp.c are compiled and linked to provide the server program together. The header file, cliserv. H, is used to connect the two to a common definition header file.
The app_ui.c file and cd_dbm.c file have only some small modifications. In principle, they can be divided into two programs. Because the program is very large now and the main part of the code is not significantly changed compared with the previous version, we will only discuss the files cliserv. H, client_if.c and pipe_imp.c here.
First, let's take a look at cliserv. h. This file defines the client/server interface. This is required for the implementation of the client and server.
Test-header file cliserv. h
1 The following is the # include header file declaration:
# Include <unistd. h>
# Include <stdlib. h>
# Include <stdio. h>
# Include <fcntl. h>
# Include <limits. h>
# Include <sys/types. h>
# Include <sys/STAT. h>
2. Then we define a famous Pipeline. We use one pipe for the server, and the other pipe for all clients. Because there may be multiple clients, the client combines a process ID in its name to ensure that its pipeline is unique:
# Define server_pipe "/tmp/server_pipe"
# Define client_pipe "/tmp/client _ % d_pipe"
# Define err_text_len 80
3. Implement the command as the enumeration type, instead of # define definition.
This is a good way to allow the compiler to perform more type checks and help program debugging, because many debuggers can display the name of enumeration constants, however, the name defined by the # define command cannot be displayed.
The first typdef specifies the type of request to be sent to the server; the second one specifies the type of response from the server to the client.
Typedef Enum {
S_create_new_database = 0,
S_get_cdc_entry,
S_get_cdt_entry,
S_add_cdc_entry,
S_add_cdt_entry,
S_del_cdc_entry,
S_del_cdt_entry,
S_find_cdc_entry
} Client_request_e;
Typedef Enum {
R_success = 0,
R_failure,
R_find_no_more
} Server_response_e;
4. Next, we declare a structure that constitutes a message that can be passed between two processes in two directions.
Typedef struct {
Pid_t client_pid;
Client_request_e request;
Server_response_e response;
Cdc_entry cdc_entry_data;
Cdt_entry cdt_entry_data;
Char error_text [err_text_len + 1];
} Message_db_t;
5. Finally, let's take a look at the pipeline interface function for data transmission and implement it in pipe_imp.c. These functions are divided into server-side and client-side functions, respectively in the first and second blocks:
Int server_starting (void );
Void server_ending (void );
Int read_request_from_client (message_db_t * rec_ptr );
Int start_resp_to_client (const message_db_t mess_to_send );
Int send_resp_to_client (const message_db_t mess_to_send );
Void end_resp_to_client (void );
Int client_starting (void );
Void client_ending (void );
Int send_mess_to_server (message_db_t mess_to_send );
Int start_resp_from_server (void );
Int read_resp_from_server (message_db_t * rec_ptr );
Void end_resp_from_server (void );
The rest of the discussion is divided into client interface functions and definitions in pipe_imp.c. The server side and client functions are discussed in detail, and necessary source code is discussed.
Client interface functions
Now let's discuss client_if.c. He provides a virtual version for the database access routine. He encodes the request into the message_db_t structure, and then uses the routine in pipe_imp.c to pass these requests to the server. This will make the minimum Modification on the original app_ui.c.
Test-client Interpreter
1. This file implements nine database functions defined in cd_data.h. It performs operations by passing requests to the server and returning the server response from the function, similar to a man-in-the-middle. This file starts with # include and constant definition:
# DEFINE _ posix_source
# Include <unistd. h>
# Include <stdlib. h>
# Include <stdio. h>
# Include <fcntl. h>
# Include <limits. h>
# Include <sys/types. h>
# Include <sys/STAT. h>
# Include "cd_data.h"
# Include "cliserv. H"
2. The static variable mypid reduces the number of getpid function calls. We use a local function read_one_response to reduce repeated code:
Static pid_t mypid;
Static int read_one_response (message_db_t * rec_ptr );
3 database_initialize and close routines are still called, but the client that is used to initialize the MPs queue interface is used now, and the excess famous MPs queue is removed when the client exits.
Int database_initialize (const int new_database)
{
If (! Client_starting () Return (0 );
Mypid = getpid ();
Return (1 );
}/* Database_initialize */
Void database_close (void ){
Client_ending ();
}
4 get_cdc_entry is called to obtain a category entity from the data when a CD category title is specified. Here we encode the request into the message_db_t structure and pass it to the server. Then we read the returned response and put it in another different message_db_t structure. If an object is found, it will be included in the message_db_t structure as a cdc_entry structure. Therefore, we need to introduce the relevant parts of the structure:
Cdc_entry get_cdc_entry (const char * cd_catalog_ptr)
{
Cdc_entry ret_val;
Message_db_t mess_send;
Message_db_t mess_ret;
Ret_val.catalog [0] = '/0 ';
Mess_send.client_pid = mypid;
Mess_send.request = s_get_cdc_entry;
Strcpy (mess_send.cdc_entry_data.catalog, cd_catalog_ptr );
If (send_mess_to_server (mess_send )){
If (read_one_response (& mess_ret )){
If (mess_ret.response = r_success ){
Ret_val = mess_ret.cdc_entry_data;
} Else {
Fprintf (stderr, "% s", mess_ret.error_text );
}
} Else {
Fprintf (stderr, "server failed to respond/N ");
}
} Else {
Fprintf (stderr, "server not accepting requests/N ");
}
Return (ret_val );
}
5 The following is the source code of the read_one_response function we used to avoid repeated code:
Static int read_one_response (message_db_t * rec_ptr ){
Int return_code = 0;
If (! Rec_ptr) Return (0 );
If (start_resp_from_server ()){
If (read_resp_from_server (rec_ptr )){
Return_code = 1;
}
End_resp_from_server ();
}
Return (return_code );
}
6. The implementation methods of other get_xxx, del_xxx and add_xxx routines are similar to those of the get_cdc_entry function. for completeness, we will introduce them here. The first is the function source code used to read the CD audio track:
Cdt_entry get_cdt_entry (const char * cd_catalog_ptr, const int track_no)
{
Cdt_entry ret_val;
Message_db_t mess_send;
Message_db_t mess_ret;
Ret_val.catalog [0] = '/0 ';
Mess_send.client_pid = mypid;
Mess_send.request = s_get_cdt_entry;
Strcpy (mess_send.cdt_entry_data.catalog, cd_catalog_ptr );
Mess_send.cdt_entry_data.track_no = track_no;
If (send_mess_to_server (mess_send )){
If (read_one_response (& mess_ret )){
If (mess_ret.response = r_success ){
Ret_val = mess_ret.cdt_entry_data;
} Else {
Fprintf (stderr, "% s", mess_ret.error_text );
}
} Else {
Fprintf (stderr, "server failed to respond/N ");
}
} Else {
Fprintf (stderr, "server not accepting requests/N ");
}
Return (ret_val );
}
7. The following two functions are used to add data. The first function is used to add data to the category, and the second function is used to add data to the audio track:
Int add_cdc_entry (const cdc_entry entry_to_add)
{
Message_db_t mess_send;
Message_db_t mess_ret;
Mess_send.client_pid = mypid;
Mess_send.request = s_add_cdc_entry;
Mess_send.cdc_entry_data = entry_to_add;
If (send_mess_to_server (mess_send )){
If (read_one_response (& mess_ret )){
If (mess_ret.response = r_success ){
Return (1 );
} Else {
Fprintf (stderr, "% s", mess_ret.error_text );
}
} Else {
Fprintf (stderr, "server failed to respond/N ");
}
} Else {
Fprintf (stderr, "server not accepting requests/N ");
}
Return (0 );
}
Int add_cdt_entry (const cdt_entry entry_to_add)
{
Message_db_t mess_send;
Message_db_t mess_ret;
Mess_send.client_pid = mypid;
Mess_send.request = s_add_cdt_entry;
Mess_send.cdt_entry_data = entry_to_add;
If (send_mess_to_server (mess_send )){
If (read_one_response (& mess_ret )){
If (mess_ret.response = r_success ){
Return (1 );
} Else {
Fprintf (stderr, "% s", mess_ret.error_text );
}
} Else {
Fprintf (stderr, "server failed to respond/N ");
}
} Else {
Fprintf (stderr, "server not accepting requests/N ");
}
Return (0 );
}
8. There are two functions used for data deletion:
Int del_cdc_entry (const char * cd_catalog_ptr)
{
Message_db_t mess_send;
Message_db_t mess_ret;
Mess_send.client_pid = mypid;
Mess_send.request = s_del_cdc_entry;
Strcpy (mess_send.cdc_entry_data.catalog, cd_catalog_ptr );
If (send_mess_to_server (mess_send )){
If (read_one_response (& mess_ret )){
If (mess_ret.response = r_success ){
Return (1 );
} Else {
Fprintf (stderr, "% s", mess_ret.error_text );
}
} Else {
Fprintf (stderr, "server failed to respond/N ");
}
} Else {
Fprintf (stderr, "server not accepting requests/N ");
}
Return (0 );
}
Int del_cdt_entry (const char * cd_catalog_ptr, const int track_no)
{
Message_db_t mess_send;
Message_db_t mess_ret;
Mess_send.client_pid = mypid;
Mess_send.request = s_del_cdt_entry;
Strcpy (mess_send.cdt_entry_data.catalog, cd_catalog_ptr );
Mess_send.cdt_entry_data.track_no = track_no;
If (send_mess_to_server (mess_send )){
If (read_one_response (& mess_ret )){
If (mess_ret.response = r_success ){
Return (1 );
} Else {
Fprintf (stderr, "% s", mess_ret.error_text );
}
} Else {
Fprintf (stderr, "server failed to respond/N ");
}
} Else {
Fprintf (stderr, "server not accepting requests/N ");
}
Return (0 );
}