c++面試題 網絡編程篇

守護進程編程規範

  • 1.首先要做的是調用umask,重設文件權限掩碼。
    文件權限掩碼是指屏蔽掉文件權限中的對應位。比如,有個文件權限掩碼是050,它就屏蔽了文件組擁有者的可讀與可執行權限。
    由於使用fork函數新建的子進程繼承了父進程的文件權限掩碼,這就給該子進程使用文件帶來了諸多的麻煩。
    因此,把文件權限掩碼設置爲0,可以大大增強該守護進程的靈活性。
    設置文件權限掩碼的函數是umask。在這裏,通常的使用方法爲umask(0)。



  • 2.調用fork,然後將父進程退出(exit)
    這是編寫守護進程的第一步。由於守護進程是脫離控制終端的,因此,完成第一步後就會在Shell終端裏造成一程序已經運行完畢的假象。
    之後的所有工作都在子進程中完成,而用戶在Shell終端裏則可以執行其他命令,從而在形式上做到了與控制終端的脫離。
    在Linux中父進程先於子進程退出會造成子進程成爲孤兒進程,而每當系統發現一個孤兒進程是,就會自動由1號進程(init)收養它。這樣,原先的子進程就會變成init進程的子進程。


  • 3.調用setsid以創建一個新會話
    由於創建守護進程的第一步調用了fork函數來創建子進程,再將父進程退出。由於在調用了fork函數時,子進程全盤拷貝了父進程的會話期、進程組、控制終端等。
    雖然父進程退出了,但會話期、進程組、控制終端等並沒有改變,因此,還還不是真正意義上的獨立開來,而setsid函數能夠使進程完全獨立出來,從而擺脫其他進程的控制。

  • 4.當前工作目錄更改爲根目錄。從父進程繼承過來的當前工作目錄可能在一個裝配文件系統中。因爲守護進程可能會在系統再引導之前就一直存在,所以如果守護進程的當前工作目錄。在一個裝配文件系統中,那麼該文件系統就不可拆卸。這與裝配文件系統的原意不符。

  • 5.閉不再需要的文件描述符。用fork函數新建的子進程會從父進程那裏繼承一些已經打開了的文件。這些被打開的文件可能永遠不會被守護進程讀寫,但它們一樣消耗系統資源,而且可能導致所在的文件系統無法卸下。

  • 6.些守護進程打開/dev/null使其具有文件描述符0、1和2
    因爲守護進程並不與終端設備相關聯,所以不能再終端設備上顯示其輸出,也無處從交互式用戶那裏接收輸入。

主機字節序

不同的CPU有不同的字節序類型,這些字節序是指 整數 在內存中保存的順序,這個叫做 主機序。
最常見的有兩種:

  • 1.Little endian:將低序字節存儲在起始地址
  • 2.Big endian:將高序字節存儲在起始地址

LE little-endian(小端)

  • 最符合人的思維的字節序;
  • 地址低位存儲值的低位;
  • 地址高位存儲值的高位;
  • 怎麼講是最符合人的思維的字節序,是因爲從人的第一觀感來說;
  • 低位值小,就應該放在內存地址小的地方,也即內存地址低位;
  • 反之,高位值就應該放在內存地址大的地方,也即內存地址高位;

BE big-endian(大端)

  • 最直觀的字節序;
  • 地址低位存儲值的高位;
  • 地址高位存儲值的低位;
  • 爲什麼說直觀,不要考慮對應關係;
  • 只需要把內存地址從左到右按照由低到高的順序寫出;
  • 把值按照通常的高位到低位的順序寫出;
  • 兩者對照,一個字節一個字節的填充進去;

例子:在內存中雙字 0x01020304(DWORD) 的存儲方式

內存地址

4000 4001 4002 4003 
LE 04 03 02 01 
BE 01 02 03 04 

例子:如果我們將0x1234abcd寫入到以0x0000開始的內存中,則結果爲

      big-endian  little-endian
0x0000  0x12      0xcd
0x0001  0x23      0xab
0x0002  0xab      0x34
0x0003  0xcd      0x12

x86系列CPU都是little-endian的字節序。

網絡字節序

網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操作系統等無關,從而可以保證數據在不同主機之間傳輸時能夠被正確解釋。網絡字節序採用big endian排序方式。

網絡字節序和主機字節序的轉換

htons 把unsigned short類型從主機序轉換到網絡序
htonl 把unsigned long類型從主機序轉換到網絡序
ntohs 把unsigned short類型從網絡序轉換到主機序
ntohl 把unsigned long類型從網絡序轉換到主機序


網絡與主機字節轉換函數:htons()、ntohs()、htonl()、ntohl() (注意:s 就是short l是long h是host n是network)

Linux 進程間通訊的各種方式的比較

各種通信方式的比較和優缺點

  • 管道:速度慢,容量有限,只有父子進程能通訊
  • FIFO:任何進程間都能通訊,但速度慢
  • 消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題
  • 信號量:不能傳遞複雜消息,只能用來同步
  • 共享內存區:能夠很容易控制容量,速度快,但要保持同步,比如一個進程在寫的時候,另一個進程要注意讀寫的問題,相當於線程中的線程安全,當然,共享內存區同樣可以用作線程間通訊,不過沒這個必要,線程間本來就已經共享了同一進程內的一塊內存

fork 函數的返回值

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

返回值有三種:

  • 負數:如果出錯,則fork()返回-1,此時沒有創建新的進程。最初的進程仍然運行。
  • :在子進程中,fork()返回0
  • 正數:在負進程中,fork()返回正的子進程的PID

wait 和waitpid

#include <sys/wait.h>
pid_t wait(int * statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);

