socket网络编程
NIO 中 selector 用 epoll 实现
Socket 套接字:网络上具有唯一标识的 IP 地址和端口组合在一起才能构成唯一能识别的标识符套接字。
Socket 原理机制:通信的两端都有 Socket、网络通信其实就是 Socket 间的通信、数据在两个 Socket 间通过 IO 传输
Java 中基于 TCP 实现网络通信的类:客户端的 Socket 类、服务器端的ServerSocket 类
select,poll,epoll 都是 IO 多路复用的机制(一个监控好多个)。I/O 多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。**但 select,poll,epoll 本质上都是同步 I/O,**因为他们都需要在读写事件就绪后自己负责进行读写,**也就是说这个读写过程是阻塞的,**而异步 I/O则无需自己负责进行读写,异步 I/O 的实现会负责把数据从内核拷贝到用户空间。Epoll 在Linux2.6 内核中正式引入
select 模型
- 最大并发数限制,因为一个进程所打开的 **FD(文件描述符)**是有限制的,由 FD_SETSIZE设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …
- 效率问题, select 每次调用都会线性扫描全部的 FD 集合(遍历),这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了。
- 内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法
总结为:1.数据由内核拷贝到用户态 2.遍历导致查找配对速度慢 3.连接数受限
poll模型
poll 的实现和 select 非常相似,只是描述 fd 集合的方式不同,poll 使用 pollfd 结构而不是 select 的 fd_set 结构,其他的都差不多。poll 打开 fd 集合的数量没有限制,但也是遍历 fd 集合
epoll 模型
epoll 既然是对 select 和 poll 的改进,就应该能避免上述的三个缺点。那 epoll 都是怎么解决的呢?在此之前,我们先看一下 epoll 和 select 和 poll 的调用接口上的不同,select 和 poll 都只提供了一个函数——select 或者 poll 函数。而 epoll 提供了三个函数,epoll_create,epoll_ctl 和 epoll_wait,
- epoll_create 是创建一个 epoll 句柄;
- epoll_ctl是注册要监听的事件类型;
- epoll_wait 则是等待事件的产生。
对于第一个缺点,epoll 的解决方案在 epoll_ctl 函数中。每次注册新的事件到 epoll句柄中时(在 epoll_ctl 中指定 EPOLL_CTL_ADD),**会把所有的 fd 拷贝进内核,**而不是在epoll_wait 的时候重复拷贝。epoll 保证了每个 fd 在整个过程中只会拷贝一次。
对于第二个缺点,epoll 的解决方案不像 select 或 poll 一样每次都把 current 轮流加入 fd 对应的设备等待队列中,而只在 epoll_ctl 时把 current 挂一遍(这一遍必不可少)并为每个 fd 指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的 fd 加入一个就绪链表。epoll_wait 的工作实际上就是在这个就绪链表中查看有没有就绪的 fd
对于第三个缺点,epoll 没有这个限制,它所支持的 FD 上限是最大可以打开文件的数目,这个数字一般远大于 2048,举个例子,在 1GB 内存的机器上大约是 10 万左右,具体数目可以 cat /proc/sys/fs/file-max 察看,一般来说这个数目和系统内存关系很大。
总结:
- select,poll 实现需要自己不断轮询所有 fd 集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而 epoll 其实也需要调用 epoll_wait 不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它在设备就绪时,调用回调函数,把就绪 fd 放入就绪链表中,并唤醒在 epoll_wait 中进入睡眠的进程。**虽然都要睡眠和交替,但是 select 和 poll 在“醒着”的时候要遍历整个 fd 集合,而 epoll 在“醒着”的时候只要判断一下就绪链表是否为空就行了,**这节省了大量的 CPU 时间。这就是回调机制带来的性能提升。
- select,poll **每次调用都要把 fd 集合从用户态往内核态拷贝一次,并且要把 current往设备等待队列中挂一次,**而 epoll 只要一次拷贝,而且把 current 往等待队列上挂也只挂一次(在 epoll_wait 的开始,注意这里的等待队列并不是设备等待队列,只是一个 epoll内部定义的等待队列)。这也能节省不少的开销。
Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!
Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!
select(),poll()模型都是水平触发模式,信号驱动 IO 是边缘触发模式,epoll()模型即支持水平触发,也支持边缘触发,默认是水平触发。
java.io 中用到的设计模式
java 语言 I/O 库的设计中,使用了两个结构模式,即装饰模式和适配器模式
byte(即字节)占有 1 个字节,只能存 1 个字节如’a’
char(即字符)占有 2 个字节,可以存’a’,’哈’
装饰模式:
提供更强的功能。可以在不改变原有对象的情况下拓展其功能。装饰器模式通过组合替代继承来扩展原始类的功能,在一些继承关系比较复杂的场景(IO 这一场景各种类的继承关系就比较复杂)更加实用。
BufferedInputStream(InputStream in)
BufferInputStream:用来从硬盘将数据读入到一个内存缓冲区中,并从此缓冲区提供数据。
BufferedInputStream “装饰”了 InputStream 的内部工作方式,使得流的读入操作使用缓冲机制。在使用了缓冲机制后,不会对每一次的流读入操作都产生一个物理的读盘动作,从而提高了程序的效率。在涉及到物理流的读入时,都应当使用这个装饰流类。(InputStream的 read 方法:从输入流中读取数据的下一个字节)
BufferedReader、BufferedWriter、BufferedOutputStream
BufferInputStream:内部数组 byte 类型,protected volatile byte[] buf = new byte[8192];(即缓存数组默认 8m)内部 read 等方法都是用 synchronized 修饰(在方法名上)BufferedReader:内部数组 char 类型,private char[] cb = new char[8192]; 内部 readLine等方法是 synchronized(lock){}代码块
适配器模式(接口转换)
主要用于接口互不兼容的类的协调工作,你可以将其联想到我们日常经常使用的电源适配器。
适配器模式中存在被适配的对象或者类称为 适配者(Adaptee) ,作用于适配者的对象或者类称为适配器(Adapter) 。适配器分为对象适配器和类适配器。类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
IO 流中的字符流和字节流的接口不同,它们之间可以协调工作就是基于适配器模式来做的,更准确点来说是对象适配器。通过适配器,我们可以将字节流对象适配成一个字符流对象,这样我们可以直接通过字节流对象来读取或者写入字符数据。
ByteArrayInputStream 是一个适配器类 。ByteArrayInputStream 继承了 InputStream的接口,而封装了一个 byte 数组。换而言之,它将一个 byte 数组的接口适配成了InputStream 流处理器的接口。
ByteArrayInputStream:从数组中读取数据;
FileInputStream:从文件中读取数据;
ByteArrayOutputStream 、 FileOutputStream 、 CharArrayReader 、 StringReader 、CharArrayWriter
InputStreamReader 是从 byte 流到 char 流的一个桥梁,它读入 byte 数据并根据指定的编码将之翻译成 char 数据。从 byte 输入流到 char 输入流的一个适配器(将字节转换为字符,即可以输出中文)