Linux非阻塞IO(八)使用epoll重新实现非阻塞的回射服务器

本文无太多内容,主要是几个前面提到过的注意点:

一是epoll的fd需要重新装填。我们将tcp_connection_t的指针保存在数组中,所以我们以这个数组为依据,重新装填fd的监听事件。

//重新装填epoll内fd的监听事件
        int i;
        for(i = 0; i < EVENTS_SIZE; ++i)
        {
            if(connsets[i] != NULL)
            {
                int fd = i; //fd
                tcp_connection_t *pt = connsets[i]; //tcp conn
                uint32_t event = 0;
                if(buffer_is_readable(&pt->buffer_))
                    event |= kWriteEvent;
                if(buffer_is_writeable(&pt->buffer_))
                    event |= kReadEvent;
                //重置监听事件
                epoll_mod_fd(epollfd, fd, event);
            }
        }

二是,建立连接时,需要做的工作是:

1.新建tcp_connection_t结构,初始化

2.将fd加入epoll,不监听任何事件

3.将tcp_connection_t的指针加入数组。

代码如下:

//建立连接
int peerfd = accept4(listenfd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
if(peerfd == -1)
      ERR_EXIT("accept4");
//新建tcp连接
tcp_connection_t *pt = (tcp_connection_t*)malloc(sizeof(tcp_connection_t));
buffer_init(&pt->buffer_);
//将该tcp连接放入connsets
connsets[peerfd] = pt;
epoll_add_fd(epollfd, peerfd, 0);

连接关闭时需要:

//close
epoll_del_fd(epollfd, fd);
close(fd);
free(pt);
connsets[fd] = NULL;

 

还有一点:前面我们记录fd和connsets的关系,采用的是数组下标的方式,其实我们还可以将指针存入epoll的data中,其中:

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 */
};

我们对于data这个联合体,不再使用fd,而是使用ptr,指向一个tcp_connection_t的指针。不过我们需要将fd存储在tcp_connection_t数据结构中。

这里为了简便起见,仍采用以前的方法,读者可以自行尝试。

 

完整的代码如下:

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <sys/socket.h>
#include "sysutil.h"
#include "buffer.h"
#include <assert.h>
#include <sys/epoll.h>

#define EVENTS_SIZE 1024

typedef struct{
    buffer_t buffer_;
} tcp_connection_t; //表示一条TCP连接

tcp_connection_t *connsets[EVENTS_SIZE]; //提供从fd到TCP连接的映射

