相关文章
java网络编程—NIO与Netty(四)
java网络编程—NIO与Netty(三)
java网络编程—NIO与Netty(二)
java网络编程—NIO与Netty(一)
java网络编程—基石:五种IO模型及原理(多路复用\epoll)
理解Netty首先要理解NIO,理解NIO首先要理解reactor模型、多路复用select\poll\epoll等等原理。所以我尝试按照这个来详细讲解网络编程的来龙去脉。
是一个用于表述在操作系统中,指向一个文件(文件可理解为信息载体,包括socket)的引用指针的抽象化概念。
对一个 socket 的读写也会有相应的描述符,称为 socketfd(socket 描述符)
文件描述符形式上是一个非负整数。实际上,它是一个索引值,指向内核中为每个进程所维护的该进程打开文件的记录表。
当程序打开一个文件或者新创建一个文件,内核就会向进程返回一个对应的文件描述符(fd)。这个概念只适用于unix、linux操作系统。
当一个进程在执行时,CPU 的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文
Linux从整体上分为内核态与用户态。
比如一个32位的操作系统,寻址地址(虚拟内存空间)是2的32次方,也就是4G。操作系统将较高的1G字节作为内核空间,而将较低的3G字节作为用户空间。内核空间具有用户空间所不具备的操作权限。
内核态就是内核所处的空间,内核负责调用底层硬件资源,并为上层应用程序提供运行环境。用户态即应用程序的活动空间。
应用程序通常运行在用户空间,当某些操作需要内核权限时,通过系统调用(System calls),进入内核态执行。这也就是一次用户态\内核态的转换。
应用程序的运行必须依托于内核提供的硬件资源(如cpu\存储\IO),内核通过暴露外部接口供应用程序调用——称为“SystemCall,系统调用”。系统调用是操作系统的最小功能单位。每次系统调用都会发生一次用户态与内核态的切换,切换过程中涉及了各种函数的调用以及数据的复制。
最终通过内核态、用户态的划分与协助,保证了操作系统的安全性、稳定性。
多路复用通过linux的select\poll\epoll模型实现的,但它们本质上都是同步 IO。
IO 多路复用通过把多个 IO 阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下,可以同时处理多个 client 请求
select\poll\epoll它们均属于实现多路复用的SystemCall(系统调用)。
select
select可以同时监听多个fd,调用select后产生阻塞,当其中一个fd处于就绪状态便从阻塞中返回,然后遍历fdset(被监听的fd集合),找出就绪的fd。
select缺点
int select(int numfd, fd_set * readfds, fd_set * writefds,
fd_set * exceptfds, struct timeval * tv);
poll
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
poll与select类似,唯一区别是没有使用fdset而是pollset,对fd的个数没有限制。但是随着fd个数增大,一样会产生性能问题。
epoll
jdk1.5之后使用epoll代替了传统的select/poll,极大提升了NIO通信的新能。
//注册监听事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event),
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
//是等待事件的产生
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
对select\poll的改进(区别)
Linux 提供的 mmap 系统调用, 它可以将一段用户空间内存映射到内核空间, 当映射成功后,
用户对这段内存区域的修改可以直接反映到内核空间; 同样地, 内核空间对这段区域的修改也直接反映用户空间. 正因为有这样的映射关系,
我们就不需要在 用户态(User-space) 与 内核态(Kernel-space) 之间拷贝数据, 提高了数据传输的效率.
不需要遍历所有的fd,会给每个监听的fd增加一个回调函数,当就绪后回调将fd放入就绪列表中,只需要从就绪列表中获取就绪的fd即可。
没有fd限制,1g内存可以处理10w个fd
epoll的LT(level trigger)与ET(edge trigger)模式
同步和异步关注的是消息通信机制
同步:发出一个调用时,等待直到,调用结果的完成后返回。
异步:发送一个调用后,立刻返回,不需要等待调用结果。通过通知机制或者回调函数通知。阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞:发出调用后,当前线程被挂起,直到结果返回。
非阻塞:发出调用后,当前线程继续保持运行状态。
总之,同步异步要看是否需要等待操作执行的结构。阻塞非阻塞指请求后是否能够理解返回保持Running的状态。
而在IO中,一个完整的IO实际分为如下两个部分:
1. 等待数据准备就绪
2. 将数据从内核拷贝到用户空间。
(准确来说:数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间)
对于阻塞IO\非阻塞IO主要针对的是阶段1(等待数据准备就绪):
对于同步IO\异步IO主要针对的是阶段2:
可以看到,等待数据准备就绪是阻塞等待的,另外将数据从内核拷贝到用户空间 也是阻塞等待的。所以BIO是阻塞同步IO
可以看到,等待数据准备就绪是非阻塞等待的,另外将数据从内核拷贝到用户空间 也是阻塞等待的。所以NIO是非阻塞同步IO
多路复用的NIO作为NIO的补充,它允许通过一个线程持有的selector对象管理多个Socket连接(channel),并通过这一个Selector去轮序哪些Socket准备就绪。
这样便可以避免每个线程持有一个Socket,并且这个线程不停轮序自己的Socket是否准备就绪的操作。
最终实现了一个线程管理多个Socket,解决了高并发的线程问题。
完全非阻塞的IO。
如图应用程序发送了一个读操作后立即返回,当一些完成,操作系统通知应用程序。
内核在I/O操作完成后再通知应用进程操作结果。
内核是通过向应用程序发送 signal 或执行一个基于线程的回调函数来完成这次 IO 处理过程,告诉用户 read 操作已经完成
Netty系列之Netty线程模型