epoll的起源:
epoll 是Linux内核中的一种I/O事件通知机制。它是 select 和 poll 这两个系统调用的增强版本,专为处理大量文件描述符而设计,特别是在需要高并发处理的应用程序中,如网络服务器和分布式系统。他的起源可以追溯到21世纪初,当时Linux系统在处理大量并发连接时遇到了性能瓶颈。select 和 poll 方法的性能随着文件描述符数量的增加而线性下降,因为它们需要检查每个文件描述符的状态。为了解决这个问题,Linux内核引入了 epoll。
那么问题来了:
1.select是什么,poll又是什么?
2.用上面的为什么会产生瓶颈,而epoll有比他们强在了哪里?
要了解这些,我们要从最基本的开始将其
一、从网卡接收数据说起
通过硬件传输,网卡接收的数据存放到内存中
二、如何知道接收了数据?
当网卡把数据写入到内存后,网卡向cpu发出一个中断信号,操作系统便能得知有新数据到来,再通过网卡中断程序去处理数据.
三,进程阻塞为什么不占用cpu资源?
阻塞是进程调度的关键一环,指的是进程在等待某事件(如接收到网络数据)发生之前的等待状态,recv、select和epoll都是阻塞方法。
看1.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
// 创建socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);//创建了一个基于ipv4地址族和tcp协议的socket套字节
if (server_socket == -1) {
perror("Error creating socket");
exit(EXIT_FAILURE);
}
// 绑定
struct sockaddr_in server_addr;//存储服务器地址信息
server_addr.sin_family = AF_INET;//地址族
server_addr.sin_addr.s_addr = INADDR_ANY;//可以监视所有的网络接口(ip地址)
server_addr.sin_port = htons(8080);//端口号
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Error binding");
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_socket, 5) == -1) {
perror("Error listening");
exit(EXIT_FAILURE);
}
// 接受客户端连接
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_socket == -1) {
perror("Error accepting connection");
exit(EXIT_FAILURE);
}
// 接收客户端数据
char buffer[1024];
int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received == -1) {
perror("Error receiving data");
exit(EXIT_FAILURE);
}
// 将数据打印出来%s\n", buffer);
printf("Received data from client: ");
close(client_socket);
close(server_socket);
return 0;
}
绑定这两个的目的是什么?
绑定端口的目的:当内核收到 TCP 报文,通过 TCP 头里面的端口号,来找到我们的应用程序,然后把数据传递给我们。
绑定 IP 地址的目的:一台机器是可以有多个网卡的,每个网卡都有对应的 IP 地址,当绑定一个网卡时,内核在收到该网卡上的包,才会发给我们;
那么阻塞的原理是什么?
比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。
非阻塞忙轮询。接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?”
很明显一般人不会用第二种做法,不仅显很无脑,浪费话费不说,还占用了快递员大量的时间。
大部分程序也不会用第二种做法,因为第一种方法经济而简单,经济是指消耗很少的CPU时间,如果线程睡眠了,就掉出了系统的调度队列,暂时不会去瓜分CPU宝贵的时间片了。
操作系统为了支持多任务,实现了是阻塞状态,比如上述程序运行到recv时,程序会从运行状态变为等待状态,接收到数据后又变回运行状进程调度的功能,会把进程分为“运行”和“等待”等几种状态。运行状态是进程