学习笔记--3.网络IO

3.网络IO

3.1 初步认识IO

3.1.1 什么是 IO 以及 IO 流的作用

I/O 实际上是input和output,也就是输入和输出。而流其实是一种抽象的概念,它表示的是数据的无结构化传递。


IO流的作用.png
IO流的应用场景.png

3.1.2 Java 中的 IO 流以及 IO 流的分类

Java中的IO体系:在Java中I/O流操作的类很多,但是核心体系实际上就只有FileInputStreamOutputStreamReaderWriter

IO流的分类:

  1. 字节流:操作的数据单元是8位的字节。InputStreamOutputStream作为抽象基类。字节流能处理所有文件。

  2. 字符流:操作的数据单元是字符。以WriterReader作为抽象基类。字符流只能处理文本文件。

字符流:操作的数据单元是字符。以Writer、Reader作为抽象基类。

IO流的分类2.png
IO流的分类.png

3.1.3 IO流实战

public class FileInputDemo {

 public static void main(String[] args) throws IOException {
     FileInputStream fileInputStream = new FileInputStream("d:/test.txt");
     int i = 0;

     while ((i=fileInputStream.read()) != -1) {
         System.out.print((char)(i));
     }
 }
}

3.2 深入分析Java中的IO流

3.2.1 IO流的数据来源及操作的

IO流的数据源:

  1. 硬盘

  2. 内存

  3. 键盘

  4. 网络

public class TestDemo {

 public static void main(String[] args) throws IOException {
 int i = 0;

 // 硬盘
 FileInputStream fileInputStream = new FileInputStream("D:/test.txt");

 while ((i = fileInputStream.read()) != -1) {
 System.out.println((char)i);
 }

 // 内存
 String str = "test data";
 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes());
 while ((i = byteArrayInputStream.read()) != -1) {
 System.out.println((char)i);
 }


 // 键盘
 //Scanner
 InputStream inputStream = System.in;
 while ((i = inputStream.read()) != -1) {
 System.out.print((char) i);
 }

 // 网络IO
 Socket socket = null;
 socket.getInputStream();
 socket.getOutputStream();
 }
}

3.2.2 本地磁盘文件操作之File

3.2.3 基于文件的字节输入输出流实战

public class InputAndOutputDemo {
 public static void main(String[] args) throws IOException {
 InputStreamReader inputStreamReader = new InputStreamReader(System.in);
 BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
 String path = bufferedReader.readLine();
 FileInputStream fileInputStream = new FileInputStream(new File(path));
 FileOutputStream fileOutputStream = new FileOutputStream("D:/test.jpg");
 int len = 0;
 // 缓存 一次写入多个字节,否则一次写入1字节
 byte[] buffer = new byte[1024];
 while ((len = fileInputStream.read(buffer)) != -1) {
 fileOutputStream.write(buffer,0, len);
 }

 inputStreamReader.close();
 bufferedReader.close();
 fileInputStream.close();
 fileOutputStream.close();
 }
}

流使用后一定要及时关闭,也可以使用如下图所示,将流写入到try的括号内,这样try结束后会自动关闭流。前提是流的操作类必须实现Closeable接口.

3.2.4 深入浅出 read

深入浅出 read.png

3.2.5 基于内存的字节输入输出流实战

public class MemroryDemo {

 public static void main(String[] args) throws IOException {
 String str = "Hello World";
 // 从内存中读数据
 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes());
 // 写入到文件
 FileOutputStream fileOutputStream = new FileOutputStream("D:/TEST1.txt");
 // 写入到内存中
 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
 int i = 0;
 while ((i = byteArrayInputStream.read()) != -1) {
 byteArrayOutputStream.write(Character.toUpperCase((char) i));
 fileOutputStream.write(Character.toUpperCase((char) i));
 }

 System.out.println(byteArrayOutputStream.toString());
 // 因为内存流并没有操作实际的文件所以不需要关闭
 fileOutputStream.close();
 }
}

3.2.6 基于缓冲流的输入输出实战

缓冲流简介: 缓冲流是带缓冲区的处理流,它会提供一个缓冲区,缓冲区的作用的主要目的是:避免每次和硬盘打交道,能够提高输入/输出的执行效率。

