TCP预先派生子进程服务程序,accept无上锁保护

okay,今天是我们linux服务器模型的第二篇—TCP预先派生子进程服务程序,accept无上锁保护。

从字面上理解,就是在启动阶段派生一定数量的子进程,当各个客户连接到达时,这些子进程立即就能为他们服务。注意与我们第一篇的不同,我们第一篇是为每一个客户派生一个子进程,来一个,派生一个。另外值得注意的是,如果某个时刻,客户数量正好等于预先派生的子进程,那么对于下一个客户,依然能够执行三次握手,进行连接,只不过得等待到至少有一个子进程可用。

另外,我们的服务器业务还是与第一篇一样,就是根据客户端发的指令判断返回相应数据。

okay,来看我们的代码:

serv.c:

#include "pub.h"

static  int nchildren;
static pid_t *pids;

void sig_int(int signo);

int main(int argc, char **argv)
{
    int listenfd;
    struct sockaddr_in servaddr;
    int on = 1;
    int i = 0;
    pid_t child_make(int, int);

    if(argc != 3)
    {
        fprintf(stderr,"usage: serv <port> <nchildren>\n"); //这里指定端口与预先派生子进程的数目
        exit(-1);
    }

    if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("serv socket error ");
        exit(-1);
    }

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(atoi(argv[1]));
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    //设置可重用
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){
        perror("serv bind error ");
        exit(-1);
    }

    if(listen(listenfd, LISTENQ) < 0){
        perror("serv listen error ");
        exit(-1);
    }

    nchildren = atoi(argv[2]);
    pids = calloc(nchildren, sizeof(pid_t));

    for(i = 0;i < nchildren; i++)
        pids[i] = child_make(i ,listenfd);

    Signal(SIGINT, sig_int);

    for( ; ; )  //parent just pause
        pause();        //everything done by children

    exit(0);
}

void sig_int(int signo)
{
    int i = 0;
    for(i = 0; i < nchildren; i++){
        kill(pids[i], SIGTERM);
    }

    while(wait(NULL) > 0);  //wait for all children

    //对于wait
    //如果调用的父进程没有子进程则返回-1,并设置errno

    if(errno != ECHILD)
    {
        perror("sig_int error ");
        exit(-1);
    }

    exit(0);
}

关注这里:

for(i = 0;i < nchildren; i++)
        pids[i] = child_make(i ,listenfd);

Signal(SIGINT, sig_int);

for( ; ; )  //parent just pause
    pause();        //everything done by children

这里的nchildren是从我们的参数中提取出来的,代表预先派生的子进程的数量。对于pids数组,用child_make()函数派生子进程,同时将子进程的PID返回给pids数组的某一项。这里的pids是我们动态分配的数组,用来储存我们的各个子进程的PID。这里的child_make()函数是用来派生子进程,同时包括处理的代码。带会我们来讲解它。

看后面的Signal(SIGINT, sig_int);就是注册SIGINT信号的处理函数。我们将在收到SIGINT信号后,将所有派生的子进程kill.

这与后面的for循环以及pause()函数就是为了让父进程挂起。让所有的处理交由预先派生的子进程。

来看我们的sig_int()信号处理函数:

void sig_int(int signo)
{
    int i = 0;
    for(i = 0; i < nchildren; i++){
        kill(pids[i], SIGTERM);
    }

    while(wait(NULL) > 0);  //wait for all children

    //对于wait
    //如果调用的父进程没有子进程则返回-1,并设置errno

    if(errno != ECHILD)
    {
        perror("sig_int error ");
        exit(-1);
    }

    exit(0);
}

一目了然,就是我们之前说的,kill掉所有派生子进程。通过while(wait(NULL) > 0);回收所有的紫禁城的资源。注意我们注释中说的:对于wait,如果调用的父进程没有子进程则返回-1,并设置errno。这个errno就是ECHILD。这并不算错误。

okay,接下来是真正的关于派生子进程的函数。

来看child_make()函数:

#include "pub.h"

pid_t child_make(int i, int listenfd)
{
    pid_t pid;
    void child_main(int, int);

    if((pid = fork()) > 0)//parent just return pid of child
        return pid;
    //child 

    child_main(i ,listenfd);    //nerver return
}

同样很简洁,fork返回如果是大于0,便是子进程的PID,返回即可。留给上面我们讲的pids数组保存。如果是子进程,就进入child_main()函数,这是我们真正进行与客户端连接的函数。

来看一下child_main()函数:

#include "pub.h"

