1. 程式人生 > >多程序伺服器

多程序伺服器

前面的話

  伺服器按處理方式可以分為迭代伺服器和併發伺服器兩類。平常用C寫的簡單Socket客戶端伺服器通訊,伺服器每次只能處理一個客戶的請求,它實現簡單但效率很低,通常這種伺服器被稱為迭代伺服器。 然而在實際應用中,不可能讓一個伺服器長時間地為一個客戶服務,而需要其具有同時處理 多個客戶請求的能力,這種同時可以處理多個客戶請求的伺服器稱為併發伺服器,其效率很 高卻實現複雜。在實際應用中,併發伺服器應用的最廣泛。 
迭代伺服器與併發伺服器

  linux有3種實現併發伺服器的方式:多程序併發伺服器,多執行緒併發伺服器,IO複用,先來看多程序併發伺服器的實現。

建立程序

Linux下的程序

  在建立新程序時,要進行資源拷貝。Linux 有三種資源拷貝的方式:

  1. 共享:新老程序共享通用的資源。當共享資源時,兩個程序共同用一個數據結構,不需要為新程序另建。
  2. 直接拷貝:將父程序的檔案、檔案系統、虛擬記憶體等結構直接拷貝到子程序中。子程序建立後,父子程序擁有相同的結構。
  3. Copy on Write:拷貝虛擬記憶體頁是相當困難和耗時的工作,所以能不拷貝就最好不 要拷貝,如果必須拷貝,也要儘可能地少拷貝。為此,Linux 採用了 Copy on Write 技術,把真正的虛擬記憶體拷貝推遲到兩個程序中的任一個試圖寫虛擬頁的時候。如 果某虛擬記憶體頁上沒有出現寫的動作,父子程序就一直共享該頁而不用拷貝。

程序建立函式fork與vfork

  下面介紹建立新程序的兩個函式:fork()和 vfork()。 
  其中,fork 用於普通程序的建立,採用的是 Copy on Write 方式;而 vfork 使用完全共享的建立,新老程序共享同樣的資源,完全沒有拷貝。

● fork函式原型如下:

  #include <unistd.h>
  pid_t fork (void);

  函式呼叫失敗會返回-1。fork 函式呼叫失敗的原因主要有兩個:

  1. 系統中已經有太多的進 程;
  2. 該實際使用者 ID 的程序總數超過了系統限制。

   而如果呼叫成功,該函式呼叫會在父子程序中分別返回一次。在呼叫程序也就是父程序中,它的返回值是新派生的子程序的 ID 號,而在子程序中它的返回值為 0。因此可以通過返回值來區別當前程序是子程序還是父程序。

  為什麼在 fork 的子程序中返回的是 0,而不是父程序 id 呢? 
  原因在於:沒有子程序都只 有一個父程序,它可以通過呼叫 getppid 函式來得到父程序的 ID,而對於父程序,它有很多 個子程序,他沒有辦法通過一個函式得到各子程序的ID。如果父程序想跟蹤所有子程序的ID, 它必須記住 fork 的返回值。

● vfork函式原型如下:

  #include <unistd.h>
  pid_t vfork (void);

  vfork 是完全共享的建立,新老程序共享同樣的資源,完全沒有拷貝。當使用 vfork()創 建新程序時,父程序將被暫時阻塞,而子程序則可以借用父程序的地址空間執行。這個奇特 狀態將持續直到子程序要麼退出,要麼呼叫 execve(),至此父程序才繼續執行。 
  可以通過下面的程式來比較 fork 和 vfork 的不同。

#include <sys/types.h> 
#include <unistd.h> 
int main(void) 
{
    pid_t   pid; 
    int status;
     if ((pid = vfork()) == 0) 
    { 
       sleep(2); 
        printf("child running.\n"); 
         printf("child sleeping.\n"); 
         sleep(5); 
        printf("child dead.\n"); 
         exit(0); 
    } 
    else if ( pid > 0) 
    { 
         printf("parent running .\n"); 
         printf("parent exit\n"); 
         exit(0); 21.     
    } 
    else
    {
         printf("fork error.\n"); 
        exit(0); 
    } 
} 

  程式執行結果如下:

child running. 
child sleeping. 
child dead. 
parent running . 
parent exit

  如果將 vfork 函式換成 fork 函式,該程式執行的結果如下:

parent running . 
parent exit 
[[email protected] test]# 
child running. 
child sleeping. 
child dead. 

