Linux Server Development Preliminary
Chen Qin Yang
server development needs to consider a lot of things, such as server architecture, stability, performance and load capacity, and so on. In fact, in the process of developing a server, there are a number of factors that need to be taken into account, such as in the case of a server with a shorter and more frequent client connection (for example, an HTTP server), in an optional server structure, the pre-derived/threaded structure is more efficient than the concurrency structure, which will be in the following articles A detailed description of it. Then there are details of the server implementation, such as the need to support cross-platform capabilities, what development language and development tools to use, and how to improve server system performance. All of these issues need to be fully considered in the process of defining and designing the server. In fact, whether it is a Windows server, or a Linux server, they have a common feature. The first is running in the background, at present, the vast majority of servers are running in the background, because the main task of the server is to provide clients with the requested services, usually do not need to interact with the user interface, users only need to be able to start the service, suspend service or stop the service on it, so the service There is no need to occupy a terminal session (or to have a visual user interface); second, because the server is running in the background, it does not have a visual user interface, so the parameters required for the server to run can be read only through file [1], and then processed differently according to the data read from the file ; again, because the server is running in the background, it cannot display the running state and some necessary processing results to the user through the interface, so it needs to write this information to a file [2] so that the user can diagnose the server's failure according to the contents of the server when the problem occurs; Or is it related to the background operation of the server, for the users of the computer, the server is not a need for frequent interaction of the program, in comparison with the general application, in the server design process, the server should take more account of the issue of system resources, the resources mentioned here include CPU, IO and memory resources. This is especially important for Windows services, because Windows services are likely to be installed on one user's machine rather than on a particular Windows server. Imagine that if a Windows service consumes too much system resources, users of the system will likely not be able to perform other tasks normally. It summarizes the features that are common to various servers, and the design and implementation of these common features is described in detail below, and the differences between Windows servers and Linux servers are explained as necessary. first, the background operationIn the Linux environment, to implement the server's background is very simple, only to the server program file name after the "&" symbol on it. For example, the file name of a server program is Test_server, so the way to start the server and run it in the background is to enter "test_server&" at the prompt. Suppose you now have a Test_server server with the following code: #include <stdio.h>intMain (intargcChar*argv[]) { while(1) {//code to serve the client} return0; When you run the program using "test_server&", the system automatically returns to the prompt without taking up any terminals (if you start the program by using the program name directly, the system will be whileA dead loop consumes the terminal until the program process is killed). We can use the PS command to see if the program is running in the background, and we may get a message similar to the following: root 1417 1358 14:47 tty1 00:12:10./test_server Why you need to use this simple program whileDead loops. There are two reasons, first, if it's not a dead loop, then the program will just run out, we simply do not see whether it has been running, let alone to check whether it has been running in the background, and secondly, this program structure is a server structure of the abstraction, most of the server data receiving, processing and sending operations are in this whileIn a dead loop until a signal or instruction is obtained, the server program quits. Unfortunately, there is not a more professional way to get the server program to quit, we can kill the server process with the KILL command, which seems a bit barbaric, but also a good way. In a later introduction, we will use another way of looking more professional to solve this problem. Another way to make a server program run in the background is to use the daemon (daemon process). To run a server program as a daemon, it is common practice to first define a daemon initialization function and then call the initialization function in the main function. In general, the daemon initialization function can be defined as follows:voidInitdaemon () {pid_t pid;if((PID = fork ())!= 0) exit (0); Setsid ();if((PID = fork ())!= 0) exit (0); ChDir ("/"); Umask (0); The program first invokes fork and lets the parent process exit, and the child process continues to run. Because the subprocess is derived by the parent process through the fork system call, the child process inherits the process group number of the parent process, but it has its own process number, and then calls Setsid to create a new session[3], which is the only process in that session. And is the leader (or creator) of the session, and the leader of the newly created process group. After calling Setsid, the calling process will no longer have a control terminal. The purpose of the second fork is to ensure that when the process opens the terminal device, the control terminal cannot be obtained, because the subprocess derived from session leader must not be an leader, and the control terminal cannot be obtained. ; The next chdir system call is to change the working directory of the Daemon, assuming that the daemon was started in the Vfat file system, and if chdir is not used to change the working directory of the Daemon, then after the daemon has entered the background, This VFAT file system may not be uninstalled (unmount). In fact, in a Linux system, even if you use ChDir to change the working directory of the daemon, the file system that started the daemon is still unable to unload. In practice, chdir is more used to meet the processing needs of the server itself; the final umask (0) is for the daemon to create its own files, the file permission bits are not affected by the permission bits of the original file creation mask. After you have defined the daemon initialization function, you simply call the initialization function at the necessary time (typically in the main function), for example:intMain (intargcChar*argv[]) {Initdaemon (); while(1) {} return0; As mentioned earlier, to enable a server process running in the background to exit, you can kill the server process with the KILL command, which is also valid for server programs that use daemon initialization functions to implement a background run. This may seem a bit barbaric, but it's a good way to get the server process out. But if you're using this approach, when designing a server program, we should have the ability to catch some kind of signal, which is usually specified by the kill command, and after capturing the signal, we can finish the cleanup before the server process exits, such as releasing the memory, closing the file descriptor, etc. May cause a resource leak. For example, we can use the KILL command to send a specified signal to a process, which can be specified by parameters, of course we can not specify this parameter, so the KILL command sends a SIGTERM signal to the process by default. What we are going to do now is to add a handler function to the SIGTERM signal in the server program to perform some necessary processing after the signal is obtained. We can use code similar to the following to implement this function: #include <signal.h>voidHandle_term_signal (intSIG) {//here to complete the necessary processing logic}intMain (intargcChar*argv[]) {//... signal (sigterm, handle_term_signal); // . . . return 0; The sigterm signal is then sent to the specified server process using the Kill–sigterm process number or kill process number command, and the Handle_term_signal function is executed when the server process receives the SIGTERM signal. Because the sigterm signal can be blocked and intercepted, this is different from the sigkill signal, so in the server program, we need to capture the sigterm signal, not the sigkill signal. It should be noted that in a Linux system, a process receives a sigterm signal and does not exit, and it needs to send a sigkill signal for the running process to exit. That is, first send a sigterm signal to the server process to finish the task before exiting, then send the sigkill signal to kill the process. For ease of operation, you can write a very simple shell program to complete the operation described above. Open VI, enter the following shell command, and then save it as Killit file name: Kill–sigterm sleep 2 kill–sigkill So, you can use the Killit process number command to exit the server process running in the background. In the above shell program, the Sleep command is used between the two kill commands to ensure that the server process completes the necessary finishing work before receiving the Sigkill signal. Notably, if you add the exit () function call to the function handle_term_signal (), when the process receives the sigterm signal, it automatically exits without having to use the Kill–sigkill command again. Of course, we can get the server process out of the way without using the KILL command, and use command-line arguments as a relatively professional approach. If the server program file name is "Test_server", then we can use "test_server start" to start the server, with "Test_server stop" to stop the server, and "Test_server restart" To reboot the server. The basic idea for achieving this approach is: To determine the command line parameters, if it is start, it depends on whether there is a server process running, if it is, then prompted that the server has been running, otherwise start the server; if the command-line argument is stop, then see if the server process is running. If so, the server process exits, or it prompts that the server is not running; if the command-line argument is restart, the original server process exits before starting. Doing so can both makeOur server programs look more professional and can avoid some of the resource conflicts and waste of resources caused by repeated startup of the server. The basic process is outlined in the following figure: Figure around the server program to start the process then the server program at the start, how to determine whether there is already a process is running (that is, the two diamond boxes in figure one condition, usually such a problem is called the program of the two restart problem). The most straightforward way to solve this problem is to use process communication, where the server process to be started queries some flag bits first or attempts to communicate with the server process that is already running. If you set a flag bit in the global domain, or if you can successfully communicate with a server process that is already running, the server process is started, no need to start again, or the server process is started. Linux implementation process Communication methods are many, sockets, pipelines, mutual-exclusion locks and shared memory, etc., can achieve interprocess communication. We can use shared memory to do this because shared memory can save some useful data in addition to the flags, such as the PID of the process's running server process. In this case, when you start the server process, first, determine the bit in the shared memory, if the flag bit exists, it means that there is already a server process running, and then determine what the current server program parameters are, if the parameter is "start", then obviously to the current server process to be run directly out of the If the parameter is "Stop", then get the PID of the running server process from shared memory, and then send the sigterm signal to it using the Kill function, and the server process runs to handle_term_signal immediately after capturing the sigterm signal function to perform the cleanup before the process exits, and then the process exits. Let's say we use shared memory to solve the two-reboot and process exit problems. The process described in figure I can be detailed as follows: Figure II with the server START process (refinement) Note that the above illustration does not draw a specific implementation process for shared memory, such as a shared memory request failure, a shared memory read-write failure process. This shows that Linux under the server program is more complex than Windows, it requires developers to more consider the server startup, stop and restart the details of the problem, not only that, developers also need to Linux environment in the advanced application of C language has a certain understanding. Second, the configuration fileAs mentioned earlier, because the server program does not have a user interface, users cannot set the parameters required for the server to run through the interface. This problem can be resolved using a configuration file, which allows the server program to obtain the required parameters as long as it reads the information in the configuration file. Typically, the server program reads the configuration file only when it is started, so if the user modifies the contents of the configuration file, the server program must be restarted in order for the changes to take effect. Another reason to restart the server after modifying the profile is that some parameters need to be used more than once while the server program is running, and if the values of these parameters are modified during use, it can affect the running logic of the server program. There are two common configuration file formats, one in the form of INI and the other in XML format. It doesn't matter what format the configuration file uses, as long as the server can read the data correctly from it. Of course, the configuration file must be a text file, which is intended to make it easier for the server administrator to modify the configuration file. If the system is designed to provide an editor for the configuration file, the configuration file can also be a binary file. In the Linux system, using C to read and write INI format files or XML files is not a simple matter, developers can use a third-party provided by the development library, but in my current situation, I am using the INI format file, and for the INI format file of the data to write a set of functions library. Unless the third party's development library is very good, credibility is very high, otherwise try not to use, because you can not control the error rate in the program written by others, once the program running problems, debugging other people's programs will be a headache. In Windows, reading and writing INI files is very simple, developers do not need to write their own file resolver, using the Windows API GetPrivateProfileString, WritePrivateProfileString and other functions can easily read and write INI file, read and write XML file is not too difficult, the. NET Framework provides good support for the operation of XML files. In a 32-bit Windows system, you should try to write configuration information in the system registry for the server program to read, rather than using the INI file, which is the best practice for application read-write configuration information in Windows systems. If you are designing a server system that needs to be compatible with some programs in a 16-bit Windows system, you can decide whether to use the INI file as appropriate.third, the log fileLog files are an important part of the server system. Most of the servers that currently appear have their own log files. Because of the background running characteristics of the server program and its special working mode, it cannot display some process, state and result information on the screen, the log file becomes the main way for the server program to record data. Because the server program processing is recorded in the log file, critical data such as working status and dates, so the log file is the primary basis for server system error tracking, and if the server program is running, the system administrator can determine the source of the problem based on the data description in the log file. and solve the problem to make the server work properly. In a Linux environment, programmers can add code that logs information to the appropriate location in the server program, so that when the server program runs to that location, it automatically outputs the information to the specified log file. The following code snippet attempts to output exception information to a log file after the program has an exception:voidMyserverapp::main (intargcChar*argv[]) {//...Try{ // . . . }Catch(Myserverexception &ex) {Gapplog->writelog (Loglev_err, "Exception raised:%s/n", ex. message); } // . . . } Now, let's talk about how to implement the write processing of the log file, that is, how to implement the Gapplog->writelog function in the example above [4]. It is not hard to find that the Writelog function is a function of a variable parameter, and the parameters can be set according to different conditions. For example, the Writelog function above writes only the log entry level and the necessary information string to the log file, and the server System Designer can also modify the Writelog function so that the function can also output the server name, log entry time, and so on to the log file. In a Linux system, the function of designing variable parameters is very simple, as long as the function declaration of the use of ellipsis format, in the function definition when the use of Va_list-related macros to achieve specific operations. The following example illustrates the specific implementation of the Writelog function, and the true log output function should be defined according to the specific requirements of the server system.intWritelog (intLevelChar*FMT, ...) {CharLOGLEV_STR[512]; FILE *FP; Va_list args; memset (Loglev_str, 0x00,sizeof(LOGLEV_STR)); fp = fopen ("Serversystem.log", "A +"); The first parameter here specifies that the log file name//second parameter opens the file in a + mode to ensure that the log is correctly writtenif(NULL = fp) return-1;Switch(level) { Caseloglev_ok:strcpy (Loglev_str, "[OK]"); Break; Caseloglev_err:strcpy (Loglev_str, "[ERR]"); Break; Caseloglev_war:strcpy (Loglev_str, "[WARN]"); Break;default: Break; } va_start (args, FMT); fprintf (FP, "%s", LOGLEV_STR); vfprintf (FP, FMT, args); Va_end (args); Fclose (FP); return0; In the above code, the Writelog function blindly write information to the Serversystem.log file, a long time, will inevitably lead to the indefinite increase in log file capacity, which is a very serious problem, too large log files may occupy most of the disk's effective space, This causes the server system to fail or even crash because there is not enough disk space. Since the server program started very little need for human intervention, the log file capacity indefinitely increased the problem is easily ignored by server system administrators, and as administrators, to every certain period of time to clean up the log files for the server is also a troublesome thing, And a little careless can affect the normal operation of the server system, these problems for mission-critical servers (for example, the core servers of large business systems, etc.) is intolerable. This shows that the server system requires a log management mechanism to provide a central location for server system log management, which requires at least two functions: ① the large log files for backup, and rewrite (overwrite) log files; ② Delete expired backup log files. The server's log management mechanism can be either a subsystem of the current server system or a stand-alone server system, which can be determined based on the size of the server system being designed. Log management mechanism is not necessary to always be in the clean state of the log, because the operation of the log management mechanism also needs to occupy the system resources, will inevitably affect the efficiency of the main server system. It is common practice to select a time of a relatively small server access rate, such as midnight or wee hours, every day or every few days to clean up your system logs. If the server system cannot be stopped during this period, the log processing module also needs to provide log write buffering and log file locking mechanisms to ensure that log information is written correctly when the log management mechanism cleans up the log files.
At this point, the three main features of the server program is the basic introduction on the ending. In this paper, the characteristics of the server program are briefly introduced, the main features of the server program are introduced: background operation, this feature also determines the server program configuration file and log file the necessity of existence; Then, this paper describes the characteristics of server program running in Linux environment in detail, Some design and implementation schemes are proposed. Limited to space, this article can not explain every detail of implementation, such as the above mentioned log write buffer and log locking mechanism, but in the subsequent Linux server development article, the author will try to clarify the specific details of the problem, It gives readers a deeper understanding of the implementation process of Linux server programs.
[1] This file is commonly referred to as the server configuration file [2] This file is commonly referred to as the server log file [3] Exactly, should be when calling Setsid, if the calling process is not a leader of the process group, then the SETSID function will create a new session. But here, the derived subprocess is not the leader of the process group, so calling Setsid will create a new session. [4] For illustration convenience, future use Writelog function instead of this log file write function to go from: http://blog.csdn.net/empro/archive/2006/10/25/1350789.aspx