1、Unix IO 与 IPChtml
Unix IO:Open-Read or Write-Closejava
IPC:open socket - receive and send to socket - close socket数据库
IPC 全称是 InterProcess Communication。编程
当消息发出后,消息进入 SendQ队列 一直等待 sending socket 处理,才真正发出(一直等待是阻塞的)。当消息到达时,消息进入RecvQ队列 一直等待 receiving socket 处理(同前)。segmentfault
底层的 TCP 协议关联的 RecvQ 或者 SendQ 队列就是一个操做系统缓冲区。数组
缓存
服务器
网络
多线程
public static void simpleIo() throws IOException { InputStream input = new BufferedInputStream(new FileInputStream("/home/lg/Pictures/wallpapers/2047.png")); OutputStream out = new BufferedOutputStream(new FileOutputStream("dem.png")); byte[] buf = new byte[1024]; while (input.read(buf) > 0) { out.write(buf); } out.flush(); }
基于字符的
public static void simpleIo() throws IOException { BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); PrintWriter out = new PrintWriter(System.out, true); String myLine; while ((myLine = input.readLine()) != null) { out.println(myLine); } } // new String(char[] chars, "utf-8") 字节序列到字符串的转换
* PrintWriter 与 BufferedWriter 区别
1. PrintWriter的print、println方法能够接受任意类型的参数,而BufferedWriter的write方法只能接受字符、字符数组和字符串;
2. PrintWriter的println方法自动添加换行,BufferedWriter须要显示调用newLine方法;
3. PrintWriter的方法不会抛异常,若关心异常,须要调用checkError方法看是否有异常发生;
4. PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush);
5. PrintWriter的构造方法更广。
3、Java 中的 socket 编程
服务端
public class Server { public static void main(String[] args) { { ServerSocket myServer = null; Socket acceptSocket = null; BufferedReader input = null; PrintWriter output = null; try { myServer = new ServerSocket(4000); while (true) { acceptSocket = myServer.accept(); input = new BufferedReader(new InputStreamReader(acceptSocket.getInputStream())); output = new PrintWriter(acceptSocket.getOutputStream(), true); String rLine; while ((rLine = input.readLine()) != null) { System.out.println("服务器接受到一条消息:\n" + rLine + "\n---------------\n\n"); if (rLine.equals("hello server")) { System.out.println("而后打了个招呼\n---------------\n\n"); output.println("hello client"); } else { System.out.println("而后嘲讽了一波\n---------------\n\n"); output.println("233333"); } } } } catch (IOException e) { e.printStackTrace(); } } } }
客户端
public class Client { private static Socket myClient; //静态初始化时处理异常 static { try { myClient = new Socket("localhost", 4000); } catch (IOException e) { e.printStackTrace(); } } private static void sendMessage() { try { PrintWriter output = new PrintWriter(myClient.getOutputStream(), true); BufferedReader readConsole = new BufferedReader(new InputStreamReader(System.in)); String cline; while ((cline = readConsole.readLine()) != null) { output.println(cline); System.out.println("消息发送成功!\n---------------\n\n"); } } catch (IOException e) { e.printStackTrace(); } } private static void receiveMessage() { try { BufferedReader input = new BufferedReader(new InputStreamReader(myClient.getInputStream())); // 这里省略掉了 OutputStream(System.out), 直接使用打印替代 String rLine; while ((rLine = input.readLine()) != null) { System.out.println("客户端收到来自服务器的消息:\n" + rLine + "\n---------------\n\n"); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new Thread(Client::sendMessage).start(); new Thread(Client::receiveMessage).start(); } }
说明:
客户端使用了两个线程:一个用于发送消息,另外一个用于接受消息。服务端只考虑了一个 socket 链接,所以并无用多线程。实际上,服务端一般须要同时处理多个 socket 链接,所以可考虑多线程。创建一个线程池,对于每一个 socket 链接,分派一个线程去处理。
4、NIO 与 socket 编程
1. 处理流程对比
普通的 BIO,消息要等待处理,须要先放入操做系统的 Socket 缓冲区(阻塞的SendQ与RecvQ队列),这样一直等待,直到 sending socket 或 receiving socket 就位后,才进行处理。
而 NIO,是 消息进入到操做系统的 Socket 缓冲区,直接复制到 Buffer (ByteBuffer.allocate)中,抑或直接进入与系统底层关联的缓冲区(ByteBuffer.allocateDirector)。不用一直等待 sending socket 或 receiving socket 就续就进行处理,是非阻塞的。
须要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操做的阻塞是使用CPU的,真正在"干活",并且这个过程很是快,属于memory copy,带宽一般在1GB/s级别以上,能够理解为基本不耗时。
下图是几种常见I/O模型的对比:(图片来自:UNIX网络编程 -- I/O复用:select和poll函数)
(摘自 Linux IO模式及 select、poll、epoll详解 )
当用户进程调用了select,那么整个进程会被block
,而同时,kernel会“监视”全部select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操做,将数据从kernel拷贝到用户进程。注意与 nio 的关系:在IO multiplexing Model中,实际中,对于每个socket,通常都设置成为non-blocking,可是,如上图所示,整个用户的process实际上是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
2.线程模型对比
NIO一个重要的特色是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操做是同步阻塞的(消耗CPU但性能很是高)。
回忆BIO模型,之因此须要多线程,是由于在进行I/O操做的时候,一是没有办法知道到底能不能写、能不能读,只能"傻等",即便经过各类估算,算出来操做系统没有能力进行读写,也无法在socket.read()和socket.write()函数中返回,这两个函数没法进行有效的中断。因此除了多开线程另起炉灶,没有好的办法利用CPU。
NIO的读写函数能够马上返回,这就给了咱们不开线程利用CPU的最好机会:若是一个链接不能读写(socket.read()返回0或者socket.write()返回0),咱们能够把这件事记下来,记录的方式一般是在Selector上注册标记位,而后切换到其它就绪的链接(channel)继续进行读写。
3.事件模型
NIO的主要事件有几个:读就绪、写就绪、有新链接到来。Selector 主要围绕这几个事件作分发。注册全部感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。
服务端
public class Server { private static Selector selector; static { try { selector = Selector.open(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { sscRegister(); } private static void sscRegister() { try { ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); ssc.socket().bind(new InetSocketAddress("localhost", 8000)); ssc.register(selector, SelectionKey.OP_ACCEPT); dispatcher(selector); } catch (IOException e) { e.printStackTrace(); } } private static void dispatcher(Selector selector) { try { while (true) { int select = selector.select(); if (select == 0) { System.out.println("返回 0 表示 SelectedKeys 未更新"); continue; } Iterator<SelectionKey> selectedIterator = selector.selectedKeys().iterator(); while (selectedIterator.hasNext()) { SelectionKey key = selectedIterator.next(); selectedIterator.remove(); // 只是从 SelectedKeys 中移除,而并非从整个选择器的键集中移除。 if (!key.isValid()) { continue; } if (key.isConnectable()) { System.out.println(233); } if (key.isAcceptable()) { accept(key); } if (key.isReadable()) { read(key); } } } } catch (IOException e) { e.printStackTrace(); } } private static void accept(SelectionKey key) { ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); try { SocketChannel socketChannel; if ((socketChannel = serverSocketChannel.accept()) != null) { // 服务端非阻塞时,直接返回 null socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } } catch (IOException e) { e.printStackTrace(); } } private static void read(SelectionKey key) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); try { if (channel.read(buffer) > 0) { System.out.println("客户端:" + new String(buffer.array())); buffer.clear(); } } catch (IOException e) { e.printStackTrace(); } finally { buffer.clear(); } } }
客户端
public class Client { private static void clientSendMsg() { try { SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", 8000)); System.out.println("已向服务器发出请求!"); String msg = "hello" + Thread.currentThread().getName(); ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); client.write(buffer); buffer.flip(); Thread.sleep(5); client.close(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { new Thread(Client::clientSendMsg, "Thread-A").start(); } }
参考文章
https://tech.meituan.com/nio.html?utm_source=tool.lu
https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html
https://www.bysocket.com/?p=615 (大小端模式)