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

沒有留言:
張貼留言