使用fork函式實現多程序併發伺服器

  fork 呼叫後,父程序和子程序繼續執行 fork 函式後的指令,是父程序先執行還是子程序 先執行是不確定的,這取決於系統核心所使用的排程演算法。 
  而在網路程式設計中,父程序中呼叫 fork 之前開啟的所有套接字描述符在函式 fork 返回之後都是共享。如果父、子程序同時對同一個描述符進行操作, 而且沒有任何形式的同步,那麼它們的輸出就會相互混合。

  fork函式在併發伺服器中的應用: 
  父、子程序各自執行不同的程式段,這是非常典型的網路伺服器。父程序等待客戶 的服務請求。當這種請求到達時,父程序呼叫 fork 函式,產生一個子程序,由子程序對該請求作處理。父程序則繼續等待下一個客戶的服務請求。並且這種情況下,在 fork 函式之後,父、子程序需要關閉各自不使用的描述符,即父程序將不需要的 已連線描述符關閉,而子程序關閉不需要的監聽描述符。這麼做的原因有3個:

  1. 節省系統資源
  2. 防止上面提到的父、子程序同時對共享描述符程序操作
  3. 最重要的一點,是確保close函式能夠正確關閉套接字描述符

  我們在socket程式設計中呼叫 close 關閉已連線描述符時,其實只是將訪問計數值減 1。而描述符只在訪 問計數為 0 時才真正關閉。所以為了正確的關閉連線,當呼叫 fork 函式後父程序將不需要的 已連線描述符關閉,而子程序關閉不需要的監聽描述符。

  好了,有了上面的知識,我們現在可以總結出編寫多程序併發伺服器的基本思路:

  1. 建立連線
  2. 伺服器呼叫fork()產生新的子程序
  3. 父程序關閉連線套接字,子程序關閉監聽套接字
  4. 子程序處理客戶請求,父程序等待另一個客戶連線。

伺服器端程式碼示例(來源於網路):

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <netdb.h>
#define SERV_PORT 1113
#define LISTENQ  32
#define MAXLINE 1024
/***連線處理函式***/
void str_echo(int fd);
int 
main(int argc, char *argv[]){
  int listenfd,connfd;
  pid_t childpid;
  socklen_t clilen;
  struct sockaddr_in servaddr;
  struct sockaddr_in cliaddr;
  if((listenfd = socket(AF_INET, SOCK_STREAM,0))==-1){
     fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
     exit(1);
  }
  /* 伺服器端填充 sockaddr結構*/ 
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);
  /* 繫結listenfd描述符  */ 
  if(bind(listenfd,(struct sockaddr*)(&servaddr),sizeof(struct sockaddr))==-1){
    fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
    exit(1);
   }
   /* 監聽listenfd描述符*/
    if(listen(listenfd,5)==-1){
        fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
        exit(1);
    }
  for ( ; ; )  {
    clilen = sizeof(cliaddr);
    /* 伺服器阻塞,直到客戶程式建立連線  */
    if((connfd=accept(listenfd,(struct sockaddr*)(&cliaddr),&clilen))==-1){
        fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
        exit(1);
    }
    //有客戶端建立了連線後
    if ( (childpid = fork()) == 0) { /*子程序*/
     close(listenfd);    /* 關閉監聽套接字*/
     str_echo(connfd);   /*處理該客戶端的請求*/
     exit (0);
    }
    close(connfd);/*父程序關閉連線套接字,繼續等待其他連線的到來*/
 }
}
void str_echo(int sockfd){
 ssize_t n;
    char  buf[MAXLINE];
    again:
      while ( (n = read(sockfd, buf, MAXLINE)) > 0)
          write(sockfd, buf, n);
      if (n < 0 && errno == EINTR)//被中斷,重入
          goto again;
      else if (n < 0){//出錯
        fprintf(stderr,"read error:%s\n\a",strerror(errno));
        exit(1);
      }
}

  傳統的網路伺服器程式大都在新的連線到達時,fork一個子程序來處理。雖然這種模式很多年使用得很好,但fork有一些問題:

  1. fork是昂貴的。fork時需要複製父程序的所有資源,包括記憶體映象、描述字等;目前的實現使用了一種寫時拷貝(copy-on-write)技術,可有效避免昂貴的複製問題,但fork仍然是昂貴的;
  2. fork子程序後,父子程序間、兄弟程序間的通訊需要程序間通訊IPC機制,給通訊帶來了困難;
  3. 多程序在一定程度上仍然不能有效地利用系統資源;
  4. 系統中程序個數也有限制。

  下一篇文章介紹基於多執行緒的併發伺服器程式設計。

