中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

沈陽模板 網(wǎng)站建設(shè)淘寶網(wǎng)店代運(yùn)營正規(guī)公司

沈陽模板 網(wǎng)站建設(shè),淘寶網(wǎng)店代運(yùn)營正規(guī)公司,建設(shè)政府信息網(wǎng)站,網(wǎng)站401錯誤概述 TCP客戶端/服務(wù)器程序示例是執(zhí)行如下步驟的一個回射服務(wù)器: 客戶端從標(biāo)準(zhǔn)輸入讀入一行文本,并寫給服務(wù)器。服務(wù)器從網(wǎng)絡(luò)輸入讀入這行文本,并回射給客戶端??蛻舳藦木W(wǎng)絡(luò)輸入讀入這行回射文本,并顯示在標(biāo)準(zhǔn)輸出上。 TCP服務(wù)器…

概述


TCP客戶端/服務(wù)器程序示例是執(zhí)行如下步驟的一個回射服務(wù)器:

  1. 客戶端從標(biāo)準(zhǔn)輸入讀入一行文本,并寫給服務(wù)器。
  2. 服務(wù)器從網(wǎng)絡(luò)輸入讀入這行文本,并回射給客戶端。
  3. 客戶端從網(wǎng)絡(luò)輸入讀入這行回射文本,并顯示在標(biāo)準(zhǔn)輸出上。

TCP服務(wù)器程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <arpa/inet.h>
#include <arpa/inet.h>#define MAXLINE     4096
#define SERV_PORT   9877
#define LISTENQ     1024
#define SA  struct sockaddr// 從客戶端讀入數(shù)據(jù),并把它們回射給客戶端
void str_echo(int sockfd) {ssize_t n;char    buf[MAXLINE];
again:// 從套接字讀入數(shù)據(jù)// 套接字中接收緩沖區(qū)和發(fā)送緩沖區(qū)是分開的,因此讀和寫不會發(fā)生混淆while ((n = read(sockfd, buf, MAXLINE)) > 0)write(sockfd, buf, n);    // 把套接字中的內(nèi)容回射給客戶端// 如果n<0表示讀取數(shù)據(jù)出錯或到達(dá)文件末尾// 如果errno等于EINTR,表示讀取操作被信號中斷// 如果上述兩個條件同時滿足,則重新嘗試讀取數(shù)據(jù)if (n < 0 && errno == EINTR)goto again;// 如果表示文件描述符到達(dá)文件末尾else if (n < 0)printf("str_echo: read error");
}int main(int argc, char **argv)
{int                 listenfd, connfd;pid_t               childpid;socklen_t           clilen;struct sockaddr_in  cliaddr, servaddr;/* --------------------------------------------- *///1) 創(chuàng)建一個TCP連接套接字listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd < 0) {printf("socket error");return -1;}/* --------------------------------------------- *///2) 把服務(wù)器對應(yīng)端口綁定到套接字 bzero(&servaddr, sizeof(servaddr));     // 開辟內(nèi)存servaddr.sin_family      = AF_INET;     // 地址族// 指定IP地址為INADDR_ANY,這樣要是服務(wù)器主機(jī)有多個網(wǎng)絡(luò)接口,服務(wù)器進(jìn)程就可以在任意網(wǎng)絡(luò)接口上接受客戶端連接servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port        = htons(SERV_PORT);if (bind(listenfd, (SA *) &servaddr, sizeof(servaddr)) < 0) {printf("bind error");return -1;}/* --------------------------------------------- *///3) 把套接字轉(zhuǎn)換為監(jiān)聽套接字// LISTENQ表示系統(tǒng)內(nèi)核允許在這個監(jiān)聽描述符上排隊(duì)的最大客戶端連接數(shù)if(listen(listenfd, LISTENQ) < 0) {printf("listen error");return -1;}/* --------------------------------------------- *///4) 接受客戶端連接,發(fā)送應(yīng)答for ( ; ; ) {clilen = sizeof(cliaddr);// connfd為已連接描述符,用于和客戶端進(jìn)行通信connfd = accept(listenfd, (SA *) &cliaddr, &clilen);if(connfd < 0) {printf("accept error");return -1;}if ((childpid = fork()) == 0) {// 子進(jìn)程關(guān)閉監(jiān)聽套接字if (close(listenfd) == -1) {printf("child close listenfd error");return -1;           }str_echo(connfd);    // 子進(jìn)程處理客戶端請求exit(0);             // 清理描述符    }/* --------------------------------------------- *///5) 父進(jìn)程關(guān)閉已連接套接字if (close(connfd) == -1) {printf("parent close connfd error");return -1;}}
}

