大家好,欢迎来到IT知识分享网。
一、从网卡接收数据说起
通过硬件传输,网卡接收的数据存放到内存中
二、如何知道接收了数据?
当网卡把数据写入到内存后,网卡向cpu发出一个中断信号,操作系统便能得知有新数据到来,再通过网卡中断程序去处理数据.
三,进程阻塞为什么不占用cpu资源?
#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; }
那么问题有来了
(1)操作系统如何知道网络数据对应于哪个socket?
解答:因为一个socket对应着一个端口号,而网络数据包中包含了ip和端口的信息,内核可以通过端口号找到对应的socket。具体就是,网卡接收数据包,将他传给操作系统的网络推zhan,操作系统解析数据包的头部信息,通过ip和端口信息,查处对应的socket。
(2)如何同时监视多个socket的数据?
下面详解
四,select!!!
看2.c
int s = socket(AF_INET, SOCK_STREAM, 0); bind(s, ...) listen(s, ...) int fds[] = 存放需要监听的socket while(1){ int n = select(..., fds, ...) for(int i=0; i < fds.count; i++){ if(FD_ISSET(fds[i], ...)){ //fds[i]的数据处理 } } }
当任何一个socket收到数据后,中断程序将唤起进程。sock2接收到了数据,中断程序唤起进程A
唤起进程,就是将进程从所有的等待队列中移除,加入到工作队列里面
当进程A被唤醒后,它知道至少有一个socket接收了数据。程序只需遍历一遍socket列表,就可以得到就绪的socket。
selet/poll的缺点:
五、epoll过程如下:
- 文件描述符的创建
#include <sys/epoll.h> int epoll_create ( int size );
- 在epoll早期的实现中,对于监控文件描述符的组织并不是使用红黑树,而是hash表。这里的size实际上已经没有意义。创建一个epoll的句柄。参数size是内核保证能够正确处理的最大文件描述符数目(现在内核使用红黑树组织epoll相关数据结构,自从linux2.6.8之后,size参数是被忽略的,>0的数即可)。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
- 注册监控事件
#include <sys/epoll.h> int epoll_ctl ( int epfd, int op, int fd, struct epoll_event event );
操作上面建立的epoll fd,例如,将刚建立的socket fd加入到epoll中让其监控,或者把 epoll正在监控的某个socket fd移出epoll,不再监控它等等。这个函数用于向epoll注册一个事件,而且明确监听的事件类型;第一个参数为epoll句柄,第二个参数表示对这个fd监听事件
函数说明:
fd:要操作的文件描述符
op:指定操作类型
操作类型:
EPOLL_CTL_ADD:往事件表中注册fd上的事件
EPOLL_CTL_MOD:修改fd上的注册事件
EPOLL_CTL_DEL:删除fd上的注册事件
event:指定事件,它是epoll_event结构指针类型
epoll_event定义:struct epoll_event { __unit32_t events; // epoll事件 epoll_data_t data; // 用户数据 };
结构体说明:
events:描述事件类型,和poll支持的事件类型基本相同(两个额外的事件:EPOLLET和EPOLLONESHOT,高效运作的关键)
data成员:存储用户数据typedef union epoll_data { void ptr; //指定与fd相关的用户数据 int fd; //指定事件所从属的目标文件描述符 uint32_t u32; uint64_t u64; } epoll_data_t;
- epoll_wait函数
#include <sys/epoll.h> int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );
在给定的timeout时间内,当在监控的这些文件描述符中的某些文件描述符上有事件发生时,就返回用户态的进程。这个函数用于等待事件的发生.第二个参数是用户自己开辟的一块事件数组,用于存储就绪的事件,第三个参数为这个数组的最大值,就是第二个参数事件数组的最大值,用户想监听fd的个数,第四个参数为超时时间(0表示立即返回,-1表示永久阻塞,直到有就绪事件)
函数说明:
返回:成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno
timeout:指定epoll的超时时间,单位是毫秒。当timeout为-1是,epoll_wait调用将永远阻塞,直到某个时间发生。当timeout为0时,epoll_wait调用将立即返回。
maxevents:指定最多监听多少个事件
events:检测到事件,将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中。
总结:我们在调用 epoll_create 时,内核除了帮我们在 epoll 文件系统里建了个 file 结点,在内核 cache 里建了个红黑树用于存储以后 epoll_ctl 传来的 socket 外,还会再建立一个 rdllist 双向链表,用于存储准备就绪的事件,当 epoll_wait 调用时,仅仅观察这个 rdllist 双向链表里是否有 epitem 元素,如果 rdllist 链表不为空,则这里的事件复制到用户态内存中,同时将事件数量返回给用户,没有数据就 sleep,等到 timeout 时间到后即使链表没数据也返回。所以,epoll_wait 非常高效。、 所有添加到 epoll 中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法。这个回调方法在内核中叫做 ep_poll_callback,它会把这样的事件放到上面的 rdllist 双向链表中。
六、epoll的实现细节
struct eventpoll{ .... /红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件/ struct rb_root rbr; /双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件/ struct list_head rdlist; .... };
struct epitem{ struct rb_node rbn;//红黑树节点 struct list_head rdllink;//双向链表节点 struct epoll_filefd ffd; //事件句柄信息 struct eventpoll *ep; //指向其所属的eventpoll对象 struct epoll_event event; //期待发生的事件类型 }
七、epoll边缘触发和水平触发(ET与LT模式)
#include <stdio.h> #include <unistd.h> #include <sys/epoll.h> int main(void) { int epfd,nfds; struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件 epfd = epoll_create(1); //只需要监听一个描述符 ev.data.fd = STDIN_FILENO;//表示要监听的文件描述符——标准输入 ev.events = EPOLLIN ; //监听读状态同时设置ET模式 EPOLLET epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); //注册epoll事件,将标准输入文件描述符加入到 epoll 实例中进行监听 for(;;) { nfds = epoll_wait(epfd, events, 5, -1);//等待事件发生,当标准输入有可读事件发生时,会返回就绪的事件列表 for(int i = 0; i < nfds; i++) { if(events[i].data.fd==STDIN_FILENO)//判断返回的事件是否是标准输入的事件 { char buf[1024]={0}; read(STDIN_FILENO,buf,sizeof(buf)); printf("welcome to epoll's word!\n"); } } } }
那么说了这么多,同学心中是否村有自己大大小小的疑问呢?
加油!!!go!go!go!
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/125160.html