0730------Linux网络编程----------服务器端模型(迭代,多进程,多线程,select,poll,epoll 等)

1.迭代服务器模型

  1.1 迭代服务器是处理多个请求时一种最简单直接的思路,即使用while循环,它不具有并发能力,即必须一个一个的处理客户的请求。

  1.2 程序示例。

#include "def.h"

int  listenfd_init(); //返回一个处于监听状态的套接字描述符
void do_service(int peerfd); // 处理客户端的请求

int main(int argc, const char *argv[])
{
    if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
        ERR_EXIT("signal");
    int listenfd = listenfd_init(); // 生成一个套接字并使其处于监听状态
    struct sockaddr_in peeraddr;
    int len = sizeof(peeraddr);
    int peerfd;
    while(1){
        if((peerfd = accept(listenfd, (struct sockaddr*)&peeraddr, &len)) == -1) //接受一个TCP连接请求
            ERR_EXIT("accpet");
        do_service(peerfd); // 处理请求
        close(peerfd);
    }
    close(listenfd);
    return 0;
}

int listenfd_init(){
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int on = 1;
    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
        ERR_EXIT("setsockopt");
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(9999);
    if(listenfd == -1)
        ERR_EXIT("socket");
    if(bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1)
        ERR_EXIT("bind");
    if(listen(listenfd, 10) == -1)
        ERR_EXIT("listen");
    return listenfd;
}

void do_service(int peerfd){
    char recvbuf[1024] = {0};
    int ret;
    while(1){
       ret = readline(peerfd, recvbuf, 1024);
       if(ret <= 0)
            break;
       printf("recv data : %s", recvbuf);
       writen(peerfd, recvbuf, strlen(recvbuf));
    }
}

2.多进程服务器模型

  2.1 使用多进程编写并发服务器的一般步骤:

    a)while(1)循环,每次accept一个连接都fork一个进程;

    b)在子进程中要close(listenfd),最后要exit(这里子进程一定要退出,否则会继续执行);

    c)父进程要关闭accept返回的peerfd。

  2.2 一些要点

    a)父进程要关闭peerfd,这主要是因为close根据引用计数关闭fd,如果父进程不这样做,那么所有通过accept创建的fd都不会被真正释放,这将造成资源耗尽

    b)父进程不可以直接采用waitpid来回收子进程,这样会使得server变为一个迭代服务器,而不具备并发的能力,,必须采用信号这种异步的处理手段;

    c)使用信号处理必须注意,使用while而不是if,尽可能多处理僵尸进程,这是为了防止信号的额阻塞和丢失问题,waitpid要使用WNOHANG悬选项。

    d)子进程要关闭listenfd;

    e)子进程执行do_service之后务必exit(EXIT_SUCCESS)

  2.3 程序示例。

#include "def.h"
/*
 * 服务器端使用 多进程 模型
 *
 */
int  listenfd_init(); //返回一个处于监听状态的套接字描述符
void do_service(int peerfd); // 处理客户端的请求
void handler(int signum); //处理sigchld信号 回收子进程资源

int main(int argc, const char *argv[])
{
    if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
       ERR_EXIT("signal");
    if(signal(SIGCHLD, handler) == SIG_ERR)
       ERR_EXIT("signal");
    int listenfd = listenfd_init(); // 生成一个套接字并使其处于监听状态

    //多进程模型
    while(1){
        struct sockaddr_in peeraddr;
        memset(&peeraddr, 0,sizeof(peeraddr));
        int len = sizeof(peeraddr);
        int peerfd;

        // 接受一个TCP连接请求
        if((peerfd = accept(listenfd, (struct sockaddr*)&peeraddr, &len)) == -1)
            ERR_EXIT("accpet");
        pid_t pid;
        if((pid = fork()) < 0)
            ERR_EXIT("fork");
        else if(pid == 0){
            close(listenfd); //子进程要关闭listenfd
            do_service(peerfd); // 处理请求
            exit(EXIT_SUCCESS);
        }
        close(peerfd); //这里必须关闭peerfd,否则导致资源耗尽
    }
    close(listenfd);
    return 0;
}

int listenfd_init(){
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int on = 1;
    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
        ERR_EXIT("setsockopt");
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(9999);
    if(listenfd == -1)
        ERR_EXIT("socket");
    if(bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1)
        ERR_EXIT("bind");
    if(listen(listenfd, 10) == -1)
        ERR_EXIT("listen");
    return listenfd;
}