void child_main(int i, int listenfd)
{
    int connfd;
    void child_handle(int, int);
    struct sockaddr_in cliaddr;
    socklen_t len;
    char *buff;

    for( ; ; ){
        len = sizeof(cliaddr);
        if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len)) < 0){
            if(errno == EINTR){
                continue;
            }else{
                perror("the num %d child_main accept error ");
                continue;
            }
        }

        buff = inet_ntoa(cliaddr.sin_addr);
        fprintf(stdout,"%s has been handled\n",buff);
        fflush(stdout);

        child_handle(i, connfd);

        fprintf(stdout,"%s has left\n",buff);
        fflush(stdout);

        close(connfd);
    }

    close(listenfd);
}

不是很难,就是与我们第一篇中的连接差不多。通过一个for循环,等待accept返回,一旦返回并且无错,就先打印某某has been handled,进入child_handle()函数,这是我们处理子进程的业务代码函数,待会再讲。

每当一个子进程处理完业务,就答应某某has left,并关闭connfd,注意我们这个循环并不会退出,永远待命。

再看我们的child_handle()函数:

#include "pub.h"

void child_handle(int i,int sockfd)
{
    char buff[MAXLINE];
    int n;
    time_t mytime;
    for( ; ; ){

        if((n = read(sockfd, buff, MAXLINE)) <= 0){
            if(n < 0 && errno == EINTR)
                continue;
            else if(n == 0){
                //断开连接,交由child_main处理
                break;
            }else{
                perror("child num %d child_handle read error ");
                break;
            }
        }

        //比较前几个字符串,是不是GETTIME,是则返回时间,否则返回 Gettime Command Error
        if((strncmp(buff,"GETTIME", 7) == 0) ||
            (strncmp(buff,"gettime", 7) == 0) )
        {
            mytime = time(NULL);
            snprintf(buff, sizeof(buff), "%s", ctime(&mytime));
            writen(sockfd, buff, strlen(buff));//这里最好用writen(自定义)
        }
        else
        {//不是的话,就返回 Gettime Command Error
            snprintf(buff, sizeof(buff), "Gettime Command Error ");
            writen(sockfd, buff, strlen(buff));
        }

    }

}

这段业务代码与我们之前第一篇的do_child()函数基本一样。比较前几个字符串,是不是GETTIME或者gettime,是则返回时间,否则返回 Gettime Command Error。

其中还包含writen函数,我把它贴出来,就是第一篇中的writen函数,就不讲解了;

#include "pub.h"

int writen(int sockfd,const char *buff, int n)
{
    int nleft = n;
    int ncount = 0;

    const char *ptr = buff;

    while(nleft > 0)
    {
        if((ncount = write(sockfd, ptr, nleft)) <= 0)
        {
            if(errno == EINTR)
                ncount = 0; //call again
            else
                return -1;
        }
        nleft -= ncount;
        ptr += ncount;
    }

    return n - nleft;
}

另外,还有我们的信号处理函数Signal()函数,我也贴出来。

/* include signal */
#include    "pub.h"

