我对 C 中的事件驱动编程非常感兴趣,尤其是套接字,所以我将花一些时间进行研究。
假设我想构建一个包含大量文件和网络 I/O 的程序,比如客户端/服务器应用程序,基本上,第一个问题是这个模型背后的理念是什么。虽然在正常编程中我会产生新的进程,但一个进程实际上可以服务于许多其他请求。例如,有一些网络服务器可以在不创建线程或其他进程的情况下处理连接,只需一个主进程。
我知道这很复杂,但很高兴知道不同的解决方案是如何工作的。
我对 C 中的事件驱动编程非常感兴趣,尤其是套接字,所以我将花一些时间进行研究。
假设我想构建一个包含大量文件和网络 I/O 的程序,比如客户端/服务器应用程序,基本上,第一个问题是这个模型背后的理念是什么。虽然在正常编程中我会产生新的进程,但一个进程实际上可以服务于许多其他请求。例如,有一些网络服务器可以在不创建线程或其他进程的情况下处理连接,只需一个主进程。
我知道这很复杂,但很高兴知道不同的解决方案是如何工作的。
您绝对必须阅读以下内容:http: //www.kegel.com/c10k.html。该页面是事件驱动和异步技术的完美概述。
然而,一个快速而肮脏的答案:事件驱动既不是非阻塞的,也不是异步的。
事件驱动意味着,该进程将监视其文件描述符(和套接字),并且仅在某些描述符上发生某些事件时才采取行动(事件是:接收到的数据,错误,变得可写,...)。
BSD 套接字具有“select()”函数。调用时,操作系统将监视描述符,并在其中一个描述符上发生某些事件时立即返回进程。
但是,上面的网站有更好的描述(以及有关不同 API 的详细信息)。
“这个模型背后的哲学是什么”
事件驱动意味着没有“监控”,而是事件本身启动动作。
通常这是由中断启动的,中断是来自外部设备的系统信号,或者(在软件中断的情况下)异步过程。
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也有一些中断驱动套接字的示例,以及其他套接字编程示例。
这种 TCP 服务器/客户端可以通过使用select(2)
调用和非阻塞套接字来实现。
使用非阻塞套接字比使用阻塞套接字更棘手。
例子:
connect
EINPROGRESS
调用通常立即返回 -1 并在使用非阻塞套接字时设置 errno 。在这种情况下,您应该select
在连接打开或失败时使用等待。connect
也可能返回 0。如果您创建到本地主机的连接,就会发生这种情况。这样,您可以在一个套接字打开 TCP 连接时为其他套接字提供服务。
它实际上是非常特定于平台的,它是如何工作的。
如果您在 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 它有一些东西可以帮助生成和多客户端,但是就我个人而言,我通常只是分离一个单独的线程并将套接字连接复制到该线程,然后返回循环监听我的服务器。