void do_service(int peerfd){
    char recvbuf[1024] = {0};
    int ret;
    while(1){
       ret = readline(peerfd, recvbuf, 1024);
       if(ret <= 0){
           close(peerfd);
           exit(EXIT_SUCCESS);
       }
       printf("recv data : %s", recvbuf);
       writen(peerfd, recvbuf, strlen(recvbuf));
    }
    close(peerfd);
}

void handler(int signum){
    while(waitpid(-1, 0, WNOHANG) > 0) ; //while尽可能多回收子进程
}

3.多线程服务器模型

  3.1 使用多线程编写并发服务器的一般步骤: while(1)循环内,每次accpet一个连接,都创建一个线程。

  3.2 一些要点:

    a)往线程中传fd,最好使用动态分配内存(有些机器int和void*不兼容),在线程中务必释放内存,防止内存泄露;

    b)线程务必使用detach函数,将自己设置为分离状态,自动回收资源。

  3.3 程序示例。

#include "def.h"
/*
 * 服务器端使用 多线程 模型
 *
 */
int  listenfd_init(); //返回一个处于监听状态的套接字描述符
void do_service(int peerfd); // 处理客户端的请求
void handler(int signum); //处理sigchld信号 回收子进程资源
void *thread_func(void *arg); //线程处理函数

int main(int argc, const char *argv[])
{
    if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
       ERR_EXIT("signal");
    if(signal(SIGCHLD, handler) == SIG_ERR)
       ERR_EXIT("signal");
    int listenfd = listenfd_init(); // 生成一个套接字并使其处于监听状态

    //多线程模型
    while(1){
        struct sockaddr_in peeraddr;
        memset(&peeraddr, 0,sizeof(peeraddr));
        int len = sizeof(peeraddr);
        int peerfd;
        if((peerfd = accept(listenfd, (struct sockaddr*)&peeraddr, &len)) == -1)
            ERR_EXIT("accpet");
        int *pfd = (int *)malloc(sizeof(int));
        *pfd = peerfd;
        pthread_t tid;
        pthread_create(&tid, NULL, thread_func, pfd);
    }
    close(listenfd);
    return 0;
}

int listenfd_init(){
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int on = 1;
    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
        ERR_EXIT("setsockopt");
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(9999);
    if(listenfd == -1)
        ERR_EXIT("socket");
    if(bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1)
        ERR_EXIT("bind");
    if(listen(listenfd, 10) == -1)
        ERR_EXIT("listen");
    return listenfd;
}

void *thread_func(void *arg){ //线程处理函数
    pthread_detach(pthread_self()); //将当前线程设置为分离状态 自己主动回收资源
    int *pfd = (int *)arg;
    int peerfd = *pfd;
    free(pfd);
    do_service(peerfd);
}

void do_service(int peerfd){
    char recvbuf[1024] = {0};
    int ret;
    while(1){
       ret = readline(peerfd, recvbuf, 1024);
       if(ret <= 0){
           close(peerfd);
           exit(EXIT_SUCCESS);
       }
       printf("recv data : %s", recvbuf);
       writen(peerfd, recvbuf, strlen(recvbuf));
    }
    close(peerfd);
}

void handler(int signum){
    while(waitpid(-1, 0, WNOHANG) > 0) ; //while尽可能多回收子进程
}

4.select服务器模型

  4.1 使用 select 编写并发服务器的一般步骤:

    a)初始化参数,这里包括readset(描述符集合,用作readyset的备份),readyset用作select函数的传出参数,clients数组(存储所有已连接的fd,这里的数组长度FD_SETSIZE是一个系统定义的宏),maxi(数组的最大下标,便于提高效率),maxfd(要监听的fd的最大值,用作select的第一个参数),nready(用作select的返回值);

    b)进入while(1)循环,readyset = readset;

    c)执行select函数,并检查其返回值;

    d)检查listenfd是否在准备好的集合中,此时这里需要accpet一个连接,将返回的fd加入到clients数组中和readset结合中,并且还要更新maxi和maxfd;

    e)遍历clients数组,依次查看每个fd是否在准备好的集合readyset中,这里要注意,当某一个客户关闭连接时,本地需要close这个fd,更新clients数组,将该fd从readset集合中移除。

  4.2 程序示例。 