缓冲流与之前的byte[] buffer = new byte[1024];一样利用了缓冲区的概念,默认缓冲区为8KB.

public class BufferDemo {
    public static void main(String[] args) {
        int i = 0;
        try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("d:/test.txt"));
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("d:/test1/txt"));
        ){
            byte[] buffer = new byte[1024];
            while ((i=bufferedInputStream.read(buffer)) != -1) {
                bufferedOutputStream.write(buffer);
            }
             bufferedOutputStream.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.2.7 详解 Flush

缓冲区写时,若缓冲区没有写满,不会真正的写磁盘,需要手动flushclose也会触发flush

3.2.8 基于文件的字符输入输出流实战

public class ReadWriteDemo {

    public static void main(String[] args) {
     try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("d:/test.txt"))){
       InputStreamReader inputStreamReader = new InputStreamReader(bis, "UTF-8");
       BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
         System.out.println(bufferedReader.readLine());


         // 字符输出流
         OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("d:/test_1.txt"), "UTF-8");
         BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
         bufferedWriter.write("爱你 媳妇");
            bufferedWriter.flush();
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }

    }
}

3.2.9 序列化和反序列化

序列化: 是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化。

反序列化: 是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化。

3.3 网络IO

3.3.1 Socket和ServerSocket

3.3.2 网络通信协议分析

网络分层模型:

网络分层模型

请求发送过程
请求接收过程

3.3.3 使用Socket实现RPC通信

什么是 RPC 协议:RPC(Remote Procedure Call) 远程过程调用,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。一般用来实现部署在不同机器上的系统之间的方法调用,使得程序能够像访问本地系统资源一样,通过网络传输去访问远端系统资源。

3.4 深入分析NIO

3.4.2 深入分析5中IO模型

  1. 阻塞IO
    进程阻塞,影响性能。


    阻塞IO
  2. 非阻塞IO
    每个socket都创建一个线程进行轮询,会创建多个线程,影响性能。


    非阻塞
  3. IO复用


    IO复用
  • select/poll
    适用于服务器需要处理多个socket。最大的缺陷是受陷于系统开销。最大1024,随着该值的增大,因为轮询增加,因此导致性能下降,增加网络延迟。(JDK1.5之前使用)

  • epoll
    为了解决select/poll的缺陷。

  • 对单个进程所打开的连接数没有限制(受限于硬件)

  • 利用每个fd()callback()函数来实现异步回调,省略了轮询开销。

  • mmp (减少内存复制),有点像AIO(伪AIO)
    epoll类似与,打电话点餐,然后做其他事。餐馆做好饭后打电话,让我去饭店取。
    mmap:内核空间和用户空间映射同一块内存,因此不存在复制。


    mmap
  1. 信号驱动IO


    信号驱动IO

类似于epoll.

  1. 异步IO(AIO)
    AIO在发起读请求后,内核在数据准备好后,会发起信号,并把数据发送到用户空间。
    与epoll的不同之处在于,AIO是打电话点餐后,餐馆不仅做好饭,还送到我家,再打电话给我让我取。
异步IO

5种IO模型都是为了提升服务端并处处理连接的数量。

3.4.3 NIO的概述以及应用

NIO 从JDK1.4 提出的,本意是New IO,它的出现为了弥补IO的不足,提供了更高效的方式。

NIO的新特性

核心组件

  • 通道( Channel ): Java NIO数据来源,可以是网络,也可以是本地磁盘
  • 缓冲区( Buffer ):数据读写的中转区。
  • 选择器( Selectors ):异步IO的核心类,可以实现异步非阻塞IO,一个selectors可以管理多个通道Channel。

IO 和 NIO的区别

IO 和 NIO的区别

3.4.4 NIO 详解 Channel 和 Buffer

Channel都是对buffer进行操作的。

Channel 的实现

  • FileChannel : 从文件中读写数据。
  • DatagramChannel : 通过 UDP 协议读写网络中的数据。
  • SocketChannel : 通过 TCP 协议读写网络中的数据。
  • ServerSocketChannel : 监听一个 TCP 连接,对于每一个新的客户端连接都会创建一个 SocketChannel 。

