LINUX網絡服務器

一、在之前設計的網絡編程服務器中,涉及到的服務器都是最爲簡單的、一對一的服務器,也就是隻與一個客戶端建立通信。然而在網絡程序裏面,一般來說都是許多客戶對應一個服務器,爲了處理客戶的請求, 對服務端的程序就提出了特殊的要求。


二、循環服務器和併發服務器
1、循環服務器:循環服務器描述了在一個時刻只處理一個請求的服務器實現方式,通過在單線程內設置循環控制實現對多個客戶端請求的逐一響應,這種服務器的設計、編程、調試和修改往往比較容易去實現。在循環執行的服務器對預期的負載能提供足夠的反應速度時常使用這種類型的服務器。循環服務器有有UDP循環服務器和TCP循環服務器兩種類型。
①UDP循環服務器:
UDP循環服務器的實現方法:UDP服務器每次從套接字上讀取一個客戶端的請求,再對請求進行處理,然後將結果返回給客戶機。其具體模型如下所示:
[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
<span style="font-family:KaiTi_GB2312;font-size:18px;">                socket(...);  
        bind(...);  
        while(1)  
           {  
               recvfrom(...);  
               process(...);  
               sendto(...);  
           }  
</span>  
對於此類服務器,由於UDP是非面向連接的,沒有一個客戶端可以老是佔住服務端,因此UDP循環服務器對於每一個客戶機的請求總是能夠滿足。
下面是UDP循環服務器的運用:
[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <errno.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <arpa/inet.h>  
  
#define PORT 3212  
#define MAX_SIZE 512  
  
int main()  
{  
    int sockfd;  
    int len = sizeof(struct sockaddr);  
  
    char buf[MAX_SIZE];  
    char buffer[MAX_SIZE];  
  
    struct sockaddr_in serv_addr;  
      
    //創建套接字,IPV4/UDP  
    sockfd = socket(AF_INET,SOCK_DGRAM,0);  
    if(sockfd < 0)  
    {  
        printf("create socket error!\n");  
        exit(1);  
    }  
  
    //填充服務器信息  
    bzero(&serv_addr,sizeof(struct sockaddr_in));  
    serv_addr.sin_family = AF_INET;  
    serv_addr.sin_port = htons(PORT);  
    serv_addr.sin_addr.s_addr = inet_addr("192.168.1.132");  
  
    //綁定套接字  
    if(bind(sockfd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr)) < 0)  
    {  
        printf("bind error!\n");  
        exit(1);  
    }  
  
    //循環接收網絡上發送來的數據並回復消息  
    while(1)  
    {  
        //接收數據  
        if(recvfrom(sockfd,buf,MAX_SIZE,0,(struct sockaddr*)&serv_addr,&len) < 0)  
        {  
            printf("recv error!\n");  
            exit(1);  
        }  
        printf("recv is: %s\n ",buf);  
              
      
        printf("write some text:");  
        scanf("%s",buffer);  
          
        //發送數據  
        if(sendto(sockfd,buffer,MAX_SIZE,0,(struct sockaddr*)&serv_addr,len) < 0)  
        {  
            printf("send error!\n");  
            fprintf(stderr,"send error:%s\n",strerror(errno));  
            exit(1);  
        }    
    }  
  
    //關閉連接  
    close(sockfd);  
  
  
    return 0;  
}  
</span>  




②TCP循環服務器:
TCP服務器接受一個客戶端的連接,然後處理,完成了這個客戶的所有請求後,斷開連接。
[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
<span style="font-family:KaiTi_GB2312;font-size:18px;">        socket(...);  
    bind(...);  
    listen(...);  
    while(1)  
    {  
        accept(...);  
        process(...);  
        close(...);  
     }  
  
</span>  


因爲TCP是面向連接的,故TCP循環服務器在同一時刻一次只能處理一個客戶端的請求。只有在這個客戶的所有請求都滿足後, 服務器纔可以繼續後面的請求。這樣如果有一個客戶端佔住服務器不放時,其它的客戶機都不能工作了,因此,TCP服務器一般很少用循環服務器模型。
TCP循環服務器的運用:
[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <stdlib.h>   
#include <stdio.h>   
#include <errno.h>   
#include <string.h>   
#include <netdb.h>   
#include <sys/types.h>   
#include <netinet/in.h>   
#include <sys/socket.h>   
  
#define portnumber 3000 //通信端口號  
  
int main(int argc, char *argv[])   
{   
    int sockfd,new_fd;   
    struct sockaddr_in server_addr;   
    struct sockaddr_in client_addr;   
    int sin_size;   
    int nbytes;  
    char buffer[1024];  
      
  
    /* 服務器端開始建立sockfd描述符 */   
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // IPV4協議;TCP協議  
    {   
        fprintf(stderr,"Socket error:%s\n\a",strerror(errno));   
        exit(1);   
    }   
  
    /* 服務器端填充 sockaddr結構 */   
    bzero(&server_addr,sizeof(struct sockaddr_in)); // 初始化,置0  
    server_addr.sin_family=AF_INET;                 // IPV4  
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);  // (將本機器上的long數據轉化爲網絡上的long數據)和任何主機通信,INADDR_ANY :可以接收任意IP地址  
    //server_addr.sin_addr.s_addr=inet_addr("192.168.1.132");  //用於綁定到一個固定IP,inet_addr用於把數字加格式的ip轉化爲整形ip  
    server_addr.sin_port=htons(portnumber);         // (將本機器上的short數據轉化爲網絡上的short數據)端口號  
      
    /* 捆綁sockfd描述符到IP地址 */   
    if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)   
    {   
        fprintf(stderr,"Bind error:%s\n\a",strerror(errno));   
        exit(1);   
    }   
  
    /* 設置允許連接的最大客戶端數 */   
    if(listen(sockfd,5)==-1)   
    {   
        fprintf(stderr,"Listen error:%s\n\a",strerror(errno));   
        exit(1);   
    }   
  
    while(1)   
    {   
        /* 服務器阻塞,直到客戶程序建立連接 */   
        sin_size=sizeof(struct sockaddr_in);   
        if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)   
        {   
            fprintf(stderr,"Accept error:%s\n\a",strerror(errno));   
            exit(1);   
        }   
        fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr)); // 將網絡地址轉換成.字符串  
          
        if((nbytes=read(new_fd,buffer,1024))==-1)   
        {   
            fprintf(stderr,"Read Error:%s\n",strerror(errno));   
            exit(1);   
        }         
        buffer[nbytes]='\0';  
        printf("Server received %s\n",buffer);  
          
        /* 這個通訊已經結束 */   
        close(new_fd);   
        /* 循環下一個 */   
    }   
  
    /* 結束通訊 */   
    close(sockfd);   
    exit(0);   
} </span>  