TCP客戶端程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h> /* basic socket definitions */#define MAXLINE     4096
#define SERV_PORT   9877
#define SA  struct sockaddr   char *Fgets(char *ptr, int n, FILE *stream)
{char    *rptr;// 當(dāng)遇到文件結(jié)束符或錯誤時,fgets函數(shù)將返回一個空指針,于是客戶端處理循環(huán)終止if ( (rptr = fgets(ptr, n, stream)) == NULL && ferror(stream)) {printf("fgets error");return NULL;     }return (rptr);
}ssize_t readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char    c, *ptr;ptr = vptr;for (n = 1; n < maxlen; n++) {if ( (rc = read(fd, &c, 1)) == 1) {*ptr++ = c;if (c == '\n')break;} else if (rc == 0) {if (n == 1)return(0);  /* EOF, no data read */elsebreak;      /* EOF, some data was read */} elsereturn(-1); /* error */}*ptr = 0;return(n);
}
/* end readline */void str_cli(FILE *fp, int sockfd) {char sendline[MAXLINE], recvline[MAXLINE];// 從控制臺讀入一行文本while (Fgets(sendline, MAXLINE, fp) != NULL) {// 把該行文本發(fā)送給服務(wù)器if (write(sockfd, sendline, strlen(sendline)) != strlen(sendline)) {printf("writen error");return;             }// 從服務(wù)器讀入回射行if (readline(sockfd, recvline, MAXLINE) < 0){printf("readline error");return;        }// 把它寫到標(biāo)準(zhǔn)輸出if (fputs(recvline, stdout) == EOF) {printf("fputs error");return;        }}
}int main(int argc, char **argv)
{int                 sockfd;char                recvline[MAXLINE + 1];struct sockaddr_in  servaddr;if (argc != 2)exit(1);/* --------------------------------------------- *///1) 創(chuàng)建一個TCP連接套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {printf("socket error");return -1;}/* --------------------------------------------- *///2) 指定服務(wù)器的IP地址和端口bzero(&servaddr, sizeof(servaddr));         // 初始化內(nèi)存servaddr.sin_family = AF_INET;              // 地址族servaddr.sin_port   = htons(SERV_PORT);     // 時間獲取服務(wù)器端口為13// 注意:此處的IP和端口是服務(wù)器的IP和端口// 把點(diǎn)分十進(jìn)制的IP地址(如:206.168.112.96)轉(zhuǎn)化為合適的格式if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {printf("inet_pton error for %s", argv[1]);return -1;}/* --------------------------------------------- *///3) 建立客戶端(sockfd)與服務(wù)器(servaddr)的連接,TCP連接if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) {printf("connect error");return -1;}// 完成剩余部分的客戶端處理工作str_cli(stdin, sockfd);/* --------------------------------------------- *///5) 終止程序運(yùn)行,關(guān)閉該進(jìn)程打開的所有描述符和TCP套接字exit(0);
}

正常啟動

1)啟動TCP服務(wù)器程序

gcc -o tcpserv tcpserv.c 
gcc -o tcpcli tcpcli.c ./tcpserv &

服務(wù)器啟動后,它調(diào)用socked、bind、listen和accept,并阻塞于accept調(diào)用。

2)啟動TCP客戶端程序

./tcpcli 127.0.0.1// 輸入字符串
kaikaixinxinxuebiancheng

啟動客戶端程序并指定服務(wù)器主機(jī)的IP地址??蛻舳苏{(diào)用socket和connect,后者引起TCP三次握手過程。當(dāng)三次握手完成后,客戶端中的connect和服務(wù)器中的accept均返回,連接于是被建立。

接著發(fā)生步驟如下:

  1. 客戶端調(diào)用str_cli函數(shù),該函數(shù)將阻塞于fgets調(diào)用,因?yàn)槲覀冞€未曾鍵入過一行文本。
  2. 當(dāng)服務(wù)器中的accept返回時,服務(wù)器調(diào)用fork,再由子進(jìn)程調(diào)用str_echo。該函數(shù)調(diào)用readline,readline調(diào)用read,而read在等待客戶端送入一行文本期間阻塞。
  3. 服務(wù)器父進(jìn)程再次調(diào)用accept并阻塞,等待下一個客戶端連接。

連接建立后,不論在客戶端中輸入什么,都會回射到它的標(biāo)準(zhǔn)輸出中。

接著在終端輸入EOF字符(Ctrl+D)以終止客戶端。

此時如果立刻執(zhí)行netstat命令,則將看到如下結(jié)果:

// 服務(wù)器本地端口為9877,客戶端本地端口為42758
netstat -a | grep 9877

當(dāng)前連接的客戶端(它的本地端口號為42758)進(jìn)入了TIME_WAIT狀態(tài),而監(jiān)聽服務(wù)器仍在等待另一個客戶端連接。

正常終止

正常終止客戶端與服務(wù)器步驟:

1)當(dāng)鍵入EOF字符時,fgets返回一個空指針,于是str_cli函數(shù)返回。

2)當(dāng)str_cli返回到客戶端的main函數(shù)時,main通過調(diào)用exit終止。

3)進(jìn)程終止處理的部分工作是關(guān)閉所有打開的描述符,因此客戶端打開的套接字由內(nèi)核關(guān)閉。這導(dǎo)致客戶端TCP發(fā)送一個FIN給服務(wù)器,服務(wù)器則以ACK響應(yīng),這就是TCP連接終止序列的前半部分。至此,服務(wù)器套接字處于CLOSE_WAIT狀態(tài),客戶端套接字則處于FIN_WAIT_2狀態(tài)。

4)當(dāng)服務(wù)器TCP接收FIN時,服務(wù)器子進(jìn)程阻塞于read調(diào)用,于是read返回0,這導(dǎo)致str_echo函數(shù)返回服務(wù)器子進(jìn)程的main函數(shù)。

5)服務(wù)器子進(jìn)程通過調(diào)用exit來終止。

