C中的事件驱动模型

时间:2012-06-16 10:32:38

标签: c linux unix operating-system event-driven

我对C语言中的事件驱动编程非常感兴趣,特别是对于套接字,所以我将花一些时间进行研究。

让我们假设我想构建一个包含大量文件和网络I / O的程序,如客户端/服务器应用程序,基本上第一个问题是这个模型背后的哲学是什么。在正常编程中,我会生成新进程,为什么单个进程实际上可以为许多其他请求提供服务。例如,有些Web服务器可以处理连接而无需创建线程或其他进程,只需要一个主要进程。

我知道这很复杂,但至少知道这些编程的基础设施是多么好。

5 个答案:

答案 0 :(得分:19)

您一定要阅读以下内容:http://www.kegel.com/c10k.html。该页面是事件驱动和异步技术的完美概述。

然而,快速&肮脏的回答:事件驱动既不是非阻塞也不是异步。

事件驱动意味着进程将监视其文件描述符(和套接字),并仅在某些描述符上发生某些事件时才会起作用(事件是:接收数据,错误,变得可写,......)。 p>

BSD套接字具有“select()”功能。调用时,操作系统将监视描述符,并在其中一个描述符上发生某些事件后立即返回该进程。

但是,上面的网站有更好的描述(以及有关不同API的详细信息)。

答案 1 :(得分:3)

“这种模式背后的哲学是什么”

事件驱动意味着没有“监控”,但事件本身会启动操作。

通常这是由中断启动的,中断是从外部设备向系统发出的信号,或者(在软件中断的情况下)异步过程。

https://en.wikipedia.org/wiki/Interrupt

进一步阅读似乎在这里:

https://docs.oracle.com/cd/E19455-01/806-1017/6jab5di2m/index.html#sockets-40   - “中断驱动的套接字I / O”

http://cs.baylor.edu/~donahoo/practical/CSockets/textcode.html还有一些中断驱动套接字的例子,以及其他套接字编程示例。

答案 2 :(得分:1)

事件驱动编程基于事件循环。循环只是等待一个新事件,调度代码来处理事件,然后循环返回以等待下一个事件。在套接字的情况下,您谈论的是“异步网络编程”。这涉及select()或其他选项,如Kqueue(),以等待事件循环中的事件。套接字需要设置为非阻塞,这样当你读取()或write()时,你的代码就不会等待I / O完成。

异步网络编程可能非常复杂,并且难以实现。查看几个介绍herehere。我强烈建议您使用libeventliboop这样的库来实现这一目标。

答案 3 :(得分:1)

可以使用select(2)调用和非阻塞套接字实现这种TCP服务器/客户端。

使用非阻塞套接字比阻塞套接字更棘手。

示例:

connect调用通常立即返回-1,并在使用非阻塞套接字时设置errno EINPROGRESS。在这种情况下,您应该使用select在连接打开或失败时等待。 connect也可能返回0.如果您创建与本地主机的连接,则会发生这种情况。 这样,您可以为其他套接字提供服务,而一个套接字正在打开TCP连接。

答案 4 :(得分:0)

它实际上是非常具体的平台,如何运作。

如果你在Linux系统上运行它并不困难,你只需要使用' fork'生成一个过程的副本。像下面这样的东西可以解决问题:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet.h>
#include <signal.h>
#include <unistd.h>

int main()
{
  int server_sockfd, client_sockfd;
  int server_len, client_len;
  struct sockaddr_in server_address;
  struct sockaddr_in client_address;

  server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

  server_address.sin_family = AF_INET;
  server_address.sin_addr.s_addr = htonl(INADDR_ANY);
  server_Address.sin_port = htons(1234);
  server_len = sizeof(server_address);
  bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

  listen(server_sockfd, 5);

  signal(SIGCHLD, SIG_IGN);

  while(1)
  {
    char ch;
    printf("Server Waiting\n");
    client_len = sizeof(client_address);
    client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len)

    // Here's where we do the forking, if you've forked already then this will be the child running, if not then your still the parent task.

    if(fork() == 0)
    {
      // Do what ever the child needs to do with the connected client
      read(client_sockfd, &ch, 1);
      sleep(5); // just for show :-)
      ch++;
      write(client_sockfd, &ch, 1);
      close(client_sockfd);
      exit(0);
    }
    else
    {
      // Parent code here, close and loop for next connection
      close(client_sockfd);
    }
  }
}

你可能不得不摆弄一下这个代码,我现在不在Linux机器附近进行测试编译,而且我几乎从内存中输入了它。

然而,使用fork是在基于Linux / Unix的系统下在C中执行此操作的标准方法。

在Windows下它是一个非常不同的故事,而且我可以完全记住所需的所有代码(我现在习惯用C#进行编码)但设置了套接字几乎是一样的,除非你需要使用&#39; Winsock&#39; API可提供更好的兼容性。

你可以(我相信无论如何)仍在窗户下使用标准的berkley插座,但它充满了陷阱和漏洞,对于windows winsock来说,这是一个很好的起点:

http://tangentsoft.net/wskfaq/

据我所知,如果你使用Winsock它有东西帮助产生和多客户端,我个人而言,我通常只是分离一个单独的线程并将套接字连接复制到那个,然后回到听我服务器的循环。