精华内容
下载资源
问答
  • 图控大叔构图传递思想阅读从未如此简单!!!01前言 在进程的开发中,我们常常需要创建子进程来实现进程的“分身”,但是子进程执行完任务...02函数讲解wait()函数说明wait函数需要包含头文件#include#include函数...
    68e343ec73ff6754d0a7e3aa50ffa2ff.png

    图控大叔

    构图传递思想

    阅读从未如此简单!!!

    01

    前言

           在进程的开发中,我们常常需要创建子进程来实现进程的“分身”,但是子进程执行完任务之后,需要父进程对其进行资源回收,否则该子进程将变成僵尸进程。那么父进程对子进程的资源回收所用到的函数则有我们今天的主角:wait和waitpid。

    02

    函数讲解

    df2bbdc6a407b63dd4de89fbfe8155cd.gifwait()函数说明
    wait函数

    需要包含头文件
    #include 
    #include 

    函数原型
    pid_t wait (int * status);

    参数说明:
    1、如果父进程需要知道子进程结束时候的状态值.则可以填充参数
      如:
        int status;
        //创建子进程及相应判断
        printf("wait(&status) is %d\n",wait(&status));
    2、如果父进程不需要知道子进程结束时候的状态值.则可以填充NULL
      如:
        int status;
        //创建子进程及相应判断
        printf("wait(NULL) is %d\n",wait(NULL));

    返回值
      1-1 ---》该子进程不存在
      2、 子进程pid
      
    其他说明
    1、wait函数的调用暂时停止目前进程的执行, 直到有信号来到或子进程结束
    2、如果同时创建了多个子进程,并且这些子进程都有调用退出函数,如:exit
       哪个子进程先介绍,wait就先返回哪个子进程的pid值
      (小编做了好多次测试,发现都是其他一样效果,后来在老师的指导下才纠正了这个思维定式的问题)
    df2bbdc6a407b63dd4de89fbfe8155cd.gifwaitpid()函数说明
    waitpid函数

    需包含头文件
    #include 
    #include 

    函数原型
    pid_t waitpid(pid_t pid, int * status, int options);

    参数说明:
    pid:
       pid<-1 等待进程组识别码为 pid 绝对值的任何子进程.
       pid=-1 等待任何子进程, 相当于 wait().
       pid=0  等待进程组识别码与目前进程相同的任何子进程. (和wait()一样)
       pid>0  等待任何子进程识别码为 pid 的子进程.
       说明:pid 乱填,该进程组内没有此pid的子进程,会返回-1
       
    status:
      用法和wait一样
      
    options:
    一般填0,有以下其他种选择,小编还没有弄懂,以后弄懂了再补充

    其他选择为:
    WNOHANG
      如果没有任何已经结束的子进程则马上返回, 不予以等待.
    WUNTRACED
      如果子进程进入暂停执行情况则马上返回, 但结束状态不予以理会.
      子进程的结束状态返回后存于 status, 底下有几个宏可判别结束情况
    WIFEXITED(status)
      如果子进程正常结束则为非 0 值.
    WEXITSTATUS(status)
      取得子进程 exit()返回的结束代码,
      一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏.
    WIFSIGNALED(status)
      如果子进程是因为信号而结束则此宏值为真
    WTERMSIG(status)
      取得子进程因信号而中止的信号代码, 一般会先用 WIFSIGNALED 来判断后才使用此宏.
    WIFSTOPPED(status)
      如果子进程处于暂停执行情况则此宏值为真. 一般只有使用 WUNTRACED 时才会有此情况.
    WSTOPSIG(status)
      取得引发子进程暂停的信号代码, 一般会先用 WIFSTOPPED 来判断后才使用此宏.

    返回值:
      和wait一样。
      
    其他说明:

    waitpid
      1、如果所填pid所对应子进程不存在,则立即返回-1
      2、如果所填pid存在,但是一直不退出,则一直等待
        该子进程不退出为可能
          1、调用了pause()
          2、vfork函数创建的子进程未调用exit
            因为fork函数创建的子进程调用return或者exit都可以退出
          3、一直处于while循环中
          4、还有其他情况,小编可能不知道
    a792189d8f28db5eca94c2b5c79d27a1.png01

    图片补充

    03

    代码

    df2bbdc6a407b63dd4de89fbfe8155cd.gif参考测试代码
    #include 
    #include 

    int main(int argc,const char **argv){
      pid_t cpid[2];
      int status;
      
      cpid[0] = fork();

      if(cpid[0] == -1)
      {
        perror("进程1创建失败\n");
        return -1;
      }
      else if(cpid[0]==0)
      {

          printf("client1 id %d father id %d\n",getpid(),getppid());
          //sleep(0);
          //pause();
            exit(0);
      }
      
      cpid[1] = fork();
      //printf("开始 2\n");
      if(cpid[1] == -1)
      {
        perror("进程2创建失败\n");
        return -1;
      }
        if(cpid[1]==0)
      {

            printf("client2 id %d father id %d\n",getpid(),getppid());
            //sleep(2);
            //pause();
            //return 0;
                 exit(0);
      }
      else{
          //wait(&status);
        
          //printf("wait is %d\n",wait(&status));//首先结束第一个创建的子程序
          //printf("wait is %d\n",wait(&status));
          //printf("wait(&status) is %d\n",wait(&status));
          printf("wait is %d\n",waitpid(cpid[1],&status,WIFEXITED(status)));
          //printf("wait cpid[1] %d is %d\n",cpid[1],waitpid(cpid[1],&status,0));
      
          printf("father id is %d\n",getpid());
        
      }
      return 0;
    }

    04

    留个问题

           exit函数让调用者所对应进程结束,那么exit函数有负责进程资源的回收吗?答案是没有。exit和wait有什么不一样?当然有,自己总结!

    05

    结尾

           本次关于Linux环境下子进程内存资源的回收所涉及的部分函数:wait和waitpid就分享到这里,如有纰漏,希望不吝赐教!谢谢阅读!

    展开全文
  • Linux网络编程--epoll 模型原理详解以及实例1.简介Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数。Linux 2.6内核中有提高网络I/O性能的新方法,即epoll 。 epoll是什么?按照man...

    Linux网络编程--epoll 模型原理详解以及实例

    59a91920ca7a29bb73059d891bd9b7ed.png

    1.简介

    Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数。Linux 2.6内核中有提高网络I/O性能的新方法,即epoll 。

    epoll是什么?按照man手册的说法是为处理大批量句柄而作了改进的poll。要使用epoll只需要以下的三个系统函数调用: epoll_create(2),epoll_ctl(2),epoll_wait(2)。

    2.select模型的缺陷

    (1) 在Linux内核中,select所用到的FD_SET是有限的

    内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数:#define __FD_SETSIZE 1024。也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的;或者同时检测1025个句柄的可写状态也是不可能的。

    (2) 内核中实现select是使用轮询方法

    每次检测都会遍历所有FD_SET中的句柄,显然select函数的执行时间与FD_SET中句柄的个数有一个比例关系,即select要检测的句柄数越多就会越费时

    3.Windows IOCP模型的缺陷

    windows完成端口实现的AIO,实际上也只是使用内部用线程池实现的,最后的结果是IO有个线程池,你的应用程序也需要一个线程池。很多文档其实已经指出了这引发的线程context-switch所带来的代价。

    4.EPOLL模型的优点

    (1) 支持一个进程打开大数目的socket描述符(FD)

    epoll没有select模型中的限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于select 所支持的2048。下面是我的小PC机上的显示:

    pt@Ubuntu:~$ cat /proc/sys/fs/file-max

    6815744

    那么对于服务器而言,这个数目会更大。

    (2) IO效率不随FD数目增加而线性下降

    传统select/poll的另一个致命弱点就是当你拥有一个很大的socket集合,由于网络得延时,使得任一时间只有部分的socket是”活跃”的,而select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对”活跃”的socket进行操作:这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。于是,只有”活跃”的socket才会主动去调用callback函数,其他idle状态的socket则不会。在这点上,epoll实现了一个”伪”AIO”,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的,比如一个高速LAN环境,epoll也不比select/poll低多少效率,但若过多使用的调用epoll_ctl,效率稍微有些下降。然而一旦使用idle connections模拟WAN环境,那么epoll的效率就远在select/poll之上了。

    (3) 使用mmap加速内核与用户空间的消息传递

    无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就显得很重要。在这点上,epoll是通过内核于用户空间mmap同一块内存实现。

    5.EPOLL模型的工作模式

    (1) LT模式

    LT:level triggered,这是缺省的工作方式,同时支持block和no-block socket,在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

    (2) ET模式

    LT:edge-triggered,这是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核就通过epoll告诉你,然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作而导致那个文件描述符不再是就绪状态(比如你在发送,接收或是接受请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核就不会发送更多的通知(only once)。不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

    6.EPOLL模型的使用方法

    epoll用到的所有函数都是在头文件sys/epoll.h中声明的,下面简要说明所用到的数据结构和函数:

    (1) epoll_data、epoll_data_t、epoll_event

    typedef union epoll_data {

    void *ptr;

    int fd;

    __uint32_t u32;

    __uint64_t u64;

    } epoll_data_t;

    struct epoll_event {

    __uint32_t events; /* Epoll events */

    epoll_data_t data; /* User data variable */

    };

    结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件。epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件,可能的取值为:

    EPOLLIN: 表示对应的文件描述符可以读;

    EPOLLOUT: 表示对应的文件描述符可以写;

    EPOLLPRI: 表示对应的文件描述符有紧急的数据可读;

    EPOLLERR: 表示对应的文件描述符发生错误;

    EPOLLHUP: 表示对应的文件描述符被挂断;

    EPOLLET: 表示对应的文件描述符有事件发生;

    联合体epoll_data用来保存触发事件的某个文件描述符相关的数据。例如一个client连接到服务器,服务器通过调用accept函数可以得到于这个client对应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段,以便后面的读写操作在这个文件描述符上进行。

    (2)epoll_create

    函数声明:intepoll_create(intsize)

    函数说明:该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围。

    (3) epoll_ctl函数

    函数声明:intepoll_ctl(int epfd,int op, int fd, struct epoll_event *event)

    函数说明:该函数用于控制某个文件描述符上的事件,可以注册事件、修改事件、删除事件。

    epfd:由 epoll_create 生成的epoll专用的文件描述符;

    op:要进行的操作,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除;

    fd:关联的文件描述符;

    event:指向epoll_event的指针;

    如果调用成功则返回0,不成功则返回-1。

    (4) epoll_wait函数

    函数声明:int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout)

    函数说明:该函数用于轮询I/O事件的发生。

    epfd:由epoll_create 生成的epoll专用的文件描述符;

    epoll_event:用于回传代处理事件的数组;

    maxevents:每次能处理的事件数;

    timeout:等待I/O事件发生的超时值;

    返回发生事件数。

    7 设计思路及模板

    首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你的epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作都将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

    然后在你的网络主循环里面,调用epoll_wait(int epfd, epoll_event events, int max_events,int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写。基本的语法为:

    nfds = epoll_wait(kdpfd, events, maxevents, -1);

    其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait函数操作成功之后,events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout参数指示 epoll_wait的超时条件,为0时表示马上返回;为-1时表示函数会一直等下去直到有事件返回;为任意正整数时表示等这么长的时间,如果一直没有事件,则会返回。一般情况下如果网络主循环是单线程的话,可以用-1来等待,这样可以保证一些效率,如果是和主循环在同一个线程的话,则可以用0来保证主循环的效率。epoll_wait返回之后,应该进入一个循环,以便遍历所有的事件。

    对epoll 的操作就这么简单,总共不过4个API:epoll_create, epoll_ctl,epoll_wait和close。以下是man中的一个例子。

    struct epoll_event ev, *events;

    for(;;)

    {

    nfds = epoll_wait(kdpfd, events, maxevents, -1); //等待IO事件

    for(n = 0; n < nfds; ++n)

    {

    //如果是主socket的事件,则表示有新连接进入,需要进行新连接的处理。

    if(events[n].data.fd == listener)

    {

    client = accept(listener, (struct sockaddr *) &local, &addrlen);

    if(client < 0)

    {

    perror("accept error");

    continue;

    }

    // 将新连接置于非阻塞模式

    setnonblocking(client);

    ev.events = EPOLLIN | EPOLLET;

    //注意这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听,

    //如果有写操作的话,这个时候epoll是不会返回事件的,

    //如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET。

    // 并且将新连接也加入EPOLL的监听队列

    ev.data.fd = client;

    // 设置好event之后,将这个新的event通过epoll_ctl

    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0)

    {

    //加入到epoll的监听队列里,这里用EPOLL_CTL_ADD

    //来加一个新的 epoll事件。可以通过EPOLL_CTL_DEL来减少

    //一个epoll事件,通过EPOLL_CTL_MOD来改变一个事件的监听方式。

    fprintf(stderr, "epoll set insertion error: fd=%d"0, client);

    return -1;

    }

    }

    else

    // 如果不是主socket的事件的话,则代表这是一个用户的socket的事件,

    // 则用来处理这个用户的socket的事情是,比如说read(fd,xxx)之类,或者一些其他的处理。

    do_use_fd(events[n].data.fd);

    }

    }8 EPOLL模型的简单实例

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #define MAXLINE 10

    #define OPEN_MAX 100

    #define LISTENQ 20

    #define SERV_PORT 5555

    #define INFTIM 1000

    void setnonblocking(int sock)

    {

    int opts;

    opts = fcntl(sock, F_GETFL);

    if(opts < 0)

    {

    perror("fcntl(sock, GETFL)");

    exit(1);

    }

    opts = opts | O_NONBLOCK;

    if(fcntl(sock, F_SETFL, opts) < 0)

    {

    perror("fcntl(sock,SETFL,opts)");

    exit(1);

    }

    }

    int main()

    {

    int i, maxi, listenfd, connfd, sockfd, epfd, nfds;

    ssize_t n;

    char line[MAXLINE];

    socklen_t clilen;

    //声明epoll_event结构体的变量, ev用于注册事件, events数组用于回传要处理的事件

    struct epoll_event ev,events[20];

    //生成用于处理accept的epoll专用的文件描述符, 指定生成描述符的最大范围为256

    epfd = epoll_create(256);

    struct sockaddr_in clientaddr;

    struct sockaddr_in serveraddr;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    setnonblocking(listenfd); //把用于监听的socket设置为非阻塞方式

    ev.data.fd = listenfd; //设置与要处理的事件相关的文件描述符

    ev.events = EPOLLIN | EPOLLET; //设置要处理的事件类型

    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //注册epoll事件

    bzero(&serveraddr, sizeof(serveraddr));

    serveraddr.sin_family = AF_INET;

    char *local_addr = "200.200.200.204";

    inet_aton(local_addr, &(serveraddr.sin_addr));

    serveraddr.sin_port = htons(SERV_PORT); //或者htons(SERV_PORT);

    bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));

    listen(listenfd, LISTENQ);

    maxi = 0;

    for( ; ; )

    {

    nfds = epoll_wait(epfd, events, 20, 500); //等待epoll事件的发生

    for(i = 0; i < nfds; ++i) //处理所发生的所有事件

    {

    if(events[i].data.fd == listenfd) //监听事件

    {

    connfd = accept(listenfd, (sockaddr *)&clientaddr, &clilen);

    if(connfd < 0)

    {

    perror("connfd<0");

    exit(1);

    }

    setnonblocking(connfd); //把客户端的socket设置为非阻塞方式

    char *str = inet_ntoa(clientaddr.sin_addr);

    std::cout << "connect from " << str <<:endl>

    ev.data.fd=connfd; //设置用于读操作的文件描述符

    ev.events=EPOLLIN | EPOLLET; //设置用于注测的读操作事件

    epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

    //注册ev事件

    }

    else if(events[i].events&EPOLLIN) //读事件

    {

    if ( (sockfd = events[i].data.fd) < 0)

    {

    continue;

    }

    if ( (n = read(sockfd, line, MAXLINE)) < 0) // 这里和IOCP不同

    {

    if (errno == ECONNRESET)

    {

    close(sockfd);

    events[i].data.fd = -1;

    }

    else

    {

    std::cout<

    }

    }

    else if (n == 0)

    {

    close(sockfd);

    events[i].data.fd = -1;

    }

    ev.data.fd=sockfd; //设置用于写操作的文件描述符

    ev.events=EPOLLOUT | EPOLLET; //设置用于注测的写操作事件

    //修改sockfd上要处理的事件为EPOLLOUT

    epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

    }

    else if(events[i].events&EPOLLOUT)//写事件

    {

    sockfd = events[i].data.fd;

    write(sockfd, line, n);

    ev.data.fd = sockfd; //设置用于读操作的文件描述符

    ev.events = EPOLLIN | EPOLLET; //设置用于注册的读操作事件

    //修改sockfd上要处理的事件为EPOLIN

    epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

    }

    }

    }

    }9.epoll进阶思考

    9.1. 问题来源

    最近学习EPOLL模型,介绍中说将EPOLL与Windows IOCP模型进行比较,说其的优势在于解决了IOCP模型大量线程上下文切换的开销,于是可以看出,EPOLL模型不需要多线程,即单线程中可以处理EPOLL逻辑。如果引入多线程反而会引起一些问题。但是EPOLL模型的服务器端到底可以不可以用多线程技术,如果可以,改怎么取舍,这成了困扰我的问题。上网查了一下,有这样几种声音:

    (1) “要么事件驱动(如epoll),要么多线程,要么多进程,把这几个综合起来使用,感觉更加麻烦。”;

    (2) “单线程使用epoll,但是不能发挥多核;多线程不用epoll。”;

    (3) “主通信线程使用epoll所有需要监控的FD,有事件交给多线程去处理”;

    (4) “既然用了epoll, 那么线程就不应该看到fd, 而只看到的是一个一个的业务请求/响应; epoll将网络数据组装成业务数据后, 转交给业务线程进行处理。这就是常说的半同步半异步”。

    我比较赞同上述(3)、(4)中的观点

    EPOLLOUT只有在缓冲区已经满了,不可以发送了,过了一会儿缓冲区中有空间了,就会触发EPOLLOUT,而且只触发一次。如果你编写的程序的网络IO不大,一次写入的数据不多的时候,通常都是epoll_wait立刻就会触发 EPOLLOUT;如果你不调用 epoll,直接写 socket,那么情况就取决于这个socket的缓冲区是不是足够了。如果缓冲区足够,那么写就成功。如果缓冲区不足,那么取决你的socket是不是阻塞的,要么阻塞到写完成,要么出错返回。所以EPOLLOUT事件具有较大的随机性,ET模式一般只用于EPOLLIN, 很少用于EPOLLOUT。

    9.2. 具体做法

    (1) 主通信线程使用epoll所有需要监控的FD,负责监控listenfd和connfd,这里只监听EPOLLIN事件,不监听EPOLLOUT事件;

    (2) 一旦从Client收到了数据以后,将其构造成一个消息,放入消息队列中;

    (3) 若干工作线程竞争,从消息队列中取出消息并进行处理,然后把处理结果发送给客户端。发送客户端的操作由工作线程完成。直接进行write。write到EAGAIN或EWOULDBLOCK后,线程循环continue等待缓冲区队列

    发送函数代码如下:

    bool send_data(int connfd, char *pbuffer, unsigned int &len,int flag)

    {

    if ((connfd < 0) || (0 == pbuffer))

    {

    return false;

    }

    int result = 0;

    int remain_size = (int) len;

    int send_size = 0;

    const char *p = pbuffer;

    time_t start_time = time(NULL);

    int time_out = 3;

    do

    {

    if (time(NULL) > start + time_out)

    {

    return false;

    }

    send_size = send(connfd, p, remain_size, flag);

    if (nSentSize < 0)

    {

    if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR))

    {

    continue;

    }

    else

    {

    len -= remain_size;

    return false;

    }

    }

    p += send_size;

    remain_size -= send_size;

    }while(remain_size > 0);

    return true;

    }10 epoll 实现服务器和客户端例子

    最后我们用C++实现一个简单的客户端回射,所用到的代码文件是

    net.h server.cpp client.cpp服务器端:epoll实现的,干两件事分别为:1.等待客户端的链接,2.接收来自客户端的数据并且回射;

    客户端:select实现,干两件事为:1.等待键盘输入,2.发送数据到服务器端并且接收服务器端回射的数据;

    /***********

    net.h

    ***********/

    #include

    #ifndef _NET_H

    #define _NET_H

    #include

    #include

    #include

    #include

    #include

    #include //epoll ways file

    #include

    #include //block and noblock

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    using namespace std;

    #define hand_error(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)

    #endif

    /***********

    server.c

    ***********/

    #include "net.h"

    #define MAX_EVENTS 10000

    int setblock(int sock)

    {

    int ret = fcntl(sock, F_SETFL, 0);

    if (ret < 0 )

    hand_error("setblock");

    return 0;

    }

    int setnoblock(int sock) //设置非阻塞模式

    {

    int ret = fcntl(sock, F_SETFL, O_NONBLOCK );

    if(ret < 0)

    hand_error("setnoblock");

    return 0;

    }

    int main()

    {

    signal(SIGPIPE,SIG_IGN);

    int listenfd;

    listenfd = socket( AF_INET, SOCK_STREAM,0 ); //create a socket stream

    if( listenfd < 0 )

    hand_error( "socket_create");

    setnoblock(listenfd);

    int on = 1;

    if( setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))< 0)

    hand_error("setsockopt");

    struct sockaddr_in my_addr;

    memset(&my_addr, 0, sizeof(my_addr));

    my_addr.sin_family = AF_INET;

    my_addr.sin_port = htons(18000); //here is host sequeue

    my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if( bind( listenfd, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0)

    hand_error("bind");

    int lisId = listen(listenfd, SOMAXCONN);

    if( lisId < 0) //LISTEN

    hand_error("listen");

    struct sockaddr_in peer_addr; //用来 save client addr

    socklen_t peerlen;

    //下面是一些初始化,都是关于epoll的。

    vector clients;

    int count = 0;

    int cli_sock = 0;

    int epfd = 0; //epoll 的文件描述符

    int ret_events; //epoll_wait()的返回值

    struct epoll_event ev_remov, ev, events[MAX_EVENTS]; //events 用来存放从内核读取的的事件

    ev.events = EPOLLET | EPOLLIN; //边缘方式触发

    ev.data.fd = listenfd;

    epfd = epoll_create(MAX_EVENTS); //create epoll,返回值为epoll的文件描述符

    //epfd = epoll_create(EPOLL_CLOEXEC); //新版写法

    if(epfd < 0)

    hand_error("epoll_create");

    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //添加时间

    if(ret < 0)

    hand_error("epoll_ctl");

    while(1)

    {

    ret_events = epoll_wait(epfd, events, MAX_EVENTS, -1); //类似于select函数,这里是等待事件的到来。

    if(ret_events == -1)

    {

    cout<

    hand_error("epoll_wait");

    }

    if( ret_events == 0)

    {

    cout<

    continue;

    }

    // cout<

    for( int num = 0; num < ret_events; num ++)

    {

    cout<

    cout<

    if(events[num].data.fd == listenfd) //client connect

    {

    cout<

    cli_sock = accept(listenfd, (struct sockaddr*)&peer_addr, &peerlen);

    if(cli_sock < 0)

    hand_error("accept");

    cout<

    printf("ip=%s,port = %d", inet_ntoa(peer_addr.sin_addr),peer_addr.sin_port);

    clients.push_back(cli_sock);

    setnoblock(cli_sock); //设置为非阻塞模式

    ev.data.fd = cli_sock;// 将新连接也加入EPOLL的监听队列

    ev.events = EPOLLIN | EPOLLET ;

    if(epoll_ctl(epfd, EPOLL_CTL_ADD, cli_sock, &ev)< 0)

    hand_error("epoll_ctl");

    }

    else if( events[num].events & EPOLLIN)

    {

    cli_sock = events[num].data.fd;

    if(cli_sock < 0)

    hand_error("cli_sock");

    char recvbuf[1024];

    memset(recvbuf, 0 , sizeof(recvbuf));

    int num = read( cli_sock, recvbuf, sizeof(recvbuf));

    if(num == -1)

    hand_error("read have some problem:");

    if( num == 0 ) //stand of client have exit

    {

    cout<

    close(cli_sock);

    ev_remov = events[num];

    epoll_ctl(epfd, EPOLL_CTL_DEL, cli_sock, &ev_remov);

    clients.erase(remove(clients.begin(), clients.end(), cli_sock),clients.end());

    }

    fputs(recvbuf,stdout);

    write(cli_sock, recvbuf, strlen(recvbuf));

    }

    }

    }

    return 0;

    }

    /***********

    client.c

    ***********/

    #include "net.h"

    int main()

    {

    signal(SIGPIPE,SIG_IGN);

    int sock;

    sock = socket( AF_INET, SOCK_STREAM,0 ); //create a socket stream

    if( sock< 0 )

    hand_error( "socket_create");

    struct sockaddr_in my_addr;

    //memset my_addr;

    memset(&my_addr, 0, sizeof(my_addr));

    my_addr.sin_family = AF_INET;

    my_addr.sin_port = htons(18000); //here is host sequeue

    // my_addr.sin_addr.s_addr = htonl( INADDR_ANY );

    my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int conn = connect(sock, (struct sockaddr *)&my_addr, sizeof(my_addr)) ;

    if(conn != 0)

    hand_error("connect");

    char recvbuf[1024] = {0};

    char sendbuf[1024] = {0};

    fd_set rset;

    FD_ZERO(&rset);

    int nready = 0;

    int maxfd;

    int stdinof = fileno(stdin);

    if( stdinof > sock)

    maxfd = stdinof;

    else

    maxfd = sock;

    while(1)

    {

    //select返回后把原来待检测的但是仍没就绪的描述字清0了。所以每次调用select前都要重新设置一下待检测的描述字

    FD_SET(sock, &rset);

    FD_SET(stdinof, &rset);

    nready = select(maxfd+1, &rset, NULL, NULL, NULL);

    cout<

    if(nready == -1 )

    break;

    else if( nready == 0)

    continue;

    else

    {

    if( FD_ISSET(sock, &rset) ) //检测sock是否已经在集合rset里面。

    {

    int ret = read( sock, recvbuf, sizeof(recvbuf)); //读数据

    if( ret == -1)

    hand_error("read");

    else if( ret == 0)

    {

    cout<

    close(sock);

    break;

    }

    else

    {

    fputs(recvbuf,stdout); //输出数据

    memset(recvbuf, 0, strlen(recvbuf));

    }

    }

    if( FD_ISSET(stdinof, &rset)) //检测stdin的文件描述符是否在集合里面

    {

    if(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)

    {

    int num = write(sock, sendbuf, strlen(sendbuf)); //写数据

    cout<

    memset(sendbuf, 0, sizeof(sendbuf));

    }

    }

    }

    }

    return 0;

    }

    展开全文
  • } } } 对应的头文件: // epoll.h by ailx10 #include typedef int (*ep_callback_func)(int event,int fd); typedef struct tagEpEntry { int fd; ep_callback_func fCallback; }ep_entry_s; void ep_init(); void...
    • ailx10:这里我想让程序定时2秒,打印一个tick,我要怎么做?
    • 大黑客:这不很简单吗?
    • ailx10:sleep就是个弟弟,能不能不用sleep做定时器?
    • 大黑客:不用sleep怎么定时啊?
    • ailx10:Epoll + timerfd
    • 大黑客:要不要这么专业啊?
    • ailx10: 嘿嘿,我可是做hacker的男人,能不专业吗?
    • 大黑客:那就开始你的表演吧 ...

    2ea6e7e2405ca831b919b4a1f3544213.png

    这里有一个坑,我在写timer定时器的时候,发现read函数返回-1,错误码errno=22,说是参数错误,你绝对想不到第2个参数必须是uint64_t类型。

    read的函数原型:(大雾!定时器这里需要小心)

    #include <unistd.h>
    ssize_t read(int filedes, void *buf, size_t nbyetes);

    主函数:

    // main.c by ailx10
    #include <stdio.h>
    #include "epoll.h"
    #include "timer.h"
    
    int main()
    {
      ep_init();
      timer_cre();
      ep_wait();
      return 0;
    }

    Epoll事件驱动函数:

    // epoll.c by ailx10
    #include <stdio.h>
    #include <sys/epoll.h>
    #include "epoll.h"
    
    static int g_epfd = 0;
    
    void ep_init()
    {
       int epfd = epoll_create(1);
       g_epfd = epfd;
       return ;
    }
    
    void ep_add(ep_entry_s* p)
    {
      struct epoll_event stEpEvent;
      stEpEvent.events = EPOLLIN | EPOLLHUP | EPOLLERR;
      stEpEvent.data.ptr = (void*)p;
      epoll_ctl(g_epfd,EPOLL_CTL_ADD,p->fd,&stEpEvent);
      return;
    }
    
    void ep_del(ep_entry_s* p)
    {
      struct epoll_event stEpEvent;
      stEpEvent.events = EPOLLIN | EPOLLHUP | EPOLLERR;
      stEpEvent.data.ptr = (void*)p;
      epoll_ctl(g_epfd,EPOLL_CTL_DEL,p->fd,&stEpEvent);
      return;
    }
    
    void ep_exit()
    {
      g_epfd = -1;
      return;
    }
    
    void ep_wait()
    {
      int fd;
      int num;
      ep_entry_s* pstp;
      ep_callback_func fcallback;
      struct epoll_event astEvent[1];
      while(1)
      {
        fd = epoll_wait(g_epfd,astEvent,1,-1);
        for(num = 0; num < fd;++num)
        {
          pstp = (ep_entry_s*)astEvent[num].data.ptr;
          fcallback = pstp->fCallback;
          fcallback(astEvent[num].events,pstp->fd);
        }
      }
    }

    对应的头文件:

    // epoll.h by ailx10
    #include <stdio.h>
    
    typedef int (*ep_callback_func)(int event,int fd);
    
    typedef struct tagEpEntry
    {
      int fd;
      ep_callback_func fCallback;
    }ep_entry_s;
    
    void ep_init();
    void ep_add(ep_entry_s* p);
    void ep_del(ep_entry_s* p);
    void ep_exit();
    void ep_wait();

    定时器函数:

    // timer.c by ailx10
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/epoll.h>
    #include <sys/timerfd.h>
    #include "epoll.h"
    #include "timer.h"
    
    static ep_entry_s g_ep_entry = {-1,NULL};
    
    static int _callback(int event,int fd)
    {
      uint64_t count = 0;
      if(0 != (EPOLLIN & event))
      {
        int n =read(fd,(void*)&count,sizeof(count));
        //printf("%dn",n);
        if (n > 0)
        {
          printf("tick.n");
        }
      }
      return 0;
    }
    
    void timer_cre()
    {
      struct itimerspec timer;
      int timerfd = timerfd_create(CLOCK_MONOTONIC,0);
      printf("timefd=%dn",timerfd);
      memset(&timer,0,sizeof(timer));
      timer.it_interval.tv_sec = 2;
      timer.it_value.tv_sec = 2;
      timerfd_settime(timerfd,0,&timer,NULL);
      g_ep_entry.fd = timerfd;
      g_ep_entry.fCallback = (ep_callback_func)_callback;
      ep_add(&g_ep_entry);
      return;
    }
    
    void _timer_del()
    {
      ep_del(&g_ep_entry);
      close(g_ep_entry.fd);
      g_ep_entry.fCallback = NULL;
      return;
    }

    对应的头文件:

    // timer.h by ailx10
    #include <stdio.h>
    
    void timer_cre();

    谢谢阅读 ~

    展开全文
  • 嵌入式开发直播课 - Linux中的poll机制 - 创客学院直播室​...进程退出Linux 下进程的退出分为正常退出和异常退出两种:1.正常退出a. 在main()函数中执行return 。b.调用exit()函数c.调用_exit()函数...
    嵌入式开发直播课 - Linux中的poll机制 - 创客学院直播室www.makeru.com.cn
    988b47b1fabf287e2752bb8e9b060c40.png

    当一个进程结束了运行或在半途中终止了运行,那么内核就需要释放该进程所占用的系统资源。这包括进程运行时打开的文件,申请的内存等。

    de8399dfa4ce49c2e6144df06eaab3c1.png

    进程退出

    Linux 下进程的退出分为正常退出和异常退出两种:

    1.正常退出

    a. 在main()函数中执行return 。

    b.调用exit()函数

    c.调用_exit()函数

    2.异常退出

    a.调用about函数

    b.进程收到某个信号,而该信号使程序终止。

    不管是哪种退出方式,系统最终都会执行内核中的同一代码。这段代码用来关闭进程所用已打开的文件描述符,释放它所占用的内存和其他资源。

    几种退出方式的比较

    1.exit和return 的区别:

    exit是一个函数,有参数。exit执行完后把控制权交给系统

    return是函数执行完后的返回。renturn执行完后把控制权交给调用函数。

    2.exit和abort的区别:

    exit是正常终止进程

    about是异常终止。

    exit()和_exit()函数

    exit和_exit函数都是用来终止进程的。当程序执行到exit或_exit时,系统无条件的停止剩下所有操作,清除各种数据结构,并终止本进程的运行。

    exit在头文件stdlib.h中声明,而_exit()声明在头文件unistd.h中声明。 exit中的参数exit_code为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生。

    exit()和_exit()的区别

    _exit()执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。

    调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr ...). exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。

    exit()函数与_exit()函数最大区别就在于exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。由于Linux的标准函数库中,有一种被称作“缓冲I/O”的操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续的读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取;同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也给编程代来了一点儿麻烦。比如有一些数据,认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区的数据就会丢失。因此,要想保证数据的完整性,就一定要使用exit()函数。

    通过一个函数实例来看看它们之间的区别:

    函数实例1 : exit.c

    #include#includeint main(){printf("using exit----n");printf("This is the content in buffern");exit(0);}

    执行结果为:

    using exit----This is the content in buffer

    函数实例2:_exit.c

    #include#includeint main(){printf("using _exit--n");printf("This is the content in buffer");_exit(0);}

    执行结果为 :

    using _exit--

    printf函数就是使用缓冲I/O的方式,该函数在遇到“n”换行符时自动的从缓冲区中将记录读出。所以exit()将缓冲区的数据写完后才退出,而_exit()函数直接退出。

    大家也可以把函数实例2中的printf("This is the content in buffer");改为printf("This is the content in buffern")(即在printf中最后加一个n看运行结果是什么,为什么会产生这样的结果呢?)

    父子进程终止的先后顺序不同会产生不同的结果

    1.父进程先于子进程终止:

    此种情况就是我们前面所用的孤儿进程。当父进程先退出时,系统会让init进程接管子进程 。

    2.子进程先于父进程终止,而父进程又没有调用wait函数

    此种情况子进程进入僵死状态,并且会一直保持下去直到系统重启。子进程处于僵死状态时,内核只保存进程的一些必要信息以备父进程所需。此时子进程始终占有着资源,同时也减少了系统可以创建的最大进程数。

    什么是 僵死状态呢?

    一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占有的资源)的进程被称为僵死进程(zombie)。

    3.子进程先于父进程终止,而父进程调用了wait函数

    此时父进程会等待子进程结束。

    展开全文
  • 大家知道,互斥锁可以用于线程间同步,但是,每次只能有一个线程抢到互斥锁,这样限制了程序的并发行。如果我们希望允许多个线程...于是,Linux系统提出了信号量的概念。这是一种相对比较折中的处理方式,它既能保证...
  • 在上一篇《手把手教Linux驱动8-Linux IO模型》我们已经了解了阻塞、非阻塞、同步和异步等相关概念,本文主要讲解如何通过等待队列实现对进程的阻塞。应用场景: 当进程要获取某些资源(例如从网卡读取数据)的时候,但...
  • 信号量概念信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也...信号量分类因为各种原因,Linux下有多种信号量实现机制,可以分别应用于不同的场合,分类如下:[信号量分...
  • linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序。在linux新的内核中,有了一种替换它的机制,就是epoll。select()和poll() IO多路复用模型select的缺点...
  • 前言 在上一节中以拆分的方式学习完 Linux 、C++、网络等知识后,这节会将这三个模块糅合起来,站在项目的基础上再次去学习这三个模块。Linux 网路编程比较经典的有 Redis、Muduo、TeamTalk等开源项目。本文将以 ...
  • Tasklet机制是一种较为特殊的软...参考Linux内核中的下半部机制之软中断(softirq),理解了软中断的实现机制后,再理解tasklet就简单了,因为tasklet也是一种软中断,考虑到优先级问题,分别占用了向量表(softirq_vec...
  • 并且,在linux/posix_types.h头文件有这样的声明: #define __FD_SETSIZE 1024 表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。 epoll_create int epoll...
  • 等待队列由等待队列头来管理,其定义在头文件中,是一个wait_queue_head_t结构体。可以通过以下方式进行定义并初始化一个等待队列头: // 静态定义并初始化DECLARE_WAIT_QUEUE_HEAD(name);// 动态定义并初始化wait_...
  • 驱动的poll函数在之前的文章《Linux设备驱动程序》(四)——字符设备驱动(上) 中有说过,是poll、epoll、select这三个系统调用的后端实现,用于实现IO的多路复用。 驱动中的poll操作 驱动中的poll函数原型如下: __...
  • linux 中进程的状态第10节曾提到,进程一共只有 5 种状态,也必定是这 5 种状态之一:TASK_RUNNING,表示进程是可执行的,或者正在运行,或者正在运行队列里排队等待运行。TASK_INTERRUPTIBLE,表示进程正在睡眠,...
  • 在前两章中我们了解了创建进程,这一章我们来了解下,在linux下怎么结束进程。在linux中,有3种正常结束进程的方法和2种异常终止的方法:1、 正常结束:a、 在main函数中调用return。这个相当于调用exit。b、 调用...
  • 当一个进程结束时,会产生一个终止状态字,然后内核发出一个SIGCHLD信号通知父...下面我们来了解下wait和waitpid这两个函数:要使用这两个函数需要引入sys/wait.h头文件和sys/types.h头文件我们来看下wait函数的格式...
  • Linux wait和waitpid和kill

    2019-09-16 07:41:30
    1. Linux wait 1) 功能:等待子进程中断或结束 2) 头文件 #include<sys/types.h> #include<sys/wait.h> 3) 函数定义: pid_t wait (int * status); 4) 函数说...
  • #include <sys/types.h> #include <unistd.h> #include <sys/wait.h>
  • 在标准的Unix中wait头文件定义为: #include <sys/wait.h> pid_t wait(int *statloc); 在Linux中,定义为: /*come from /usr/include/sys/wait.h  Wait for a child to die....
  • Linuxwait函数

    2020-10-22 20:56:10
    头文件 #include<sys/wait.h> 函数原型 pid_t wait(int *status) //status就是用来间接记录子进程的退出值的。 返回值: 执行成功:退出的子进程的pid 失败:-1 处理子进程退出状态值的宏 WIFEXITED(status...
  • 1、头文件  #includesys/types.h> #include 2、函数原型  pid_t waitpid(pid_t pid,int * status,int options); 3、函数参数 waitpid函数有三个参数:pid和指向返回状态所在单元的指针和一个...
  • 头文件 #include<sys/types.h> #include<sys/wait.h> 函数原型 pid_t wait (int * status); 参数 status 是一个整形指针。如果status不是一个空指针,则终止进程的终止状态将存储在该指针所指向的内存...
  • 这里了解一下函数的使用,头文件包含以及函数原型,以及返回值等等。 wait函数 #include<sys/types.h> #include<sys/wait.h> pid_t wait(int *status); //该函数的参数为输出型参数,该参数可以获得被...
  • Linux系统下编译包含pthread.h头文件的代码时遇到undefined reference to semwait等错误的解决办法 在Linux环境下编译包含头文件pthread.h的代码时可能发生以下等报错 undefined reference to sem_wait undefined ...
  • Linux进程间同步之wait

    2021-02-06 19:23:54
    一、关于系统级函数wait ...头文件:sys/wait.h 函数签名:pid_t wait(int *status) 说明: 形参:如果status是一个指向整型数的指针,当wait 返回时,该指针就指向子进程退出时的状态信息...
  • 包含头文件及原型 #include <sys/types.h> /* 提供类型pid_t的定义 */ #include <sys/wait.h> pid_t wait(int *status) 进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子...
  • Linux多进程--wait/waitpid

    2018-11-28 00:24:12
    头文件:#include &amp;amp;amp;lt;sys/types.h&amp;amp;amp;gt; #include &amp;amp;amp;lt;sys/wait.h&amp;amp;amp;gt; 输入参数: status: int型指针,用于获取子进程状态发生改变时,返回的...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 142
精华内容 56
关键字:

linuxwait头文件

linux 订阅