6)服務(wù)器子進(jìn)程中打開的所有描述符(包括已連接套接字)隨之關(guān)閉。子進(jìn)程關(guān)閉已連接套接字時會引發(fā)TCP連接終止序列的最后兩個分節(jié):一個從服務(wù)器到客戶端的FIN和一個從客戶端到服務(wù)器的ACK。至此,連接完全終止,客戶端套接字進(jìn)入TIME_WAIT狀態(tài)(允許老的重復(fù)分節(jié)在網(wǎng)絡(luò)中消逝)。

7)進(jìn)程終止處理的另一部分內(nèi)容是:在服務(wù)器進(jìn)程終止時,給父進(jìn)程發(fā)送一個SIGCHLD信號,這一點(diǎn)在上述程序示例中發(fā)生了,但是沒有在代碼中捕獲該信號,而信號的默認(rèn)行為是被忽略。既然父進(jìn)程未加處理,子進(jìn)程于是進(jìn)入僵死狀態(tài)(僵尸進(jìn)程)。可以通過ps命令進(jìn)行驗(yàn)證:

// 查看當(dāng)前終端編號
tty// 查看子進(jìn)程狀態(tài)
ps -t /dev/pts/0 -o pid,ppid,tty,stat,args,wchan

查看結(jié)果:

子進(jìn)程狀態(tài)表現(xiàn)為Z(表示僵死)。針對僵死進(jìn)程(僵尸進(jìn)程),必須清理。

POSIX信號處理

信號(signal)就是告知某個進(jìn)程發(fā)生了某個事件的通知,有時也稱為軟件中斷。信號通常是異步發(fā)生的,也就是說進(jìn)程預(yù)先不知道信號的準(zhǔn)確發(fā)生時刻。

注意:

1)信號可以由一個進(jìn)程發(fā)給另一個進(jìn)程(或自身)。

2)信號可以由內(nèi)核發(fā)給某個進(jìn)程。

上一小節(jié)提到的SIGCHLD信號就是由內(nèi)核在任何一個進(jìn)程終止時發(fā)給它的父進(jìn)程的一個信號。

每個信號都有一個與之關(guān)聯(lián)的處置,也稱為行為。

SIGCHLD信號處理

思考:為什么必須要處理僵死進(jìn)程?

答:因?yàn)榻┧肋M(jìn)程占用內(nèi)核空間,最終可能導(dǎo)致耗盡進(jìn)程資源。所以,無論何時針對fork出來的子進(jìn)程都得使用wait函數(shù)處理它們,以防止它們變?yōu)榻┧肋M(jìn)程。

TCP服務(wù)器程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <arpa/inet.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>#define MAXLINE     4096
#define SERV_PORT   9877
#define LISTENQ     1024
#define SA  struct sockaddrtypedef void    Sigfunc(int);   /* for signal handlers */// SIGCHLD信號處理函數(shù),防止子進(jìn)程變?yōu)榻┧肋M(jìn)程
void sig_chld(int signo)
{pid_t   pid;int     stat;// 等待子進(jìn)程結(jié)束,并獲取子進(jìn)程的PID和退出狀態(tài)pid = wait(&stat);// 在此處調(diào)用諸如printf這樣的標(biāo)準(zhǔn)I/O是不合適的,此處只是作為查看子進(jìn)程何時終止的診斷手段printf("child %d terminated\n", pid);return;
}Sigfunc *signal(int signo, Sigfunc *func)
{// 定義信號動作struct sigaction    act, oact;act.sa_handler = func;        // 設(shè)置信號處理函數(shù)sigemptyset(&act.sa_mask);    // 清空信號掩碼集act.sa_flags = 0;             // 設(shè)置信號處理方式為默認(rèn)if (signo == SIGALRM) {
#ifdef  SA_INTERRUPTact.sa_flags |= SA_INTERRUPT;   /* SunOS 4.x */
#endif} else {
#ifdef  SA_RESTARTact.sa_flags |= SA_RESTART;     /* SVR4, 44BSD */
#endif}if (sigaction(signo, &act, &oact) < 0)return(SIG_ERR);return(oact.sa_handler);
}
/* end signal */// 捕捉指定信號并采取行動
Sigfunc *Signal(int signo, Sigfunc *func)    /* for our signal() function */
{Sigfunc *sigfunc;if ( (sigfunc = signal(signo, func)) == SIG_ERR) {printf("signal error");    }return(sigfunc);
}// 從客戶端讀入數(shù)據(jù),并把它們回射給客戶端
void str_echo(int sockfd) {ssize_t n;char    buf[MAXLINE];
again:// 從套接字讀入數(shù)據(jù)// 套接字中接收緩沖區(qū)和發(fā)送緩沖區(qū)是分開的,因此讀和寫不會發(fā)生混淆while ((n = read(sockfd, buf, MAXLINE)) > 0)write(sockfd, buf, n);    // 把套接字中的內(nèi)容回射給客戶端// 如果n<0表示讀取數(shù)據(jù)出錯或到達(dá)文件末尾// 如果errno等于EINTR,表示讀取操作被信號中斷// 如果上述兩個條件同時滿足,則重新嘗試讀取數(shù)據(jù)if (n < 0 && errno == EINTR)goto again;// 如果表示文件描述符到達(dá)文件末尾else if (n < 0)printf("str_echo: read error");
}int main(int argc, char **argv)
{int                 listenfd, connfd;pid_t               childpid;socklen_t           clilen;struct sockaddr_in  cliaddr, servaddr;/* --------------------------------------------- *///1) 創(chuàng)建一個TCP連接套接字listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd < 0) {printf("socket error");return -1;}/* --------------------------------------------- *///2) 把服務(wù)器對應(yīng)端口綁定到套接字 bzero(&servaddr, sizeof(servaddr));     // 開辟內(nèi)存servaddr.sin_family      = AF_INET;     // 地址族// 指定IP地址為INADDR_ANY,這樣要是服務(wù)器主機(jī)有多個網(wǎng)絡(luò)接口,服務(wù)器進(jìn)程就可以在任意網(wǎng)絡(luò)接口上接受客戶端連接servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port        = htons(SERV_PORT);if (bind(listenfd, (SA *) &servaddr, sizeof(servaddr)) < 0) {printf("bind error");return -1;}/* --------------------------------------------- *///3) 把套接字轉(zhuǎn)換為監(jiān)聽套接字// LISTENQ表示系統(tǒng)內(nèi)核允許在這個監(jiān)聽描述符上排隊(duì)的最大客戶端連接數(shù)if(listen(listenfd, LISTENQ) < 0) {printf("listen error");return -1;}// 捕捉指定信號并采取行動Signal(SIGCHLD, sig_chld);    /* must call waitpid() *//* --------------------------------------------- *///4) 接受客戶端連接,發(fā)送應(yīng)答for ( ; ; ) {clilen = sizeof(cliaddr);// connfd為已連接描述符,用于和客戶端進(jìn)行通信connfd = accept(listenfd, (SA *) &cliaddr, &clilen);if(connfd < 0) {if (errno == EINTR) {continue;     // 重啟被中斷的accept           } else {printf("accept error");return -1;           }}if ((childpid = fork()) == 0) {// 子進(jìn)程關(guān)閉監(jiān)聽套接字if (close(listenfd) == -1) {printf("child close listenfd error");return -1;           }str_echo(connfd);    // 子進(jìn)程處理客戶端請求exit(0);             // 清理描述符    }/* --------------------------------------------- *///5) 父進(jìn)程關(guān)閉已連接套接字if (close(connfd) == -1) {printf("parent close connfd error");return -1;}}
}

