在項目中,有如下需求:Android用戶端向伺服器發送資料,收到伺服器傳回的資料發送成功辨別後,用戶端即與伺服器建立資料一來一往的心跳連接配接,若伺服器端斷開時,用戶端接收到通知,關閉Service停止發送資料;代碼如下:
<b>[java]</b> view plain copy
public class BackService extends Service {
private static final String TAG = "BackService";
/** 心跳檢測時間 */
private static final long HEART_BEAT_RATE = 3* 1000;
/** 主機IP位址 */
private static String HOST = "192.168.1.30";
/** 端口号 */
public static final int PORT =10801;
/** 消息廣播 */
public static final String MESSAGE_ACTION = "org.feng.message_ACTION";
/** 心跳廣播 */
public static final String HEART_BEAT_ACTION = "org.feng.heart_beat_ACTION";
private long sendTime = 0L;
private Socket socket;
private ReadThread mReadThread;
private InputStream is;//輸入流
private int count;//讀取的位元組長度
private IBackService.Stub iBackService = new IBackService.Stub() {
@Override
public boolean sendMessage(String message) throws RemoteException {
return sendMsg(message);
}
};
@Override
public IBinder onBind(Intent arg0) {
return (IBinder) iBackService;
}
public void onCreate() {
super.onCreate();
HOST= (String) Utils.getShare(this, ConfigUrl.SERVER_IP,"ip");
Log.i(TAG,"ip-->"+HOST);
Log.i(TAG,"port-->"+PORT);
new InitSocketThread().start();
// 發送心跳包
private Handler mHandler = new Handler();
private Runnable heartBeatRunnable = new Runnable() {
public void run() {
ReadThread thread=new ReadThread(socket);
thread.start();
public boolean sendMsg(String msg) {
if (null == socket) {
return false;
try {
if (!socket.isClosed() && !socket.isOutputShutdown()) {
OutputStream os = socket.getOutputStream();
os.write(msg.getBytes());
os.flush();
sendTime = System.currentTimeMillis();// 每次發送成功資料,就改一下最後成功發送的時間,節省心跳間隔時間
Log.i(TAG, "發送成功的時間:" + sendTime+" 内容-->"+msg);
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
Intent intent = new Intent(HEART_BEAT_ACTION);
intent.putExtra("message", "exit");
sendBroadcast(intent);
Log.i(TAG,"send-->"+e.getMessage());
return true;
// 初始化socket
private void initSocket() throws UnknownHostException, IOException {
socket = new Socket(HOST, PORT);
socket.setSoTimeout(13000);//?
if (socket.isConnected()){//連接配接成功
if (sendMsg("mdc_"+Utils.getShare(this,ConfigUrl.DEVICE_ID,"手機裝置id"))){//發送成功
//接收伺服器傳回的資訊
mReadThread = new ReadThread(socket);
mReadThread.start();
// 釋放socket
private void releaseLastSocket(Socket mSocket) {
if (null != mSocket) {
if (!mSocket.isClosed()) {
is.close();
mSocket.close();
}
mSocket = null;
class InitSocketThread extends Thread {
super.run();
try {
initSocket();
} catch (UnknownHostException e) {
e.printStackTrace();
Log.i(TAG,"socket-->"+e.getMessage());
Log.i(TAG,"連接配接失敗");
Intent intent = new Intent(HEART_BEAT_ACTION);
intent.putExtra("message", "fail");
sendBroadcast(intent);
} catch (IOException e) {
public class ReadThread extends Thread {
private Socket rSocket;
private boolean isStart = true;
public ReadThread(Socket socket) {
rSocket=socket;
public void release() {
isStart = false;
releaseLastSocket(rSocket);
@SuppressLint("NewApi")
String line="";
if (null != rSocket) {
while (isStart&&!rSocket.isClosed()&&!rSocket.isInputShutdown()){
try {
Log.i(TAG,"開始讀取消息");
is=rSocket.getInputStream();
count=is.available();
byte[] data=new byte[count];
is.read(data);
line=new String(data);
if (line!=null){
Log.i(TAG, "收到伺服器發送來的消息:"+line);
if ("mdc_ok".equals(line)||"mdc_exist".equals(line)||"exist".equals(line)){
sendMsg("connect");
mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);// 初始化成功後,就準備發送心跳
return;
}else if ("mdc_connect".equals(line)){//伺服器發送繼續接收的消息
boolean isSuccess=sendMsg("connect");
if (isSuccess){//成功發送,接收回執資訊
mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);// 初始化成功後,就準備發送心跳
}else {//發送失敗,處理善後工作
mHandler.removeCallbacks(heartBeatRunnable);
release();
releaseLastSocket(socket);
}
}else if ("mdc_connectexit".equals(line)||"exit".equals(line)){//斷開連接配接消息
Intent intent = new Intent(HEART_BEAT_ACTION);
intent.putExtra("message", "exit");
sendBroadcast(intent);
}
}else if (line==null){
Log.i(TAG, "伺服器發送過來的消息是空的");
}
} catch (IOException e) {
Log.i(TAG,"Read-->"+e.getClass().getName());
e.printStackTrace();
continue;
}
public void onDestroy() {
super.onDestroy();
Log.i(TAG,"onDestroy");
mHandler.removeCallbacks(heartBeatRunnable);
mReadThread.release();
releaseLastSocket(socket);
}
以上代碼隻是Service中的代碼親測可用;在開發過程中也遇到很多問題,有些已解答,有些仍未解決,在此記錄,i希望有了解的可以告知以下。
1.當 OutputStream os,調用os.close()或者 InputStream os,調用is.close()時,會将socket關閉,開始時調用os.close()導緻scoket關閉,在後面讀取伺服器的消息時不斷抛出socket closed異常,經檢查後發現此處有問題;若想将輸出/入流關閉,可調用 socket.shutdownInput();/socket.shutdownOutput();此時socket不會關閉;
2.在ReadThread線程中,讀取伺服器消息時開始使用BufferedReader in=new BufferedReader(new InputStreamReader(rSocket.getInputStream())); String line=in.readLine();然後一直抛出逾時異常,使用Debug模式調試後發現,line當中已經讀取到正确的内容,但是readLine()方法在讀到“\n”辨別符後才會完成讀取,不然會一直等待讀取直到抛出逾時異常,并且readLIne()是阻塞線程,未完成讀取時程式一直阻塞,無法繼續向下進行,直到逾時進入catch中。此時應該使用位元組讀取。
3.由于用戶端一直在輪詢發送并接收服務起的消息,當伺服器端socket主動斷開時,用戶端也要斷開,但是在開發中,伺服器端的socket斷開時,用戶端一直在while循環中讀取消息,使用位元組讀取時,調用到is.read(data);方法也不會抛出異常(但是好多文章中都寫此時會抛出異常,但是我這裡沒有抛出,原因未知),而是一直讀取位元組長度為0 的空資料卻不結束,使用socket的isClosed()、isConnected()、isInputStreamShutdown()、isOutputStreamShutdown()等判斷socket與伺服器的連接配接狀态均無效,原因在于上這些方法都是通路socket在記憶體駐留的狀态,而非與伺服器的實時狀态,是以判斷是無效的,以下三張截圖:

此為用戶端與伺服器剛建立連接配接,此時未通消息
此為用戶端向伺服器發送消息成功後
此為伺服器socket斷開後的情況
從圖中可以看到,三種情況下各方法傳回的狀态均相同,無法作為判斷用戶端與伺服器實時連接配接情況的方法使用,網上查閱資料有網友提到,可以使用socket.sendUrgentData(0xFF);方法,向伺服器發送資料,檢測伺服器是否斷開連接配接,類似于ping,且該方法可往輸出流發送一個位元組的資料,隻要對方Socket的SO_OOBINLINE屬性沒有打開,就會自動舍棄這個位元組,而SO_OOBINLINE屬性預設情況下就是關閉的,這樣防止向伺服器發送無效資料,若伺服器斷開了,則會在報出用戶端報出異常,此時即可得知伺服器的socket是否處于連接配接狀态;本以為找到一個好方法,使用時才發現,即使伺服器處于正常的連接配接狀态,也會抛出異常(有好多文章說此方法可行,然而我沒有搞通,不知是不是使用方法出錯了,有了解相關情況的望告知),最後隻有讓伺服器在斷開socket時,發送一個辨別符,即在代碼中mdc_connectexit及exit(之是以會用這兩個是因為伺服器在發送資料時,會出現“粘包”的情況)。