int main(int argc, char const *argv[])
{
    //获取监听fd
    int listenfd = tcp_server("localhost", 9981);
    //将监听fd设置为非阻塞
    activate_nonblock(listenfd);

    //初始化connsets
    int ix;
    for(ix = 0; ix < EVENTS_SIZE; ++ix)
    {
        connsets[ix] = NULL;
    }

    //初始化epoll
    int epollfd = epoll_create1(0);
    epoll_add_fd(epollfd, listenfd, kReadEvent);
    struct epoll_event events[1024];

    while(1)
    {
        //重新装填epoll内fd的监听事件
        int i;
        for(i = 0; i < EVENTS_SIZE; ++i)
        {
            if(connsets[i] != NULL)
            {
                int fd = i; //fd
                tcp_connection_t *pt = connsets[i]; //tcp conn
                uint32_t event = 0;
                if(buffer_is_readable(&pt->buffer_))
                    event |= kWriteEvent;
                if(buffer_is_writeable(&pt->buffer_))
                    event |= kReadEvent;
                //重置监听事件
                epoll_mod_fd(epollfd, fd, event);
            }
        }

        //epoll监听fd
        int nready = epoll_wait(epollfd, events, 1024, 5000);
        if(nready == -1)
            ERR_EXIT("epoll wait");
        else if(nready == 0)
        {
            printf("epoll timeout.\n");
            continue;
        }

        //处理fd
        for(i = 0; i < nready; ++i)
        {
            int fd = events[i].data.fd;
            uint32_t revents = events[i].events;
            if(fd == listenfd) //处理listen fd
            {
                if(revents & kReadREvent)
                {
                    //建立连接
                    int peerfd = accept4(listenfd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
                    if(peerfd == -1)
                        ERR_EXIT("accept4");
                    //新建tcp连接
                    tcp_connection_t *pt = (tcp_connection_t*)malloc(sizeof(tcp_connection_t));
                    buffer_init(&pt->buffer_);
                    //将该tcp连接放入connsets
                    connsets[peerfd] = pt;
                    epoll_add_fd(epollfd, peerfd, 0);
                }
            }
            else //处理普通客户的fd
            {
                //取出指针
                tcp_connection_t *pt = connsets[fd];
                assert(pt != NULL);
                if(revents & kReadREvent)
                {
                    if(buffer_read(&pt->buffer_, fd) == 0)
                    {
                        //close
                        epoll_del_fd(epollfd, fd);
                        close(fd);
                        free(pt);
                        connsets[fd] = NULL;
                        continue; //继续下一次循环
                    }
                }

                if(revents & kWriteREvent)
                {
                    buffer_write(&pt->buffer_, fd);
                }
            }
        }
    }

    close(listenfd);

    return 0;
}

 

下文使用epoll的ET模式。

时间: 2024-10-12 20:01:37

Linux非阻塞IO(八)使用epoll重新实现非阻塞的回射服务器的相关文章

Linux非阻塞IO(五)使用poll实现非阻塞的回射服务器客户端

前面几节我们讨论了非阻塞IO的基本概念.Buffer的设计以及非阻塞connect的实现,现在我们使用它们来完成客户端的编写. 我们在http://www.cnblogs.com/inevermore/p/4049165.html中提出过,客户端需要监听stdin.stdout和sockfd. 这里需要注意的是 只有缓冲区可写的时候,才去监听sockfd和stdin的读事件. 过去在阻塞IO中,我们总是监听sockfd的读事件,因为每当sockfd可读,我们就去调用用户的回调函数处理read事件

Linux下使用epoll函数同时处理TCP请求和UDP请求的回射服务器

#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include

第八篇:并发回射服务器的最基本实现思路

前言 一个服务器,通常会在一段时间内接收到多个请求.如果非要等到处理完一个请求再去处理下一个,势必会造成大部分用户的不满( 尤其当有某个请求需要占用大量时间时 ). 如何解决这个问题?让处理这些用户请求的进程并发起来吧! 实现方法 方法一:当客户请求到达,服务器进程创建一个自身副本( 调用fork函数 ).这是网络服务器的经典用法. 方法二:当客户请求到达,服务器进程创建一个自身副本( 调用fork函数 ),然后用本机内的另一个程序替换自身( 调用execve函数 ). 注:fork和execv

实例浅析epoll的水平触发和边缘触发,以及边缘触发为什么要使用非阻塞IO

一.基本概念                                                          我们通俗一点讲: Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写.如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就

Linux非阻塞IO(四)非阻塞IO中connect的实现

我们为客户端的编写再做一些工作. 这次我们使用非阻塞IO实现connect函数. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 非阻塞IO有以下用处: 1.将三次握手的处理过程生下来,处理其他事情. 2.使用这个同时建立多个连接. 3.实现超时connect功能,本节实现的connect就可以指定时间,超时后算作错误处理.   在阻塞IO中,调用connect后一般会阻塞,直到确定连接成功或者失败

Linux非阻塞IO(三)非阻塞IO中缓冲区Buffer的实现

本文我们来实现回射服务器的Buffer.   Buffer的实现   上节提到了非阻塞IO必须具备Buffer.再次将Buffer的设计描述一下: 这里必须补充一点,writeIndex指向空闲空间的第一个位置. 这里有三个重要的不变式: 1. 0 <= readIndex <= writeIndex <= BUFFER_SIZE 2. writeIndex – readIndex 为可以从buffer读取的字节数 3. BUFFER_SIZE – writeIndex 为buffer还

3.6.1.非阻塞IO

本节讲解什么是非阻塞IO,如何将文件描述符修改为非阻塞式 3.6.1.1.阻塞与非阻塞 (1)阻塞是指函数调用会被阻塞.本质是当前进程调用了函数,进入内核里面去后,因为当前进程的执行条件不满足,内核无法里面完成操作,就挂起这个进程,去执行其他进程.默认使用阻塞IO (2)非阻塞IO当条件不满足时直接停止当前操作,退出程序(?).或者非阻塞IO条件不满足时会一直占用CPU资源 (3)结合程序状态转换图一起理解 3.6.1.2.为什么有阻塞式 (1)常见的阻塞:wait.pause.sleep等函数

深入理解JAVA I/O系列六:Linux中的IO模型

IO模型 linux系统IO分为内核准备数据和将数据从内核拷贝到用户空间两个阶段. 这张图大致描述了数据从外部磁盘向运行中程序的内存中移动的过程. 用户空间.内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟储存空间)为4G(2的32次方).操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限.为了保证用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两个部分,一个部分为内核空间,一部分为用户空间

一起来写web server 06 -- 单线程非阻塞IO版本

阻塞IO的效率是在是低下,你如果要写高性能的web server的话,你必须使用非阻塞IO. 非阻塞IO的读写 在谈到非阻塞IO之前,必须先谈一谈阻塞IO,在网络编程中,我们假设有一个监听套接字的sockfd,这个sockfd是你调用了socket,listen, bind这一大票的函数得到的一个文件描述符.其实它默认就是阻塞的,具体的表现是: 使用accept函数监听sockfd时,如果没有连接到来,这个函数会一直阻塞在那里. 对sockfd调用recv函数的时候,如果对方还没有发送数据过来,