注意:如果connect函數(shù)返回EINTR,則不能重啟,否則將立即返回一個錯誤。當(dāng)connect被一個捕獲的信號中斷而且不自動重啟時,必須調(diào)用select來等待連接完成。

TCP客戶端程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h> /* basic socket definitions */#define MAXLINE     4096
#define SERV_PORT   9877
#define SA  struct sockaddr   char *Fgets(char *ptr, int n, FILE *stream)
{char    *rptr;// 當(dāng)遇到文件結(jié)束符或錯誤時,fgets函數(shù)將返回一個空指針,于是客戶端處理循環(huán)終止if ( (rptr = fgets(ptr, n, stream)) == NULL && ferror(stream)) {printf("fgets error");return NULL;     }return (rptr);
}ssize_t readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char    c, *ptr;ptr = vptr;for (n = 1; n < maxlen; n++) {if ( (rc = read(fd, &c, 1)) == 1) {*ptr++ = c;if (c == '\n')break;} else if (rc == 0) {if (n == 1)return(0);  /* EOF, no data read */elsebreak;      /* EOF, some data was read */} elsereturn(-1); /* error */}*ptr = 0;return(n);
}
/* end readline */void str_cli(FILE *fp, int sockfd) {char sendline[MAXLINE], recvline[MAXLINE];// 從控制臺讀入一行文本while (Fgets(sendline, MAXLINE, fp) != NULL) {// 把該行文本發(fā)送給服務(wù)器if (write(sockfd, sendline, strlen(sendline)) != strlen(sendline)) {printf("writen error");return;             }// 從服務(wù)器讀入回射行if (readline(sockfd, recvline, MAXLINE) < 0){printf("readline error");return;        }// 把它寫到標(biāo)準(zhǔn)輸出if (fputs(recvline, stdout) == EOF) {printf("fputs error");return;        }}
}int main(int argc, char **argv)
{int                 sockfd;char                recvline[MAXLINE + 1];struct sockaddr_in  servaddr;if (argc != 2)exit(1);/* --------------------------------------------- *///1) 創(chuàng)建一個TCP連接套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {printf("socket error");return -1;}/* --------------------------------------------- *///2) 指定服務(wù)器的IP地址和端口bzero(&servaddr, sizeof(servaddr));         // 初始化內(nèi)存servaddr.sin_family = AF_INET;              // 地址族servaddr.sin_port   = htons(SERV_PORT);     // 時間獲取服務(wù)器端口為13// 注意:此處的IP和端口是服務(wù)器的IP和端口// 把點(diǎn)分十進(jìn)制的IP地址(如:206.168.112.96)轉(zhuǎn)化為合適的格式if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {printf("inet_pton error for %s", argv[1]);return -1;}/* --------------------------------------------- *///3) 建立客戶端(sockfd)與服務(wù)器(servaddr)的連接,TCP連接if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) {printf("connect error");return -1;}// 完成剩余部分的客戶端處理工作str_cli(stdin, sockfd);/* --------------------------------------------- *///5) 終止程序運(yùn)行,關(guān)閉該進(jìn)程打開的所有描述符和TCP套接字exit(0);
}