void do_select(int listenfd){
    //初始化参数
    fd_set readset, readyset;//readset 用作备份 存储要监听的所有fd,readyset用作返回
    FD_ZERO(&readset);
    FD_ZERO(&readyset);
    FD_SET(listenfd, &readset);
    //定义数组,存储所有已连接的客户fd 初始化为-1
    int clients[FD_SETSIZE]; //FD_SETSIZE 是一个系统的宏定义
    int i;
    for(i = 0; i < FD_SETSIZE; i++){
        clients[i] = -1;
    }
    int maxi = -1; //数组的最大下标 //?
    int nready; //select的返回值
    int maxfd = listenfd;//监听的最大fd

    while(1){
        //执行select,检查返回值
        readyset = readset;
        nready = select(maxfd + 1, &readyset, NULL, NULL, NULL);
        if(nready == -1){
            if(errno == EINTR)
                continue;
            ERR_EXIT("select");
        }

        //检查listenfd 是否在准备好的集合中
        if(FD_ISSET(listenfd, &readyset)){
            int peerfd = accept(listenfd, NULL, NULL);
            if(peerfd == -1)
                ERR_EXIT("accept");
            //为新的fd在clients数组中找一个空位,并更新maxi
            int i;
            for(i = 0; i < FD_SETSIZE; i++){
                if(clients[i] == -1){
                    clients[i] = peerfd;
                    if(i > maxi)
                        maxi = i;
                    break;
                }
            }
            if(i == FD_SETSIZE){//找不到一个空闲fd位置给新的fd
                fprintf(stderr, "too many clients\n");
                close(peerfd);
                continue;
            }
            //将新的fd添加到集合中, 更新maxfd
            FD_SET(peerfd, &readset);
            if(peerfd > maxfd)
                maxfd = peerfd;
            if(--nready <= 0)//执行下一次select
                continue;
        }//if listenfd

        //依次检查每个普通fd是否在准备好的集合中
        int i;
        char recvbuf[1024] = {0};
        for(i = 0; i <= maxi; i++){
            if(FD_ISSET(clients[i], &readyset)){
                int ret = readline(clients[i], recvbuf, 1024);
                if(ret == -1)
                    ERR_EXIT("readline");
                else if(ret == 0){ //对端已关闭tcp连接
                    //从监听集合中移除fd ,并关闭连接
                    printf("client closed\n");
                    close(clients[i]);
                    FD_CLR(clients[i], &readset);
                    clients[i] = -1;
                    break;
                }
                printf("recv data: %s", recvbuf);
                writen(clients[i], recvbuf, strlen(recvbuf));

                if(--nready <= 0)
                    break; //不在轮询后面的fd
            }
        }//for
    }//while(1)
}

5.poll服务器模型

  5.1 使用poll编写并发服务器的一般步骤:

    a)创建struct pollfd events[]数组存放用来监听的fd,并且初始化为-1;

    b)将listenfd加入到该数组中;

    c)进入while(1)循环,执行poll系统调用,并检查返回值;

    d)检查listenfd是否可读,若可读,则需要accept一个连接,从events数组中找一个空闲的位置,将返回的fd加入到数组中;

    e)检查其他的fd,这里要注意fd关闭的问题(如果客户关闭了连接,本地需要关闭连接,并将数组元素置为-1)。

  5.2 程序示例。

void do_poll(int listenfd){
    struct pollfd clients[2048]; //存储所有要监听的fd
    //初始化数组
    int i;
    for(i = 0; i < 2048; i++){
        clients[i].fd = -1;
    }
    clients[0].fd = listenfd;
    clients[0].events = POLLIN;
    int maxi = 0; // clients数组的最大下标
    int nready; // 接收poll 的返回值

    //执行 poll 函数
    while(1){
        nready = poll(clients, maxi+1, -1);
        if(nready == -1){
            if(errno == EINTR)
                continue;
            ERR_EXIT("poll");
        }

        // 1.处理 listenfd
        if(clients[0].revents & POLLIN){
            int peerfd = accept(listenfd, NULL, NULL);
            if(peerfd == -1)
                ERR_EXIT("accept");

            // 为新的fd 找一个空位置
            int i;
            for(i = 0; i < 2048; i++){
                if(clients[i].fd  == -1){
                    clients[i].fd = peerfd;
                    clients[i].events = POLLIN;
                    if(i > maxi) //更新maxi
                        maxi = i;
                    break;
                }
            }
            if(i == 2048){
                fprintf(stderr, "too many clients\n");
                close(peerfd);
                continue;
            }
            if(--nready <= 0)
                continue;
        }

        // 2.依次轮询已连接的fd
        int i;
        for(i = 0; i <= maxi; i++){
            if(clients[i].fd == -1) //当前位置没有client 连接
                continue;

            char recvbuf[1024] = {0};
            if(clients[i].revents & POLLIN){
                //处理请求 回显
                int ret = readline(clients[i].fd, recvbuf, 1024);
                if(ret == -1)
                    ERR_EXIT("readline");
                else if(ret == 0){
                    close(clients[i].fd);
                    clients[i].fd = -1;
                    printf("client closed\n");
                    continue;
                }
                printf("recv data: %s", recvbuf);
                writen(clients[i].fd, recvbuf, strlen(recvbuf));
                if(--nready <= 0)
                    break;
            }
        }

    }
}

