【问题标题】:how to assign a context to a socket or an epoll event如何将上下文分配给套接字或 epoll 事件
【发布时间】:2013-09-29 13:03:39
【问题描述】:

我想使用 epoll 编写一个基于事件的服务器。

每个客户端都有一个不同的请求,服务器应该响应它们。 服务器将等待连接,当连接可用时,它们将排队等待读取。 从客户端读取数据,它们将排队等待写入。 处理完数据后,应向每个人发送适当的响应。

所有操作都是异步的。

问题是,当套接字准备好写入时,我如何确定哪个响应是针对哪个套接字的? 一种方法,我可以存储一个 (socket, data) 元组,但这是一种糟糕的编程方式。

我想知道是否可以为每个套接字或每个 epoll 事件分配一个上下文,以便确定哪些数据属于哪个套接字。

有什么想法吗?

对使用 SIGIO 代替 epoll 有什么建议吗? 如果我可以为文件描述符或信号分配上下文(我不熟悉 linux 编程),那么我可以无限期地休眠并等待信号...

现在忘记网络,看看这个例子,我打开一个预先创建的 FIFO,并暂停线程直到我得到一个 SIGIO,在另一种情况下,考虑我打开 10 个 FIFO 并为每个分配一个随机数,当我想把那个数字打印到控制台,不知何故我必须能够检索到这个数字,也许我可以为文件描述符分配一个上下文?

#include <stdlib.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
static void sigioHandler(int sig)
{
}

int main()
{
    int fd, epfd, ret, i, nr_events, flags;
    struct sigaction sa;
    struct epoll_event event, *events;
    char buf[10];
    memset(buf, 0, 10);
    sa.sa_flags = SA_RESTART;
    sa.sa_handler = sigioHandler;
    if (sigaction(SIGIO, &sa, NULL) == -1)
    {
        perror("sigaction");
        exit(1);
    }
    events = malloc (sizeof (struct epoll_event) * 10);
    if (!events) {
          perror ("malloc");
          return 1;
  }

    fd = open("/tmp/foo", O_RDONLY);

    if(fcntl(fd, F_SETOWN, getpid())==-1){
        perror("own");
        exit(1);
    }
    flags = fcntl(fd, F_GETFL);
    if(fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK)==-1){
        perror("set");
        exit(1);
    }
    read(fd, buf, 10);
    epfd = epoll_create(10);
    if(epfd<0)
        perror("epoll_create");

    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;

    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    if(ret)
        perror("epol_ctl");
    while(1){
        pause();
        nr_events = epoll_wait (epfd, events, 10, -1);
        if (nr_events < 0) {
            perror ("epoll_wait");
            free (events);
            return 1;
        }

        for (i = 0; i < nr_events; i++) {
            if(events[i].events & EPOLLIN)
            {
                read(events[i].data.fd, buf, 10);
                if(buf[0] == '#')
                    goto end;
                printf("%s", buf);
            }
        }
    }
end:
    free (events);

    close(epfd);
    close(fd);
    return 0;
}

稍微改了一下:

static void sigioHandler(int status, siginfo_t *ioinfo, void * context)
{
    if(ioinfo == NULL)
        return;

    switch (ioinfo->si_code)
    {
        case POLL_IN:
            printf("signal received for input chars.sig:%d -%d\n",status, ioinfo->si_code);
            break;

        case POLL_OUT:
        default:
            printf("signal received for something else.sig:%d -%d\n",status, ioinfo->si_code);
            break;
    }
}

in main:
...
sa.sa_sigaction = sigioHandler;
...

我遇到了一个奇怪的分段错误。

不知道FreeBSD的“mac_set_fd(int fd, mac_t label);”和这个问题有关。

【问题讨论】:

    标签: linux sockets networking signals epoll


    【解决方案1】:

    您传递给 epoll_ctl() 并由 epoll_wait() 填充的 epoll_event 结构有一个 data_ptr 字段。

    【讨论】:

    • 奇怪的是之前没有看到:D
    【解决方案2】:

    正如 Benito 所指出的,传递给 epoll_ctl 的 epoll_event 结构有一个 data_ptr 字段。为了完全正确,您创建上下文的字段定义为

    typedef union epoll_data 
        void        *ptr;
        int          fd;
        uint32_t     u32;
        uint64_t     u64;
    } epoll_data_t;
    

    即一个工会。在许多示例中,通常 fd 字段用于上下文。但是我发现 ptr 字段在 C++ 中最常用,它可以用来指向一个包含套接字或连接的所有状态的对象。

    如果你有这样的对象:

    class Connection
    {
      private:
        int m_fd;
    
      public:
        Connection(int fd) : m_fd(fd) {}
    
        // Other methods like Send/Receive
    };
    
    
    /////
    // Somewhere in your server accept loop for a server
    struct sockaddr sin;
    socklen_t len = sizeof(sin);
    int fd = accept(s_listen, &sin, &len);
    if (fd == -1) {
        // handle error
    }
    
    // or if it's a client you'd have just created the socket with a call to socket()
    
    // and then you add the new connection to epoll.
    epoll_event ev;
    ev.data.ptr = new Connection(fd);
    ev.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP;    
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
        ; // handle error
    }
    

    稍后当您使用 epoll_wait 获取 epoll 事件时,您可以访问 event.data.ptr 并将其转换回 Connection 类型并调用默认方法来处理事件。然后该类可以调用适当的方法来完成读/写等工作。

    【讨论】:

    • 使用用户数据指针很简单,除了一个难点:如何在关闭套接字/连接时确定何时可以释放对象。当你有多个线程执行epoll_wait() 时,不加锁是相当棘手的。
    • @kralyk - 是的,这可能是个问题。使用参考计数器。在这种情况下,这对我来说效果很好。
    • 我认为这不能通过引用计数器来解决。您可以在epoll_wait() 返回后增加引用计数,但在epoll_wait() 和引用计数增加之间仍然存在竞争窗口。您也不能在epoll_wait() 之前增加引用计数,因为它可能永远不会再次减少。通常必须使用所谓的基于时代的方法或QSRB 方法(它们彼此非常相似)。基本上它是一个小型的专用垃圾收集器:)
    【解决方案3】:

    关于您的第一个问题,我已经尝试过这样的事情并且它奏效了。 对于您与其他对等方的每个连接(假设您使用的是 TCP),您都有一个新的文件描述符。所以每个连接都是唯一的描述符。

    在我的项目中,我使用了 select。您必须使用 FD_SET 添加作为 connect() 结果的描述符。有关详细信息,请参阅 Linux 的 select 手册页。

    【讨论】:

    • 正如您在我提供的源代码中看到的那样,将文件描述符添加到 epoll 上下文没有问题,问题是,我想在内核中保存每个文件描述符的附加信息编程是用对象保存用户定义的上下文的常用方法,我想要这里相同的功能
    猜你喜欢
    • 2013-08-08
    • 2016-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-02
    • 1970-01-01
    相关资源
    最近更新 更多