Linux系統程式設計(35)—— socket程式設計之TCP伺服器的併發處理

尹成發表於2014-09-04


我們知道,伺服器通常是要同時服務多個客戶端的,如果我們執行上一篇實現的server和client之後,再開一個終端執行client試試,新的client就不能能得到服務了。因為伺服器之支援一個連線。

網路伺服器通常用fork來同時服務多個客戶端,父程式專門負責監聽埠,每次accept一個新的客戶端連線就fork出一個子程式專門服務這個客戶端。但是子程式退出時會產生殭屍程式,父程式要注意處理SIGCHLD訊號和呼叫wait清理殭屍程式。

下面是程式碼框架:

 

listenfd = socket(...);
bind(listenfd, ...);
listen(listenfd, ...);
while (1) {
         connfd= accept(listenfd, ...);
         n= fork();
         if(n == -1) {
                   perror("callto fork");
                   exit(1);
         }else if (n == 0) {
                   close(listenfd);
                   while(1) {
                            read(connfd,...);
                            ...
                            write(connfd,...);
                   }
                   close(connfd);
                   exit(0);
         }else
                   close(connfd);
}

 

現在做一個測試,首先啟動server,然後啟動client,然後用Ctrl-C使server終止,這時馬上再執行server,結果是:

 binderror: Address already in use

這是因為,雖然server的應用程式終止了,但TCP協議層的連線並沒有完全斷開,因此不能再次監聽同樣的server埠。server終止時,socket描述符會自動關閉併發FIN段給client,client收到FIN後處於CLOSE_WAIT狀態,但是client並沒有終止,也沒有關閉socket描述符,因此不會發FIN給server,因此server的TCP連線處於FIN_WAIT2狀態。

 

現在用Ctrl-C把client也終止掉,再觀察現象結果是:

 binderror: Address already in useclient

終止時自動關閉socket描述符,server的TCP連線收到client發的FIN段後處於TIME_WAIT狀態。TCP協議規定,主動關閉連線的一方要處於TIME_WAIT狀態,等待兩個MSL(maximum segment lifetime)的時間後才能回到CLOSED狀態,因為我們先Ctrl-C終止了server,所以server是主動關閉連線的一方,在TIME_WAIT期間仍然不能再次監聽同樣的server埠。MSL在RFC1122中規定為兩分鐘,但是各作業系統的實現不同,在Linux上一般經過半分鐘後就可以再次啟動server了。

在server的TCP連線沒有完全斷開之前不允許重新監聽是不合理的,因為,TCP連線沒有完全斷開指的是connfd(127.0.0.1:8000)沒有完全斷開,而我們重新監聽的是listenfd(0.0.0.0:8000),雖然是佔用同一個埠,但IP地址不同,connfd對應的是與某個客戶端通訊的一個具體的IP地址,而listenfd對應的是wildcard address。解決這個問題的方法是使用setsockopt()設定socket描述符的選項SO_REUSEADDR為1,表示允許建立埠號相同但IP地址不同的多個socket描述符。在server程式碼的socket()和bind()呼叫之間插入如下程式碼: 

int opt = 1;
setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));

select是網路程式中很常用的一個系統呼叫,它可以同時監聽多個阻塞的檔案描述符(例如多個網路連線),哪個有資料到達就處理哪個,這樣,不需要fork和多程式就可以實現併發服務的server。

 

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"
 
#define MAXLINE 80
#define SERV_PORT 8000
 
int main(int argc, char **argv)
{
         inti, maxi, maxfd, listenfd, connfd, sockfd;
         intnready, client[FD_SETSIZE];
         ssize_tn;
         fd_setrset, allset;
         charbuf[MAXLINE];
         charstr[INET_ADDRSTRLEN];
         socklen_tcliaddr_len;
         structsockaddr_in  cliaddr, servaddr;
 
         listenfd= Socket(AF_INET, SOCK_STREAM, 0);
 
         bzero(&servaddr,sizeof(servaddr));
         servaddr.sin_family      = AF_INET;
         servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
         servaddr.sin_port        = htons(SERV_PORT);
 
         Bind(listenfd,(struct sockaddr *)&servaddr, sizeof(servaddr));
 
         Listen(listenfd,20);
 
         maxfd= listenfd;               /* initialize */
         maxi= -1;                            /* indexinto client[] array */
         for(i = 0; i < FD_SETSIZE; i++)
                  client[i] = -1;     /* -1 indicates available entry */
         FD_ZERO(&allset);
         FD_SET(listenfd,&allset);
 
         for( ; ; ) {
                   rset= allset;    /* structure assignment */
                   nready= select(maxfd+1, &rset, NULL, NULL, NULL);
                   if(nready < 0)
                            perr_exit("selecterror");
 
                   if(FD_ISSET(listenfd, &rset)) { /* new client connection */
                            cliaddr_len= sizeof(cliaddr);
                            connfd= Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
 
                            printf("receivedfrom %s at PORT %d\n",
                                   inet_ntop(AF_INET, &cliaddr.sin_addr,str, sizeof(str)),
                                   ntohs(cliaddr.sin_port));
 
                            for(i = 0; i < FD_SETSIZE; i++)
                                     if(client[i] < 0) {
                                               client[i]= connfd; /* save descriptor */
                                               break;
                                     }
                            if(i == FD_SETSIZE) {
                                     fputs("toomany clients\n", stderr);
                                     exit(1);
                            }
 
                            FD_SET(connfd,&allset);         /* add newdescriptor to set */
                            if(connfd > maxfd)
                                     maxfd= connfd; /* for select */
                            if(i > maxi)
                                     maxi= i;   /* max index in client[] array */
 
                            if(--nready == 0)
                                     continue; /* no more readable descriptors */
                   }
 
                   for(i = 0; i <= maxi; i++) { /* check allclients for data */
                            if( (sockfd = client[i]) < 0)
                                     continue;
                            if(FD_ISSET(sockfd, &rset)) {
                                     if( (n = Read(sockfd, buf, MAXLINE)) == 0) {
                                               /*connection closed by client */
                                               Close(sockfd);
                                               FD_CLR(sockfd,&allset);
                                               client[i]= -1;
                                     }else {
                                               intj;
                                               for(j = 0; j < n; j++)
                                                        buf[j]= toupper(buf[j]);
                                               Write(sockfd,buf, n);
                                     }
 
                                     if(--nready == 0)
                                               break;       /* no more readable descriptors */
                            }
                   }
         }
}


 

 

相關文章