擴充套件-程序的終止

  程序終止存在兩種可能:父程序先於子程序終止;子程序先於父程序終止。

  • 如果父程序在子程序之前終止,則所有子程序的父程序被改為 init 程序,就是由 init 進 程領養。在一個程序終止是,系統會逐個檢查所有活動程序,判斷 這些程序是否是正要終止 的程序的子程序。如果是,則該程序的父程序 ID 就更改為 1(init 的 ID)。這就保證了每個 程序都有一個父程序。
  • 如果子程序在父程序之前終止,系統核心會為每個終止子程序儲存一些資訊,這樣父進 程就可以通過呼叫 wait()或 waitpid()函式,獲得子程序的終止資訊。終止子程序儲存的資訊 包括程序 ID、該程序的終止狀態,以及該程序使用的 CPU 時間總量。當父程序呼叫 wait() 或 waitpid()函式時,系統核心可以釋放終止程序所使用的所有儲存空間,關閉其所有開啟文 件。一個已經終止,但是其父程序尚未對其進行善後處理的程序稱為殭屍程序。

  當子程序正常或異常終止時,系統核心向其父程序傳送 SIGCHLD 訊號,預設情況下, 父程序忽略該訊號,或者提供一個該訊號發生時即被呼叫的函式。 
  父程序可以通過呼叫 wait()或 waitpid()函式,獲得子程序的終止資訊。

  • wait 函式原型如下:
#include <sys/wait.h> 
pid_t wait(int *statloc);

  引數 statloc 返回子程序的終止狀態(一個整數)。當呼叫該函式時,如果有一個子程序 已經終止,則該函式立即返回,並釋放子程序所有資源,返回值是終止子程序的 ID 號。如果當前沒有終止的子程序,但有正在執行的子程序,則 wait 將阻塞直到有子程序終止時才返 回。如果當前既沒有終止的子程序,也沒有正在執行的子程序,則返回錯誤-1。

  函式 waitpid 對等待哪個程序終止及是否採用阻塞操作方式方面給了更多的控制。

  • waitpid函式原型如下:
#include <sys/wait.h> 
waitpid(pid_t pid ,int *statloc, int option);

  當引數 pid 等於-1 而 option 等於 0 時,該函式等同於 wait()函式。 引數 pid 指定了父程序要求知道哪些子程序的狀態,當 pid 取-1 時,要求知道任何一個子程序的終止狀態。當 pid 取值大於 0 時,要求知道程序號為 pid 的子程序的終止狀態。當 pid 取值小於-1 時,要求知道程序組號為 pid 的絕對值的子程序的終止狀態。 
  引數 option 讓使用者指定附加選項。最常用的選項是 WNO_HANG,它通知核心在沒有已 終止子程序時不要阻塞。 
  當前有終止的子程序時,返回值為子程序的 ID 號,同時引數 statloc 返回子程序的終止 狀態。否則返回值為-1。 
  和wait較大的不同是waitpid可以迴圈呼叫,等待所有任意程序結束,而wait只有一次機會。

相關推薦

node程序伺服器

node提供了四種方法來建立子程序,分別是child_process.exec(),child_process.execFile(), child_process.fork(),child_process.spawn()。他們都返回子程序物件。exec:啟動一個子程序執行命令,並且有一個回撥函式獲知子程序的狀

程序伺服器中,epoll的建立應該在建立子程序之後

#include <iostream>#include <sys/socket.h>#include <sys/epoll.h>#include <netinet/in.h>#include <arpa/inet.h>#include <fcn

基於TCP協議實現Linux下客戶端與伺服器之間的通訊,實現執行緒、程序伺服器

TCP是TCP/IP協議族中一個比較重要的協議,這是一種可靠、建立連結、面向位元組流的傳輸,工作在傳輸層。和TCP相對的不可靠、無連結、面向資料報的協議UDP,瞭解UDP客戶端與伺服器之間通訊請戳UDP協議實現的伺服器與客戶端通訊 TCP協議建立連線 首

程序伺服器(python 版)

多程序伺服器 1. 多程序伺服器from socket import * from multiprocessing import * from time import sleep # 處理客戶端的請

網路程式設計 筆記(六) 程序伺服器