waitpid則可以通過設置一個選項來設置爲非阻塞,另外waitpid並不是等待第一個結束的進程而是等待參數中pid指定的進程。

waitpid的option常量:
WNOHANGwaitpid將不阻塞如果指定的pid並未結束
WUNTRACED如果子進程進入暫停執行情況則馬上返回,但結束狀態不予以理會。

waitpid中pid的含義依據其具體值而變:

  • pid==-1 等待任何一個子進程,此時waitpid的作用與wait相同
  • pid >0 等待進程ID與pid值相同的子進程
  • pid==0 等待與調用者進程組ID相同的任意子進程
  • pid<-1 等待進程組ID與pid絕對值相等的任意子進程

waitpid提供了wait所沒有的三個特性:

  • 1 waitpid使我們可以等待指定的進程
  • 2 waitpid提供了一個無阻塞的wait
  • 3 waitpid支持工作控制

XSI信號量函數API

信號量(信號燈)本質上是一個計數器,用於協調多個進程(包括但不限於父子進程)對共享數據對象的讀/寫。它不以傳送數據爲目的,主要是用來保護共享資源(信號量、消息隊列、socket連接等),保證共享資源在一個時刻只有一個進程獨享。

信號量是一個特殊的變量,只允許進程對它進行等待信號和發送信號操作。最簡單的信號量是取值0和1的二元信號量,這是信號量最常見的形式。

通用信號量(可以取多個正整數值)和信號量集方面的知識比較複雜,應用場景也比較少。

下面側重介紹二元信號量。

相關函數
Linux中提供了一組函數用於操作信號量,程序中需要包含以下頭文件:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

1、semget函數
semget函數用來獲取或創建信號量,它的原型如下:

int semget(key_t key, int nsems, int semflg);
  • 1)參數key是信號量的鍵值,typedef unsigned int key_t,是信號量在系統中的編號,不同信號量的編號不能相同,這一點由程序員保證。key用十六進制表示比較好。

  • 2)參數nsems是創建信號量集中信號量的個數,該參數只在創建信號量集時有效,這裏固定填1。

  • 3)參數sem_flags是一組標誌,如果希望信號量不存在時創建一個新的信號量,可以和值IPC_CREAT做按位或操作。如果沒有設置IPC_CREAT標誌並且信號量不存在,就會返錯誤(errno的值爲2,No such file or directory)。

  • 4)如果semget函數成功,返回信號量集的標識;失敗返回-1,錯誤原因存於error中。

PS:
可以使用ftok函數將文件名轉變成一個key_t

 key_t ftok(const char *pathname, int proj_id);

其中參數fname是指定的文件名,這個文件必須是存在的而且可以訪問的。id是子序號,它是一個8bit的整數。即範圍是0~255。當函數執行成功,則會返回key_t鍵值,否則返回-1

示例

1)獲取鍵值爲0x5000的信號量,如果該信號量不存在,就創建它,代碼如下:

int semid=semget(0x5000,1,0640|IPC_CREAT);

2)獲取鍵值爲0x5000的信號量,如果該信號量不存在,返回-1,errno的值被設置爲2,代碼如下:

int semid= semget(0x5000,1,0640);

2、semctl函數
該函數用來控制信號量(常用於設置信號量的初始值和銷燬信號量),它的原型如下:

int semctl(int semid, int sem_num, int command, ...);
  • 1)參數semid是由semget函數返回的信號量標識。
  • 2)參數sem_num是信號量集數組上的下標,表示某一個信號量,填0。
  • 3)參數cmd是對信號量操作的命令種類,常用的有以下兩個:

IPC_RMID:銷燬信號量,不需要第四個參數;
SETVAL:初始化信號量的值(信號量成功創建後,需要設置初始值),這個值由第四個參數決定。第四參數是一個自定義的共同體,如下:

// 用於信號燈操作的共同體。
  union semun
  {
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
  };
  • 4)如果semctl函數調用失敗返回-1;如果成功,返回值比較複雜,暫時不關心它。

示例:

1)銷燬信號量。

semctl(semid,0,IPC_RMID);

2)初始化信號量的值爲1,信號量可用。

union semun sem_union;
  sem_union.val = 1;
  semctl(semid,0,SETVAL,sem_union);

3、semop函數
該函數有兩個功能:1)等待信號量的值變爲1,如果等待成功,立即把信號量的值置爲0,這個過程也稱之爲等待鎖;2)把信號量的值置爲1,這個過程也稱之爲釋放鎖。

int semop(int semid, struct sembuf *sops, unsigned nsops);

1)參數semid是由semget函數返回的信號量標識。
2)參數nsops是操作信號量的個數,即sops結構變量的個數,設置它的爲1(只對一個信號量的操作)。
3)參數sops是一個結構體,如下:

struct sembuf
{
  short sem_num;   // 信號量集的個數,單個信號量設置爲0。
  short sem_op;    // 信號量在本次操作中需要改變的數據:-1-等待操作;1-發送操作。
  short sem_flg;   // 把此標誌設置爲SEM_UNDO,操作系統將跟蹤這個信號量。
                   // 如果當前進程退出時沒有釋放信號量,操作系統將釋放信號量,避免資源被死鎖。
};

示例:

1)等待信號量的值變爲1,如果等待成功,立即把信號量的值置爲0;

struct sembuf sem_b;
  sem_b.sem_num = 0;
  sem_b.sem_op = -1;
  sem_b.sem_flg = SEM_UNDO;
  semop(sem_id, &sem_b, 1);

2)把信號量的值置爲1。

struct sembuf sem_b;
  sem_b.sem_num = 0;
  sem_b.sem_op = 1;
  sem_b.sem_flg = SEM_UNDO;
  semop(sem_id, &sem_b, 1);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章