Server setup (TCP socket)-fork version (server)

    Although the basic version of the server basically implements the basic functions of the server, if the client's concurrency is relatively large, the pressure and performance of the server will be greatly reduced. In order to improve the concurrency performance of the server, you can fork the child process. Each client with a successful connection forks a child process, which not only meets the concurrency requirements, but also achieves the effect of client isolation.

1. fork

1.1.Header file

#include <sys/types.h>
#include <unistd.h>

1.2. Prototype

pid_t fork(void);

fork() is a system call function used to create a new process in Unix-like operating systems. It copies the current process (called the parent process) and continues execution in a new process (called the child process).

The fork() function returns a value of type pid_t, whose meaning is as follows:

  • In the parent process, fork() returns the process ID (PID) of the newly created child process.
  • In the child process, fork() returns 0.
  • If the creation of the child process fails, fork() returns -1.

    The fork() function returns twice when creating a child process because it is a system call that copies the current process. Specifically, the fork() function creates a new process (child process) and copies all the contents of the parent process (including code, data, stack, etc.) to the child process.

Return for the first time:

  • In the parent process , fork() returns the process ID (PID) of the newly created child process.
  • If the creation of the child process fails, fork() returns -1.

Second return:

  • In the child process , fork() returns 0.

Through these two returns, the parent process and the child process can take different logical branches based on different return values.

In the parent process, you can do some operations related to the child process based on the returned PID of the child process, such as recording the PID of the child process, waiting for the termination of the child process, etc.

In the child process, since fork() returns 0, you can distinguish yourself as a child process based on this feature, so as to execute specific child process code logic.

It should be noted that the parent process and child process will continue to execute the code after the fork() call, and they run in different process contexts and have independent memory spaces and resources. Therefore, when using fork() to create a child process, it is usually necessary to perform different processing in the parent and child processes to avoid race conditions and unnecessary resource sharing issues.

1.3. Code implementation

#include <iostream>
//socket
#include <sys/types.h>
#include <sys/socket.h>
//close
#include <unistd.h>
//exit
#include <stdlib.h>
//perror
#include <stdio.h>
//memset
#include <string.h>
//htons
#include <arpa/inet.h>


#define PORT 8596
#define MESSAGE_SIZE 1024

int main(){
    
    

  int ret=-1;
  int socket_fd=-1;
  int accept_fd=-1;
  int backlog=10;
  int flag=1;
  int pid;

  struct sockaddr_in local_addr,remote_addr;

  //create socket
  socket_fd=socket(AF_INET,SOCK_STREAM,0);
  if(socket_fd == -1){
    
    
    perror("create socket error");
    exit(1);
  }
 //set option of socket
  ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
  if ( ret == -1 ){
    
    
    perror("setsockopt error");
  }
 //set socket address
  local_addr.sin_family=AF_INET;
  local_addr.sin_port=htons(PORT);
  local_addr.sin_addr.s_addr=INADDR_ANY;
  bzero(&(local_addr.sin_zero),8);
 //bind socket
 ret=bind(socket_fd, (struct sockaddr *)&local_addr,sizeof(struct sockaddr_in));
 if(ret == -1){
    
    
    perror("bind socket error");
    exit(1);
 }

  ret=listen(socket_fd, backlog);
  if(ret ==-1){
    
    
   perror("listen error");
   exit(1);
  }
  //loop to accept client
  for(;;){
    
    
   socklen_t addrlen = sizeof(remote_addr);
   accept_fd=accept(socket_fd,( struct sockaddr *)&remote_addr, &addrlen);
   pid=fork();
   //子进程
   if(pid==0){
    
    
    char in_buf[MESSAGE_SIZE]={
    
    0,};
    for(;;){
    
    
     memset(in_buf,0,MESSAGE_SIZE);
     //read data
     ret =recv(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);
     pid_t currentID = getpid();
     std::cout << "Current Process ID: " << currentID << std::endl;
     if(ret ==0){
    
    
        break;
     }
     printf("receive data:%s\n",in_buf);
     send(accept_fd, (void *)in_buf, MESSAGE_SIZE, 0);
   }
   printf("close client connection......");
   close(accept_fd);
  }
 }
 if(pid !=0){
    
    
  printf("quit server....");
  close(socket_fd);
 }
  return 0;
}

1.4. Achieve results

1.4.1. Client 1

The connection is successful and data is sent and received successfully

1.4.2. Server receives client 1

Insert image description here

1.4.3. Client 2

Insert image description here

1.4.4. Server receives client 2

Insert image description here
It can be seen that the server processes the two clients in different sub-processes.

Servers that use the fork() function to create child processes have the following advantages and disadvantages:

advantage:

  • Simple and easy to use: Using the fork() function to create a child process server is relatively simple and does not require the use of complex multi-threaded or multi-process programming models. By copying the memory space of the parent process, the child process can run independently and handle client requests.
  • High concurrency processing: Each client connection can create an independent sub-process, so that the server can handle multiple client requests at the same time to achieve high concurrency performance.
  • Data sharing: The parent process and the child process share file descriptors and can easily share some resources and status information, such as open files, buffers, etc.
  • Reliability: Since each child process runs independently, the crash or exception of one child process will not affect other child processes or the main server process.

shortcoming:

  • Memory overhead: Each child process needs to copy the memory space of the parent process, so in the case of large-scale concurrency, the memory overhead of the server will be relatively large.
  • Process switching overhead: Since each client connection requires the creation of a child process, it involves switching overhead between processes, including context switching and inter-process communication overhead, which may have a certain impact on server performance.
  • Scalability: Since each client connection requires the creation of a child process, the server's scalability may be limited. In large-scale concurrency situations, creating child processes for each connection may cause system resource exhaustion.
    Inter-process communication complexity: If sub-processes need to communicate or share data, they need to use inter-process communication (IPC) mechanisms, such as pipes, shared memory, etc. This increases programming complexity.

    In summary, servers that use the fork() function to create child processes are suitable for simple concurrency scenarios and smaller-scale applications. However, in cases of large-scale high concurrency, large resource consumption, or higher scalability, Other concurrency models may need to be considered, such as multithreading or event-driven models.

Guess you like

Origin blog.csdn.net/u011557841/article/details/132981657