一、阻塞IO与非阻塞IO
Linux网络IO模型(5种)
(1)阻塞IO模型
所有文件操作都是阻塞的,以套接字接口为例,在进程空间中调用recvfrom,系统调用直到数据包到达且被复制到应用进程缓冲区或发生错误时才返回,期间会一直等待(阻塞)。模型如图:
(2)非阻塞IO模型
recvfrom从应用层到内核时,如果该缓冲区没数据,直接返回一个EWOULDBLOCK错误,反复轮询检查这个状态,看是否有数据到来。如图:
(3)IO复用模型
Linux提高select/poll,进程通过将一个或多个fd(file descriptor)传递给select或poll系统调用,阻塞在select操作上,侦测多个fd是否处于就绪状态。select/poll顺序扫描fd是否就绪,而且支持的fd数量有限。Linux还提供了一个epoll系统调用,使用基于事件驱动的方式代替顺序扫描,性能更高。当有fd就绪时,立即回调函数rollback。如图:
(4)信号驱动IO模型
首先开启套接口信号驱动IO功能,通过系统调用sigaction执行一个信号处理函数,该函数立即返回,进程继续工作,它是非阻塞的。当数据准备就绪时,就为该进程生成一个SIGIO信号,通过信号回调通知应用程序调用recfrom来读取数据,通知主循环函数处理数据。如图:
(5)异步IO模型
告知内核启动某个操作,让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。它与信号驱动的主要区别是:信号驱动IO由内核告知我们何时开始一个IO操作,异步IO模型由内核通知我们IO操作何时已经完成。如图所示:
IO多路复用的应用:
通过把多个IO的阻塞复用到一个select的阻塞上,使系统在单线程下可处理多个客户端请求。与传统多线程模型相比,最大优势是系统开销小,不需要创建额外进程或线程。主要应用场景如下:
(1)服务器需要同时处理多个处于监听状态或连接状态的套接字
(2)服务器需要同时处理多种网络协议的套接字
Linux最终选择epoll支持IO多路复用的系统调用,优点如下:
(1)支持一个进程打开的socket描述符(FD)不受限制(select单线程默认1024太少,epoll仅受限操作系统最大文件句柄数,1GB内存机器大约10万句柄)
(2)IO效率不会随FD数目增加而线性下降(只对“活跃”的socke进行t操作,活跃socket才会去主动调用callback函数)
(3)使用mmap加速内核与用户空间消息传递(同一块内存,避免不必要复制)
(4)API简单:创建epoll描述符,添加监听事件,阻塞等待监听事件发生,关闭epoll描述符等
二、阻塞IO的例子(结合线程池)
//1.服务端
package com.xbai.io;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import com.xbai.executor.TimeServerHandlerExecutePool;
import com.xbai.handler.TimeServerHandler;
public class TimeServerExecutor {
public static void main(String[] args)throws IOException {
int port =8080;
if(args !=null && args.length >0){
try {
port = Integer.valueOf(args[0]);
}catch (Exception e) {
// TODO: handle exception
}
}
ServerSocket server =null;
try {
server =new ServerSocket(port);
System.out.println("The time server is started in port : " + port);
TimeServerHandlerExecutePool singleExecutor =new TimeServerHandlerExecutePool(50,10000);
while(true){
Socket socket = server.accept();
singleExecutor.execute(new TimeServerHandler(socket));
}
}finally {
if(server !=null){
System.out.println("The time server closed");
server.close();
server =null;
}
}
}
}
//2.服务端线程池
package com.xbai.executor;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TimeServerHandlerExecutePool {
private ExecutorService executor;
public TimeServerHandlerExecutePool(int maxPoolSize,int queueSize){
executor =new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),maxPoolSize,120L,TimeUnit.SECONDS,
new ArrayBlockingQueue(queueSize));//线程池要执行的任务阻塞成一个队列,其内部的机制是等待唤醒生产者和消费者线程,有一个生产就可唤醒一个消费,去看源码的线程池原理
}
public void execute(Runnable task){
executor.execute(task);
}
}
//3.服务端处理器
package com.xbai.handler;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.sql.Date;
public class TimeServerHandlerimplements Runnable{
private Socketsocket;
public TimeServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// TODO Auto-generated method stub
BufferedReader br =null;
PrintWriter pw =null;
try {
br =new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw =new PrintWriter(socket.getOutputStream(),true);
String curTime =null;
String msg =null;
while(true){
msg = br.readLine();
if(msg ==null){
break;
}
System.out.println("The time server received order:" + msg);
curTime ="query time order".equalsIgnoreCase(msg) ?new Date(
System.currentTimeMillis()).toString() :"bad order";
pw.println(curTime);//这里不写println,就无法插入换行符,那边就不能readLine,一直阻塞,无法获取数据
}
}catch (IOException e) {
if(br !=null){
try {
br.close();
}catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
if(pw !=null){
pw.close();
pw =null;
}
if(socket !=null){
try {
socket.close();
}catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
socket =null;
}
}
}
}
//4.客户端代码
package com.xbai.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class TimeClient {
public static void main(String[] args) {
int port =8080;
if(args !=null && args.length >0){
try {
port = Integer.valueOf(args[0]);
}catch (Exception e) {
// TODO: handle exception
}
}
Socket socket =null;
BufferedReader br =null;
PrintWriter pw =null;
try {
socket =new Socket("localhost",port);
br =new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw =new PrintWriter(socket.getOutputStream(),true);
pw.println("query time order");
System.out.println("send order succeed");
String resp = br.readLine();
System.out.println("Now is :" + resp);
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(pw !=null){
pw.close();
pw =null;
}
if(br !=null){
try {
br.close();
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
br =null;
}
if(socket !=null){
try {
socket.close();
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
socket =null;
}
}
}
}
原文:https://www.cnblogs.com/free-wings/p/9309148.html