Android socket高級(jí)用法(自定義協(xié)議和Protocol Buffer使用)

前提

之前寫過(guò)兩篇關(guān)于socket的文章荐吵,但是,只是簡(jiǎn)單的介紹了一下關(guān)于socket Tcp和Udp的簡(jiǎn)單使用入撒。如果沒有看過(guò)的朋友可以去看看Android Socket編程(tcp)初探Android Socket編程(udp)初探隆豹。相信很多朋友在公司使用socket開發(fā)的時(shí)候都會(huì)自定義協(xié)議來(lái)傳遞信息。一方面是為了安全排除臟數(shù)據(jù)茅逮,另一個(gè)方面是為了更加高效的處理自己所需要的數(shù)據(jù)璃赡。今天就來(lái)介紹一下關(guān)于socket自定義協(xié)議和使用Protocol Buffer解析數(shù)據(jù)。

首先

既然說(shuō)到了Protocol Buffer氮唯,那么我們就簡(jiǎn)單介紹一下Protocol Buffer是什么鉴吹?并且使用為什么要使用Protocol Buffer?

  • 1惩琉、什么是Protocol Buffer

一種 結(jié)構(gòu)化數(shù)據(jù) 的數(shù)據(jù)存儲(chǔ)格式(類似于 XML豆励、Json ),其作用是通過(guò)將 結(jié)構(gòu)化的數(shù)據(jù) 進(jìn)行 串行化(序列化)瞒渠,從而實(shí)現(xiàn) 數(shù)據(jù)存儲(chǔ) / RPC 數(shù)據(jù)交換的功能良蒸。至于更詳細(xì)的用法和介紹請(qǐng)移步Protocol Buffer 序列化原理大揭秘 - 為什么Protocol Buffer性能這么好?

  • 2伍玖、為什么要使用Protocol Buffer

    在回答這個(gè)問題之前嫩痰,我們還是先給出一個(gè)在實(shí)際開發(fā)中經(jīng)常會(huì)遇到的系統(tǒng)場(chǎng)景。比如:我們的客戶端程序是使用Java開發(fā)的窍箍,可能運(yùn)行自不同的平臺(tái)串纺,如:Linux、Windows或者是Android椰棘,而我們的服務(wù)器程序通常是基于Linux平臺(tái)并使用C++或者Python開發(fā)完成的纺棺。在這兩種程序之間進(jìn)行數(shù)據(jù)通訊時(shí)存在多種方式用于設(shè)計(jì)消息格式,如:
    1邪狞、 直接傳遞C/C++/Python語(yǔ)言中一字節(jié)對(duì)齊的結(jié)構(gòu)體數(shù)據(jù)祷蝌,只要結(jié)構(gòu)體的聲明為定長(zhǎng)格式,那么該方式對(duì)于C/C++/Python程序而言就非常方便了帆卓,僅需將接收到的數(shù)據(jù)按照結(jié)構(gòu)體類型強(qiáng)行轉(zhuǎn)換即可巨朦。事實(shí)上對(duì)于變長(zhǎng)結(jié)構(gòu)體也不會(huì)非常麻煩米丘。在發(fā)送數(shù)據(jù)時(shí),也只需定義一個(gè)結(jié)構(gòu)體變量并設(shè)置各個(gè)成員變量的值之后糊啡,再以char*的方式將該二進(jìn)制數(shù)據(jù)發(fā)送到遠(yuǎn)端拄查。反之,該方式對(duì)于Java開發(fā)者而言就會(huì)非常繁瑣悔橄,首先需要將接收到的數(shù)據(jù)存于ByteBuffer之中靶累,再根據(jù)約定的字節(jié)序逐個(gè)讀取每個(gè)字段,并將讀取后的值再賦值給另外一個(gè)值對(duì)象中的域變量癣疟,以便于程序中其他代碼邏輯的編寫。對(duì)于該類型程序而言潮酒,聯(lián)調(diào)的基準(zhǔn)是必須客戶端和服務(wù)器雙方均完成了消息報(bào)文構(gòu)建程序的編寫后才能展開睛挚,而該設(shè)計(jì)方式將會(huì)直接導(dǎo)致Java程序開發(fā)的進(jìn)度過(guò)慢。即便是Debug階段急黎,也會(huì)經(jīng)常遇到Java程序中出現(xiàn)各種域字段拼接的小錯(cuò)誤扎狱。
    2、 使用SOAP協(xié)議(WebService)作為消息報(bào)文的格式載體勃教,由該方式生成的報(bào)文是基于文本格式的淤击,同時(shí)還存在大量的XML描述信息,因此將會(huì)大大增加網(wǎng)絡(luò)IO的負(fù)擔(dān)故源。又由于XML解析的復(fù)雜性污抬,這也會(huì)大幅降低報(bào)文解析的性能∩總之印机,使用該設(shè)計(jì)方式將會(huì)使系統(tǒng)的整體運(yùn)行性能明顯下降。
    對(duì)于以上兩種方式所產(chǎn)生的問題门驾,Protocol Buffer均可以很好的解決射赛,不僅如此,Protocol Buffer還有一個(gè)非常重要的優(yōu)點(diǎn)就是可以保證同一消息報(bào)文新舊版本之間的兼容性奶是。對(duì)于Protocol Buffer具體的用法請(qǐng)移步Protocol Buffer技術(shù)詳解(語(yǔ)言規(guī)范)今天主要講解的是socket自定義協(xié)議這塊