執(zhí)行流程

// 啟動服務(wù)器程序
./tcpserv02 &// 啟動客戶端程序
./tcpserv02 127.0.0.1
hi there
hi there
^D                                        鍵入EOF字符
child 16942 terminated                    信號處理函數(shù)中的printf輸出
accept error:Interrupted system call      main函數(shù)終止執(zhí)行

具體各步驟如下:

1)鍵入EOF字符終止客戶端??蛻舳税l(fā)送一個FIN給服務(wù)器,服務(wù)器響應(yīng)一個ACK。

2)收到客戶端的FIN導(dǎo)致服務(wù)器TCP遞送一個EOF給子進(jìn)程阻塞中的readline,從而子進(jìn)程終止。

3)當(dāng)SIGCHLD信號遞交時,父進(jìn)程阻塞與accept調(diào)用。sig_chld函數(shù)(信號處理函數(shù))執(zhí)行,其wait調(diào)用渠道子進(jìn)程的PID和終止?fàn)顟B(tài),隨后是printf調(diào)用,最后返回。

4)既然該信號是在父進(jìn)程阻塞于慢系統(tǒng)調(diào)用(accept)時由父進(jìn)程捕獲的,內(nèi)核就會使accept返回一個EINTR錯誤(被中斷的系統(tǒng)調(diào)用)。父進(jìn)程不處理該錯誤,于是父進(jìn)程中止,無法接受新的連接。

wait和waitpid函數(shù)

問1:什么是孤兒進(jìn)程?什么是僵尸進(jìn)程?二者分別會帶來什么危害?

答:

1)孤兒進(jìn)程:如果父進(jìn)程在子進(jìn)程結(jié)束前退出,那么子進(jìn)程就會成為孤兒進(jìn)程。在這種情況下,父進(jìn)程沒有機(jī)會調(diào)用wait或waitpid函數(shù)。每當(dāng)出現(xiàn)一個孤兒進(jìn)程的時候,內(nèi)核就把孤兒進(jìn)程交給init進(jìn)程管理。即init進(jìn)程會代替該孤兒進(jìn)程的父進(jìn)程回收孤兒進(jìn)程的資源,因此孤兒進(jìn)程并不會有什么危害。

2)僵尸進(jìn)程:如果子進(jìn)程結(jié)束時,父進(jìn)程未調(diào)用wait或waitpid函數(shù)回收其資源,那么子進(jìn)程就會稱為僵尸進(jìn)程。如果釋放僵尸進(jìn)程的相關(guān)資源,其進(jìn)程號就會被一致占用,但是系統(tǒng)所能使用的進(jìn)程號是有限的,如果產(chǎn)生大量的僵尸進(jìn)程,最終將會因?yàn)闆]有可用的進(jìn)程號而導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程,所以應(yīng)該避免僵尸進(jìn)程的產(chǎn)生。

問2:為什么父進(jìn)程需要在fork之前調(diào)用wait或waitpid函數(shù)等待子進(jìn)程退出?

答:父進(jìn)程使用fork函數(shù)創(chuàng)建子進(jìn)程是為了處理多個客戶端連接。fork會創(chuàng)建一個與父進(jìn)程幾乎完全相同的子進(jìn)程,包括內(nèi)存空間、文件描述符等。這樣做的好處是父進(jìn)程可以繼續(xù)監(jiān)聽新的連接請求,而子進(jìn)程可以專注于處理已接受的連接。因此,父進(jìn)程調(diào)用wait或waitpid函數(shù)主要是為了防止出現(xiàn)僵尸進(jìn)程。

wait和waitpid函數(shù):

#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);返回:若成功則返回已終止的進(jìn)程ID,若出錯則返回0或-1

函數(shù)wait和waitpid均返回兩個值:已終止的進(jìn)程ID號,以及通過statloc指針返回的子進(jìn)程終止?fàn)顟B(tài)(一個整數(shù))。

可以調(diào)用三個宏來檢查終止?fàn)顟B(tài),并辨別子進(jìn)程是正常終止、由某個信號殺死還是僅僅由作業(yè)控制停止而已。另有些宏用于接著獲取子進(jìn)程的推出狀態(tài)、殺死子進(jìn)程的信號值或停止子進(jìn)程的作業(yè)控制號值。

如果調(diào)用wait的進(jìn)程沒有已終止的子進(jìn)程,不過有一個或多個子進(jìn)程仍在執(zhí)行,那么wait將阻塞到有子進(jìn)程第一個終止為止。

wait和waitpid的區(qū)別

客戶端程序