6.epoll服务器模型

  6.1 使用epoll编写并发服务器的一般步骤:

    a)创建epoll句柄(使用epoll_create函数),把listenfd加入到epollfd中(这里使用epoll_ctl函数);

    b)创建一个数组,用于接收epoll_wait的返回结果;

    c)进入while(1)循环,执行epoll_wait,并检查返回值;

    d)判断数组中的每个fd,如果是listenfd,那么需要accept,如果是普通fd,需要echo服务。

  6.2 程序示例。

void do_epoll(int listenfd){
    //创建epoll句柄
    int epollfd = epoll_create(2048);
    if(epollfd == -1)
        ERR_EXIT("epoll_create");
    //把listenfd加入到epollfd中
    struct epoll_event ev;
    ev.data.fd = listenfd;
    ev.events = EPOLLIN;
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1)
        ERR_EXIT("epoll_ctl");
    //创建数组 用作返回
    struct epoll_event events[2048];
    int nready;

    while(1){
        //执行wait 检查返回值
        nready = epoll_wait(epollfd, events, 2048, -1);
        if(nready == -1){
            if(errno == EINTR)
                continue;
            ERR_EXIT("epoll_wait");
        }
        //遍历events数组
        int i;
        for(i = 0; i < nready; i++){
            if(events[i].data.fd == listenfd){ //listenfd
                int peerfd = accept(listenfd, NULL, NULL);
                if(peerfd == -1)
                    ERR_EXIT("accept");

                //新的fd加入到句柄中
               struct epoll_event ev;
               ev.data.fd = peerfd;
               ev.events = EPOLLIN;
               if(epoll_ctl(epollfd, EPOLL_CTL_ADD, peerfd, &ev) == -1)
                    ERR_EXIT("epoll_ctl");
            }
            else{//普通fd 回显
                int fd = events[i].data.fd;
                char recvbuf[1024] = {0};
                int ret = readline(fd, recvbuf, 1024);
                if(ret == -1)
                    ERR_EXIT("readline");
                else if(ret == 0){ //客户端关闭连接
                    //移除fd
                    printf("client closed\n");
                    struct epoll_event ev;
                    ev.data.fd = fd;
                    if(epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev) == -1)
                        ERR_EXIT("epoll_ctl");
                    //关闭该连接
                    close(fd);//注意这里要先移除在关闭 否则会出错
                    continue;
                }
                printf("recv data: %s", recvbuf);
                writen(fd, recvbuf, strlen(recvbuf));
            }
        }
    }
   //关闭epoll句柄
   close(epollfd);
}

7.总结

  7.1 select、poll、epoll之间的区别:

    a)select 文件描述符的大小受到限制,而且FD_SETSIZE受内核参数的限制,如果需要更改,需要重新编译内核;

    b)poll没有文件描述符大小的限制;

    c)select和poll共同的缺点是:内部的数组不同在内核空间和用空间中相互拷贝。而epoll采用共享内存,避免了这一开销;

    d)select和poll内部都是采用“轮询”机制,随着fd的增多,select和poll的效率随之下降,而epoll只关心已经准备好的fd,不存在这个缺点。

  7.2 write是把数据从用户空间拷贝至内核空间,而read是把数据从内核空间拷贝至用户空间。因此,当用read,write读写文件时,效率很低,有一个sendfile函数直接将数据从在内核之间拷贝,不经过用户空间。这叫做零拷贝技术。

0730------Linux网络编程----------服务器端模型(迭代,多进程,多线程,select,poll,epoll 等),布布扣,bubuko.com

时间: 2024-10-11 08:48:33