其次

說(shuō)了那么多楣责,我們來(lái)看看我們今天的主要內(nèi)容— 自定義socket協(xié)議
先看一張心跳返回的圖

心跳返回.png
  • 1、Protobuf協(xié)議
  • 假設(shè)客戶端請(qǐng)求包體數(shù)據(jù)協(xié)議如下

request.proto

syntax = "proto3";
// 登錄的包體數(shù)據(jù)
message Request {
 int32   uid = 0;
string  api_token = 1;
}

發(fā)送的格式:

{包頭}{命令}{包體}
{包頭} -> 包體轉(zhuǎn)成protubuf的長(zhǎng)度
{命令} -> 對(duì)應(yīng)功能的命令字參數(shù)
{包體} -> 對(duì)應(yīng)的protubuf數(shù)據(jù)

  • 假設(shè)服務(wù)端返回包體數(shù)據(jù)協(xié)議

response.proto

syntax = "proto3";
// 登錄成功后服務(wù)器返回的包體數(shù)據(jù)
message Response {
int32   login = 1;
}

服務(wù)器返回的格式:

{包頭}{命令}{狀態(tài)碼}{包體}
{包頭} -> 包體轉(zhuǎn)成protubuf的長(zhǎng)度
{命令} -> 對(duì)應(yīng)功能的命令字參數(shù)
{狀態(tài)碼} -> 對(duì)應(yīng)狀態(tài)的狀態(tài)碼
{包體} -> 對(duì)應(yīng)的protubuf數(shù)據(jù)

  • 2聂沙、客戶端socket寫法

  • 分析:試想一下秆麸,要socket不會(huì)因?yàn)槭謾C(jī)屏幕的熄滅或者其他什么的而斷開,我們應(yīng)該把socket放到哪里去寫逐纬,又要怎么保證socket的連接狀態(tài)呢蛔屹?對(duì)于Android來(lái)說(shuō)放到 service里面去是最合適的,并且為了保證連接狀態(tài)豁生。那么兔毒,就要發(fā)送一個(gè)心跳包保證連接狀態(tài)漫贞。既然這樣,那么我們來(lái)寫service和socket育叁。

  • 3迅脐、service寫法

     public class SocketService extends Service {
    
      Thread mSocketThread;
      Socket mSocket;
      InetSocketAddress mSocketAddress;
      //心跳線程
       Thread mHeartThread;
      //接收線程
      Thread mReceiveThread;
       //登錄線程
      Thread mLoginThread;
       boolean isHeart = false;
       boolean isReceive = false;
    
    SocketBinder mBinder = new SocketBinder(this);
    
    public SocketService() {
    
    }
    
    @Override
    public void onCreate() {
       super.onCreate();
       createConnection();
       receiveMsg();
       isHeart = true;
       isReceive = true;
    }
    
    
    @Override
    public IBinder onBind(Intent intent) {
     return mBinder;
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
     startGps();
     sendHeart();
     if (!TextUtils.isEmpty(intent.getStringExtra(AppConfig.SERVICE_TAG))) {
         String TAG = intent.getStringExtra(AppConfig.SERVICE_TAG);
         switch (TAG) {
             case AppConfig.STOP_SERVICE_VALUE: {//停止服務(wù)
                 ClientSocket.getsInstance().shutDownConnection(mSocket);
                 stopSelf();
                 mSocket = null;
                 mHeartThread = null;
                 mReceiveThread = null;
                 mLoginThread = null;
                 mSocketThread = null;
                 isHeart = false;
                 isReceive = false;
                 break;
             }
            
             default:
                 break;
         }
     }
     return super.onStartCommand(intent, flags, startId);
    
     }
    
    
    /**
     * 發(fā)送心跳包
     */
    private void sendHeart() {
     mHeartThread = new Thread(new Runnable() {
         @Override
         public void run() {
             while (isHeart) {
                 ClientSocket.getsInstance().sendHeart(mSocket, SocketStatus.HEART_CODE);
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
     });
     mHeartThread.start();
     }
    
     /**
      * 登錄
      */
     private void login(final double mLatitude, final double mLongitude) {
       mLoginThread = new Thread(new Runnable() {
         @Override
         public void run() {
             if (PreferencesUtils.getInt(SocketService.this, Constants.USER_ID) != 0 &&
                     !TextUtils.isEmpty(PreferencesUtils.getString(SocketService.this,
                             Constants.USER_TOKEN))) {
                 Request.Request requestLogin =
                         Request.Request.newBuilder()
                                 .setUid(PreferencesUtils.getInt(SocketService.this,
                                         Constants.USER_ID))
                                 .setApiToken(PreferencesUtils.getString(SocketService.this,
                                         Constants.USER_TOKEN).trim())
                                 .build();
                
                 ClientSocket.getsInstance().sendLogin(mSocket, requestLogin, SocketStatus.LOGIN_CODE);
             
             }
         }
       });
       mLoginThread.start();
    
     }
    
    /**
     * 創(chuàng)建連接
     *
     * @return
     */
     public void createConnection() {
     mSocketThread = new Thread(new Runnable() {
         @Override
         public void run() {
             try {
                 mSocket = new Socket();
                 mSocketAddress = new InetSocketAddress(AppConfig.TCP_IP, AppConfig.TCP_PORT);
                 mSocket.connect(mSocketAddress, 20 * 1000);
                 // 設(shè)置 socket 讀取數(shù)據(jù)流的超時(shí)時(shí)間
                 mSocket.setSoTimeout(20 * 1000);
                 // 發(fā)送數(shù)據(jù)包,默認(rèn)為 false豪嗽,即客戶端發(fā)送數(shù)據(jù)采用 Nagle 算法谴蔑;
                 // 但是對(duì)于實(shí)時(shí)交互性高的程序,建議其改為 true龟梦,即關(guān)閉 Nagle
                 // 算法隐锭,客戶端每發(fā)送一次數(shù)據(jù),無(wú)論數(shù)據(jù)包大小都會(huì)將這些數(shù)據(jù)發(fā)送出去
                 mSocket.setTcpNoDelay(true);
                 // 設(shè)置客戶端 socket 關(guān)閉時(shí)计贰,close() 方法起作用時(shí)延遲 30 秒關(guān)閉钦睡,如果 30 秒內(nèi)盡量將未發(fā)送的數(shù)據(jù)包發(fā)送出去
                 // socket.setSoLinger(true, 30);
                 // 設(shè)置輸出流的發(fā)送緩沖區(qū)大小,默認(rèn)是4KB躁倒,即4096字節(jié)
                 mSocket.setSendBufferSize(10 * 1024);
                 // 設(shè)置輸入流的接收緩沖區(qū)大小荞怒,默認(rèn)是4KB,即4096字節(jié)
                 mSocket.setReceiveBufferSize(10 * 1024);
                 // 作用:每隔一段時(shí)間檢查服務(wù)器是否處于活動(dòng)狀態(tài)秧秉,如果服務(wù)器端長(zhǎng)時(shí)間沒響應(yīng)褐桌,自動(dòng)關(guān)閉客戶端socket
                 // 防止服務(wù)器端無(wú)效時(shí),客戶端長(zhǎng)時(shí)間處于連接狀態(tài)
                 mSocket.setKeepAlive(true);
             } catch (UnknownHostException e) {
                 Logger.e(e.getMessage() + "========+UnknownHostException");
                 e.printStackTrace();
             } catch (IOException e) {
                 createConnection();
                 Logger.e(e.getMessage() + "========IOException");
                 e.printStackTrace();
             } catch (NetworkOnMainThreadException e) {
                 Logger.e(e.getMessage() + "========NetworkOnMainThreadException");
                 e.printStackTrace();
             }
         }
     });
     mSocketThread.start();
    }
    
    
    /**
     * 接收
     */
    private void receiveMsg() {
     mReceiveThread = new Thread(new Runnable() {
         @Override
         public void run() {
             while (isReceive) {
                 try {
                     if (mSocket != null && mSocket.isConnected()) {
                         DataInputStream dis = ClientSocket.getsInstance().getMessageStream(mSocket);
                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
                         if (dis != null) {
                             int length = 0;
                             int head = 0;
                             int buffer_size = 4;
                             byte[] headBuffer = new byte[4];
                             byte[] cmdBuffer = new byte[4];
                             byte[] stateBuffer = new byte[4];
                             length = dis.read(headBuffer, 0, buffer_size);
                             if (length == 4) {
                                 bos.write(headBuffer, 0, length);
                                 System.arraycopy(bos.toByteArray(), 0, headBuffer, 0, buffer_size);
                                 head = ByteUtil.bytesToInt(headBuffer, 0);
                                 length = dis.read(cmdBuffer, 0, buffer_size);
                                 bos.write(cmdBuffer, 0, length);
                                 System.arraycopy(bos.toByteArray(), 4, cmdBuffer, 0, buffer_size);
                                 int cmd = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(ByteUtil.byte2hex(cmdBuffer)));
                                 int heartNumber = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(SocketStatus.HEART));
                                 String discover = Integer.toHexString(0x0101);
                                 int discoverNumber = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(SocketStatus.DISCOVER));
                                 int giftNumber = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(SocketStatus.GIFT));
                                 if (cmd == heartNumber) {
                                     length = dis.read(stateBuffer, 0, buffer_size);
                                     bos.write(stateBuffer, 0, length);
                                     System.arraycopy(bos.toByteArray(), 8, stateBuffer, 0, buffer_size);
                                     switch (ByteUtil.bytesToInt(stateBuffer, 0)) {
                                         case SocketStatus.LOGIN_SUCCESS: {//登錄成功
                                             Logger.e("登錄成功");
                                             mLoginValue = "1";
                                             EventUtils.sendEvent(new Event<>(Constants.MSG_LOGIN_SUCCESS));
                                             break;
                                         }
                                       
                                         case SocketStatus.HEART_SUCCESS: {//心跳返回
                                             if (ByteUtil.bytesToInt(stateBuffer, 0) == 200
                                                     && Integer.toHexString(ByteUtil.bytesToInt(cmdBuffer, 0))
                                                     .equals(discover)) {
                                                 byte[] buffer = new byte[head];
                                                 length = dis.read(buffer, 0, head);
                                                 bos.write(buffer, 0, length);
                                                 Response.Response response = Response.
                                                         Response.parseFrom(buffer);
                                                 Logger.e(responseExplore.getNickname() + responseExplore.getAvatar());
                                                 //發(fā)送到activity中對(duì)數(shù)據(jù)進(jìn)行處理
                                                 EventUtils.sendEvent(new Event<>(Constants.MSG_START_DISCOVER_RESULT,
                                                         responseExplore));
                                                 Logger.e(responseExplore + "=======response");
                                             } else {
                                                 Logger.e("心跳返回");
                                             }
                                             break;
                                         }
                                        
                                         default:
                                             break;
                                     }
                                 }
                             } else {
                                    //出錯(cuò)重連
                                 ClientSocket.getsInstance().shutDownConnection(mSocket);
                                 createConnection();
                             }
    
                         } else {
                             createConnection();
                         }
                     }
                 } catch (IOException ex) {
                     ex.printStackTrace();
                 }
                 try {
                     Thread.sleep(50);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
     });
     mReceiveThread.start();
    }
    
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        ClientSocket.getsInstance().shutDownConnection(mSocket);
        stopSelf();
        mHeartThread = null;
        mReceiveThread = null;
        mLoginThread = null;
        mSocketThread = null;
        mStopDiscoverThread = null;
        isHeart = false;
        isReceive = false;
     
    }
    
    
    
    
    /**
     * Binder
     */
    public class SocketBinder extends Binder {
    
     private SocketService mService;
     public OnServiceCallBack mCallBack;
    
     public SocketBinder(SocketService mService) {
         this.mService = mService;
     }
    
     /**
      * 發(fā)送方法
      *
      * @param object
      */
     public void sendMethod(Object object) {
         mService.sendMsg(object);
         mCallBack.onService(object);
     }
    
     /**
      * 設(shè)置回調(diào)
      *
      * @param callBack
      */
     public void setOnServiceCallBack(OnServiceCallBack callBack) {
         this.mCallBack = callBack;
       }
      }
    
    }
    
  • 分析
    上面的service中首先創(chuàng)建socket象迎,然后連接荧嵌,在socket發(fā)生錯(cuò)誤的時(shí)候(比如網(wǎng)絡(luò)異常)重新進(jìn)行創(chuàng)建在連接。然后挖帘,開一個(gè)接收線程一直接收完丽,每次接收都是接收4個(gè)字節(jié)的int值進(jìn)行判斷是否可以進(jìn)入到下一步,如果可以則繼續(xù)向下拇舀。讀取4個(gè)字節(jié)的包頭然后讀取4個(gè)字節(jié)的命令 再讀取4個(gè)字節(jié)的狀態(tài)碼 最后讀取4個(gè)字節(jié)的包體逻族,包體就包含我們所需要返回的數(shù)據(jù)。并且骄崩,在剛開始的時(shí)候就開啟了一個(gè)接收線程每隔50毫秒接收一次數(shù)據(jù)聘鳞,這樣不僅可以讀取到心跳包還可以讀取到我們需要的數(shù)據(jù)。在最后要拂,server生命周期結(jié)束的時(shí)候停止所有的線程抠璃。

  • 4、發(fā)送數(shù)據(jù)的類

     public class ClientSocket {
     private DataOutputStream out = null;
     private DataInputStream getMessageStream;
     private static ClientSocket sInstance;
    
     private ClientSocket() {
     }
    
     /**
      * 單例
      *
      * @return
      */
     public static ClientSocket getsInstance() {
      if (sInstance == null) {
          synchronized (ClientSocket.class) {
              if (sInstance == null) {
                  sInstance = new ClientSocket();
              }
          }
      }
      return sInstance;
     }
    
    
     /**
      * 登錄
      *
      * @return
      */
     public void sendLogin(Socket socket, Request.RequestLogin requestLogin, int code) {
      byte[] data = requestLogin.toByteArray();
      byte[] head = ByteUtil.intToBytes(data.length);
      byte[] cmd = ByteUtil.intToBytes(code);
      byte[] bytes = addBytes(head, cmd, data);
      if (socket != null) {
          if (socket.isConnected()) {
              try {
                  OutputStream os = socket.getOutputStream();
                  os.write(bytes);
                  os.flush();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
     }
    
    
     /**
      * 心跳
      *
      * @param code 關(guān)鍵字(命令)
      * @return
      */
     public boolean sendHeart(Socket socket, int code) {
      boolean isSuccess;
      byte[] head = ByteUtil.intToBytes(0);
      byte[] cmd = ByteUtil.intToBytes(code);
      byte[] bytes = addBytes(head, cmd);
      if (socket.isConnected()) {
          try {
              out = new DataOutputStream(socket.getOutputStream());
              out.write(bytes);
              out.flush();
              isSuccess = true;
          } catch (IOException e) {
              e.printStackTrace();
              isSuccess = false;
          }
      } else {
          isSuccess = false;
      }
      return isSuccess;
     }
    
     /**
      * 斷開連接
      */
     public void shutDownConnection(Socket socket) {
      try {
          if (out != null) {
              out.close();
          }
          if (getMessageStream != null) {
              getMessageStream.close();
          }
          if (socket != null) {
              socket.close();
          }
      } catch (IOException e) {
          e.printStackTrace();
      }
    }
    
     /**
      * 獲取服務(wù)器返回的流
      *
      * @param socket
      * @return
      */
     public DataInputStream getMessageStream(Socket socket) {
      if (socket == null) {
          return null;
      }
    
      if (socket.isClosed()) {
          return null;
      }
      if (!socket.isConnected()) {
          return null;
      }
      try {
          getMessageStream = new DataInputStream(new BufferedInputStream(
                  socket.getInputStream()));
      } catch (IOException e) {
          e.printStackTrace();
          if (getMessageStream != null) {
              try {
                  getMessageStream.close();
              } catch (IOException e1) {
                  e1.printStackTrace();
              }
          }
      }
      return getMessageStream;
      }
     }
    
  • 分析:
    這里使用了單例模式脱惰,保證了數(shù)據(jù)的唯一性搏嗡,不會(huì)重復(fù)創(chuàng)建,可以看到登錄發(fā)送了包頭、命令和數(shù)據(jù)長(zhǎng)度采盒,而心跳只是包頭和命令旧乞,因?yàn)榘w長(zhǎng)度為空,所以不用發(fā)送磅氨,最后轉(zhuǎn)成4個(gè)字節(jié)的二進(jìn)制數(shù)據(jù)進(jìn)行發(fā)送尺栖。這樣,proto buffer的優(yōu)點(diǎn)就體現(xiàn)出來(lái)了烦租,方便客戶端和服務(wù)端的解析延赌。

  • 二進(jìn)制轉(zhuǎn)換工具類

     public class ByteUtil {
    
     /**
      * 將2個(gè)byte數(shù)組進(jìn)行拼接
      */
     public static byte[] addBytes(byte[] data1, byte[] data2) {
      byte[] data3 = new byte[data1.length + data2.length];
      System.arraycopy(data1, 0, data3, 0, data1.length);
      System.arraycopy(data2, 0, data3, data1.length, data2.length);
      return data3;
    }
    
     /**
      * 將3個(gè)byte數(shù)組進(jìn)行拼接
      */
    public static byte[] addBytes(byte[] data1, byte[] data2, byte[] data3) {
      byte[] data4 = new byte[data1.length + data2.length + data3.length];
      System.arraycopy(data1, 0, data4, 0, data1.length);
      System.arraycopy(data2, 0, data4, data1.length, data2.length);
      System.arraycopy(data3, 0, data4, data1.length + data2.length, data3.length);
      return data4;
     }
    
     /**
      * int轉(zhuǎn)byte{}
      */
     public static byte[] intToBytes(int value, ByteOrder mode) {
      byte[] src = new byte[4];
      if (mode == ByteOrder.LITTLE_ENDIAN) {
          src[3] = (byte) ((value >> 24) & 0xFF);
          src[2] = (byte) ((value >> 16) & 0xFF);
          src[1] = (byte) ((value >> 8) & 0xFF);
          src[0] = (byte) (value & 0xFF);
      } else {
          src[0] = (byte) ((value >> 24) & 0xFF);
          src[1] = (byte) ((value >> 16) & 0xFF);
          src[2] = (byte) ((value >> 8) & 0xFF);
          src[3] = (byte) (value & 0xFF);
      }
      return src;
     }
    
    
     /**
      * 16進(jìn)制表示的字符串轉(zhuǎn)換為字節(jié)數(shù)組
      *
      * @param s 16進(jìn)制表示的字符串
      * @return byte[] 字節(jié)數(shù)組
      */
     public static byte[] hexStringToByteArray(String s) {
      int len = s.length();
      byte[] b = new byte[len / 2];
      for (int i = 0; i < len; i += 2) {
          // 兩位一組,表示一個(gè)字節(jié),把這樣表示的16進(jìn)制字符串叉橱,還原成一個(gè)字節(jié)
          b[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
                  .digit(s.charAt(i + 1), 16));
      }
      return b;
     }
    
    
     /**
      * byte數(shù)組中取int數(shù)值挫以,本方法適用于(低位在前,高位在后)的順序窃祝,和和intToBytes()配套使用
      *
      * @param src    byte數(shù)組
      * @param offset 從數(shù)組的第offset位開始
      * @return int數(shù)值
      */
     public static int bytesToInt(byte[] src, int offset) {
      int value;
      value = (int) ((src[offset] & 0xFF)
              | ((src[offset + 1] & 0xFF) << 8)
              | ((src[offset + 2] & 0xFF) << 16)
              | ((src[offset + 3] & 0xFF) << 24));
      return value;
      }
    
     /**
      * byte數(shù)組中取int數(shù)值屡贺,本方法適用于(低位在后,高位在前)的順序锌杀。和intToBytes2()配套使用
      */
    public static int bytesToInt2(byte[] src, int offset) {
      int value;
      value = (int) (((src[offset] & 0xFF) << 24)
              | ((src[offset + 1] & 0xFF) << 16)
              | ((src[offset + 2] & 0xFF) << 8)
              | (src[offset + 3] & 0xFF));
      return value;
     }
    
     /**
      * 將int數(shù)值轉(zhuǎn)換為占四個(gè)字節(jié)的byte數(shù)組,本方法適用于(低位在前泻仙,高位在后)的順序糕再。 和 
       bytesToInt()配套使用
      *
      * @param value 要轉(zhuǎn)換的int值
      * @return byte數(shù)組
      */
     public static byte[] intToBytes(int value) {
      byte[] src = new byte[4];
      src[3] = (byte) ((value >> 24) & 0xFF);
      src[2] = (byte) ((value >> 16) & 0xFF);
      src[1] = (byte) ((value >> 8) & 0xFF);
      src[0] = (byte) (value & 0xFF);
      return src;
     }
    
     /**
      * 將int數(shù)值轉(zhuǎn)換為占四個(gè)字節(jié)的byte數(shù)組,本方法適用于(高位在前玉转,低位在后)的順序突想。  和 
       bytesToInt2()配套使用
      */
     public static byte[] intToBytes2(int value) {
      byte[] src = new byte[4];
      src[0] = (byte) ((value >> 24) & 0xFF);
      src[1] = (byte) ((value >> 16) & 0xFF);
      src[2] = (byte) ((value >> 8) & 0xFF);
      src[3] = (byte) (value & 0xFF);
      return src;
     }
    
     /**
      * 將字節(jié)轉(zhuǎn)換為二進(jìn)制字符串
      *
      * @param bytes 字節(jié)數(shù)組
      * @return 二進(jìn)制字符串
      */
     public static String byteToBit(byte... bytes) {
      StringBuffer sb = new StringBuffer();
      int z, len;
      String str;
      for (int w = 0; w < bytes.length; w++) {
          z = bytes[w];
          z |= 256;
          str = Integer.toBinaryString(z);
          len = str.length();
          sb.append(str.substring(len - 8, len));
        }
        return sb.toString();
       }
    
        /**
         * 字節(jié)數(shù)組轉(zhuǎn)為普通字符串(ASCII對(duì)應(yīng)的字符)
         *
         * @param bytearray byte[]
         * @return String
         */
        public static String byte2String(byte[] bytearray) {
      String result = "";
      char temp;
    
      int length = bytearray.length;
      for (int i = 0; i < length; i++) {
          temp = (char) bytearray[i];
          result += temp;
      }
      return result;
     }
    
     /**
      * 二進(jìn)制字符串轉(zhuǎn)十進(jìn)制
      *
      * @param binary 二進(jìn)制字符串
      * @return 十進(jìn)制數(shù)值
      */
    public static int binaryToAlgorism(String binary) {
      int max = binary.length();
      int result = 0;
      for (int i = max; i > 0; i--) {
          char c = binary.charAt(i - 1);
          int algorism = c - '0';
          result += Math.pow(2, max - i) * algorism;
      }
      return result;
     }
    
     /**
      * 字節(jié)數(shù)組轉(zhuǎn)換為十六進(jìn)制字符串
      *
      * @param b byte[] 需要轉(zhuǎn)換的字節(jié)數(shù)組
      * @return String 十六進(jìn)制字符串
      */
     public static String byte2hex(byte b[]) {
      if (b == null) {
          throw new IllegalArgumentException(
                  "Argument b ( byte array ) is null! ");
      }
      String hs = "";
      String stmp = "";
      for (int n = 0; n < b.length; n++) {
          stmp = Integer.toHexString(b[n] & 0xff);
          if (stmp.length() == 1) {
              hs = hs + "0" + stmp;
          } else {
              hs = hs + stmp;
          }
      }
      return hs.toUpperCase();
    }
    
     /**
      * 十六進(jìn)制字符串轉(zhuǎn)換十進(jìn)制
      *
      * @param hex 十六進(jìn)制字符串
      * @return 十進(jìn)制數(shù)值
      */
    public static int hexStringToAlgorism(String hex) {
      hex = hex.toUpperCase();
      int max = hex.length();
      int result = 0;
      for (int i = max; i > 0; i--) {
          char c = hex.charAt(i - 1);
          int algorism = 0;
          if (c >= '0' && c <= '9') {
              algorism = c - '0';
          } else {
              algorism = c - 55;
          }
          result += Math.pow(16, max - i) * algorism;
      }
      return result;
     }
    
    /**
     * 字符串轉(zhuǎn)換成十六進(jìn)制字符串
     *
     * @param str 待轉(zhuǎn)換的ASCII字符串
     * @return String 每個(gè)Byte之間空格分隔,如: [61 6C 6B]
     */
     public static String str2HexStr(String str) {
    
      char[] chars = "0123456789ABCDEF".toCharArray();
      StringBuilder sb = new StringBuilder("");
      byte[] bs = str.getBytes();
      int bit;
    
      for (int i = 0; i < bs.length; i++) {
          bit = (bs[i] & 0x0f0) >> 4;
          sb.append(chars[bit]);
          bit = bs[i] & 0x0f;
          sb.append(chars[bit]);
          sb.append(' ');
      }
      return sb.toString().trim();
     }
    
     /**
      * 16進(jìn)制轉(zhuǎn)換成字符串
      *
      * @param hexStr
      * @return
      */
     public static String hexStr2Str(String hexStr) {
      String str = "0123456789ABCDEF";
      char[] hexs = hexStr.toCharArray();
      byte[] bytes = new byte[hexStr.length() / 2];
      int n;
    
      for (int i = 0; i < bytes.length; i++) {
          n = str.indexOf(hexs[2 * i]) * 16;
          n += str.indexOf(hexs[2 * i + 1]);
          bytes[i] = (byte) (n & 0xff);
      }
      return new String(bytes);
     }
    
    /**
     * 重寫了Inpustream 中的skip(long n) 方法,
     * 將數(shù)據(jù)流中起始的n 個(gè)字節(jié)跳過(guò)
     */
    public static long skipBytesFromStream(InputStream inputStream, long n) {
      long remaining = n;
      // SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
      int SKIP_BUFFER_SIZE = 2048;
      // skipBuffer is initialized in skip(long), if needed.
      byte[] skipBuffer = null;
      int nr = 0;
      if (skipBuffer == null) {
          skipBuffer = new byte[SKIP_BUFFER_SIZE];
      }
      byte[] localSkipBuffer = skipBuffer;
      if (n <= 0) {
          return 0;
      }
      while (remaining > 0) {
          try {
              nr = inputStream.read(localSkipBuffer, 0,
                      (int) Math.min(SKIP_BUFFER_SIZE, remaining));
          } catch (IOException e) {
              e.printStackTrace();
          }
          if (nr < 0) {
              break;
          }
          remaining -= nr;
      }
      return n - remaining;
    }
    }
    

最后

對(duì)于socket和 proto buffer來(lái)說(shuō),能靈活運(yùn)用燎悍,首先要感謝我們公司的同事客峭,沒有他們提供思路,估計(jì)很難靈活運(yùn)用socket和proto buffer唧席。其次,要感謝之前公司的大佬,還有給我提供寶貴意見的各位好友工腋。還有要感謝自己,能靜下心來(lái)畅卓,堅(jiān)持不懈擅腰,克服proto buffer和socket相結(jié)合的寫法。

感謝

Protocol Buffer技術(shù)詳解(語(yǔ)言規(guī)范)
Protocol Buffer 序列化原理大揭秘 - 為什么Protocol Buffer性能這么好翁潘?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末趁冈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拜马,更是在濱河造成了極大的恐慌渗勘,老刑警劉巖沐绒,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異呀邢,居然都是意外死亡洒沦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門价淌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)申眼,“玉大人,你說(shuō)我怎么就攤上這事蝉衣±ㄊ” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵病毡,是天一觀的道長(zhǎng)濒翻。 經(jīng)常有香客問我,道長(zhǎng)啦膜,這世上最難降的妖魔是什么有送? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮僧家,結(jié)果婚禮上雀摘,老公的妹妹穿的比我還像新娘。我一直安慰自己八拱,他們只是感情好阵赠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肌稻,像睡著了一般清蚀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爹谭,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天枷邪,我揣著相機(jī)與錄音,去河邊找鬼旦棉。 笑死齿风,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绑洛。 我是一名探鬼主播救斑,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼真屯!你這毒婦竟也來(lái)了脸候?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎运沦,沒想到半個(gè)月后泵额,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡携添,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年嫁盲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烈掠。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡羞秤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出左敌,到底是詐尸還是另有隱情瘾蛋,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布矫限,位于F島的核電站哺哼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏叼风。R本人自食惡果不足惜取董,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望无宿。 院中可真熱鬧甲葬,春花似錦、人聲如沸懈贺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)梭灿。三九已至,卻和暖如春冰悠,著一層夾襖步出監(jiān)牢的瞬間堡妒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工溉卓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留皮迟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓桑寨,卻偏偏與公主長(zhǎng)得像伏尼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尉尾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344