TCP客戶端程序修改后:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h> /* basic socket definitions */#define MAXLINE     4096
#define SERV_PORT   9877
#define SA  struct sockaddr   char *Fgets(char *ptr, int n, FILE *stream)
{char    *rptr;// 當(dāng)遇到文件結(jié)束符或錯誤時,fgets函數(shù)將返回一個空指針,于是客戶端處理循環(huán)終止if ( (rptr = fgets(ptr, n, stream)) == NULL && ferror(stream)) {printf("fgets error");return NULL;     }return (rptr);
}ssize_t readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char    c, *ptr;ptr = vptr;for (n = 1; n < maxlen; n++) {if ( (rc = read(fd, &c, 1)) == 1) {*ptr++ = c;if (c == '\n')break;} else if (rc == 0) {if (n == 1)return(0);  /* EOF, no data read */elsebreak;      /* EOF, some data was read */} elsereturn(-1); /* error */}*ptr = 0;return(n);
}
/* end readline */void str_cli(FILE *fp, int sockfd) {char sendline[MAXLINE], recvline[MAXLINE];// 從控制臺讀入一行文本while (Fgets(sendline, MAXLINE, fp) != NULL) {// 把該行文本發(fā)送給服務(wù)器if (write(sockfd, sendline, strlen(sendline)) != strlen(sendline)) {printf("writen error");return;             }// 從服務(wù)器讀入回射行if (readline(sockfd, recvline, MAXLINE) < 0){printf("readline error");return;        }// 把它寫到標(biāo)準(zhǔn)輸出if (fputs(recvline, stdout) == EOF) {printf("fputs error");return;        }}
}int main(int argc, char **argv)
{int                 sockfd[5];char                recvline[MAXLINE + 1];struct sockaddr_in  servaddr;if (argc != 2)exit(1);for (int i = 0; i < 5; i++) {/* --------------------------------------------- *///1) 創(chuàng)建一個TCP連接套接字sockfd[i] = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {printf("socket error");return -1;}/* --------------------------------------------- *///2) 指定服務(wù)器的IP地址和端口bzero(&servaddr, sizeof(servaddr));         // 初始化內(nèi)存servaddr.sin_family = AF_INET;              // 地址族servaddr.sin_port   = htons(SERV_PORT);     // 時間獲取服務(wù)器端口為13// 注意:此處的IP和端口是服務(wù)器的IP和端口// 把點(diǎn)分十進(jìn)制的IP地址(如:206.168.112.96)轉(zhuǎn)化為合適的格式if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {printf("inet_pton error for %s", argv[1]);return -1;}/* --------------------------------------------- *///3) 建立客戶端(sockfd)與服務(wù)器(servaddr)的連接,TCP連接if (connect(sockfd[i], (SA *) &servaddr, sizeof(servaddr)) < 0) {printf("connect error");return -1;}}// 完成剩余部分的客戶端處理工作str_cli(stdin, sockfd[0]);/* --------------------------------------------- *///5) 終止程序運(yùn)行,關(guān)閉該進(jìn)程打開的所有描述符和TCP套接字exit(0);
}

客戶端建立5個與服務(wù)器的連接,隨后在調(diào)用str_cli函數(shù)時僅用第一個連接(sockfd[0])。建立多個連接的目的是從并發(fā)服務(wù)器上派生多個子進(jìn)程,如下圖所示:

當(dāng)客戶端終止時,所有打開的文件描述符由內(nèi)核自動關(guān)閉(無需調(diào)用close,僅調(diào)用exit),且所有5個連接基本在同一時刻終止。這就引發(fā)了5個FIN,每個連接一個,它們反過來使服務(wù)器的5個子進(jìn)程基本在同一時刻終止。這又導(dǎo)致差不多在同一時刻有5個SIGCHLD信號遞交給父進(jìn)程,如圖所示:

注意:如上所述,由于調(diào)用了exit函數(shù),5個連接幾乎同時產(chǎn)生SIGCHLD信號,即多個SIGCHLD信號同時遞交給服務(wù)器。

測試結(jié)果

./tcpserv &               啟動服務(wù)器程序
./tcpcli 127.0.0.1        啟動客戶端程序
hello
hello
^D                        鍵入EOF字符
child 31591 terminated    服務(wù)器輸出

從執(zhí)行結(jié)果可以看出,只有一個printf輸出而并非5個,即信號處理函數(shù)只處理了一個SIGCHLD信號,剩下四個子進(jìn)程變?yōu)榻┦M(jìn)程。

問1:為什么只處理了一個SIGCHLD信號?

答:建立一個信號處理函數(shù)并在其中調(diào)用wait并不足以防止出現(xiàn)僵尸進(jìn)程。因?yàn)樗?個信號都在信號處理函數(shù)執(zhí)行之前產(chǎn)生,而信號處理函數(shù)只執(zhí)行一次,因?yàn)閁nix信號一般不排隊(duì)。更嚴(yán)重的是,本問題是不確定的。因?yàn)楸緦?shí)驗(yàn)是在同一個主機(jī)上,信號處理函數(shù)執(zhí)行1次,留下4個僵尸進(jìn)程。但是如果客戶端程序和服務(wù)端程序不在同一個主機(jī)上,那么信號處理函數(shù)一般執(zhí)行2次:一次是第一個產(chǎn)生的信號引起的,由于另外4個信號在信號處理函數(shù)第一次執(zhí)行時發(fā)生,因此該處理函數(shù)僅僅再被調(diào)用一次,從而留下3個僵尸進(jìn)程。不過有的時候,依賴于FIN到達(dá)服務(wù)器主機(jī)的時機(jī),信號處理函數(shù)可能會執(zhí)行3次甚至4次。

問2:如何讓信號處理函數(shù)調(diào)用多次,以防止出現(xiàn)僵尸進(jìn)程?

答:調(diào)用waitpid而不是wait函數(shù)。當(dāng)在一個循環(huán)內(nèi)調(diào)用waitpid,以獲取所有已終止子進(jìn)程的狀態(tài)時,必須指定WNOHANG選項(xiàng),它告知waitpid在有尚未終止的子進(jìn)程在運(yùn)行時不要阻塞。不能在循環(huán)內(nèi)調(diào)用wait,因?yàn)闆]有辦法防止wait在正運(yùn)行的子進(jìn)程尚有未終止時阻塞。

