Netty學習前傳

差不多有一個多月沒有更新了,因爲這段時間在做一件事情:學習netty,當然並沒有學完現在相當於是一個學習前的一個前序。我覺得是學習netty之前必須要先了解的底層知識點。一下就是我的一些學習記錄。後續還會繼續更新。

記錄

  1. jdk動態代理爲什麼要實現接口:因爲底層jdk動態代理生成的代理類需要繼承Proxy類來實現,而java只能單繼承 所以需要實現接口才能達到目的(字節碼實現)
  2. mysql 數據庫的底層數據結構分析
    • 底層數據結構採用btree的變種 B+Tree實現 mysql的表引擎分爲 mysiam(離散) 和 innodb(聚集)
    • innodb創建時爲什麼一定需要主鍵:因爲innodb索引和數據是在一起的,而在創建之初 默認是採用B+Tree的結構來存儲數據,如果沒 有主鍵會報錯
    • 聯合索引最左原則
  3. 負載均衡算法
    • 隨機
    • 加權
    • 平滑加權

Netty

  1. 學習記錄

  2. git常用命令需要學習

    地址: https://www.cnblogs.com/my--sunshine/p/7093412.html

  3. Netty大致的執行流程

    • 創建兩個NioEventLoopGroup線程組 bossGroup(接收請求) workerGroup(處理請求)
    • 創建ServerBootstrap用於啓動服務類 並將兩個線程組放入group中
    • 定義NioServerSocketChannel 通道(裏面通過反射生成實例)
    • 構造我們的子處理器childHandler
      • 子處理器繼承ChannelInitializer<SocketChannel>
      • 在pipeline加入HttpServerCodec(這個是對請求做編解碼工作完成HttpRequestDecoder, HttpResponseEncoder 所做的事情)
      • 並在pipeline管道中加入 自定義的處理器並繼承SimpleChannelInboundHandler<HttpObject>
      • 重寫channelRead0方法 這裏主要工作是 讀取客戶端發過來的請求 並返回響應
      • 後面主要是對返回內容的解析幷包裝
    • 啓動綁定端口
    • 最後優雅關閉
  4. Netty sockte編程

    • 簡易聊天室的編程 主要是對應 handler的對應事件處理方法做加深
      • channelRead0 收到客戶端任何一個消息時 這裏得到調用
      • handlerAdded 服務端與客戶端建立連接 觸發事件
      • handlerRemoved 客戶端與服務器連接斷開 觸發事件
      • channelActive 表示連接處於活動狀態 觸發事件
      • channelInactive 表示連接不處於 活動狀態 觸發事件
      • exceptionCaught 觸發異常情況 觸發的事件
    • 空閒事件監聽 IdleStateHandler(可以用作心跳檢測)
      • 讀空閒
      • 寫空閒
      • 讀寫空閒
  5. WebSockte實現與分析

    • 解決http1.0 1.1產生的問題
      • http的無狀態問題(http雖然可以採用cookie session來解決)
      • http是基於請求和響應模式的(一定是先有客戶端先發請求)
      • http1.0協議 一次請求響應之後會斷開 http1.1 可以通過keepActlive設置(需要重複建立新的連接)
      • 輪詢的方式 可以解決長連接的問題(產生的問題:消息時效性問題、資源網絡帶寬浪費、每次輪詢包含大量無效請求信息)
    • webSockte 可以建立客戶端與服務器端真正的長連接,一旦建立服務器端和客戶端就是一個平等關係(雙向數據傳遞)
    • webSockte 是構建在http協議之上的協議 (屬於html5規範的一部分)
    • 需要服務器端支持
    • Netty實現WebSockte
      • 大部分的和以前的實現其實是一樣的主要有以下注意:

           / 這裏因爲webSockte 本身也是基於http 所以需要加上http的編解碼器
           pipeline.addLast(new HttpServerCodec());
           // 以塊的方式來寫的處理器
           pipeline.addLast(new ChunkedWriteHandler());
           // http 聚合處理器 netty對http請求採取分段的方式(特別重要的處理器)
           pipeline.addLast(new HttpObjectAggregator(8192));
           // 處理http webSockte處理器(用於處理webSockte的握手以及processing of control frames (Close, Ping, Pong))
           // 注意這裏的數據是以 frames 方式來傳遞
           pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        
           **
           * 需要注意 這裏由於是處理 TextFrame  所以泛型中需要加入 TextWebSocketFrame
           */
           public class MyWebSockteServerTextFramesHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
              @Override
              protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
                 System.out.println("收到的消息:"+msg.text());
        
                 // 注意由於我們傳遞的是TextWebSocketFrame 如果用普通的字符串 handler是無法解析的
                 ctx.channel().writeAndFlush(new TextWebSocketFrame("服務器時間:"+ LocalDate.now()));
              }
        
      • 客戶端主要採用js實現

           <!DOCTYPE html>
           <html lang="en">
           <head>
              <meta charset="UTF-8">
              <title>WebSockte客戶端</title>
           </head>
           <body>
           <script type="text/javascript">
              var sockte;
              if(window.WebSocket){
                 sockte = new WebSocket("ws://localhost:8899/ws");
                 sockte.onmessage = function (ev) {
                       var ta = document.getElementById("responseText");
                       ta.value = ta.value + "\n" + ev.data;
                 }
                 sockte.onopen = function (ev) {
                       var ta = document.getElementById("responseText");
                       ta.value = "連接開啓";
                 }
                 sockte.onclose = function (ev) {
                       var ta = document.getElementById("responseText");
                       ta.value = ta,value  + "\n" + "連接關閉";
                 }
              } else {
                 alert('瀏覽器不支持');
              }
              function send(msg) {
                 if(!window.WebSocket){
                       return;
                 }
                 if(sockte.readyState == WebSocket.OPEN){
                       sockte.send(msg);
                 }else{
                       alert("連接尚未開啓");
                 }
              }
        
        
        
        
           </script>
        
        
        
           <form onsubmit="return false">
              <textarea name="message" style="width: 400px;height: 200px"></textarea>
              <input type="button" value="發送數據" onclick="send(this.form.message.value)">
        
              <h3>服務端輸出:</h3>
              <textarea id="responseText" style="width: 400px;height: 300px"></textarea>
              <input type="button" onclick="javascript:document.getElementById('responseText').value='' " value="清空內容">
           </form>
        
        
        
        
           </body>
           </html>
        
  6. google protobuf 使用方式分析 (推薦 Effective Java 這本書)

    • 自定義協議 體積更小的 序列化與反序列化

    • RPC庫使用原理 :RMI(remote method invocation 遠程方法調用)

      • client:(序列化字節碼傳輸給server)
      • server: (反序列化--一系列業務邏輯--序列化傳輸給client)
      • 代碼生成的概念 client==> stub(樁) server==> skeleton(骨架)
      • 序列化(對象轉字節)與反序列化(字節轉對象) RPC的重要機制 也叫做編碼與解碼
      • RPC:Remote Procedure Call(遠程過程調用)(大部分RPC框架是跨語言)(使用步驟:)
        • 定義接口說明文件:描述對象(結構體)、對象成員、接口方法等一系列信息
        • 通過RPC框架所提供的編譯器,將接口說明文件編譯成具體語言文件
        • 在客戶端與服務器端分別引入RPC編譯器所生成的文件,即可像調用本地方法一樣調用遠程方法
      • webService廣義上來講也是一種RPC(與真正的RPC的區別)
        • 編解碼效率
        • 傳輸壓縮比
        • 傳輸方式RPC一般是基於sockte傳輸 webService一般是基於http
    • 引入 google protobuf

    • protobuf可以解決的問題

      • 本身java自帶的序列化機制只是針對java而言如果其他的語言是實現不了的
      • 對於簡單的數據我們可以自定義編碼格式,但是針對運行時解碼對性能有一定的影響
      • 採用xml的方式 雖然xml標記語言是人類可讀的並且各種語言針對xml都有對應的解析庫,但是xml是非常佔據空間的並且對於xml的變量dom樹也是非常耗費性能的
    • 關於 .proto文件解析說明

      
         syntax = "proto2";
         // 默認包
         package tutorial;
         // 如果顯示的定義了java_package 那麼就以定義的爲準
         option java_package = "com.example.tutorial";
         // java_outer_classname 定義生成文件的類名 如果沒有定義就以 文件名轉駝峯的方式:my_proto.proto=>MyProto
         option java_outer_classname = "AddressBookProtos";
      
         message Person {
         // 這裏的 “=1、=2“ 這裏並不是賦值而是一種標記 表示在二進制編碼中解碼中
         // 1-15可以用在 標記常用的字符 因爲這裏會少一個字節表示 16以及之後的可以用作不常用的做標記 這樣可以提高性能
         // 這裏需要注意標記 與之對應的同一層次上的是不能重複的   
         // required 表示這個必須提供的 否則在初始化的時候將拋出RuntimeException 在解析的時候將拋出IOException
         required string name = 1; 
         required int32 id = 2;
         // optional表示可以不提供的 如果沒有將取設置的默認值 如:optional PhoneType type = 2 [default = HOME];
         // 否則 zero for numeric types, the empty string for strings, false for bools
         //**注意:在使用required時我們應該非常小心 因爲一旦後面這個字段變成不必要時 前面定義將會出現不兼容問題
         optional string email = 3;
      
         enum PhoneType {
            MOBILE = 0;
            HOME = 1;
            WORK = 2;
         }
      
         message PhoneNumber {
            required string number = 1;
            optional PhoneType type = 2 [default = HOME];
         }
         // repeated 表示可以重複出現 也就是說這是一個list
         repeated PhoneNumber phones = 4;
         }
      
         message AddressBook {
         repeated Person people = 1;
         }
      
    • 如果對錶量消息類型還有不理解的可以參考這個地址:https://developers.google.com/protocol-buffers/docs/proto

    • protobuf 實戰

      • proto文件(這裏先貼代碼 後續需要注意的地方將後面寫出)
         syntax = "proto2";
      
         package com.lc.protobuf;
      
         option optimize_for = SPEED;
         option java_package = "com.lc.netty.sixthexample";
         option java_outer_classname = "MyMessage";
      
         message MyMessageInfo{
            enum DataType{
               Person = 1;
               Dog = 2;
               Cat = 3;
            }
            required DataType date_type = 1;
            //oneof的意思:如果有多個可選字段,在某一個時刻只能只有一個值被設置,可以節省內存空間
            oneof dataBody{
               Person person = 2;
               Cat cat = 3;
               Dog dog = 4;
            }
         }
         message Person{
            required string name = 1;
            optional int32 age = 2;
            optional string address = 3;
         }
         message Cat{
            required string name = 1;
            optional int32 age = 2;
         }
         message Dog{
            required string name = 1;
            optional int32 age = 2;
         }
      
    • 服務端

      • 編寫初始化服務端(由於基本上是差不多的 代碼就不貼了)
      • 編寫 initiailzer pipline初始化文件(說明一下幾個處理器的作用)
        • ProtobufEncoder:用於對Probuf類型序列化。
        • ProtobufVarint32LengthFieldPrepender:用於在序列化的字節數組前加上一個簡單的包頭,只包含序列化的字節長度。
        • ProtobufVarint32FrameDecoder:用於decode前解決半包和粘包問題(利用包頭中的包含數組長度來識別半包粘包)
        • ProtobufDecoder:反序列化指定的Probuf字節數組爲protobuf類型
        • MyProtoBufServerHandler是我們自己需要編寫的handler
         public class MyProtoBufInitializer extends ChannelInitializer<SocketChannel> {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
               ChannelPipeline pipeline = ch.pipeline();
               pipeline.addLast(new ProtobufVarint32FrameDecoder());
               // 解碼器 將字節數組轉化成真正的對象
               pipeline.addLast(new ProtobufDecoder(MyMessage.MyMessageInfo.getDefaultInstance()));
               pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
               pipeline.addLast(new ProtobufEncoder());
               pipeline.addLast(new MyProtoBufServerHandler());
      
            }
         }
      
      • 編寫對應的handler
        • 這裏這樣寫的原因是因爲netty默認只是支持一種協議 這裏採用將 實體數據加一個描述字段來對類型做區分以用不同的處理方式
            public class MyProtoBufServerHandler extends SimpleChannelInboundHandler<MyMessage.MyMessageInfo> {
            @Override
            protected void channelRead0(ChannelHandlerContext ctx, MyMessage.MyMessageInfo msg) throws Exception {
               System.out.println("收到客戶端的消息:");
               switch (msg.getDateType()){
                     case Cat:
                        System.out.println(msg.getCat().getAge());
                        System.out.println(msg.getCat().getName());
      
                        break;
                     case Dog:
                        System.out.println(msg.getDog().getAge());
                        System.out.println(msg.getDog().getName());
                        break;
                     case Person:
                        System.out.println(msg.getPerson().getAddress());
                        System.out.println(msg.getPerson().getAge());
                        System.out.println(msg.getPerson().getName());
                        break;
                     default:
               }
            }
      
            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
      
               cause.printStackTrace();
               ctx.close();
            }
         }
      
    • 客戶端由於是基本上一樣的所以不貼代碼了

    • Netty 傳輸和springMVC的區別、

      • 使用springMVC 通過@GetMapping(...="/users/user/123") @PostMapping(...="/users/user/")
        • springMvc 通過DisPatcherServlet 控制器 分發到不同的controller
      • Netty本質上其實也是一樣 只不過netty沒有框架的支持路由分發
    • 使用 Netty+protobuf 如何進行合理的工程創建(前提:使用Git作爲版本控制)

      • git submodule(是什麼):git倉庫裏的一個倉庫
        • ServerProject:項目開發的工程
        • Protobuf-java:生成protojava文件工程
        • data.proto 源代碼文件
        • ClientProject:項目開發的工程
        • 通過gitsubmodule方式將Protobuf-java引入到ServerProject工程中
        • 這種方式的缺點
          • git的會採用分支的方式進行 分支切換沒有跟上
      • git subtree(思路和 git submodule一樣 只是只有一個倉庫)
  7. Apache Thrift

    • Thrift傳輸格式

      • TBinaryProtocol 二進制格式
      • TCompactProtocol 壓縮格式
      • TJSONProtocol JSON格式
      • TSimpleJSONProtocol 提供JSON只寫協議 生成的文件很容易通過腳本語言解析
      • TDebugProtocol 使用易懂的可讀文本格式 以便於debug
    • Thrift 數據傳輸方式

      • TSocket 阻塞式socket
      • TFramedTransport 以frame爲單位進行傳輸,非阻塞式服務中使用
      • TFileTransport 以文件形式進行傳輸
      • TMemoryTransport 將內存用於I/O java實現時內部實際使用簡單的ByteArrayOutputStream
      • TZlibTransport 使用zlib進行壓縮,與其他的傳輸方式聯合使用 當前無java實現
    • Thrift 支持的服務模型

      • TSimpleServer 簡單的單線程服務模型,常用與測試
      • TThreadPoolServer 多線程服務模型,使用標準的阻塞式IO
      • TNonblockingServer 多線程服務模型,使用非阻塞式IO(需要使用TFramedTransport數據傳輸方式)
      • THsHaServer THsHa引入了線程池去處理,其模型吧讀寫任務放到線程池去處理;Half-aysnc是在處理IO事件上(accept/read/write io),Half-sync用於handler對rpc的同步處理
    • 使用:

      • IDL文件
         namespace java thrift.generated
      
         typedef i16 short
         typedef i32 int
         typedef i64 long
         typedef bool boolean
         typedef string String
      
         struct Person {
            1: optional String username,
            2: optional int age,
            3: optional boolean married
         }
      
         exception DataException {
            1: optional String message,
            2: optional String callStack,
            3: optional String date
         }
      
         service PersonService {
            Person getPersonByUsername(1: required String username) throws(1: DataException dataException),
            void savePerson(1: required Person person) throws(1:DataException dataException)
         }
      
      
      • 實現類
         package com.lc.thrift;
      
         import org.apache.thrift.TException;
         import thrift.generated.DataException;
         import thrift.generated.Person;
         import thrift.generated.PersonService;
      
         public class ServicePersonImpl implements PersonService.Iface {
            @Override
            public Person getPersonByUsername(String username) throws DataException, TException {
               System.out.println("Go Client:"+username);
               Person person = new Person();
               person.setUsername(username);
               person.setAge(10);
               person.setMarried(false);
               return person;
            }
      
            @Override
            public void savePerson(Person person) throws DataException, TException {
               System.out.println("Go client params:");
               System.out.printf(person.getUsername());
               System.out.println(person.getAge());
               System.out.println(person.isMarried());
            }
         }
      
      
      • 服務端
         package com.lc.thrift;
      
         import org.apache.thrift.TProcessorFactory;
         import org.apache.thrift.protocol.TCompactProtocol;
         import org.apache.thrift.server.THsHaServer;
         import org.apache.thrift.server.TServer;
         import org.apache.thrift.transport.TFramedTransport;
         import org.apache.thrift.transport.TNonblockingServerSocket;
         import thrift.generated.PersonService;
      
         public class ThriftServer {
      
            public static void main(String[] args)  throws  Exception{
               TNonblockingServerSocket socket = new TNonblockingServerSocket(8899);
               THsHaServer.Args arg = new THsHaServer.Args(socket).minWorkerThreads(2).maxWorkerThreads(4);
               PersonService.Processor<ServicePersonImpl> processor = new PersonService.Processor<>(new ServicePersonImpl());
               // 協議工廠 壓縮
               arg.protocolFactory(new TCompactProtocol.Factory());
               // 傳輸層
               arg.transportFactory(new TFramedTransport.Factory());
               arg.processorFactory(new TProcessorFactory(processor));
               TServer server = new THsHaServer(arg);
               System.out.println("Thrift Server Started");
               server.serve();
            }
         }
      
      
      • 客戶端
         package com.lc.thrift;
      
         import org.apache.thrift.protocol.TCompactProtocol;
         import org.apache.thrift.protocol.TProtocol;
         import org.apache.thrift.transport.TFramedTransport;
         import org.apache.thrift.transport.TSocket;
         import org.apache.thrift.transport.TTransport;
         import thrift.generated.Person;
         import thrift.generated.PersonService;
      
         public class ThriftClient {
      
            public static void main(String[] args) {
               TTransport transport = new TFramedTransport(new TSocket("localhost",8899),600);
               TProtocol protocol = new TCompactProtocol(transport);
               PersonService.Client client = new PersonService.Client(protocol);
      
               try{
                     transport.open();
                     Person person = client.getPersonByUsername("張三");
                     System.out.println(person.getUsername());
                      System.out.println(person.getAge());
                     System.out.println(person.isMarried());
                     System.out.println("==============");
                     Person person1 = new Person();
                     person1.setUsername("李四");
                     person1.setAge(10);
                     person1.setMarried(true);
                     client.savePerson(person1);
      
      
      
               }catch (Exception e){
                     throw new RuntimeException(e.getMessage(),e);
               }finally {
                     transport.close();
               }
            }
         }
      
      
  8. gradlew介紹

    • gradlew => gradle wrapper
    • 目的
      • 本地沒有安裝gradle的情況下依然可以通過一條很簡單的命令就可以構建項目

      • 使用gradlew clean build構建

      • gradle wrapper 第一次構建會生成幾個重要的文件 gradlew、gradlew.bat 以及一個gradle文件

      • 我們可以在 build.gradle自定義配置設置屬性

           task wrapper (type:Wrapper){
              gradleVersion = '3.x' // 版本號
              distributionType ='all' //有all bin
              .....
           }
        
    • vi命令的小技巧
      • set nu 打開顯示行
      • Ctrl + F 向上翻屏
      • Ctrl + B 向下翻屏
      • Shift + A 到行最後
  9. gRPC

    • grpc和thrift之間有一些區別,不管在消息文件的編寫還是 對消息的實現

    • grpc的消息編寫

         syntax = "proto3";
      
         package com.lc.proto;
      
         option java_package = "com.lc.proto";
         option java_outer_classname = "StudentProto";
         option java_multiple_files = true;
      
      
         service StudentService {
            // 客戶端 發出一個請求 服務器端返回一個響應
            rpc GetRealNameByUsername(MyRequest) returns (MyResponse){}
            // 客戶端 發出一個請求 服務器端返回一個流式的響應
            rpc GetStudentsByAge(StudentRequest) returns (stream StudentResponse){}
            // 客戶端 發出一個流式請求 服務器返回一個響應List
            rpc GetStudentWrapperByAges(stream StudentRequest) returns (StudentResponseList){}
            // 客戶端 發出一個流式請求 服務器響應一個流式請求
            rpc BiTalk(stream StreamRequest) returns (stream StreamResponse) {}
         }
      
         message MyRequest {
            string username = 1;
         }
      
         message MyResponse {
            string realname = 2;
         }
      
         message StudentRequest{
            int32 age = 1;
         }
      
         message StudentResponse {
            string name = 1;
            int32 age = 2;
            string city = 3;
         }
      
         message StudentResponseList {
            repeated StudentResponse studentResponse = 1;
         }
      
         message StreamRequest {
            string request_info = 1;
         }
      
         message StreamResponse {
            string response_info = 1;
         }
      
      
    • 服務端實現

         package com.lc.grpc;
      
         import io.grpc.Server;
         import io.grpc.ServerBuilder;
      
         import java.io.IOException;
      
         public class GrpcServer {
            private Server server;
      
            private void start() throws IOException {
               // 初始化server對象
               this.server = ServerBuilder.forPort(8899).addService(new StudentServiceImpl()).build().start();
               System.out.println(" server started!");
               // 回調鉤子
               // Runtime 運行時對象 可以獲取有關環境相關信息
               Runtime.getRuntime().addShutdownHook(new Thread(()->{
                     System.out.println("關閉jvm");
                     GrpcServer.this.stop();// 關閉sockte
               }));
               System.out.println("執行到這裏");
            }
      
            private void stop(){
               if(null != this.server){
                     this.server.shutdown();
               }
            }
      
            private void awaitTermination() throws InterruptedException {
               if (null != this.server) {
                     this.server.awaitTermination();
               }
            }
      
            public static void main(String[] args) throws InterruptedException, IOException {
               GrpcServer server = new GrpcServer();
               server.start();
               server.awaitTermination();
      
            }
      
         }
      
      
    • service實現類的實現

      
         package com.lc.grpc;
      
         import com.lc.proto.*;
         import io.grpc.stub.StreamObserver;
      
         public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase {
      
            @Override
            public void getRealNameByUsername(MyRequest request, StreamObserver<MyResponse> responseObserver) {
      
               System.out.println("接收到客戶端的請求:"+request.getUsername());
               responseObserver.onNext(MyResponse.newBuilder().setRealname("張三").build());
               responseObserver.onCompleted();
            }
      
            @Override
            public void getStudentsByAge(StudentRequest request, StreamObserver<StudentResponse> responseObserver) {
               System.out.println("接收到客戶端的請求:"+request.getAge());
               responseObserver.onNext(StudentResponse.newBuilder().setAge(10).setCity("北京").setName("張三").build());
               try {
                     Thread.sleep(1000);
               } catch (InterruptedException e) {
                     e.printStackTrace();
               }
               responseObserver.onNext(StudentResponse.newBuilder().setAge(10).setCity("南京").setName("李四").build());
               try {
                     Thread.sleep(1000);
               } catch (InterruptedException e) {
                     e.printStackTrace();
               }
               responseObserver.onNext(StudentResponse.newBuilder().setAge(10).setCity("上海").setName("王五").build());
               try {
                     Thread.sleep(1000);
               } catch (InterruptedException e) {
                     e.printStackTrace();
               }
               responseObserver.onNext(StudentResponse.newBuilder().setAge(10).setCity("深圳").setName("楊柳").build());
               responseObserver.onCompleted();
            }
      
            // 客戶端發送一個流 服務端返回一個響應
            @Override
            public StreamObserver<StudentRequest> getStudentWrapperByAges(StreamObserver<StudentResponseList> responseObserver) {
               System.out.println("接收到客戶端的流式請求:----------");
               return new StreamObserver<StudentRequest>() {
                     @Override
                     public void onNext(StudentRequest value) {
                        System.out.println("接收到客戶端的請求:"+value.getAge());
                     }
      
                     @Override
                     public void onError(Throwable t) {
      
                     }
      
                     @Override
                     public void onCompleted() {
                        StudentResponseList build = StudentResponseList.newBuilder().build();
                        StudentResponseList build1 = StudentResponseList.newBuilder()
                                 .addStudentResponse(StudentResponse.newBuilder().setName("張三").setCity("南京").setAge(10).build())
                                 .addStudentResponse(StudentResponse.newBuilder().setName("李四").setCity("北京").setAge(10).build())
                                 .addStudentResponse(StudentResponse.newBuilder().setName("王五").setCity("上海").setAge(10).build())
                                 .build();
                        responseObserver.onNext(build1);
                        responseObserver.onCompleted();
                     }
               };
      
            }
      
            @Override
            public StreamObserver<StreamRequest> biTalk(StreamObserver<StreamResponse> responseObserver) {
               return new StreamObserver<StreamRequest>() {
                     @Override
                     public void onNext(StreamRequest value) {
                        System.out.println("onNext:"+value.getRequestInfo());
                        responseObserver.onNext(StreamResponse.newBuilder().setResponseInfo("你好:"+value.getRequestInfo()).build());
                     }
      
                     @Override
                     public void onError(Throwable t) {
                        System.out.println(t.getMessage());
                     }
      
                     @Override
                     public void onCompleted() {
                        responseObserver.onCompleted();
                     }
               };
      
            }
         }
      
      
    • 客戶端實現

      
         package com.lc.grpc;
      
         import com.lc.proto.*;
         import io.grpc.ManagedChannel;
         import io.grpc.ManagedChannelBuilder;
         import io.grpc.stub.StreamObserver;
      
         import java.time.LocalDateTime;
         import java.util.Iterator;
         import java.util.List;
         import java.util.concurrent.CountDownLatch;
         import java.util.concurrent.TimeUnit;
      
         public class GrpcClient {
      
            public static void main(String[] args) {
               ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost",8899)
                        .usePlaintext(true) //普通文本的連接方式
                        .build();
               StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(channel);
               StudentServiceGrpc.StudentServiceStub studentServiceFutureStub = StudentServiceGrpc.newStub(channel);
      
               /*
               MyResponse my = blockingStub.getRealNameByUsername(MyRequest.newBuilder().setUsername("lisi").build());
               System.out.println(my.getRealname());
      
               System.out.println("----------------------------");
      
               Iterator<StudentResponse> iter = blockingStub.getStudentsByAge(StudentRequest.newBuilder().setAge(10).build());
               while (iter.hasNext()){
                     StudentResponse next = iter.next();
                     System.out.println(next.getAge()+":"+next.getCity()+":"+next.getName());
               }
               System.out.println("-----------------------------");
               System.out.println("客戶端發送流到服務端");
      
      
               StreamObserver<StudentResponseList> streamObserver = new StreamObserver<StudentResponseList>() {
                     @Override
                     public void onNext(StudentResponseList value) {
                        System.out.println("接收到服務端的請求");
                        List<StudentResponse> studentResponseList = value.getStudentResponseList();
                        for (StudentResponse studentResponse : studentResponseList) {
                           System.out.println(studentResponse.getAge()+":"+studentResponse.getCity()+":"+studentResponse.getName());
                        }
                     }
                     @Override
                     public void onError(Throwable t) {
                        System.out.printf(t.getMessage());
                     }
      
                     @Override
                     public void onCompleted() {
                        System.out.println("------");
                     }
               };
               StreamObserver<StudentRequest> studentWrapperByAges = studentServiceFutureStub.getStudentWrapperByAges(streamObserver);
               studentWrapperByAges.onNext(StudentRequest.newBuilder().setAge(10).build());
               studentWrapperByAges.onNext(StudentRequest.newBuilder().setAge(20).build());
               studentWrapperByAges.onNext(StudentRequest.newBuilder().setAge(30).build());
               studentWrapperByAges.onCompleted();
               */
      
               StreamObserver<StreamResponse> streamResponseStreamObserver = new StreamObserver<StreamResponse>() {
                     @Override
                     public void onNext(StreamResponse value) {
                        System.out.println("接收到請求:");
                        System.out.println(value.getResponseInfo());
                        System.out.println("**************");
                     }
      
                     @Override
                     public void onError(Throwable t) {
                        System.out.println(t.getMessage());
                     }
      
                     @Override
                     public void onCompleted() {
                        System.out.println("---------end---------");
                     }
               };
               StreamObserver<StreamRequest> streamRequestStreamObserver = studentServiceFutureStub.biTalk(streamResponseStreamObserver);
               for(int i=0;i<10;i++) {
                     streamRequestStreamObserver.onNext(StreamRequest.newBuilder().setRequestInfo(LocalDateTime.now().toString()).build());
                     try {
                        Thread.sleep(1000);
                     } catch (InterruptedException e) {
                        e.printStackTrace();
                     }
               }
               streamRequestStreamObserver.onCompleted();
      
               try {
                     channel.awaitTermination(1, TimeUnit.SECONDS);
               } catch (InterruptedException e) {
                     e.printStackTrace();
               }
      
            }
         }
      
    • I/O 與NIO

      • java.io

        • 最核心的概念(Stream)流,面向流的編程。在java中一個流 要麼是輸入流,要麼是輸出流
      • java.nio

        • 最核心的概念有三個 Selector,Channel,Buffer。在java.nio中是面向塊(Block)或者是緩衝區(Buffer)編程

        • buffer本身就是一塊內存,底層實現上它實際上是一個數組。數據的讀和寫都是通過buffer實現的

        • 除了數組之外 buffer還提供了數據的結構化訪問方式,並且可以追蹤到系統的讀寫過程

        • java中的8種原生數據類型都有對應的buffer類型 IntBuffer LongBuffer ....

        • channel 指的是可以向其讀取或者寫入數據的對象 java中類似於Stream

        • 注意所有數據的讀寫都是通過buffer來進行的,永遠不會直接向channle寫入或者讀取數據的情況

        • 與Stream不同的是 channel是雙向的,一個流只能是InputStream 或者OutputStream,Channel打開後則可以進行讀取或者寫入、讀取操作。由於channel是雙向的,因此他能更好的反應出操作系統的真實情況:在linux系統中,底層底層操作系統的通道就是雙向的

        • 演示用例:

             public static void main(String[] args) {
                // 分配一個大小爲10的緩衝區 裏面只能放置整數
                IntBuffer intBuffer = IntBuffer.allocate(10);
                // 生成隨機數
                for(int i=0;i<intBuffer.capacity();++i){
                      // SecureRandom 生成的隨機數 更加具有隨機性
                      int randomNumer = new SecureRandom().nextInt(20);
                      intBuffer.put(randomNumer);
                }
                // 翻轉 這裏的作用是 在放入數據之後 如果需要進行讀 則需要進行翻轉 讀寫切換
                intBuffer.flip();
                // intBuffer 還有沒有元素
                while (intBuffer.hasRemaining()){
                      System.out.println(intBuffer.get());
                }
             }
          
          
             // 傳統io 如何切換到nio
             public static void main(String[] args)  throws  Exception{
                FileInputStream fileInputStream = new FileInputStream("niotest.txt");
                FileChannel channel = fileInputStream.getChannel();
                // 構造ByteBuffer對象並分配大小
                ByteBuffer byteBuffer = ByteBuffer.allocate(512);
                // 將文件對象讀入到 byteBuffer中
                channel.read(byteBuffer);
          
                byteBuffer.flip();
          
                while (byteBuffer.remaining() >0) {
                      byte b = byteBuffer.get();
                      System.out.println("char:"+(char)b);
                }
          
                fileInputStream.close();
             }
          
             // 輸出流
             public static void main(String[] args) throws  Exception{
          
                try(
                      FileOutputStream fileOutputStream = new FileOutputStream("niotest2.txt");
                      FileInputStream fileInputStream = new FileInputStream("niotest.txt");
                      ){
                      FileChannel channel1 = fileInputStream.getChannel();
                      FileChannel channel = fileOutputStream.getChannel();
          
                      ByteBuffer byteBuffer = ByteBuffer.allocate(512);
                      channel1.read(byteBuffer);
                      byteBuffer.flip();
                      while (byteBuffer.hasRemaining()){
                         channel.write(byteBuffer);
                      }
                }
             }
          
             public static void main(String[] args) throws Exception {
                FileOutputStream fileOutputStream = new FileOutputStream("niotest3.txt");
                FileChannel channel = fileOutputStream.getChannel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(512);
                // 將數據寫入buffer
                byte[] message = "Hello,message!".getBytes();
                for(int i=0;i<message.length;i++){
                      byteBuffer.put(message[i]);
                }
                byteBuffer.flip();
                channel.write(byteBuffer);
                fileOutputStream.close();
             }
          
        • 關於nio buffer中的三個重要的狀態屬性的含義:position limit capacity 一下是javadoc文檔中的內容:

          A container for data of a specific primitive type.
          <p> A buffer is a linear, finite sequence of elements of a specific
          primitive type. Aside from its content, the essential properties of a
          buffer are its capacity, limit, and position: </p>
          <blockquote>
          <p> A buffer's <i>capacity</i> is the number of elements it contains.
          capacity of a buffer is never negative and never changes. </p>
          <p> A buffer's <i>limit</i> is the index of the first element that sho
          not be read or written. A buffer's limit is never negative and is nev
          greater than its capacity. </p>
          <p> A buffer's <i>position</i> is the index of the next element to be
          read or written. A buffer's position is never negative and is never
          greater than its limit. </p>
          </blockquote>
          <p> There is one subclass of this class for each non-boolean primitive t
          <h2> Transferring data </h2>
          <p> Each subclass of this class defines two categories of <i>get</i> and
          <i>put</i> operations: </p>
          <blockquote>
          <p> <i>Relative</i> operations read or write one or more elements star
          at the current position and then increment the position by the number
          elements transferred. If the requested transfer exceeds the limit the
          relative <i>get</i> operation throws a {@link BufferUnderflowException
          and a relative <i>put</i> operation throws a {@link
          BufferOverflowException}; in either case, no data is transferred. </p
          <p> <i>Absolute</i> operations take an explicit element index and do n
          affect the position. Absolute <i>get</i> and <i>put</i> operations th
          an {@link IndexOutOfBoundsException} if the index argument exceeds the
          limit. </p>
          </blockquote>
          <p> Data may also, of course, be transferred in to or out of a buffer by
          I/O operations of an appropriate channel, which are always relative to t
          current position.
          <h2> Marking and resetting </h2>
          <p> A buffer's <i>mark</i> is the index to which its position will be re
          when the {@link #reset reset} method is invoked. The mark is not always
          defined, but when it is defined it is never negative and is never greate
          than the position. If the mark is defined then it is discarded when the
          position or the limit is adjusted to a value smaller than the mark. If
          mark is not defined then invoking the {@link #reset reset} method causes
          {@link InvalidMarkException} to be thrown.
          <h2> Invariants </h2>
          <p> The following invariant holds for the mark, position, limit, and
          capacity values:
          <blockquote>
          <tt>0</tt> <tt><=</tt>
          <i>mark</i> <tt><=</tt>
          <i>position</i> <tt><=</tt>
          <i>limit</i> <tt><=</tt>
          <i>capacity</i>
          </blockquote>
          <p> A newly-created buffer always has a position of zero and a mark that
          undefined. The initial limit may be zero, or it may be some other value
          that depends upon the type of the buffer and the manner in which it is
          constructed. Each element of a newly-allocated buffer is initialized
          to zero.
          <h2> Clearing, flipping, and rewinding </h2>
          <p> In addition to methods for accessing the position, limit, and capaci
          values and for marking and resetting, this class also defines the follow
          operations upon buffers:
          <ul>
          <li><p> {@link #clear} makes a buffer ready for a new sequence of
          channel-read or relative <i>put</i> operations: It sets the limit to t
          capacity and the position to zero. </p></li>
          <li><p> {@link #flip} makes a buffer ready for a new sequence of
          channel-write or relative <i>get</i> operations: It sets the limit to
          current position and then sets the position to zero. </p></li>
          <li><p> {@link #rewind} makes a buffer ready for re-reading the data t
          it already contains: It leaves the limit unchanged and sets the positi
          to zero. </p></li>
          </ul>
          <h2> Read-only buffers </h2>
          <p> Every buffer is readable, but not every buffer is writable. The
          mutation methods of each buffer class are specified as <i>optional
          operations</i> that will throw a {@link ReadOnlyBufferException} when
          invoked upon a read-only buffer. A read-only buffer does not allow its
          content to be changed, but its mark, position, and limit values are muta
          Whether or not a buffer is read-only may be determined by invoking its
          {@link #isReadOnly isReadOnly} method.
          <h2> Thread safety </h2>
          <p> Buffers are not safe for use by multiple concurrent threads. If a
          buffer is to be used by more than one thread then access to the buffer
          should be controlled by appropriate synchronization.
          <h2> Invocation chaining </h2>
          <p> Methods in this class that do not otherwise have a value to return a
          specified to return the buffer upon which they are invoked. This allows
          method invocations to be chained; for example, the sequence of statement
          <blockquote><pre>
          b.flip();
          b.position(23);
          b.limit(42);</pre></blockquote>
          can be replaced by the single, more compact statement
          <blockquote><pre>
          b.flip().position(23).limit(42);</pre></blockquote>

        • 通過nio讀取文件的3個步驟

          1. 從FileInputStream 獲取 channel對象
          2. 創建buffer對象
          3. 將數據從channel讀取到buffer中
        • 絕對方法和相對方法的含義:

          1. 相對方法:limit和position值在操作時會被考慮到(隨着讀取或者是filp操作時 他們的值可能會發生相應的變化)
          2. 絕對方法:是完全忽略掉limit和position值(根據buffer的索引來直接get或者put)
        • 割或者分片buffer(slice)左閉右開 (注意這裏分片之後的數據 還是和原來的數據共享一份 修改數據也會影響原來的數據)

              // 分割或者分片buffer
             public static void main(String[] args) {
          
                ByteBuffer byteBuffer = ByteBuffer.allocate(10);
                for (int i=0;i<byteBuffer.capacity();i++){
                      byteBuffer.put((byte) i);
                }
                byteBuffer.position(2);
                byteBuffer.limit(6);
                ByteBuffer slice = byteBuffer.slice();
                for(int i=0;i<slice.capacity();i++){
                      byte b = slice.get(i);
                      b *=2;
                      slice.put(i,b);
                }
                byteBuffer.clear();
                while (byteBuffer.hasRemaining()){
                      System.out.println(byteBuffer.get());
                }
             }
          
        • 只讀buffer

        • 對外內存(直接內存)

          1. 直接緩衝

               public static void main(String[] args) throws Exception{
                  try(
                           FileOutputStream fileOutputStream = new FileOutputStream("niotest2.txt");
                           FileInputStream fileInputStream = new FileInputStream("niotest.txt");
                  ){
                        FileChannel inputChannel = fileInputStream.getChannel();
                        FileChannel outputChannel = fileOutputStream.getChannel();
            
                        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(512);
            
                        while (true){
                           byteBuffer.clear();
                           int read = inputChannel.read(byteBuffer);
                           System.out.println("read:"+read);
                           if(-1 == read){
                              break;
                           }
                           byteBuffer.flip();
                           outputChannel.write(byteBuffer);
                        }
                  }
               }
            
          2. 文件映射

               // 內存映射文件  一個文件的內存映射區域
               public static void main(String[] args) throws  Exception {
                  // rw 表示可讀寫
                  RandomAccessFile randomAccessFile = new RandomAccessFile("NIOTest9.txt","rw");
                  // 獲取文件通道對象
                  FileChannel fileChannel = randomAccessFile.getChannel();
                  // 獲取MappedBuffer對象
                  MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE,0,5);
                  // 通過MappedByteBuffer 修改文件信息
                  mappedByteBuffer.put(0,(byte)'a');
                  mappedByteBuffer.put(3,(byte)'b');
                  randomAccessFile.close();
               }
            
          3. 文件鎖

               // 文件鎖的概念
               public static void main(String[] args) throws  Exception {
                  RandomAccessFile randomAccessFile = new RandomAccessFile("NIOTest9.txt","rw");
                  FileChannel fileChannel = randomAccessFile.getChannel();
                  FileLock lock = fileChannel.lock(2, 4, true);
                  System.out.println(lock.isValid());
                  System.out.println(lock.isShared());
                  lock.release();
                  randomAccessFile.close();
               }
            
        • 關於buffer的Scattering(分散) 和Gathering(合併)

          1. Scattering:來自channel的數據可以按順序讀入一個buffer的數組之中 (按照第一個buffer讀滿接着往下一個讀取)
          2. Gathering:可以將來自多個buffer的數據按順序寫入(和上面相反)
          3. 使用場景:自定義協議時 一個協議可能有 一個header還有一個標識體最後一個body,因爲一般頭部或者標識這裏的字節長度一般是固定的 可變的是body:這裏就可以採用三個buffer來接收,可以達到天然的區分數據,不用後期手動的去做拆分了。
        • 網絡編程

          1. 關於NIO編程實例:

               public static void main(String[] args) throws Exception {
                  int[] ports = new int[5];
                  ports[0] = 5000;
                  ports[1] = 5001;
                  ports[2] = 5002;
                  ports[3] = 5003;
                  ports[4] = 5004;
            
                  // 構造一個selector
                  Selector selector = Selector.open();
                  // 將 Selector 註冊到對應的監聽端口上
                  for(int i=0;i<ports.length;++i){
                        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                        // 配置是否阻塞 true 阻塞 false 不阻塞
                        serverSocketChannel.configureBlocking(false);
                        // 構造sockte
                        ServerSocket serverSocket = serverSocketChannel.socket();
                        // 綁定
                        InetSocketAddress socketAddress = new InetSocketAddress(ports[i]);
                        serverSocket.bind(socketAddress);
                        // 註冊 通道和選擇器之間的關聯關係  將當前的selector註冊到通道上 並且對應的感興趣的key是 接受連接
                        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
                        System.out.println("監聽端口:"+ports[i]);
                  }
            
                  while (true) {
                        // 表示返回的鍵的數量
                        int numbers = selector.select();
                        System.out.println("numbers:"+numbers);
                        // 一旦有返回 獲取相應的事件
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        System.out.println("selectionKeys:"+selectionKeys);
                        // 獲取他的迭代器
                        Iterator<SelectionKey> iterator = selectionKeys.iterator();
                        while (iterator.hasNext()) {
                           SelectionKey selectionKey = iterator.next();
            
                           if( selectionKey.isAcceptable() ) {
                              // 獲取對應的 真正所關聯的channel對象
                              ServerSocketChannel    serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                              // 獲取對應的 SockteChannel 這時表示的是真正的連接對象通道
                              SocketChannel socketChannel = serverSocketChannel.accept();
                              socketChannel.configureBlocking(false);
                              // 連接建立後之後 需要將新的連接註冊到 selector當中 感興趣的事件是 讀
                              socketChannel.register(selector,SelectionKey.OP_READ);
                              // 以上調用完之後 需要調用迭代器的 remove  將其從 selectionKeys 集合中移除(這裏特別重要,不然一直還是會監聽)
                              iterator.remove();
                              System.out.println("獲取到客戶端的連接:"+socketChannel);
                           } else  if (selectionKey.isReadable()) {
                              // 進行數據的讀取
                              // 通過 selectionKey 獲取對應的 channel
                              SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            
                              int byteRead = 0;
                              while (true) {
                                    // 讀取數據
                                    ByteBuffer byteBuffer = ByteBuffer.allocate(512);
                                    int read = socketChannel.read(byteBuffer);
                                    if ( read <=0 ) {
                                       break;
                                    }
                                    // 往會寫
                                    byteBuffer.flip();
                                    socketChannel.write(byteBuffer);
                                    byteRead += read;
                              }
                              System.out.println("讀取的數據:"+byteRead+" 來自於:"+socketChannel);
                              // 特別注意 這裏處理完 一定要把當前的 事件rremove
                              iterator.remove();
            
                           }
            
                        }
                  }
            
               }
            
          2. 實例2:

               // 維護所有客戶端的連接信息
               private static Map<String,SocketChannel> clientMap = new HashMap();
            
               public static void main(String[] args)  throws Exception{
                  // 創建一個serverSockteChannel對象
                  ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                  // 配置成非阻塞的
                  serverSocketChannel.configureBlocking(false);
                  // 獲取serverSocket對象
                  ServerSocket serverSocket = serverSocketChannel.socket();
                  // 綁定端口
                  serverSocket.bind(new InetSocketAddress(8899));
            
                  // 創建selector對象
                  Selector selector = Selector.open();
                  // 將serverSocketChannel對象註冊到selector上
                  serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
                  // 註冊完成之後 進行相應的事件處理
                  // 進行服務器的監聽
                  while (true) {
                        try {
                           // 這個方法將阻塞 一直到他所監聽的 感興趣的事件發生 返回他所關注的事件數量
                           // 當這個方法返回之後 就可以獲取他的selectionKey 所構成的集合
                           selector.select();
            
                           // 獲取返回的集合對象
                           Set<SelectionKey> selectionKeys = selector.selectedKeys();
                           // 遍歷SelectionKey集合 取出對應的每種SelectionKey 判斷對應的是什麼事件 並進行相應的處理
                           selectionKeys.forEach(selectionKey -> {
                              try {
                                    // 表示對應客戶端的 channel對象
                                    final SocketChannel client;
                                    if (selectionKey.isAcceptable()) {
                                       // 可以通過 selectionKey 來獲取與之關聯的 serverSocketChannel對象
                                       ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                                       // 服務端 真正接收了客戶端的連接會返回一個 SocketChannel對象
                                       client = server.accept();
                                       // client 連接真正建立之後 將客戶端的channel對象 配置成非阻塞的並 註冊到selector上
                                       client.configureBlocking(false);
                                       client.register(selector,SelectionKey.OP_READ);
                                       // 將客戶端的連接信息 記錄到服務端 這樣纔可以實現服務端實現消息的分發
                                       String key = "["+ UUID.randomUUID().toString()+"]";
                                       clientMap.put(key,client);
                                    }else if (selectionKey.isReadable()) {
                                       // 判斷是否有新進來的數據
                                       // 獲取 socketChannel
                                       client = (SocketChannel) selectionKey.channel();
                                       // 定義byteBuffer對象
                                       ByteBuffer readBuffer = ByteBuffer.allocate(512);
                                       // 將數據讀入 buffer
                                       int count = client.read(readBuffer);
                                       if(count > 0){
                                          // 進行寫操作
                                          readBuffer.flip();
                                          // 進行字符集編碼
                                          Charset charset = Charset.forName("utf-8");
                                          // 將buffer對象進行解碼成字符串
                                          String receiveMessage = String.valueOf(charset.decode(readBuffer).array());
                                          System.out.println(client + ":"+receiveMessage);
                                          // 獲取到 sendKey
                                          String sendKey = null;
                                          for(Map.Entry<String,SocketChannel> entry : clientMap.entrySet()) {
                                                if ( client == entry.getValue()) {
                                                   sendKey = entry.getKey();
                                                   break;
                                                }
                                          }
                                          // 進行分發
                                          for (Map.Entry<String,SocketChannel> entry : clientMap.entrySet()) {
                                                // 獲取每一個與服務端連接的 sockteChannel對象
                                                SocketChannel socketChannel = entry.getValue();
                                                // 進行數據的寫入
                                                ByteBuffer writeBuffer = ByteBuffer.allocate(512);
                                                // 將需要發送的數據 寫入
                                                writeBuffer.put((sendKey+":"+receiveMessage).getBytes());
                                                writeBuffer.flip();
                                                socketChannel.write(writeBuffer);
                                          }
            
                                       }
                                    }
                              } catch (Exception e ) {
                                    e.printStackTrace();
                              }
                           });
                           selectionKeys.clear();
                        } catch (Exception e) {
                           e.printStackTrace();
                        }
                  }
               }
            
               public static void main(String[] args) throws  Exception{
            
                  try {
                        // 建立連接
                        SocketChannel socketChannel = SocketChannel.open();
                        // 配置非阻塞模式
                        socketChannel.configureBlocking(false);
            
                        // 定義 selector 並註冊
                        Selector selector = Selector.open();
                        socketChannel.register(selector, SelectionKey.OP_CONNECT);
                        // 連接
                        socketChannel.connect(new InetSocketAddress("127.0.0.1",8899));
                        while (true) {
                           selector.select();
                           Set<SelectionKey> selectionKeys = selector.selectedKeys();
                           // 遍歷
                           for (SelectionKey selectionKey : selectionKeys) {
                              if (selectionKey.isConnectable()) {
                                    // 表示 連接上了 獲取 socketChannel對象
                                    SocketChannel client = (SocketChannel) selectionKey.channel();
                                    // 連接是否處在是否進行的狀態
                                    if ( client.isConnectionPending()) {
                                       // 完成連接
                                       client.finishConnect();
                                       // 表示連接真正的建立好了
                                       ByteBuffer wirteBuffer = ByteBuffer.allocate(512);
                                       wirteBuffer.put((LocalDateTime.now()+"連接成功").getBytes());
                                       wirteBuffer.flip();
                                       client.write(wirteBuffer);
                                       ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
                                       executorService.submit(()->{
                                          while (true) {
                                                try {
                                                   // 鍵盤輸入
                                                   wirteBuffer.clear();
                                                   InputStreamReader inputStreamReader = new InputStreamReader(System.in);
                                                   BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                                                   String sendMessage = bufferedReader.readLine();
                                                   wirteBuffer.put(sendMessage.getBytes());
                                                   wirteBuffer.flip();
                                                   client.write(wirteBuffer);
                                                } catch (Exception e){
                                                   e.printStackTrace();
                                                }
                                          }
                                       });
                                    }
                                    // 註冊讀取事件
                                    client.register(selector,SelectionKey.OP_READ);
                              }else  if( selectionKey.isReadable()){
                                    SocketChannel client = (SocketChannel) selectionKey.channel();
                                    // 讀取服務器端發送過來的數據
                                    ByteBuffer readBuffer = ByteBuffer.allocate(512);
                                    int read = client.read(readBuffer);
                                    if( read > 0) {
                                       String receivedMessage = new String(readBuffer.array(),0,read);
                                       System.out.println(receivedMessage);
                                    }
                              }
                           }
                           selectionKeys.clear();
                        }
            
            
                  } catch (Exception e) {
                        e.printStackTrace();
                  }
            
               }
            
            
      • 編解碼
        1. 實例:

           ```java
              // java編解碼
              public static void main(String[] args)  throws  Exception{
        
                 // 定義一個輸入文件
                 String inputFile = "NIOTest13_in.txt";
                 String outputFile = "NIOTest13_out.txt";
        
                 // 將 NIOTest13_in.txt文件內容拷貝到 NIOTest13_out中 使用內存映射
                 RandomAccessFile inputRandomAccessFile = new RandomAccessFile(inputFile,"r");
                 RandomAccessFile outputRandomAccessFile = new RandomAccessFile(outputFile,"rw");
                 // 獲取輸入文件的長度
                 long fileLong = new File(inputFile).length();
                 // 獲取輸入和輸出的文件channle
                 FileChannel inputChannel = inputRandomAccessFile.getChannel();
                 FileChannel outputChannel = outputRandomAccessFile.getChannel();
                 // 通過內存映射文件 修改內存內容 直接反應在文件上
                 MappedByteBuffer mappedByteBuffer = inputChannel.map(FileChannel.MapMode.READ_ONLY,0,fileLong);
                 // 指定字符集
                 Charset charset = Charset.forName("utf-8");
                 // decoder 將字節數組轉化成字符串
                 CharsetDecoder decoder = charset.newDecoder();
                 // encoder 將字符串轉化成字節數組
                 CharsetEncoder encoder = charset.newEncoder();
                 // 將內存映射的buffer 解碼成一個 charbuffer
                 CharBuffer charBuffer = decoder.decode(mappedByteBuffer);
                 // 將 charbuffer 編碼成 bytebuffer
                 ByteBuffer outputData = encoder.encode(charBuffer);
                 // 將 outputData 輸出到文件通道
                 outputChannel.write(outputData);
                 // 關閉
                 inputRandomAccessFile.close();
                 outputRandomAccessFile.close();
        
              }
           ```         
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章