什么是Buffer
buffer 是一个对象,它包含了需要写入或者刚读出的数据,最常用的缓冲区类型是 ByteBuffer。

public class NIOReadWriteDemo {

    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("d:/test.txt");
             FileOutputStream fos = new FileOutputStream("d:/test_1.txt")) {

            //FileChannel 针对本地文件进行操作
            FileChannel fic = fis.getChannel();
            FileChannel foc = fos.getChannel();

            // 分配缓冲区大小
            ByteBuffer byteBuffer = ByteBuffer.allocate(100);
            // byteBuffer 与byteBuffer1等价
            //byte[] bytes = new byte[30];
            //ByteBuffer byteBuffer1 = ByteBuffer.wrap(bytes);

            // 写数据到缓冲区
            byteBuffer.put("大家好,这是NIOReadWriteDemo\n".getBytes());
            // 缓冲区反转,表示从读转到写
            byteBuffer.flip();
            // 从缓冲区写数据到文件
            foc.write(byteBuffer);

            //清空缓冲区
            byteBuffer.clear();
            int len = 0;

            // nio读文件 写文件
            while ((len = fic.read(byteBuffer)) != -1) {
                System.out.println(new String(byteBuffer.array()));
                byteBuffer.flip();
                foc.write(byteBuffer);
                byteBuffer.clear();
            }

        } catch (Exception e) {

        }
    }
}

3.4.5 剖析缓冲区的内部细节

Buffer的本质:缓冲区本质上是一块可以写入数据,以及从中读取数据的内存,实际上也是一个byte[]数据,只是在NIO中被封装成了NIO Buffer对象,并提供了一组方法来访问这个内存块,要理解buffer的工作原理,需要知道几个属性:

  • capacity
  • position
  • limit

3.4.7 SocketChannel 和ServerSocketChannel

3.4.8 选择器Selector

什么是SelectorSelector(选择器,多路复用器)是Java NIO中能够检测一到多个NIO通道,是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

Selector的工作流程

Selector的服务器实例

public class NIOSelectorServer {

    static Selector selector = null;

    public static void main(String[] args) {
        try {
            selector = Selector.open();
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 设置连接的非阻塞
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.bind(new InetSocketAddress("localhost", 8080));
            // 监听连接事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                // 阻塞方法
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    // 每次都删除 避免重复处理
                    iterator.remove();
                    if (selectionKey.isAcceptable()) {
                        handleAccept(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        handleRead(selectionKey);
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void handleAccept(SelectionKey selectionKey) {
        ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
        try {
            SocketChannel socketChannel = channel.accept();
            // 设置IO的非阻塞
            socketChannel.configureBlocking(false);
            if (socketChannel.isConnectionPending()) {
                socketChannel.finishConnect();
            }

            socketChannel.write(ByteBuffer.wrap("Hello, I‘m Hades. This is a Server".getBytes()));
            // 注册SocketChannel的读事件
            socketChannel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void handleRead(SelectionKey selectionKey) {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try {
            socketChannel.read(buffer);
            System.out.println("time:" + System.currentTimeMillis() + "   Server Recv: " + new String(buffer.array()));
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
}

Selector的客户端实例

public class NIOSelectorClient {

    static Selector selector = null;

    public static void main(String[] args) {
        try {
            selector = Selector.open();
            SocketChannel socketChannel = SocketChannel.open();
            // 设置为非阻塞
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("localhost", 8080));
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            while (true) {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isConnectable()) {
                        handleConnect(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        handleRead(selectionKey);
                    }
                }

            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleConnect(SelectionKey selectionKey) {
        try {
            SocketChannel channel = (SocketChannel) selectionKey.channel();
            if (channel.isConnectionPending()) {
                channel.finishConnect();
            }

            channel.write(ByteBuffer.wrap("Hello Server! I'm a Client".getBytes()));
            channel.register(selector, SelectionKey.OP_READ);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleRead(SelectionKey selectionKey) {
        try {
            SocketChannel channel = (SocketChannel) selectionKey.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            channel.read(buffer);
            System.out.println("time:" + System.currentTimeMillis() + " Client Recv:" + new String(buffer.array()));
        } catch (Exception e) {

        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容