天天看點

linux網絡程式設計之socket(四):使用fork并發處理多個client的請求和對等通信p2p

一、在前面講過的回射客戶/伺服器程式中,伺服器隻能處理一個用戶端的請求,如何同時服務多個用戶端呢?在未講到select/poll/epoll等進階IO之前,比較老土的辦法是使用fork來實作。網絡伺服器通常用fork來同時服務多個用戶端,父程序專門負責監聽端口,每次accept一個新的用戶端連接配接就fork出一個子程序專門服務這個用戶端。但是子程序退出時會産生僵屍程序,父程序要注意處理SIGCHLD信号和調用wait清理僵屍程序,最簡單的辦法就是直接忽略SIGCHLD信号。

上述程式利用了一點,就是父子程序共享打開的檔案描述符,因為在子程序已經用不到監聽描述符,故将其關閉,而連接配接描述符對父程序也沒價值,将其關閉。當某個用戶端關閉,則read 傳回0,退出循環,子程序順便exit,但如果沒有設定對SIGCHLD信号的忽略,則因為父程序還沒退出,故子程序會變成僵屍程序。

現在先運作server,再打開另外兩個終端,運作client(直接用回射客戶/伺服器程式中的用戶端程式),可以看到server輸出如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_fork 

recv connect ip=127.0.0.1 port=46452

recv connect ip=127.0.0.1 port=46453

在另一個終端ps一下:

simba@ubuntu:~$ ps aux | grep echoser

simba     3300  0.0  0.0   2008   280 pts/0    S+   22:10   0:00 ./echoser_fork

simba     3303  0.0  0.0   2008    60 pts/0    S+   22:10   0:00 ./echoser_fork

simba     3305  0.0  0.0   2008    60 pts/0    S+   22:10   0:00 ./echoser_fork

simba     3313  0.0  0.0   4392   836 pts/3    S+   22:12   0:00 grep --color=auto echoser

simba@ubuntu:~$ 

發現共有3個程序,其中一個是父程序處于監聽中,另外兩個是子程序處于對用戶端服務中,現在ctrl+c 掉其中一個client,由上面的分析可知對應服務的子程序也會退出,而因為我們設定了父程序對SIGCHLD信号進行忽略,故不會産生僵屍程序,輸出如下:

simba     3321  0.0  0.0   4392   836 pts/3    S+   22:13   0:00 grep --color=auto echoser

如果把29行代碼注釋掉,上述的情景輸出可能為:

simba     3125  0.0  0.0   2004   280 pts/0    S+   21:38   0:00 ./echoser_fork

simba     3128  0.0  0.0      0     0 pts/0    Z+   21:38   0:00 [echoser_fork] <defunct>

simba     3130  0.0  0.0   2004    60 pts/0    S+   21:38   0:00 ./echoser_fork

simba     3141  0.0  0.0   4392   832 pts/3    S+   21:40   0:00 grep --color=auto echoser

即子程序退出後變成了僵屍程序。

如果不想忽略SIGCHLD信号,則必須在信号處理函數中調用wait處理,但這裡需要注意的是wait隻能等待第一個退出的子程序,是以這裡需要使用

waitpid。若目前隻有一個子程序退出,則waitpid一次之後因為其他子程序狀态尚未改變,故傳回0退出循環;若幾個連接配接同時斷開,信号因為不能排隊

而隻接收到一個SIGCHLD信号,waitpid多次之後已經不存在子程序了,傳回-1退出循環。如下所示:

二、在最基本的回射客戶/伺服器程式中,伺服器隻能被動接收用戶端的資訊,而不能主動發送資訊給用戶端,如果我們想實作對等通信,即P2P,可以

在伺服器程式用使用兩個程序,一個程序接收使用者的輸入并發送給用戶端,另一個程序被動接收用戶端的消息并列印出來,此程序當read 傳回0 時得知

用戶端已經關閉需要退出程序,此時尚有另一個程序未退出,可以通過在退出前發送消息給它,在消息處理函數中退出。當然用戶端也必須使用雙進

程,原理與伺服器程式相同。

下面貼出伺服器端程式:

用戶端程式與伺服器端程式的改變差不多,就不貼了,這裡是使用父子程序來完成對等通信,即雙方都可以發送資訊給對方,也可以接收對方的資訊,

上面使用了kill 函數來發現自定義信号,如果子程序發送信号給父程序,可以使用getppid 函數得到父程序的id。輸出如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./p2pser

recv connect ip=127.0.0.1 port=56404

fsafd           //<< server 輸入

fds

^C

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./p2pcli

fsafd

fds            //<< client 輸入

peer close

recv a sig=10

注意,ctrl+c 會将前台程序組中的兩個程序都終止掉,即父子程序都打開了conn,隻有兩個程序都close(conn),将file 的引用計數減為0,才會真正關

閉sockfs 檔案,這樣client 其中一個程序才能read 傳回0列印peer close,并發信号給另一個程序,在信号處理函數中退出。