這種服務器有下面這些缺點:
*若服務器正在處理一個客戶端請求時另一個請求到來,系統會將該新的請求排隊,第二個請求須等待第一個請求處理完才能開始,這就造成在客戶請求過於頻繁也就是客戶請求過多時服務器的請求隊伍會越來越長,響應時間也就越來越久。
*如果一個服務器的設計的處理能力爲處理K個客戶端,而每個客戶端每秒發送N個請求,則此服務器的請求處理時間必須小於每個請求1\(K*N)s。若服務器無法達到該要求則會造成客戶的請求隊列溢出。這也就衍生出了併發服務器。


2、併發服務器:
併發服務器在同一個時刻可以響應多個客戶端的請求,將併發引入的原因是需要給多個客戶提供快速的響應時間。併發方式處理多個客戶的請求是目前最爲廣泛運用的服務器類型,常見的併發服務器的實現是多線程機制(也可以使用多進程,不過這種方法消耗資源太大,通常不使用),在多線程機制中服務器主線程爲每個到來的客戶請求創建一個子線程進行服務,這類服務器的編寫規則通常如下:
第一部分的代碼負責監聽並接收客戶請求,爲客戶請求創建新的服務器線程;第二部分的代碼負責處理單個客戶的請求。關於多線程機制併發服務器的設計流程如下:
[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
<span style="font-family:KaiTi_GB2312;font-size:18px;">   socket(...);  
   bind(...);  
   listen(...);  
   while(1)   
  {  
     accept(...);  
         if(fork(..)==0) //創建線程處理請求  
         {  
          process(...);  
          close(...);  
              exit(...); //處理結束,退出線程  
      }  
      close(...);   
   }  
</span>  


TCP併發服務器(多線程機制)的運用:
[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <stdlib.h>  
#include <stdio.h>  
#include <errno.h>  
#include <string.h>  
#include <netdb.h>  
#include <sys/types.h>  
#include <netinet/in.h>  
#include <sys/socket.h>  
  
#define portnumber 3000  
  
void * read_msg(void *arg) //處理客戶請求(讀取客戶端數據)  
{  
    int fd = *((int *)arg);  
    int nread = 0;  
    char buffer[1024];  
  
    while((nread = read(fd,buffer,sizeof(buffer))) > 0)  
    {  
        buffer[nread] = '\0';  
        printf("get client message: %s\n",buffer);  
        memset(buffer,0,sizeof(buffer));  
    }  
}  
  
  
int main(int argc, char *argv[])  
{  
    int sockfd,new_fd;  
    struct sockaddr_in server_addr;  
    struct sockaddr_in client_addr;  
    int sin_size;  
    int nbytes;  
    char buffer[1024];  
  
    pthread_t id;  
  
  
    /* 服務器端開始建立sockfd描述符 */  
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:IPV4;SOCK_STREAM:TCP  
    {  
        fprintf(stderr,"Socket error:%s\n\a",strerror(errno));  
        exit(1);  
    }  
  
    /* 服務器端填充 sockaddr結構 */  
    bzero(&server_addr,sizeof(struct sockaddr_in)); // 初始化,置0  
    server_addr.sin_family=AF_INET;                 // Internet  
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);  // 和任何主機通信,INADDR_ANY可以接收任意IP地址  
    //server_addr.sin_addr.s_addr=inet_addr("192.168.1.132");  //用於綁定到一個固定IP,inet_addr用於把數字加格式的ip轉化爲整形ip  
    server_addr.sin_port=htons(portnumber);         // (將本機器上的short數據轉化爲網絡上的short數據)端口號  
  
    /* 捆綁sockfd描述符到IP地址 */  
    if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)  
    {  
        fprintf(stderr,"Bind error:%s\n\a",strerror(errno));  
        exit(1);  
    }  
  
    /* 設置允許連接的最大客戶端數 */  
    if(listen(sockfd,5)==-1)  
    {  
        fprintf(stderr,"Listen error:%s\n\a",strerror(errno));  
        exit(1);  
    }  
  
    while(1)  
    {  
        /* 服務器阻塞,直到客戶程序建立連接 */  
        sin_size=sizeof(struct sockaddr_in);  
        printf("accepting!\n");  
        if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)  
        {  
            fprintf(stderr,"Accept error:%s\n\a",strerror(errno));  
            exit(1);  
        }  
        fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr)); // 將網絡地址轉換成.字符串  
          
        pthread_create(&id,NULL,(void *)read_msg,(void *)&new_fd); //創建線程  
  
    }  
  
    /* 結束通訊 */  
    close(sockfd);  
    exit(0);  
}</span>  


