Android完全Socketソリューション

8770 ワード

全体的なステップフロー
まず全体の手順を話しましょう.
UDP放送を送信すると、UDP放送の特性は、ネットワークセグメント全体のデバイスがこのメッセージを受信できることが知られています.受信者はUDPの放送を受け取り、自分のipアドレスと、双方が約束したポート番号をUDPの送信者に返信する.送信側が相手のipアドレスおよびポート番号を取得すると,TCPリクエストを開始し,TCP接続を確立することができる.TCPの心拍数を維持し、相手がいないことに気づいたら、タイムアウトして1ステップを繰り返し、連絡を再確立します.
全体の手順は上記と同じで、以下はコードで展開します.
UDPモジュールの構築

public UDPSocket(Context context) {
  this.mContext = context;
  int cpuNumbers = Runtime.getRuntime().availableProcessors();
  //   CPU        
  mThreadPool = Executors.newFixedThreadPool(cpuNumbers * Config.POOL_SIZE);
  //           
  lastReceiveTime = System.currentTimeMillis();
  messageReceiveList = new ArrayList<>();
  Log.d(TAG, "   UDP   ");
//  createUser();
 }

まず、初期化操作、スレッドプールの準備、オブジェクトの初期時間の記録などを行います.

public void startUDPSocket() {
  if (client != null) return;
  try {
   //      Socket            。
   client = new DatagramSocket(CLIENT_PORT);
   client.setReuseAddress(true);
   if (receivePacket == null) {
    //         packet
    receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);
   }
   startSocketThread();
  } catch (SocketException e) {
   e.printStackTrace();
  }
 }

次に、本物のUDPソケット端子が作成されました.DatagramSocket、ここに入力されたポート番号CLIENTに注意してください.PORTとは、このDatagramSocketがこのポート番号でメッセージを受信することを意味します.

/**
  *          
  */
 private void startSocketThread() {
  clientThread = new Thread(new Runnable() {
   @Override
   public void run() {
    receiveMessage();
   }
  });
  isThreadRunning = true;
  clientThread.start();
  Log.d(TAG, "   UDP       ");
  startHeartbeatTimer();
 }

Socketでデータの送信と受信を処理することはよく知られていますが、送信と受信はブロックされています.サブスレッドに置くべきです.ここでは、受信したUDPメッセージを処理するためにスレッドが開きます(UDPモジュールの前の記事は詳しく説明していますので、ここでは詳しく説明しません).

/**
  *         
  */
 private void receiveMessage() {
  while (isThreadRunning) {
   try {
    if (client != null) {
     client.receive(receivePacket);
    }
    lastReceiveTime = System.currentTimeMillis();
    Log.d(TAG, "receive packet success...");
   } catch (IOException e) {
    Log.e(TAG, "UDP       !    ");
    stopUDPSocket();
    e.printStackTrace();
    return;
   }
   if (receivePacket == null || receivePacket.getLength() == 0) {
    Log.e(TAG, "    UDP        UDP    ");
    continue;
   }
   String strReceive = new String(receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength());
   Log.d(TAG, strReceive + " from " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort());
   //       json   
   notifyMessageReceive(strReceive);
   //      UDP   ,    。                 。
   if (receivePacket != null) {
    receivePacket.setLength(BUFFER_LENGTH);
   }
  }
 }

サブスレッドでUDPデータを受信し、notifyMessageReceiveメソッドはインタフェースを介してメッセージを外部に通知する.

/**
  *      
  *
  * @param message
  */
 public void sendMessage(final String message) {
  mThreadPool.execute(new Runnable() {
   @Override
   public void run() {
    try {
     BROADCAST_IP = WifiUtil.getBroadcastAddress();
     Log.d(TAG, "BROADCAST_IP:" + BROADCAST_IP);
     InetAddress targetAddress = InetAddress.getByName(BROADCAST_IP);
     DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), targetAddress, CLIENT_PORT);
     client.send(packet);
     //       
     Log.d(TAG, "      ");
    } catch (UnknownHostException e) {
     e.printStackTrace();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  });
 }

次にstartHeartbeatTimerは心拍スレッドを開き、5秒おきにUDPメッセージをブロードキャストします.ここでgetBroadcastAddressは取得したセグメントipであり、このUDPメッセージを送信すると、セグメント全体のすべてのデバイスが受信できることに注意してください.
ここまで、私たちの送信側のUDPは構築が完了しました.
TCPモジュールの構築
次にTCPモジュールが登場し、UDPがハートビート放送を送信する目的は、対応するデバイスのipアドレスと約束されたポートを見つけることであるので、UDPデータの受信方法では:

/**
  *    udp      
  *
  * @param message
  */
 private void handleUdpMessage(String message) {
  try {
   JSONObject jsonObject = new JSONObject(message);
   String ip = jsonObject.optString(Config.TCP_IP);
   String port = jsonObject.optString(Config.TCP_PORT);
   if (!TextUtils.isEmpty(ip) && !TextUtils.isEmpty(port)) {
    startTcpConnection(ip, port);
   }
  } catch (JSONException e) {
   e.printStackTrace();
  }
 }

この方法の目的は相手のUDPServer端を取って、私に送ったUDPメッセージ、そのipアドレスを私に教えて、そして私たちが事前に約束したポート番号です.
どうやってデバイスのipを手に入れますか?

public String getLocalIPAddress() {
  WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
  return intToIp(wifiInfo.getIpAddress());
 }
 private static String intToIp(int i) {
  return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "." + ((i >> 16) & 0xFF) + "."
    + ((i >> 24) & 0xFF);
 }

今、相手のipと約束したポート番号を手に入れて、やっとTCPクライアントを開くことができます.

private boolean startTcpConnection(final String ip, final int port) {
  try {
   if (mSocket == null) {
    mSocket = new Socket(ip, port);
    mSocket.setKeepAlive(true);
    mSocket.setTcpNoDelay(true);
    mSocket.setReuseAddress(true);
   }
   InputStream is = mSocket.getInputStream();
   br = new BufferedReader(new InputStreamReader(is));
   OutputStream os = mSocket.getOutputStream();
   pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)), true);
   Log.d(TAG, "tcp     ...");
   return true;
  } catch (Exception e) {
   e.printStackTrace();
  }
  return false;
 }

TCPクライアントが正常に確立されると、TCPソケットを介してメッセージを送信および受信することができます.
詳細処理
次に、UDPの心拍数などの詳細を処理します.TCPが成功したとき、UDPの心拍数を停止します.

if (startTcpConnection(ip, Integer.valueOf(port))) {//      TCP   
     if (mListener != null) {
      mListener.onSuccess();
     }
     startReceiveTcpThread();
     startHeartbeatTimer();
    } else {
     if (mListener != null) {
      mListener.onFailed(Config.ErrorCode.CREATE_TCP_ERROR);
     }
    }
   // TCP        ,   UDP     。
   public void stopHeartbeatTimer() {
    if (timer != null) {
     timer.exit();
     timer = null;
    }
 }

TCP接続のハートビート保護:

/**
  *     
  */
 private void startHeartbeatTimer() {
  if (timer == null) {
   timer = new HeartbeatTimer();
  }
  timer.setOnScheduleListener(new HeartbeatTimer.OnScheduleListener() {
   @Override
   public void onSchedule() {
    Log.d(TAG, "timer is onSchedule...");
    long duration = System.currentTimeMillis() - lastReceiveTime;
    Log.d(TAG, "duration:" + duration);
    if (duration > TIME_OUT) {//               ,        。
     Log.d(TAG, "tcp ping   ,      ");
     stopTcpConnection();
     if (mListener != null) {
      mListener.onFailed(Config.ErrorCode.PING_TCP_TIMEOUT);
     }
    } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//              ,      。
     JSONObject jsonObject = new JSONObject();
     try {
      jsonObject.put(Config.MSG, Config.PING);
     } catch (JSONException e) {
      e.printStackTrace();
     }
     sendTcpMessage(jsonObject.toString());
    }
   }
  });
  timer.startTimer(0, 1000 * 2);
 }

まず2秒ごとに、相手にpingパッケージを送って、向こうにいるかどうか見て、15秒を超えても返事が来なければ、相手がオフラインになったことを説明して、私のTCP端を閉じます.onFailedメソッドに入ります.

@Override
    public void onFailed(int errorCode) {// tcp     
     switch (errorCode) {
      case Config.ErrorCode.CREATE_TCP_ERROR:
       break;
      case Config.ErrorCode.PING_TCP_TIMEOUT:
       udpSocket.startHeartbeatTimer();
       tcpSocket = null;
       break;
     }
    }

TCP接続がタイムアウトすると、UDPのブロードキャスト心拍を再起動し、接続待ちのデバイスを探します.次のステップループに進みます.
データ転送のフォーマットなどの詳細は、ビジネスに関連しています.自分で決めればいい.
自分のビジネスモデルによって、CPU密集型かIO密集型かで、異なるスレッドチャネルを開くこともできます.これはスレッドの知識に関連しています.
ソースの共有:https://github.com/itsMelo/AndroidSocket