用Sockets实现C语言的事件驱动模型

23

我对使用C语言进行事件驱动编程特别是与Socket相关的方面非常感兴趣,因此我打算花些时间来做一些研究。

假设我想要构建一个具有大量文件和网络I/O的程序,比如客户端/服务器应用程序,基本上,首要问题是这种模型背后的哲学是什么。在普通编程中,我会生成新的进程,但是单个进程如何实际上可以服务于许多其他请求。例如,有一些Web服务器可以在不创建线程或其他进程的情况下处理连接,只需一个主进程即可。

我知道这很复杂,但了解不同解决方案的工作原理总是不错的。


谢谢您的推荐,这本书是否解释或包含任何事件驱动编程参考? - iNDicator
1
不清楚你在问什么,但你可以先阅读libevent的文档。 - Artefacto
你可以使用posix poll函数在一个进程中监听套接字和管道,而无需创建新的。这可能是你正在寻找的。http://pubs.opengroup.org/onlinepubs/009695399/functions/poll.html - Sergey Vakulenko
@Artefacto - 我对设计一个程序感兴趣,它可以处理多个连接而不需要线程或生成进程,只需一个单一的主进程(事件驱动)。我找到的信息有限,这就是为什么我想听听更有经验的人的意见。 - iNDicator
这是一个有趣的问题。我相信nodsjs和nginx都是使用select系统调用实现的。select系统调用可以监视许多并发连接而不会生成新的进程。然后,这些选择事件被分派到nodejs的事件循环中(即您的JavaScript回调函数)。 - Mike76
显示剩余2条评论
5个回答

22
您一定要阅读以下内容:http://www.kegel.com/c10k.html。该页面是事件驱动和异步技术的完美概述。
然而,一个简略而草率的答案是:事件驱动既不是非阻塞的,也不是异步的。
事件驱动意味着进程将监视其文件描述符(和套接字),并仅在某个描述符上发生某些事件时才采取行动(事件包括:接收数据、错误、变为可写等)。
BSD套接字具有“select()”函数。调用此函数时,操作系统将监视描述符,并在某个描述符上发生某些事件时立即返回给进程。
但是,上述网站有更好的描述(以及有关不同API的详细信息)。

谢谢提供链接,看起来这正是我需要的 ;) - iNDicator
这个答案是错误的。“事件驱动”本质上是异步的。没有任何东西监视任何东西。事件本身派生操作。请参阅我的答案以获取更多详细信息。 - Graham
@Graham:事件驱动和异步通常被视为同义词。但是(至少在Linux世界中),它们是不同的东西。这是有争议的。请阅读此问题的答案:https://dev59.com/-2025IYBdhLWcg3wtoRA - Frunsi

5

"这个模型背后的哲学是什么?"

事件驱动意味着没有“监视”,而是事件本身引发动作。

通常,这是通过中断来初始化的,中断是外部设备向系统发送的信号,或者(在软件中断的情况下)是异步进程。

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 中有一些中断驱动的套接字示例,以及其他套接字编程示例。


1

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

异步网络编程可能非常复杂,且难以正确实现。请查看以下几个介绍:这里这里。我强烈建议使用库,例如libeventliboop,以确保正确性。


1

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

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

例如:

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


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 系统的电脑有点远,无法进行测试编译,而且我几乎是从记忆中输入的。

然而,在 Linux/Unix 系统下使用 fork 是标准的方法。

在 Windows 下,情况大不相同,我无法完全记住所需的所有代码(我现在太习惯用 C# 编程了),但设置套接字基本上是相同的,只是需要使用“Winsock”API以获得更好的兼容性。

我认为你仍然可以在 Windows 下使用标准的伯克利套接字,但它充满了陷阱和漏洞,对于 Windows Winsock,这是一个很好的起点:

http://tangentsoft.net/wskfaq/

据我所知,如果您正在使用Winsock,它有一些功能可帮助生成和多客户端。然而,就我个人而言,我通常只是启动一个单独的线程并将套接字连接复制到该线程中,然后回到循环中侦听我的服务器。

1
非常感谢您的示例,但是这里fork不会创建一个新的子进程吗?所以如果我们有30个客户端连接,我们会得到30个新进程吗? - iNDicator
1
确实会创建子进程,但它们不是完整的进程,而是从父进程中分离出来的部分任务,仅在服务的生命周期内存在。另一种方法是使用Linux PThreads,它仍然会生成一个子进程,但不像fork那样在自己的内存池中。绝大多数Linux服务都使用Fork方式进行操作。 - shawty
好的,这就是我想要避免的,看看Frunsi的帖子。这个似乎是我需要的!并不是说你的不有用 ;) - iNDicator
1
没关系,我不会生气的。 :-) - shawty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接