1. 程式人生 > >Linux多程序併發伺服器(TCP)

Linux多程序併發伺服器(TCP)

Linux多程序併發伺服器(TCP)

前言:在Linux環境下多程序的應用很多,其中最主要的就是網路/客戶伺服器。多程序伺服器是當客戶有請求時 ,伺服器用一個子程序來處理客戶請求。父程序繼續等待其它客戶的請求。這種方法的優點是當客戶有請求時 ,伺服器能及時處理客戶 ,特別是在客戶伺服器互動系統中。對於一個 TCP伺服器,客戶與伺服器的連線可能並不馬上關閉 ,可能會等到客戶提交某些資料後再關閉 ,這段時間伺服器端的程序會阻塞 ,所以這時作業系統可能排程其它客戶服務程序。比起迴圈伺服器大大提高了服務效能。

我們來看一下併發伺服器和迴圈伺服器的區別:

snipaste_20180826_221138
這裡寫圖片描述

TCP**併發伺服器的思想是每一個客戶機的請求並不由伺服器直接處理,而是由伺服器建立一個子程序來處理。**

int main()
{
建立套接字socket
繫結(bind)套接字
監聽(listen)套接字sockfd
while(1)

{

   int connfd = accpet(...);

   if(fork(...) == 0)//子程序

   {
    close(sockfd)//關閉監聽套接字,從父程序繼承過來的
    process(...);

    close(connfd);//關閉已連線的套接字
    exit(0);
   }
   close(connfd);
}
close(sockfd)
}

fork函式在併發伺服器中的應用:
  父、子程序各自執行不同的程式段,這是非常典型的網路伺服器。父程序等待客戶 的服務請求。當這種請求到達時,父程序呼叫 fork 函式,產生一個子程序,由子程序對該請求作處理。父程序則繼續等待下一個客戶的服務請求。並且這種情況下,在 fork 函式之後,父、子程序需要關閉各自不使用的描述符,即父程序將不需要的 已連線描述符關閉,而子程序關閉不需要的監聽描述符。這麼做的原因有3個:

  1. 節省系統資源
  2. 防止上面提到的父、子程序同時對共享描述符程序操作
  3. 最重要的一點,是確保close函式能夠正確關閉套接字描述符

我們在socket程式設計中呼叫 close 關閉已連線描述符時,其實只是將訪問計數值減 1。而描述符只在訪 問計數為 0 時才真正關閉。所以為了正確的關閉連線,當呼叫 fork 函式後父程序將不需要的 已連線描述符關閉,而子程序關閉不需要的監聽描述符。

例子:

utili.h

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<string.h> #include<netinet/in.h> #include<signal.h> #define LISTENQ 1024 #define SERV_PORT 9857 #define BUFSIZE 4096

ser.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<string.h>
#include<netinet/in.h>
#include<signal.h>
#define LISTENQ 1024
#define SERV_PORT 9857

#define BUFSIZE 4096
[email protected]:~/linux/socket/2$ cat ser.c 
#include"utili.h"

void str_echo(int sockfd)
{
    ssize_t n;
    char buf[BUFSIZE];
    while((n=read(sockfd,buf,BUFSIZE)) > 0)
        write(sockfd,buf,n);
}
void sig_chld(int signo)
{
    pid_t pid;
    int stat;
    while((pid = waitpid(-1,&stat,WNOHANG))>0)
        printf("child %d terminated\n",pid);
    return ;
}
int main()
{
    int confd,listenfd;
    struct sockaddr_in cliaddr,servaddr;
    pid_t childpid;
    socklen_t clien;
    int status;
    char buf[BUFSIZE];

    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    listenfd = socket(AF_INET,SOCK_STREAM,0);
    if(listenfd == -1)
        printf("socket error\n");
    status = bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    if(status == -1)
        printf("bind error\n");
    status = listen(listenfd,LISTENQ);
    signal(SIGCHLD,sig_chld);
    while(1)
    {
        clien = sizeof(cliaddr);
        confd = accept(listenfd,(struct sockaddr*)&cliaddr,&clien);
        if(confd == -1)
            printf("accept error\n");
        if((childpid = fork())==0)
        {
            printf("connet from %s,port %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr,buf,sizeof(buf)),ntohs(cliaddr.sin_port));
            close(listenfd);
            str_echo(confd);
            close(confd);
            exit(0);
        }
        close(confd);
    }
//  return 0;
}

cli.c

#include"utili.h"

void str_cli(FILE* fp,int sockfd)
{
    printf("connect success\n");
    char send[BUFSIZE],recv[BUFSIZE];
    while(fgets(send,BUFSIZE,fp)!=NULL)
    {
        write(sockfd,send,strlen(send));
        read(sockfd,recv,BUFSIZE);
        fputs(recv,stdout);
        bzero(recv,BUFSIZE);
    }
}

int main(int argc,char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr;
    socklen_t clien;
    int status;
    char buf[BUFSIZE];

    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
//  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,argv[1],&servaddr.sin_addr);

    sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
        printf("socket error\n");
    status = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    if(status == -1)
        printf("connecd error\n");
    str_cli(stdin,sockfd);
    return 0;
//  exit(0);
}

這就是一個很典型的例子,看具體情況請看TCP回射伺服器