服務(wù)端程序

修改后的服務(wù)端程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <arpa/inet.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>#define MAXLINE     4096
#define SERV_PORT   9877
#define LISTENQ     1024
#define SA  struct sockaddrtypedef void    Sigfunc(int);   /* for signal handlers */// SIGCHLD信號處理函數(shù),防止子進(jìn)程變?yōu)榻┧肋M(jìn)程
void sig_chld(int signo)
{pid_t   pid;int     stat;// 等待子進(jìn)程結(jié)束,并獲取子進(jìn)程的PID和退出狀態(tài)while (pid = waitpid(-1, &stat, WNOHANG)) > 0) {// 在此處調(diào)用諸如printf這樣的標(biāo)準(zhǔn)I/O是不合適的,此處只是作為查看子進(jìn)程何時終止的診斷手段printf("child %d terminated\n", pid);}return;
}Sigfunc *signal(int signo, Sigfunc *func)
{// 定義信號動作struct sigaction    act, oact;act.sa_handler = func;        // 設(shè)置信號處理函數(shù)sigemptyset(&act.sa_mask);    // 清空信號掩碼集act.sa_flags = 0;             // 設(shè)置信號處理方式為默認(rèn)if (signo == SIGALRM) {
#ifdef  SA_INTERRUPTact.sa_flags |= SA_INTERRUPT;   /* SunOS 4.x */
#endif} else {
#ifdef  SA_RESTARTact.sa_flags |= SA_RESTART;     /* SVR4, 44BSD */
#endif}if (sigaction(signo, &act, &oact) < 0)return(SIG_ERR);return(oact.sa_handler);
}
/* end signal */// 捕捉指定信號并采取行動
Sigfunc *Signal(int signo, Sigfunc *func)    /* for our signal() function */
{Sigfunc *sigfunc;if ( (sigfunc = signal(signo, func)) == SIG_ERR) {printf("signal error");    }return(sigfunc);
}// 從客戶端讀入數(shù)據(jù),并把它們回射給客戶端
void str_echo(int sockfd) {ssize_t n;char    buf[MAXLINE];
again:// 從套接字讀入數(shù)據(jù)// 套接字中接收緩沖區(qū)和發(fā)送緩沖區(qū)是分開的,因此讀和寫不會發(fā)生混淆while ((n = read(sockfd, buf, MAXLINE)) > 0)write(sockfd, buf, n);    // 把套接字中的內(nèi)容回射給客戶端// 如果n<0表示讀取數(shù)據(jù)出錯或到達(dá)文件末尾// 如果errno等于EINTR,表示讀取操作被信號中斷// 如果上述兩個條件同時滿足,則重新嘗試讀取數(shù)據(jù)if (n < 0 && errno == EINTR)goto again;// 如果表示文件描述符到達(dá)文件末尾else if (n < 0)printf("str_echo: read error");
}int main(int argc, char **argv)
{int                 listenfd, connfd;pid_t               childpid;socklen_t           clilen;struct sockaddr_in  cliaddr, servaddr;/* --------------------------------------------- *///1) 創(chuàng)建一個TCP連接套接字listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd < 0) {printf("socket error");return -1;}/* --------------------------------------------- *///2) 把服務(wù)器對應(yīng)端口綁定到套接字 bzero(&servaddr, sizeof(servaddr));     // 開辟內(nèi)存servaddr.sin_family      = AF_INET;     // 地址族// 指定IP地址為INADDR_ANY,這樣要是服務(wù)器主機(jī)有多個網(wǎng)絡(luò)接口,服務(wù)器進(jìn)程就可以在任意網(wǎng)絡(luò)接口上接受客戶端連接servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port        = htons(SERV_PORT);if (bind(listenfd, (SA *) &servaddr, sizeof(servaddr)) < 0) {printf("bind error");return -1;}/* --------------------------------------------- *///3) 把套接字轉(zhuǎn)換為監(jiān)聽套接字// LISTENQ表示系統(tǒng)內(nèi)核允許在這個監(jiān)聽描述符上排隊(duì)的最大客戶端連接數(shù)if(listen(listenfd, LISTENQ) < 0) {printf("listen error");return -1;}// 捕捉指定信號并采取行動Signal(SIGCHLD, sig_chld);    /* must call waitpid() *//* --------------------------------------------- *///4) 接受客戶端連接,發(fā)送應(yīng)答for ( ; ; ) {clilen = sizeof(cliaddr);// connfd為已連接描述符,用于和客戶端進(jìn)行通信connfd = accept(listenfd, (SA *) &cliaddr, &clilen);if(connfd < 0) {if (errno == EINTR) {continue;     // 重啟被中斷的accept           } else {printf("accept error");return -1;           }}if ((childpid = fork()) == 0) {// 子進(jìn)程關(guān)閉監(jiān)聽套接字if (close(listenfd) == -1) {printf("child close listenfd error");return -1;           }str_echo(connfd);    // 子進(jìn)程處理客戶端請求exit(0);             // 清理描述符    }/* --------------------------------------------- *///5) 父進(jìn)程關(guān)閉已連接套接字if (close(connfd) == -1) {printf("parent close connfd error");return -1;}}
}

