摘要:调用一次获得就绪文件描述符时,返回的并不是实际的描述符,而是一个代表就绪描述符数量的值,拿到这些值去指定的一个数组中依次取得相应数量的文件描述符即可,这里使用内存映射技术,避免了复制大量文件描述符带来的开销。
Nodejs定义
什么是IONode.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
IO(Input & Output),顾名思义,输入输出即是IO。磁盘,网络,鼠标,键盘等都算IO;而大家通常说的IO,大部分指磁盘和网络的数据操作。
对于磁盘,IO=读写;对于网络,IO=收发。
学习C语言时,有个作业,大意是写一个server程序和client程序,实现TCP/UDP通信。看起来代码如下:
Client端int ClientSend(SOCKET s, char* msg) { char buf[BUF_SIZE] = {0}; if (s && msg) { int len = send(s, msg, strlen(msg), 0); if (len > 0) { println("Client send OK!"); len = recv(s, buf, BUF_SIZE); if (len > 0) { println("Client receive: %s", buf); } // else socket recv error } // else socket send error } // else } int main(char* argc, char* argv[]) { // 初始化socket SOCKET s = InitSocket(); if (s != -1) { ClientSend(s, "Hi, I am Client"); } // else socket init error return 0; }Server端
int main(char* argc, char* argv[]) { char buf[BUF_SIZE] = {0}; const char* msg = "Roger that, I am Server"; // 初始化socket,略 SOCKET s = InitSocket(); SOCKET cs; sockaddr_in addr; int nAddrLen = sizeof(addr); while ((cs = accept(s, &addr, &nAddrLen)) != -1) { int len = recv(cs, buf, BUF_SIZE, 0); if (len > 0) { len = send(cs, msg, strlen(msg), 0); if (len > 0) { println("Serve one client"); } // else socket send error } } return 0; }
在这个例子中,如果一个Client通信没有结束,其它的Client是无法和Server通信的。原因就是代码里面使用的是Blocking I/O,即同步IO。因为在代码中的recv或者send,都会阻塞住当前代码的执行。单靠这种模型,是无法实现一个完善的服务器的。
Blocking I/O,多线程(多进程)为了让Server能服务更多的Client,基于Blocking I/O,可以采用多线程(进程)来处理,实现1对多的服务。
Server端int ThreadProc(void* pParam) { char buf[BUF_SIZE] = {0}; const char* msg = "Roger that, I am Server"; if (pParam) { int len = recv(cs, buf, BUF_SIZE, 0); if (len > 0) { len = send(cs, msg, strlen(msg), 0); if (len > 0) { println("Serve one client"); } // else socket send error } // else socket recv error } // else param error return 0; } int main(char* argc, char* argv[]) { // 初始化socket,略 SOCKET s = InitSocket(); SOCKET cs; sockaddr_in addr; int nAddrLen = sizeof(addr); while ((cs = accept(s, &addr, &nAddrLen)) != -1) { int pThread = CreateThread(NULL, 0, ThreadProc, cs); // serve on client } return 0; }
这样的方案,的确能同时处理多个Client请求,实现并发。但由于创建线程的成本很高(需要分配内存,调度CPU等),受Server硬件条件的限制,这种方案不能服务很多Client,即服务器性能很低下。
另外,如果把ThreadProc里面的代码增加逻辑:
// recive data from buf setenv(buf); CreateProcess(NULL, 0 ...); // parse env in child process
这就是一个简单的CGI模型了。
在一些简单的http服务器代码中,见到过这样的模型。(比如一些嵌入式系统服务器)。
因为Blocking I/O的特点,所以系统提供了另外的方法,Non-blocking I/O,即调用send,recv等接口时,不会阻塞线程,但调用者需要自己去轮训IO的状态来判定操作;就像一个监工不停的问工人,你完事儿没有。
int main(char * argc, char * argv[]) { // 初始化socket,略 SOCKET s = InitSocket(); SOCKET cs; sockaddr_in addr; int fd; int nAddrLen = sizeof(addr); SetNonblocking(s); while (running) { int ret = select(FD_SETSIZE, ...); if (ret == -1) break; if (ret == 0) continue; for (fd = 0; fd < FD_SETSIZE; fd++) { if (FD_ISSET(fd, ...) { // 有新的client进来 if (fd == s) { cs = accept(s, & addr, & nAddrLen, 0); FD_SET(cs, ...); } else // cs中的一个里面有变化 { ioctl(fd, FIONREAD, & nread); // 处理完毕 if (nread == 0) { close(fd); FD_CLR(fd, ...); } else { // 处理Client逻辑,这里可能会创建线程。 ...... } } } // serve on client } } return 0; }
在这种模型中,while和for循环不停的检查fd_set的状态,并做相应的处理,类似Apache的解决方案。
但是,这个模型里面还有一个block,就是select,当有fd发生变化时,select才会返回。
还有,select中的FD_SETSIZE有限制(一般是2048),就表明单进程还是不能支持更大量级的并发。Apache采用多进程的方式来解决这个问题。
后期有了epoll,这个限制放的更宽,很多http服务器是用epoll来实现的(Nginx)。
epoll主要有两个优点:
基于事件的就绪通知方式 ,select/poll方式,进程只有在调用一定的方法后,内核才会对所有监视的文件描述符进行扫描,而epoll事件通过epoll_ctl()注册一个文件描述符,一旦某个文件描述符就绪时,内核会采用类似call back的回调机制,迅速激活这个文件描述符,epoll_wait()便会得到通知。
调用一次epoll_wait()获得就绪文件描述符时,返回的并不是实际的描述符,而是一个代表就绪描述符数量的值,拿到这些值去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里使用内存映射(mmap)技术, 避免了复制大量文件描述符带来的开销。
Nodejs,也采用了和Nginx类似的思路,可以再深入了解下libuv。
Asynchronous I/O有些人说Nodejs是Asynchronous I/O,其实不然。Asynchronous I/O是说用户发起read等IO操作后,去做其它的事情了,而系统在完成IO操作后,用signal的方式通知用户完成。目前使用此模型的http服务器有asyncio等。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64571.html
原文 先说1.1总揽: Reactor模式 Reactor模式中的协调机制Event Loop Reactor模式中的事件分离器Event Demultiplexer 一些Event Demultiplexer处理不了的复杂I/O接口比如File I/O、DNS等 复杂I/O的解决方案 未完待续 前言 nodejs和其他编程平台的区别在于如何去处理I/O接口,我们听一个人介绍nodejs,总是...
摘要:异步和事件驱动注本文是对众多博客的学习和总结,可能存在理解错误。接触有两个月,对的两大特性一直有点模糊,即异步和事件驱动。 nodejs 异步I/O和事件驱动 注:本文是对众多博客的学习和总结,可能存在理解错误。请带着怀疑的眼光,同时如果有错误希望能指出。 接触nodejs有两个月,对nodejs的两大特性一直有点模糊,即异步IO和事件驱动。通过对《深入浅出nodejs》和几篇博客的阅...
摘要:对于而言,单线程指的是它的执行线程是单线程。对于来说,单线程不仅不是劣势,它对于降低编程复杂度还有很重要的作用,单线程避免了多线程编程模型多线程死锁状态同步等问题。单线程的应用是脆弱了,但群体的力量是强大的。 我们常听说 JavaScript 是单线程的,那这个单线程是什么意思呢?单线程是否意味 JavaScript 存在性能缺陷呢? 在浏览器端,JavaScript 单线程指的是 J...
摘要:事件驱动在中,当某个执行完毕后,会以事件的形式通知执行操作的线程而线程去执行对应事件的回调函数。为了处理异步,线程必须要有事件循环,不断的检查有没有事件要处理,并依次处理。其实在底层中,有一半的代码,都是在处理事件队列回调函数。 事件驱动 上一节中,我们提到异步I/O;当I/O处理完毕后,nodejs是怎样知道I/O已经完成了呢?又是怎样去处理的呢?答案是:事件驱动(事件循环)机制。 ...
阅读 3525·2023-04-26 00:16
阅读 1361·2021-11-25 09:43
阅读 3824·2021-11-23 09:51
阅读 2964·2021-09-24 09:55
阅读 712·2021-09-22 15:45
阅读 1386·2021-07-30 15:30
阅读 3064·2019-08-30 14:04
阅读 2236·2019-08-26 13:46