23

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

假设我想构建一个包含大量文件和网络 I/O 的程序,比如客户端/服务器应用程序,基本上,第一个问题是这个模型背后的理念是什么。虽然在正常编程中我会产生新的进程,但一个进程实际上可以服务于许多其他请求。例如,有一些网络服务器可以在不创建线程或其他进程的情况下处理连接,只需一个主进程。

我知道这很复杂,但很高兴知道不同的解决方案是如何工作的。

4

5 回答 5

21

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

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

事件驱动意味着,该进程将监视其文件描述符(和套接字),并且仅在某些描述符上发生某些事件时才采取行动(事件是:接收到的数据,错误,变得可写,...)。

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

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

于 2012-06-16T12:26:36.863 回答
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也有一些中断驱动套接字的示例,以及其他套接字编程示例。

于 2016-08-12T21:04:22.323 回答
1

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

异步网络编程可能非常复杂,而且很难正确处理。在这里这里查看一些介绍。我强烈建议使用libeventliboop 之类的库来解决这个问题。

于 2012-06-16T12:30:10.157 回答
1

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

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

例子:

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

于 2012-06-20T10:13:36.660 回答
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# 编码了)但是设置套接字几乎是一样的,除了你需要使用'Winsock' API 以获得更好的兼容性。

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

http://tangentsoft.net/wskfaq/

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

于 2012-06-16T11:13:08.993 回答