【问题标题】:Event-driven Model in C with Sockets带有套接字的 C 中的事件驱动模型
【发布时间】:2012-06-19 05:34:05
【问题描述】:

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

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

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

【问题讨论】:

  • 如果你打算做任何 linux socket 的东西,我会推荐 Beejs 指南beej.us/guide/bgnet
  • 感谢推荐,这本书是否解释或包含任何类型的事件驱动编程参考?
  • 不清楚你在问什么,但你可以从阅读例如 libevent 的文档开始。
  • @iINDicator - 据我所知没有。如果您已经对 Linux 上的一般套接字感到满意,那么您将不会学到更多。我建议它以防万一您对套接字完全陌生,因为本指南确实帮助我入门。
  • 您可以在一个进程中侦听套接字|管道,而无需使用 posix poll 功能创建新的。可能这就是你要找的。 pubs.opengroup.org/onlinepubs/009695399/functions/poll.html

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


【解决方案1】:

“这个模型背后的哲学是什么”

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

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

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】:

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

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

    例子:

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

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

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

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

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

        【讨论】:

        • 感谢您的链接,看来这就是我要找的东西;)
        • 这个答案是错误。 “事件驱动”本质上是异步的。没有任何东西监视任何东西。事件本身派生出操作。详情请参阅我的回答。
        • @Graham:事件驱动和异步是经常使用的同义词。但是(至少在 linux 世界中)它们是不同的东西。其有争议。阅读这个问题的答案:stackoverflow.com/questions/5844955/…
        【解决方案5】:

        它的工作原理实际上是非常特定于平台的。

        如果您在 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 / Unix 的系统下,使用 fork 是在 C 语言中执行此操作的标准方法。

        在 windows 下是完全不同的故事,我不太记得所有需要的代码(这些天我已经习惯了用 C# 编码)但是设置套接字几乎是一样的,除了你需要使用 'Winsock' API 以获得更好的兼容性。

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

        http://tangentsoft.net/wskfaq/

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

        【讨论】:

        • 非常感谢您的示例,但是这里的 fork 不是创建一个新的子进程吗?所以如果我们连接了 30 个客户端,我们不会得到 30 个新进程吗?
        • 确实它确实创建了子进程,但没有完全成熟的进程,部分任务从父进程中分离出来,仅持续服务的生命周期。另一种方法是使用 Linux PThreads,它仍然会产生一个子进程,而不是像 fork 那样在它自己的内存池中。绝大多数 Linux 服务都使用 Fork 做事方式。
        • 这就是我想要阻止的,请参阅 Frunsi 的帖子。这似乎是我需要的!并不是说你的没用;)
        • 不用担心,也不会冒犯。 :-)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多