對於併發服務器也可以用子進程方式實現:
[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <stdlib.h>   
#include <stdio.h>   
#include <errno.h>   
#include <string.h>   
#include <netdb.h>   
#include <sys/types.h>   
#include <netinet/in.h>   
#include <sys/socket.h>   
  
#define MY_PORT 3000  
  
int main(int argc ,char **argv)  
{  
    int listen_fd,accept_fd;  
    struct sockaddr_in  client_addr;  
    int n;  
    int nbytes;  
   
    if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)  
    {  
            printf("Socket Error:%s\n\a",strerror(errno));  
            exit(1);  
    }  
   
    bzero(&client_addr,sizeof(struct sockaddr_in));  
    client_addr.sin_family=AF_INET;  
    client_addr.sin_port=htons(MY_PORT);  
    client_addr.sin_addr.s_addr=htonl(INADDR_ANY);  
    n=1;  
   
    /* 如果服務器終止後,服務器可以第二次快速啓動而不用等待一段時間  */  
    setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));  
    if(bind(listen_fd,(struct sockaddr *)&client_addr,sizeof(client_addr))<0)  
    {  
            printf("Bind Error:%s\n\a",strerror(errno));  
            exit(1);  
    }  
    
    listen(listen_fd,5);  
      
    while(1)  
    {  
        accept_fd=accept(listen_fd,NULL,NULL);  
        if((accept_fd<0)&&(errno==EINTR))  
                continue;  
        else if(accept_fd<0)  
            {  
                printf("Accept Error:%s\n\a",strerror(errno));  
                continue;  
            }  
        if((n=fork())==0)  
        {  
                /* 子進程處理客戶端的連接 */  
                char buffer[1024];  
  
                if((nbytes=read(accept_fd,buffer,1024))==-1)   
            {   
                fprintf(stderr,"Read Error:%s\n",strerror(errno));   
                exit(1);   
            }         
            buffer[nbytes]='\0';  
            printf("Server received %s\n",buffer);  
                  
                close(listen_fd);  
                close(accept_fd);  
                exit(0);  
        }  
        else   
            close(accept_fd);  
    }  
} </span>  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章