java nio 传统标准io socket 和nio socket比较与学习
来源:互联网 发布:mac微信账号密码登录 编辑:程序博客网 时间:2024/04/25 06:25
在计算机系统中,最不可靠的就是网络请求,我们通过服务器端给客户端echo信息(客户端请求什么信息服务端就返回给客户端什么信息)。比较两种socket io的优劣。
标准io socket:
服务端使用多线程处理的结构示意图:
服务器端代码:
主线程负责不断地请求echoServer.accept(),如果没有客户端请求主线程会阻塞,当有客户端请求服务器端时,主线程会用线程池新创建一个线程执行。也就是说一个线程负责一个客户端socket,当一个客户端socket因为网络延迟时,服务器端负责这个客户端的线程就会等待,浪费资源。
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;import java.nio.Buffer;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 常规的socket服务端,服务器端采用一个线程接受一个客户端来处理。 * Created by chenyang on 2017/3/26. */public class MultiThreadEchoServer { private static ExecutorService tp= Executors.newCachedThreadPool(); static class HandleMsg implements Runnable{ Socket clientSocket; public HandleMsg(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { BufferedReader is=null; PrintWriter os=null; try { is=new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); os=new PrintWriter(clientSocket.getOutputStream(),true); //从InputStream当中读取客户端所发送的数据 String inputLine=null; long b=System.currentTimeMillis(); while ((inputLine=is.readLine())!=null){ os.println(inputLine); } long e=System.currentTimeMillis(); System.out.println("spend:"+(e-b)+"ms"); }catch (IOException e){ e.printStackTrace(); }finally { try { if(is!=null) is.close(); if(os!=null) os.close(); clientSocket.close(); }catch (IOException ex){ ex.printStackTrace(); } } } } public static void main(String[] args) { ServerSocket echoServer=null; Socket clientSocket=null; try { echoServer=new ServerSocket(8000); }catch (IOException e){ System.out.println(e); } while (true){ try { clientSocket =echoServer.accept();//阻塞 System.out.println(clientSocket.getRemoteSocketAddress()+" connect!"+System.currentTimeMillis());
//子线程负责执行与client socket 交互的操作。 tp.execute(new HandleMsg(clientSocket)); }catch (IOException e){ System.out.println(e); } } }}
客户端代码:
主线程创建10个子线程去请求server:这是个模拟网络拥堵时的客户端socket,每打一个字符就会停1秒。这样服务端的线程也要等待,这样服务器端的资源浪费的就很多。
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.InetSocketAddress;import java.net.Socket;import java.net.UnknownHostException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.locks.LockSupport;/** * 传统IO下,模拟10个网络不好的客户端同时访问server. * Created by chenyang on 2017/4/8. */public class HeavyThreadEchoClient { static ExecutorService es= Executors.newCachedThreadPool(); static Long sleep_time=1000*1000*1000L; public static class EchoClient implements Runnable{ @Override public void run() { Socket client=null; PrintWriter writer=null; BufferedReader reader=null; try { client=new Socket(); client.connect(new InetSocketAddress("localhost",8000)); writer=new PrintWriter(client.getOutputStream(),true); writer.print("h"); LockSupport.parkNanos(sleep_time); writer.print("e"); LockSupport.parkNanos(sleep_time); writer.print("l"); LockSupport.parkNanos(sleep_time); writer.print("l"); LockSupport.parkNanos(sleep_time); writer.print("o"); LockSupport.parkNanos(sleep_time); writer.print("!"); LockSupport.parkNanos(sleep_time); writer.println(); writer.flush(); reader=new BufferedReader(new InputStreamReader(client.getInputStream())); System.out.println("from server:"+reader.readLine()); }catch (UnknownHostException ex){ ex.printStackTrace(); }catch (IOException e){ e.printStackTrace(); } finally { if(writer!=null){ writer.close(); } if(reader!=null){ try { reader.close(); }catch (IOException ex){ ex.printStackTrace(); } } if(client!=null){ try { client.close(); }catch (IOException ex){ ex.printStackTrace(); } } } } } public static void main(String[] args) { EchoClient ec=new EchoClient(); for(int i=0;i<10;i++){ es.execute(ec); } }}
当服务器端和客户端代码执行后的结果:
spend:6023ms
spend:6023ms
spend:6024ms
spend:6024ms
spend:6025ms
spend:6025ms
spend:6026ms
spend:6027ms
spend:6023ms
spend:6024ms
spend:6024ms
spend:6025ms
spend:6025ms
spend:6026ms
spend:6027ms
spend:6027ms
spend:6028ms
spend:6028ms
都有6秒的延迟,这都是网络io等待时间造成的。
nio socket:
通过事件通知的机制,当数据准备好了才会通知服务器端线程进行读写,避免了网络io等待。
服务端多线程的结构示意图:
一个线程控制一个selector,一个selector可以轮询多个客户端的channel,这样服务器端线程不用等待网络io,只会处理准备好的数据。
服务器端代码:
import java.io.IOException;import java.net.InetAddress;import java.net.InetSocketAddress;import java.net.Socket;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.nio.channels.spi.SelectorProvider;import java.util.*;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by chenyang on 2017/4/8. */public class MultiThreadNIOEchoServer { public static Map<Socket,Long> geym_time_stat=new HashMap<Socket,Long>(10240); class EchoClient{ private LinkedList<ByteBuffer> outq; EchoClient(){ outq=new LinkedList<ByteBuffer>(); } //return the output queue public LinkedList<ByteBuffer> getOutputQueue(){ return outq; } //enqueue a ByteBuffer on the output queue. public void enqueue(ByteBuffer bb){ outq.addFirst(bb); } } class HandleMsg implements Runnable{ SelectionKey sk; ByteBuffer bb; public HandleMsg(SelectionKey sk, ByteBuffer bb) { this.sk = sk; this.bb = bb; } @Override public void run() { EchoClient echoClient=(EchoClient)sk.attachment(); echoClient.enqueue(bb); //we've enqueued data to be written to the client,we must //not set interest in OP_WRITE sk.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE); selector.wakeup(); } } private Selector selector; private ExecutorService tp= Executors.newCachedThreadPool(); /* accept a new client and set it up for reading */ private void doAccept(SelectionKey sk){ ServerSocketChannel server=(ServerSocketChannel)sk.channel(); SocketChannel clientChannel; try { //获取客户端的channel clientChannel = server.accept(); clientChannel.configureBlocking(false); //register the channel for reading SelectionKey clientKey=clientChannel.register(selector,SelectionKey.OP_READ); //Allocate an EchoClient instance and attach it to this selection key. EchoClient echoClient=new EchoClient(); clientKey.attach(echoClient); InetAddress clientAddress=clientChannel.socket().getInetAddress(); System.out.println("Accepted connetion from "+clientAddress.getHostAddress()+"."); }catch (Exception e){ System.out.println("Failed to accept new client"); e.printStackTrace(); } } private void doRead(SelectionKey sk){ SocketChannel channel=(SocketChannel)sk.channel(); ByteBuffer bb=ByteBuffer.allocate(8192); int len; try { len=channel.read(bb); if(len<0){ disconnect(sk); return; } }catch (Exception e){ System.out.println("Fail to read from client"); e.printStackTrace(); disconnect(sk); return; } bb.flip(); tp.execute(new HandleMsg(sk,bb)); } private void doWrite(SelectionKey sk){ SocketChannel channel=(SocketChannel)sk.channel(); EchoClient echoClient=(EchoClient)sk.attachment(); LinkedList<ByteBuffer> outq=echoClient.getOutputQueue(); ByteBuffer bb=outq.getLast(); try { int len=channel.write(bb); if(len==-1){ disconnect(sk); return; } if(bb.remaining()==0){ outq.removeLast(); } }catch (Exception e){ e.printStackTrace(); System.out.println("fail to write to client"); disconnect(sk); } if(outq.size()==0){ sk.interestOps(SelectionKey.OP_READ); } } private void disconnect(SelectionKey sk){ SocketChannel sc=(SocketChannel)sk.channel(); try { sc.finishConnect(); }catch (IOException e){ } } private void startServer() throws Exception{ //声明一个selector selector= SelectorProvider.provider().openSelector(); //声明一个server socket channel,而且是非阻塞的。 ServerSocketChannel ssc=ServerSocketChannel.open(); ssc.configureBlocking(false);// InetSocketAddress isa=new InetSocketAddress(InetAddress.getLocalHost(),8000); //声明服务器端的端口 InetSocketAddress isa=new InetSocketAddress(8000); //服务器端的socket channel绑定在这个端口。 ssc.socket().bind(isa); //把一个socketchannel注册到一个selector上,同时选择监听的事件,SelectionKey.OP_ACCEPT表示对selector如果 //监听到注册在它上面的server socket channel准备去接受一个连接,或 有个错误挂起,selector将把OP_ACCEPT加到 //key ready set 并把key加到selected-key set. SelectionKey acceptKey=ssc.register(selector,SelectionKey.OP_ACCEPT); for(;;){ selector.select(); Set readyKeys=selector.selectedKeys(); Iterator i=readyKeys.iterator(); long e=0; while (i.hasNext()){ SelectionKey sk=(SelectionKey)i.next(); i.remove(); if(sk.isAcceptable()){ doAccept(sk); }else if(sk.isValid()&&sk.isReadable()){ if(!geym_time_stat.containsKey(((SocketChannel)sk.channel()).socket())){ geym_time_stat.put(((SocketChannel)sk.channel()).socket(),System.currentTimeMillis()); doRead(sk); } }else if(sk.isValid()&&sk.isWritable()){ doWrite(sk); e=System.currentTimeMillis(); long b=geym_time_stat.remove(((SocketChannel)sk.channel()).socket()); System.out.println("spend"+(e-b)+"ms"); } } } } public static void main(String[] args) { MultiThreadNIOEchoServer echoServer=new MultiThreadNIOEchoServer(); try { echoServer.startServer(); }catch (Exception e){ e.printStackTrace(); } }}
同样的客户端代码测试nio的服务器端结果:
spend8ms
spend10ms
spend11ms
spend15ms
spend7ms
spend7ms
spend6ms
spend6ms
spend6ms
spend8ms
几乎没有多少延迟。
总结:
nio在数据准备好后,再交由应用进行处理,数据的读写过程仍在应用线程中。也就是说应用线程不用再等待网络io了,准备好了读写还是要处理的。
节省的数据准备时间(因为selector可以多个channel复用)
0 0
- java nio 传统标准io socket 和nio socket比较与学习
- 传统Socket IO与NIO的比较
- Java NIO Socket VS 标准IO Socket
- NIO与传统IO的区别 NIO Socket例子 实例
- NIO与传统IO的区别 NIO Socket例子 实例
- NIO与传统IO的区别 NIO Socket例子 实例
- 传统IO与NIO比较
- Java NIO 与 Nio Socket
- 第七篇:Java NIO Socket VS 标准IO Socket
- JAVA SOCKET IO VS NIO
- 传统IO与NIO的比较
- 传统IO与NIO的比较
- 【Java TCP/IP Socket】Java NIO Socket VS 标准IO Socket
- 【Java TCP/IP Socket】Java NIO Socket VS 标准IO Socket
- 【Java TCP/IP Socket】Java NIO Socket VS 标准IO Socket
- Java NIO与IO 区别和比较
- Java NIO与IO 区别和比较
- Java NIO 之 NIO与IO比较
- python 操作符**与*的用法
- 一篇关于正则的学习笔记
- 计算机网络之面试常考
- Java反射原理
- MySQL基础
- java nio 传统标准io socket 和nio socket比较与学习
- leetcodeOJ 64. Minimum Path Sum
- Animator
- Labview之贪吃蛇
- netstat查看当前网络下TCP的各种状态
- 不要在 foreach 循环里进行元素的 remove/add 操作
- 论文引介 | A Structured Self-attentive Sentence Embedding
- [珠玑之椟]字符串和序列:左移、哈希、最长重复子序列的后缀数组解法、最大连续子序列
- 树泽:汽车次贷,可能正在酝酿下一次经济危机