网络编程时,我们常见到同步(Sync)和异步(Async),阻塞(blocking)和非阻塞(non-blocking)四种调用模式:
①同步:
客户端发一个调用,再没有得到结果之前,该调用不返回,通俗来讲,就是必须做完前一件事情才可以 做下一件事情;
②异步:
当客户端发生一个异步请求的调用的时候,调用者不能立刻得到结果,实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者,例如ajax请求;
③阻塞
通俗的讲,做某件事情,知道完成,除非超时,如果没有完成,继续等待;当阻塞调用的时候,在调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,CPU不会给线程分配时间片,即线程暂停运行),函数只有在得到结果折后才会被 返回;
④非阻塞
指在不能得到结果之前,该函数不会阻塞当前线程,会立刻返回;
还是等快递的例子:如果用忙轮询的方法,每隔5分钟到A楼一层(内核缓冲区)去看快递来了没有。如果没来,立即返回。而快递来了,就放在A楼一层,等你去取。
传统IO的Socket的单线程通信,用非常经典的食堂例子来讲解,端口号相当于食堂大门,线程相当于服务员,在这个状态下,一个客户需要有一个服务员来服务,这样显然是不合理的,假如有100个顾客的话,就需要100服务员,这样非常浪费系统资源,代码如下:
package IoSocket; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; /** * Created by Taoyongpan on 2017/10/23. * 传统的IO是阻塞IO,每多一个用户就要创建一个新的线程,单线程模式 */ public class TraditionalSocketDemo { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务器启动。。。。"); while (true){ //接收客户端的连接 ,套接字 Socket socket = serverSocket.accept(); System.out.println("有新的客户端连接"); InputStream in = null; try { in = socket.getInputStream(); } catch (IOException e) { e.printStackTrace(); } byte[] b = new byte[1024]; while (true){ int data = 0; data = in.read(b); if (data!=-1){ String info = new String(b,0,data); System.out.println(info); }else { break; } } } } }
当程序运行的时候:
这时用命令窗口创建一个用户,我这里面的端口号是8888,我们创建客户的语句是:telnet localhost 8888,如图所示:
输入 CTRL+]的命令 进入字符串发送页面。原来的页面只可以发送字符;
send +自己所要发送的内容,如上图所示;
因为这个代码写的是单线程模式的,当我们再创建一个新的客户的时候,就会陷入等待,只有关闭上一个现成的时候,新建客户的消息的才可以发送,如下图所示:
我们也可以创建一个线程池,来实现多线程,代码如下:
package IoSocket; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by Taoyongpan on 2017/10/24. * 传统的IO是阻塞IO,每多一个用户就要创建一个新的线程 */ public class TraditionSocketDemo1 { public static void main(String[] args) throws IOException { //多线程方法,创建一个线程池 ExecutorService es = Executors.newCachedThreadPool(); ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务器启动。。。。"); while (true){ //接收客户端的连接 ,套接字 Socket socket = serverSocket.accept(); System.out.println("有新的客户端连接"); es.execute(new Runnable() { @Override public void run() { InputStream in = null; try { in = socket.getInputStream(); } catch (IOException e) { e.printStackTrace(); } byte[] b = new byte[1024]; while (true){ int data = 0; try { data = in.read(b); } catch (IOException e) { e.printStackTrace(); } if (data!=-1){ String info = new String(b,0,data); System.out.println(info); }else { break; } } } }); } } }
重复运行上面的测试步骤就可以进行操作:
测试如下图所示:
NIO是非阻塞的IO,相对于传统IO,新增了selector、channel和buffer,还用食堂的例子来讲解,我们的channel就像食堂的大门,一个食堂的服务员可以服务多个客户,这个过程用selector来调度,例如在 客户A点菜的时候,接待了客户B,代码如下:
package IoSocket; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; /** * Created by Taoyongpan on 2017/10/23. * nio,增加了selector,channel ,buffer,非阻塞 */ public class NioSocketDemo { private Selector selector;//通道选择器 public static void main(String[] args) throws IOException { NioSocketDemo nioSocketDemo = new NioSocketDemo(); nioSocketDemo.initServer(8888); nioSocketDemo.listenSelector(); } public void initServer(int port) throws IOException { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false);//设置成非阻塞 serverSocketChannel.socket().bind(new InetSocketAddress(port)); this.selector = Selector.open(); //注册监听器 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//注册监听连接请求 System.out.println("服务已启动。。。"); } public void listenSelector() throws IOException { //循环监听selector while (true){ //等待客户连接 //select模型,多路服用 this.selector.select(); this.selector.selectedKeys().iterator(); Iteratorit = this.selector.selectedKeys().iterator(); while (it.hasNext()){ SelectionKey skey = it.next(); it.remove();//把当前的元素remove掉 //处理请求 handler(skey); } } } private void handler(SelectionKey skey) throws IOException { if (skey.isAcceptable()){ //处理客户端连接事件 ServerSocketChannel serverChannel = (ServerSocketChannel)skey.channel(); SocketChannel socketChannel = serverChannel.accept(); System.out.println("新客户连接上来了"); socketChannel.configureBlocking(false);//设置为非阻塞 //接收客户端发送的信息,需要给通道设置读的权限 socketChannel.register(selector,SelectionKey.OP_READ); }else if (skey.isReadable()){ //处理读的事件 SocketChannel socketChannel = (SocketChannel) skey.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//初始化一个缓存 int data = socketChannel.read(byteBuffer); if (data>0){ String info = new String(byteBuffer.array(),"GBK").trim(); System.out.println("客户端消息"+info); }else { System.out.println("客户端关闭了"); skey.cancel();//把这个连接清除掉 } } } }
这个的运行效果和传统IO的多线程的运行结果是一样的,这里就不再进行展示;