0730------Linux网络编程----------服务器端模型(迭代,多进程,多线程,select,poll,epoll 等)的相关文章

Linux内核中网络数据包的接收-第二部分 select/poll/epoll

和前面文章的第一部分一样,这些文字是为了帮别人或者自己理清思路的,而不是所谓的源码分析,想分析源码的,还是直接debug源码最好,看任何文档以及书都是下策.因此这类帮人理清思路的文章尽可能的记成流水的方式,尽可能的简单明了. Linux 2.6+内核的wakeup callback机制 Linux 内核通过睡眠队列来组织所有等待某个事件的task,而wakeup机制则可以异步唤醒整个睡眠队列上的task,每一个睡眠队列上的节点都拥有一个 callback,wakeup逻辑在唤醒睡眠队列时,会遍历

Linux网络编程(3)——多进程、多线程

在我的里面已经介绍了linux下面c的进程.线程接口,这里就不做过多阐述了. 多进程 这里多进程采用传统的多进程模型,每当有客户端发来的连接时创建一个进程来处理连接,一个子进程对应一个连接. 有了上篇单一进程的基础,此处只做简单的修改便可以实现. while(1){ clientfd = Accept(servfd, (struct sockaddr*)&cliaddr, &clientlen); host = Gethostbyaddr((const char*)&cliaddr

linux网络编程学习笔记之三 -----多进程并发服务端

首先是fork()函数.移步APUE 8.3.  比較清晰的解释能够參考http://blog.csdn.net/lingdxuyan/article/details/4993883和http://www.oschina.net/question/195301_62902 补充一点是:fork返回后,原进程中的每一个文件或套接口描写叙述符的引用计数加1(相当于被多打开了一次),每调用一次close,引用计数减1,仅仅有当引用计数减到0时才会真正关闭该套接字. 可运行文件被linux运行的唯一方式

Linux网络编程——tcp并发服务器(多线程)

tcp多线程并发服务器 多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建.据统计,创建线程与创建进程要快 10100 倍,所以又把线程称为"轻量级"进程.线程与进程不同的是:一个进程内的所有线程共享相同的全局内存.全局变量等信息,这种机制又带来了同步问题. tcp多线程并发服务器框架: 我们在使用多线程并发服务器时,直接使用以上框架,我们仅仅修改client_fun()里面的内容. 代码示例: #

嵌入式 Linux网络编程(二)——TCP编程模型

嵌入式 Linux网络编程(二)--TCP编程模型 一.TCP编程模型 TCP编程的一般模型如下图: TCP编程模型分为客户端和服务器端编程,两者编程流程如下: TCP服务器端编程流程: A.创建套接字: B.绑定套接字: C.设置套接字为监听模式,进入被动接受连接状态: D.接受请求,建立连接: E.读写数据: F.终止连接. TCP客户端编程流程: A.创建套接字: B.与远程服务器建立连接: C.读写数据: D.终止连接. 二.TCP迭代服务器编程模型 TCP循环服务器接受一个客户端的连接

嵌入式 Linux网络编程(三)——UDP编程模型

嵌入式 Linux网络编程(三)--UDP编程模型 UDP编程模型: UDP循环服务器模型为: socket(...); bind(...); while(1) {    recvfrom(...);    process(...);    sendto(...); } server.c代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #inc

很全的linux网络编程技巧

注:作者王晓,本人认为总结得很好,故记之,绝无侵权之意. 1. LINUX网络编程基础知识 1 1.1. TCP/IP协议概述 1 1.2. OSI参考模型及TCP/IP参考模型 1 1.3. TCP协议 3 1.4. UDP协议 5 1.5. 协议的选择 6 2. 网络相关概念 6 2.1. socket概念 7 2.2. socket类型 8 2.3. socket信息数据结构 8 2.4. 数据存储优先顺序的转换 8 2.5. 地址格式转化 9 2.6. 名字地址转化 10 3. sock

Linux网络编程入门 (转载)

http://www.cnblogs.com/RascallySnake/archive/2012/01/04/2312564.html (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端        在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序. 比如我们使用ftp程序从另外一        个地方获取文件

Linux网络编程入门

(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍 客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端 在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序. 比如我们使用ftp程序从另外一 个地方获取文件的时候,是我们的ftp程序主动同外面进行通信(获取文件), 所以这个地方我们的ftp程序就是客户端程序. 服务端 和客户端相对应的程序即为服务端程序.被动的等待外面的程序来和自己通