標籤:style blog http color os 使用 ar for 2014
引言
本文會寫一個並發伺服器(concurrent server)程式,它為每個客戶請求fork出一個子進程。
注意
1. 訊號處理問題
對於相同訊號,按訊號的先後順序依次處理。可能會產生的問題是,正在處理sig1訊號時,又來了2個或更多的sig1訊號,此sig1時只會在處理完原來的sig1訊號後,再處理1個sig1訊號。因此對於相同訊號,會產生訊號掉包的問題。 一個兒子退了之後,程式在處理handler(),如果此時又退了兩個兒子,那麼必然有一個兒子的資源回收不到,稱為殭屍進程。
對於不同訊號,優先處理後者,處理完後者在回頭處理上一個。例如正在處理sig1時,來了sig2,則會先處理sig2,等處理完sig2後,回過頭繼續處理sig1。
2. 解決方案
在註冊的訊號處理函數中,使用迴圈,並在迴圈中用waitpid來回收子進程資源。只要進入訊號處理函數,那麼該訊號處理函數就可以把所有fork出的兒子的資源都回收掉。注意:waitpid要設定成非阻塞模式,不然當進入迴圈後,如果沒有子進程退出時,會阻塞在訊號處理函數中。
3. wait與waitpid
wait一定是阻塞模式。因此在訊號處理函數中,用while1{wait(NULL)}有問題,如果進入訊號處理函數後,只要有兒子不退,就會一直阻塞在這裡。
waitpid可以是阻塞模式,也可以是非阻塞模式。
4. select與accept
兩者在收到訊號時,均會返回-1。
對於系統預設不處理的訊號,程式收不到,兩者當然也不會返回-1。換句話說,SIGCHLD是系統預設不處理的訊號,如果不對其註冊訊號處理函數,當子進程退出時,select與accept也是不會返回-1的。
5. 本代碼用到了Linux網路編程9——對TCP與UDP的簡易封裝2.0中的動態庫。
代碼
server.c
/************************************************************************* > File Name: server.c > Author: KrisChou > Mail:[email protected] > Created Time: Fri 05 Sep 2014 03:31:12 PM CST ************************************************************************/#include "my_socket.h"#include <sys/wait.h>#include <signal.h>#include <errno.h>#define MY_IP "192.168.1.100"#define MY_PORT 8888#define SIZE 192#define MSG_SIZE (SIZE - 4)extern int errno ;typedef struct tag_mag{ int msg_len;//記錄msg_buf的真實大小 char msg_buf[MSG_SIZE];//msg_buf所佔空間為188byte}MSG, *pMSG;void my_handle(int num){ /*waitpid參數: * -1表示回收每一個兒子 * NULL表示不關心子進程的exit的傳回值 * WNOHANG:wait no hang 非阻塞模式 *waitpid傳回值: * -1表示沒有建立任何兒子 * 0表示沒有兒子退出 *大於0表示有兒子退出 * */ while(waitpid(-1, NULL, WNOHANG ) > 0) ;}int main(int argc, char* argv[]){ int fd_listen , fd_client ; signal(SIGCHLD, my_handle); my_socket(&fd_listen, MY_TCP, MY_IP ,MY_PORT); my_listen(fd_listen, 10); while( fd_client = accept(fd_listen, NULL, NULL)) { /* 只要不是程式預設忽略的訊號,accept都能收到,並返回-1 */ if(fd_client == -1) { if(errno == EINTR) { continue ; }else { break ; //break退出後,父親就退了。下來兒子會由init接管。 } }else { if(fork() == 0) //fork兒子用於與用戶端通訊 { MSG recv_msg ; int recvn; while(1 ) { memset(&recv_msg, 0, sizeof(MSG)); /*在my_socket.c中,my_recv接收的長度 與 my_send 發送的長度必須是精確值 * my_recv中填的長度小於等於實際要收的,是可以的,大於的話就永遠退不出迴圈了*/ my_recv(&recvn, fd_client, &recv_msg, 4); if(recvn == 0) //當對面用戶端退出(關閉socket),系統調用recv的傳回值為0 { break ; }else { my_recv(NULL,fd_client, &recv_msg.msg_buf, recv_msg.msg_len); my_send(NULL, fd_client, &recv_msg, 4 + recv_msg.msg_len); } } close(fd_client); exit(0); } close(fd_client); } } return 0 ;}
client.c
#include "my_socket.h"#define MY_IP "192.168.1.100"#define MY_PORT 6666#define SER_IP "192.168.1.100"#define SER_PORT 8888#define SIZE 192#define MSG_SIZE (SIZE - 4)typedef struct tag_mag { int msg_len ; char msg_buf[MSG_SIZE];//188}MSG, *pMSG;int main(int argc, char* argv[]){ int sfd ; my_socket(&sfd, MY_TCP, MY_IP, MY_PORT); my_connect(sfd, SER_IP, SER_PORT); MSG my_msg ; while(memset(&my_msg, 0, sizeof(MSG)), fgets(my_msg.msg_buf, MSG_SIZE, stdin)!= NULL) { my_msg.msg_len = strlen(my_msg.msg_buf); my_send(NULL, sfd, &my_msg, 4 + my_msg.msg_len ); memset(&my_msg, 0, sizeof(MSG)); my_recv(NULL, sfd, &my_msg, 4); my_recv(NULL, sfd, &my_msg.msg_buf, my_msg.msg_len); printf("recv from server : %s \n", my_msg.msg_buf); } close(sfd);}
注意:本代碼由於client.c中綁定了連接埠,因此只能連一個用戶端,讀者可以自己從命令列中輸入連接埠號碼或者讓系統自行分配。
本範式的缺陷在於,服務端在每次收到一個用戶端串連請求後,才會fork兒子進行處理,fork有時間開銷。更好的方法是,服務端提前fork好兒子,下篇博文會講進程池。
Linux客戶/伺服器程式設計範式1——並發伺服器(進程)