Concurrent TCP server網路程式設計(2):
在Linux中,網路程式設計主要全靠socket來接收及傳送資料,本次我們要探討多個client端連接至一台伺服器的情形。
若要讓TCP server同時處理多個client的連線,最簡單的方法便是使用concurrent TCP server架構(如下圖)。
Concurrent TCP server 架構:
所謂的[Concurrent TCP server]架構,是由父程序產生多個子程序,每個子程序分別與建立連線的client端進行資料傳輸,在傳輸的過程中,父程序只負責連線的接收,每次連線成功便產生對應的子程序。
如何產生子程序?
我們可以讓server呼叫 fork() 函數來產生子程序, fork() 函數會將新建立的通訊端複製到子程序中,在子程序中可以與連上的client端進行通訊。
一個concurent TCP server的基礎程式架構如下。
--------------------------------------------------------------------------------------------------------------------
int main(int argc, char *argv[])
{
{
int len_inet;
struct sockaddr_in adr_srvr,adr_clnt;
pid_t PID;
/*建立listening socket*/
sockfd = socket(AF_INET,SOCK_STREAM,0);
/*連結socket*/
bind(sockfd,(struct sockaddr*)&adr_srvr,sizeof(adr_srvr));
/*傾聽client*/
listen(sockfd,10);
while(1)
{
len_inet = sizeof(adr_clnt);
/*接受client端連線請求,建立connected socket*/
connfd = accept(sockfd, (struct sockaddr *)&adr_clnt, &len_inet);
/*建立子程序*/
PID = fork();
if(PID>0)
{
/*父程序關閉connected socket*/
close(connfd);
/*返回while開頭*/
continue;
}
printf("子程序處理....\n");
/*子程序關閉listening socket*/
close(sockfd);
/*與連線client端進行資料傳輸*/
exit(0);
}
return 0;
}
-------------------------------------------------------------------------------------------------------------------
由上述的基礎程式架構,可以發現建構concurrent TCP server的一些原則:
1. 在呼叫fork()函數前,我們必須呼叫socket()、bind()、listen()與accept(),來建立listening socket(sockfd),以接收來自client端的連線請求
2. 當server接受client端的連線請求時,會傳回connected socket(connfd)
3. 在呼叫fork()後,若為父程序,則關閉connected socket,回到while迴圈的起始點,等待另一個client端的請求。
4. 在呼叫 fork() 後,若為子程序,則關閉listening socket,並與連線的client端進行資料的輸出輸入,完畢後結束程序,並回傳 0。
注意Zombie程序:
當在設計這種concurrent TCP server架構時,由於我們會使用fork()函數,子程序在呼叫exit()結束後,會進入zombie程序,若父程序不處理這些zombie程序,便會常駐在記憶體中,最後會拖垮整個系統的效率!
所以我們可以在建立子程序時,透過接受SIGCHILD訊息,撰寫相關的訊息處理函數,一個SIGCHILD訊息處理函數程式碼如下。
-------------------------------------------------------------------------------------------------------------------
static void sigchld_handler() { pid_t PID; int status; while (PID = waitpid(-1, &status, WNOHANG) > 0) printf("子程序 %d 結束.\n", PID); /* Re-install handler */ signal(SIGCHLD, sigchld_handler); }
------------------------------------------------------------------------------
上述程式碼呼叫了 waitpid()函數來清除zombie程序,waitpid()函數的第一個引數為[-1],表示等待任何子程序,第二個引數用來儲存子程序結束的狀態,第三個引數設為[WNOHANG],表示當沒有任何zombie程序時,馬上返回,不予等待。
由上述的介紹後,我們還練習撰寫一個網路多工的程式。
動作要求:
1. server程式在執行後,可以接受多個client的連線要求。
2. server程式會在client端連線後,以每隔一秒的時間,將目前的系統時間傳送至client端
3. client端會接收server端傳來的訊息,並將訊息顯示出來。
程式:server端
/* multi_server2.c */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <time.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> static void sigchld_handler() { pid_t PID; int status; while (PID = waitpid(-1, &status, WNOHANG) > 0) printf("子程序 %d 結束.\n", PID); /* Re-install handler */ signal(SIGCHLD, sigchld_handler); } int main(int argc, char **argv) { int z, len_inet; struct sockaddr_in adr_srvr, adr_clnt; int sockfd, connfd; pid_t PID; time_t clock; struct tm *tm; int len_time, i; char buf[256]; signal(SIGCHLD, sigchld_handler); memset(&adr_srvr, 0, sizeof(adr_srvr)); if (argc ==2) adr_srvr.sin_addr.s_addr = inet_addr(argv[1]); else adr_srvr.sin_addr.s_addr = inet_addr("192.168.1.20"); adr_srvr.sin_family = AF_INET; adr_srvr.sin_port = htons(9090); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket error"); exit(1); } z = bind(sockfd, (struct sockaddr *)&adr_srvr, sizeof(adr_srvr)); if (z == -1) { perror("bind error"); exit(1); } z = listen(sockfd, 10); if (z == -1) { perror("listen error"); exit(1); } while(1) { len_inet = sizeof(adr_clnt); connfd = accept(sockfd, (struct sockaddr *)&adr_clnt, &len_inet); if (connfd == -1) { perror("connect error"); exit(1); } PID = fork(); if (PID > 0) { close(connfd); continue; } printf("子程序處理...\n"); close(sockfd); for (i=0; i<10; i++) { sleep(1); time(&clock); tm = gmtime(&clock); len_time = strftime(buf, 256, "%A %T %D\n", tm); if (write(connfd, buf, len_time) == -1) { perror("write error"); exit(1); } } sleep(1); sprintf(buf, "stop\n"); write(connfd, buf, sizeof(buf)); exit(0); } return 0; }
程式:client端
/* multi_client.c */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <time.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> int main(int argc, char **argv) { int z, len_inet; struct sockaddr_in adr_srvr; int sockfd; int i; char buf[256]; memset(&adr_srvr, 0, sizeof(adr_srvr)); if (argc ==2) adr_srvr.sin_addr.s_addr = inet_addr(argv[1]); else adr_srvr.sin_addr.s_addr = inet_addr("192.168.1.20"); adr_srvr.sin_family = AF_INET; adr_srvr.sin_port = htons(9090); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket error"); exit(1); } z = connect(sockfd, (struct sockaddr *)&adr_srvr, sizeof(adr_srvr)); if (z == -1) { perror("connect error"); exit(1); } while (1) { z = read(sockfd, buf, 256); if (z == -1) { perror("read error"); exit(1); } buf[z]=0; if (strncmp(buf, "stop", 4) == 0) break; printf("日期時間為: %s\n", buf); } close(sockfd); return 0; }
沒有留言:
張貼留言