Sigfunc *
signal(int signo, Sigfunc *func)
{
    struct sigaction    act, oact;

    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (signo == SIGALRM) {
#ifdef  SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;   /* SunOS 4.x */
#endif
    } else {
#ifdef  SA_RESTART
        act.sa_flags |= SA_RESTART;     /* SVR4, 44BSD */
#endif
    }
    if (sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}
/* end signal */

Sigfunc *
Signal(int signo, Sigfunc *func)    /* for our signal() function */
{
    Sigfunc *sigfunc;

    if ( (sigfunc = signal(signo, func)) == SIG_ERR)
    {
        perror("signal error");
        exit(1);
    }
    return(sigfunc);
}

这是一张可以概括我们今天的服务器模型的图片,注意这里我们的每一个子进程都有listenfd,每个子进程都是通过这个listenfd来进行accept,得到客户端连接。同时,这个listenfd描述符只是一个这个FILE结构体的下标而已。当前有那么多的引用计数。

okay,这就是我们今天的linux服务器模型第二篇–TCP预先派生子进程服务程序,accept无上锁保护。你明白了吗?

时间: 2024-08-02 10:09:32

TCP预先派生子进程服务程序,accept无上锁保护的相关文章

UNIX网络编程卷1 服务器程序设计范式5 预先派生子进程,由父进程向子进程传递套接字描述符

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.只让你进程调用 accept,然后把所接受的已连接套接字"传递"给某个子进程. 这样做就不用因为所有子进程都调用 accept 而需提供上锁保护 2.父进程必须跟踪子进程的忙闲状态,以便给空闲子进程传递新的套接字 typedef struct { pid_t child_pid; /* 子进程的进程 ID */ int child_pipefd; /* 父进程中连接到该子进程

UNIX网络编程卷1 服务器程序设计范式4 预先派生子进程,以线程互斥锁上锁方式保护accept

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.文件上锁文件系统操作,比较耗时 2.线程上锁,不仅适用于同一进程内各线程之间的上锁,也适用于不同进程之间的上锁. 3.在不同进程之间使用线程上锁要求: 1)互斥锁变量必须存放在由所有进程共享的内存区中 2)必须告知线程函数库这是在不同进程之间共享的互斥锁 /* include my_lock_init */ #include "unpthread.h" #include &l

TCP并发服务器(四)&mdash;&mdash;预创建子进程,accept互斥锁

1.说明 Posix文件上锁可移植到所有Posix兼容系统,但是涉及到文件系统操作,可能比较费时. 本次使用线程上锁保护accept,这不仅适用于同一进程中各线程之间上锁,也适用于不同进程之间上锁. 2.进程间使用互斥锁要求 (1) 互斥锁变量必须存放在由所有进程共享的内存去. (2) 必须告知线程函数库这是在不同进程之间共享的互斥锁.要求线程支持PTHREAD_PROCESS_SHARED属性.默认属性PTHREAD_PROCESS_PRIVATE, 只允许在单个进程内使用.   3.代码 支

国标电表DLT645转MODBUS TCP协议转换器,工业设备,浪涌三级保护

DL/T645转ModbusTcp协议转换器 MRD-5021具有1 路RS485及1路以太网接口,最多支持同时采集5个DL/T645-1997或者5个2007协议国标电表设备,支持DL/T645协议的自适应,然后将所需要的采集数据转换为Modbus Tcp协议.方便和PLC.DCS.组态等工业相关设备对接. 说明书:http://files.cnblogs.com/mored/DLT645-ModbusTcp%E4%BA%A7%E5%93%81MRD-5021%E8%AF%B4%E6%98%8

TCP的SYN队列和Accept队列

首先我们必须明白,处于“LISTENING”状态的TCP socket,有两个独立的队列: SYN队列(SYN Queue) Accept队列(Accept Queue) 这两个术语有时也被称为“reqsk_queue”,“ACK backlog”,“listen backlog”,甚至“TCP backlog”,但是这篇文章中我们使用上面两个术语以免造成混淆. SYN队列 SYN队列存储了收到SYN包的连接(对应内核代码的结构体:struct inet_request_sock).它的职责是回

【转】网络编程知识

网络编程知识 索引: 处理SIGCHLD信号 捕获信号时,注意处理被中断的系统调用 accept返回前连接夭折的处理 具有多个输入的处理 SIGPIPE的产生和处理 处理服务器主机崩溃 处理服务器主机崩溃重启 处理服务器主机关机 网络函数的可重入问题 套接口设置超时的方法 辅助数据 如何得知套接口接收队列中有多少数据? UNIX域协议 UNIX域套接口使用套接口函数的一些差别和限制 描述字传递机制 非阻塞套接口I/O 服务器程序常见设计方法 注意网络编程的移植性问题 注意对等方的不合理行为 开发

unix网络编程各种TCP客户-服务器程序设计实例(三)

第五种  TCP预先派生子进程服务器程序: 对预先派生子进程服务器的最后一种改动就是由父进程调用accept,然后再将所接受的已连接描述字传递给子进程.父进程必须跟踪子进程的忙闲状态,以便给空闲子进程传递新的描述字.为每个子进程维护一个信息结构,用来管理各子进程. 在调用fork之前,先创建一个字节流管道(Unix域的字节流套接口),它是Unix域的字节流套接口.当子进程派生后,父进程关闭一个描述字(sockfd[1]),子进程关闭另一个描述字(sockfd[0]),此外,子进程将流管道的字节所

sockets: sockets网络编程相关的知识

########################################################### unix域(本地IPC): ########################################################### 在单个主机上执行客户服务器程序,可视为IPC方法之一. unix域(本地IPC)地址结构: struct sockaddr_un { sa_family_t sun_family;  //AF_LOCAL instead of AF

客户-服务器程序设计方法

客户-服务器程序设计方法 <unix网络编程>第一卷中将客户服务器程序设计方法讲得透彻,这篇文章将其中编码的细节略去,通过伪代码的形式展现,主要介绍各种方法的思想: 示例是一个经典的TCP回射程序: 客户端发起连接请求,连接后发送一串数据:收到服务端的数据后输出到终端: 服务端收到客户端的数据后原样回写给客户端: 客户端伪代码: sockfd = socket(AF_INET,SOCK_STREAM,0); //与服务端建立连接 connect(sockfd); //连接建立后从终端读入数据并