国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

Android完整Socket解決方案

2019-10-22 18:15:23
字體:
供稿:網(wǎng)友

整體步驟流程

先來說一下整體的步驟思路吧:

發(fā)送 UDP 廣播,大家都知道 UDP 廣播的特性是整個網(wǎng)段的設(shè)備都可以收到這個消息。
接收方收到了 UDP 的廣播,將自己的 ip 地址,和雙方約定的端口號,回復(fù)給 UDP 的發(fā)送方。
發(fā)送方拿到了對方的 ip 地址以及端口號,就可以發(fā)起 TCP 請求了,建立 TCP 連接。
保持一個 TCP 心跳,如果發(fā)現(xiàn)對方不在了,超時重復(fù) 1 步驟,重新建立聯(lián)系。

整體的步驟就和上述的一樣,下面用代碼展開:

搭建 UDP 模塊

public UDPSocket(Context context) {  this.mContext = context;  int cpuNumbers = Runtime.getRuntime().availableProcessors();  // 根據(jù)CPU數(shù)目初始化線程池  mThreadPool = Executors.newFixedThreadPool(cpuNumbers * Config.POOL_SIZE);  // 記錄創(chuàng)建對象時的時間  lastReceiveTime = System.currentTimeMillis();  messageReceiveList = new ArrayList<>();  Log.d(TAG, "創(chuàng)建 UDP 對象");//  createUser(); }

首先進行一些初始化操作,準(zhǔn)備線程池,記錄對象初始的時間等等。

public void startUDPSocket() {  if (client != null) return;  try {   // 表明這個 Socket 在設(shè)置的端口上監(jiān)聽數(shù)據(jù)。   client = new DatagramSocket(CLIENT_PORT);   client.setReuseAddress(true);   if (receivePacket == null) {    // 創(chuàng)建接受數(shù)據(jù)的 packet    receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);   }   startSocketThread();  } catch (SocketException e) {   e.printStackTrace();  } }

緊接著就創(chuàng)建了真正的一個 UDP Socket 端,DatagramSocket,注意這里傳入的端口號 CLIENT_PORT 的意思是這個 DatagramSocket 在此端口號接收消息。

/**  * 開啟發(fā)送數(shù)據(jù)的線程  */ private void startSocketThread() {  clientThread = new Thread(new Runnable() {   @Override   public void run() {    receiveMessage();   }  });  isThreadRunning = true;  clientThread.start();  Log.d(TAG, "開啟 UDP 數(shù)據(jù)接收線程");  startHeartbeatTimer(); }

我們都知道 Socket 中要處理數(shù)據(jù)的發(fā)送和接收,并且發(fā)送和接收都是阻塞的,應(yīng)該放在子線程中,這里就開啟了一個線程,來處理接收到的 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數(shù)據(jù)包接收失敗!線程停止");    stopUDPSocket();    e.printStackTrace();    return;   }   if (receivePacket == null || receivePacket.getLength() == 0) {    Log.e(TAG, "無法接收UDP數(shù)據(jù)或者接收到的UDP數(shù)據(jù)為空");    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數(shù)據(jù)后,重置長度。否則可能會導(dǎo)致下次收到數(shù)據(jù)包被截斷。   if (receivePacket != null) {    receivePacket.setLength(BUFFER_LENGTH);   }  } }

在子線程接收 UDP 數(shù)據(jù),并且 notifyMessageReceive 方法通過接口來向外通知消息。

/**  * 發(fā)送心跳包  *  * @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);     // 數(shù)據(jù)發(fā)送事件     Log.d(TAG, "數(shù)據(jù)發(fā)送成功");    } catch (UnknownHostException e) {     e.printStackTrace();    } catch (IOException e) {     e.printStackTrace();    }   }  }); }

接著 startHeartbeatTimer 開啟一個心跳線程,每間隔五秒,就去廣播一個 UDP 消息。注意這里 getBroadcastAddress 是獲取的網(wǎng)段 ip,發(fā)送這個 UDP 消息的時候,整個網(wǎng)段的所有設(shè)備都可以接收到。

到此為止,我們發(fā)送端的 UDP 算是搭建完成了。

搭建 TCP 模塊

接下來 TCP 模塊該出場了,UDP 發(fā)送心跳廣播的目的就是找到對應(yīng)設(shè)備的 ip 地址和約定好的端口,所以在 UDP 數(shù)據(jù)的接收方法里:

/**  * 處理 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 端,發(fā)給我的 UDP 消息,將它的 ip 地址告訴了我,以及我們提前約定好的端口號。

怎么獲得一個設(shè)備的 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); }

現(xiàn)在拿到了對方的 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 創(chuàng)建成功...");   return true;  } catch (Exception e) {   e.printStackTrace();  }  return false; }

當(dāng) TCP 客戶端成功建立的時候,我們就可以通過 TCP Socket 來發(fā)送和接收消息了。

細節(jié)處理

接下來就是一些細節(jié)處理了,比如我們的 UDP 心跳,當(dāng) 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已經(jīng)成功建立連接,停止 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 超時,對方已經(jīng)下線");     stopTcpConnection();     if (mListener != null) {      mListener.onFailed(Config.ErrorCode.PING_TCP_TIMEOUT);     }    } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//若超過兩秒他沒收到我的心跳包,則重新發(fā)一個。     JSONObject jsonObject = new JSONObject();     try {      jsonObject.put(Config.MSG, Config.PING);     } catch (JSONException e) {      e.printStackTrace();     }     sendTcpMessage(jsonObject.toString());    }   }  });  timer.startTimer(0, 1000 * 2); }

首先會每隔兩秒,就給對方發(fā)送一個 ping 包,看看對面在不在,如果超過 15 秒還沒有回復(fù)我,那就說明對方掉線了,關(guān)閉我這邊的 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;     }    }

當(dāng) TCP 連接超時,我就會重新啟動 UDP 的廣播心跳,尋找等待連接的設(shè)備。進入下一個步驟循環(huán)。

對于數(shù)據(jù)傳輸?shù)母袷桨〉鹊燃毠?jié),這個和業(yè)務(wù)相關(guān)。自己來定就好。

還可以根據(jù)自己業(yè)務(wù)的模式,是 CPU 密集型啊,還是 IO 密集型啊,來開啟不同的線程通道。這個就涉及線程的知識了。

源碼分享:https://github.com/itsMelo/AndroidSocket


注:相關(guān)教程知識閱讀請移步到Android開發(fā)頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 五大连池市| 屯门区| 昆山市| 乌拉特中旗| 砚山县| 陆河县| 南澳县| 盐边县| 盐城市| 衡山县| 新竹县| 林西县| 监利县| 贵州省| 杭州市| 德保县| 石家庄市| 射洪县| 中卫市| 错那县| 宝兴县| 伊通| 永昌县| 泰和县| 福鼎市| 东阳市| 周宁县| 巩留县| 聂拉木县| 岳阳县| 和平县| 嘉峪关市| 汉川市| 夏河县| 怀柔区| 重庆市| 西宁市| 桃源县| 辛集市| 郧西县| 千阳县|