小結(jié)

問:SIGCHLD信號是怎么產(chǎn)生的,有什么作用?

答:SIGCHLD 信號是由操作系統(tǒng)產(chǎn)生的,當(dāng)一個子進(jìn)程結(jié)束(無論是正常退出還是被終止)時,操作系統(tǒng)都會向父進(jìn)程發(fā)送這個信號。這個信號的目的是通知父進(jìn)程子進(jìn)程的狀態(tài)已經(jīng)改變,父進(jìn)程可以采取相應(yīng)的行動,比如回收子進(jìn)程使用的資源。

注意:父進(jìn)程調(diào)用wait函數(shù)時會阻塞整個父進(jìn)程的執(zhí)行,直到某一個或幾個子進(jìn)程結(jié)束,才會結(jié)束阻塞。上述服務(wù)器程序是通過異步調(diào)用wait函數(shù),所以看上去不是那么直觀,非異步調(diào)用wait如下:

for ( ; ; ) {clilen = sizeof(cliaddr);// connfd為已連接描述符,用于和客戶端進(jìn)行通信connfd = accept(listenfd, (SA *) &cliaddr, &clilen);if(connfd < 0) {if (errno == EINTR) {continue;     // 重啟被中斷的accept           } else {printf("accept error");return -1;           }}if ((childpid = fork()) == 0) {// 子進(jìn)程關(guān)閉監(jiān)聽套接字if (close(listenfd) == -1) {printf("child close listenfd error");return -1;           }str_echo(connfd);    // 子進(jìn)程處理客戶端請求exit(0);             // 清理描述符    }// 等待子進(jìn)程結(jié)束并回收子進(jìn)程資源int status;wait(&status);/* --------------------------------------------- *///5) 父進(jìn)程關(guān)閉已連接套接字if (close(connfd) == -1) {printf("parent close connfd error");return -1;}
}

UNIX網(wǎng)絡(luò)編程總結(jié):

1)當(dāng)fork子進(jìn)程時,必須捕獲SIGCHLD信號。

2)當(dāng)捕獲信號時,父進(jìn)程必須處理被中斷的系統(tǒng)調(diào)用,如accept函數(shù)。

3)SIGCHLD的信號處理函數(shù)必須正確書寫,并使用waitpid函數(shù)以免留下僵尸進(jìn)程。

如果需要代碼包,請?jiān)谠u論區(qū)留言!!!?

如果需要代碼包,請?jiān)谠u論區(qū)留言!!!?

如果需要代碼包,請?jiān)谠u論區(qū)留言!!!?

http://www.risenshineclean.com/news/45180.html

相關(guān)文章:

  • 軟文營銷的缺點(diǎn)沈陽關(guān)鍵詞快照優(yōu)化
  • 萬網(wǎng)個人網(wǎng)站備案查詢新聞投稿
  • wordpress 南非政府網(wǎng)絡(luò)seo招聘
  • 高端手機(jī)網(wǎng)站 制作公司鄭州百度推廣開戶
  • 建設(shè)網(wǎng)站模板免費(fèi)百度掃一掃識別圖片在線
  • wordpress登錄漏洞杭州網(wǎng)站排名seo
  • p2p網(wǎng)站建設(shè)源碼seo全國最好的公司
  • 長沙網(wǎng)站seo技巧今日足球賽事數(shù)據(jù)
  • 信息類網(wǎng)站制作bt鸚鵡磁力
  • 競價網(wǎng)站做seosem推廣軟件哪家好
  • 北京營銷型網(wǎng)站建設(shè)價格什么是sem
  • 高新網(wǎng)站建設(shè)多少錢國際國內(nèi)新聞最新消息今天
  • 如何制作企業(yè)的網(wǎng)站瀏覽器里面信息是真是假
  • 給學(xué)校做網(wǎng)站合肥百度競價推廣代理公司
  • 廈門百度網(wǎng)站建設(shè)優(yōu)化大師下載安裝app
  • wordpress實(shí)現(xiàn)pdf瀏覽seo網(wǎng)絡(luò)推廣機(jī)構(gòu)
  • 寶安中心做網(wǎng)站網(wǎng)站公司網(wǎng)站建設(shè)
  • 《原始傳奇》官方網(wǎng)站seo推廣工具
  • 鐵嶺做網(wǎng)站信息網(wǎng)店推廣的渠道有哪些
  • 長治市人民政府門戶網(wǎng)站簡單的網(wǎng)站制作
  • 網(wǎng)站源碼推薦谷歌sem
  • 汽車網(wǎng)站更新怎么做口碑營銷的例子
  • 鄭州展廳設(shè)計(jì)制作公司seo綜合查詢什么意思
  • 旅游類網(wǎng)站如何做推廣百度推廣后臺
  • 目錄網(wǎng)站模板php搭建一個簡單的網(wǎng)站
  • iis7建立網(wǎng)站可以看封禁網(wǎng)站的瀏覽器
  • 做網(wǎng)站公司排名電話國際新聞快報(bào)
  • 外貿(mào)網(wǎng)站建設(shè)模板百度云客服人工電話
  • 企業(yè)網(wǎng)站注冊官網(wǎng)百度搜索結(jié)果優(yōu)化
  • 網(wǎng)站如何做微信支付寶支付寶支付社交網(wǎng)絡(luò)的推廣方法