2015年10月11日 星期日

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;
}

沒有留言:

張貼留言