我对使用C语言进行事件驱动编程特别是与Socket相关的方面非常感兴趣,因此我打算花些时间来做一些研究。
假设我想要构建一个具有大量文件和网络I/O的程序,比如客户端/服务器应用程序,基本上,首要问题是这种模型背后的哲学是什么。在普通编程中,我会生成新的进程,但是单个进程如何实际上可以服务于许多其他请求。例如,有一些Web服务器可以在不创建线程或其他进程的情况下处理连接,只需一个主进程即可。
我知道这很复杂,但了解不同解决方案的工作原理总是不错的。
我对使用C语言进行事件驱动编程特别是与Socket相关的方面非常感兴趣,因此我打算花些时间来做一些研究。
假设我想要构建一个具有大量文件和网络I/O的程序,比如客户端/服务器应用程序,基本上,首要问题是这种模型背后的哲学是什么。在普通编程中,我会生成新的进程,但是单个进程如何实际上可以服务于许多其他请求。例如,有一些Web服务器可以在不创建线程或其他进程的情况下处理连接,只需一个主进程即可。
我知道这很复杂,但了解不同解决方案的工作原理总是不错的。
"这个模型背后的哲学是什么?"
事件驱动意味着没有“监视”,而是事件本身引发动作。
通常,这是通过中断来初始化的,中断是外部设备向系统发送的信号,或者(在软件中断的情况下)是异步进程。
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 中有一些中断驱动的套接字示例,以及其他套接字编程示例。
可以使用select(2)
调用和非阻塞套接字来实现那种TCP服务器/客户端。
使用非阻塞套接字比使用阻塞套接字更加棘手。
例如:
connect
调用通常会立即返回-1,并在使用非阻塞套接字时设置errno EINPROGRESS
。在这种情况下,您应该使用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 系统的电脑有点远,无法进行测试编译,而且我几乎是从记忆中输入的。
然而,在 Linux/Unix 系统下使用 fork 是标准的方法。
在 Windows 下,情况大不相同,我无法完全记住所需的所有代码(我现在太习惯用 C# 编程了),但设置套接字基本上是相同的,只是需要使用“Winsock”API以获得更好的兼容性。
我认为你仍然可以在 Windows 下使用标准的伯克利套接字,但它充满了陷阱和漏洞,对于 Windows Winsock,这是一个很好的起点:
据我所知,如果您正在使用Winsock,它有一些功能可帮助生成和多客户端。然而,就我个人而言,我通常只是启动一个单独的线程并将套接字连接复制到该线程中,然后回到循环中侦听我的服务器。