程序 - 程序(Process):“佔用記憶體空間的正在執行的程式” - 程序ID :作業系統給程序分配的id,其值大於2,1要分配給作業系統啟動後的首個程序 -linux檢視程序的命令:ps au ;引數a和u列出所有程序的詳細資訊 通過fo

linux程序伺服器示例

伺服器端#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/i

程序伺服器

前面的話  伺服器按處理方式可以分為迭代伺服器和併發伺服器兩類。平常用C寫的簡單Socket客戶端伺服器通訊,伺服器每次只能處理一個客戶的請求,它實現簡單但效率很低,通常這種伺服器被稱為迭代伺服器。 然而在實際應用中,不可能讓一個伺服器長時間地為一個客戶服務,而需要其具有同時

select伺服器端的程式碼以及select的優缺點與執行緒程序伺服器的比較

 22     struct sockaddr_in local;  23     local.sin_family=AF_INET;  24     local.sin_port=htons(port);  25     local.sin_addr.s_addr=inet_addr(ip);  26  

一個epoll程序伺服器示例

#include<iostream> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<arpa/inet.h> #inclu

基於訊息佇列的程序伺服器

目錄一、思路二、實現2. 修改2.1 思路2.2 程式碼 一、思路 1)server程序接收時, 指定msgtyp為0, 從隊首不斷接收訊息; 2)server程序傳送時, 將mtype指定為接收到的client程序的pid; 3)client程序傳送的時候

併發伺服器的實現(程序執行緒...)

一、多程序實現併發伺服器 程式碼如下:multiprocess_server.c /* ============================================================================ Name : TCPServ

Linux程序併發伺服器(TCP)

Linux多程序併發伺服器(TCP) 前言:在Linux環境下多程序的應用很多,其中最主要的就是網路/客戶伺服器。多程序伺服器是當客戶有請求時 ,伺服器用一個子程序來處理客戶請求。父程序繼續等待其它客戶的請求。這種方法的優點是當客戶有請求時 ,伺服器能及時處理客戶 ,特別是在客戶伺服

Linux學習之網路程式設計(程序併發伺服器

言之者無罪,聞之者足以戒。 - “詩序” 上面我們所說過的通訊都是一個伺服器一個客戶端之間的通訊,下面我們來交流一下多程序併發伺服器的相關知識 邏輯上就是這個樣子的,就是一個伺服器多個客戶端進行資料的傳輸。 1、傳送資料的函式: ssize_t send(int sockfd,

socket伺服器程式設計的程序執行緒實現

socket伺服器程式設計: 顧名思義就是使用socket套接字來編寫伺服器程式的過程。不熟悉socket程式設計的小夥伴可以看我之前的文章,但是當時所實現的功能伺服器同時只能和一個客戶端進行互動,效率太低,利用多程序或者多執行緒方式來實現伺服器可以做到同時和多個客戶端進行互動。提高伺服器的效能

linux網路程式設計之程序併發伺服器

1)使用多程序併發伺服器考慮的因素:       (1)父程序描述最大檔案描述符的個數(父程序需要關閉accept返回的新檔案描述符)       (2)系統內可建立程序的個數(與記憶體大小相關)       (3)程序建立過多是否降低整體服務效能 2)多程序建立併發

程序web伺服器---01

程式碼流程 main函式設計: 建立套接字 將套接字設定成四次揮手不出bug 繫結套接字 把套接字變為監聽套接字 迴圈等待客戶端連結 接受套接字accept new_socket, client_addr = tc

嵌入式linux-sqlite3資料庫,程序併發伺服器,線上詞典

文章目錄 1,簡介: 2,框架圖 2.1,客戶端框架 2.1,伺服器端框架 3,程式碼 3.1,客戶端程式碼 3.2,伺服器端程式碼 1,簡介: 1,線上詞典

嵌入式Linux網路程式設計,TCP併發伺服器,TCP執行緒併發伺服器,TCP程序併發伺服器

文章目錄 1,TCP多執行緒併發伺服器 1.1,標頭檔案net.h 1.2,客戶端client.c 1.3,伺服器端server.c 2,TCP多程序併發伺服器 2.1,標頭檔案net.h 2.2,客

零基礎學python:併發伺服器、面向連線、程序執行緒、單程序

面向連線的併發伺服器 只能同時為一個人服務 為了幫助小夥伴們更好的學習Python,小編整理了Python的相關學習視訊及學習路線圖; ,新增小編學習群943752371即可獲取 多程序併發伺服器 多程序伺服器代表:Apache伺服器 主程序中必需