我对C语言中的事件驱动编程非常感兴趣,特别是对于套接字,所以我将花一些时间进行研究。
让我们假设我想构建一个包含大量文件和网络I / O的程序,如客户端/服务器应用程序,基本上第一个问题是这个模型背后的哲学是什么。在正常编程中,我会生成新进程,为什么单个进程实际上可以为许多其他请求提供服务。例如,有些Web服务器可以处理连接而无需创建线程或其他进程,只需要一个主要进程。
我知道这很复杂,但至少知道这些编程的基础设施是多么好。
答案 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完成。
异步网络编程可能非常复杂,并且难以实现。查看几个介绍here和here。我强烈建议您使用libevent或liboop这样的库来实现这一目标。
答案 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它有东西帮助产生和多客户端,我个人而言,我通常只是分离一个单独的线程并将套接字连接复制到那个,然后回到听我服务器的循环。