最近項目中,有使用Socket與後端進行通信,然後簡單的瞭解了下Socket使用,大致流程是配置服務端的Ip、端口號,連接,監聽數據和發送數據,數據的讀取和發送都是以流的形式實現的,然後自己將項目中的代碼寫了簡單的管理類,測試下代碼。下面是demo的兩個界面,連接和發送接收界面
這裏使用了一個Tcp調試助手,模擬服務端發送和接收數據,通訊模式選擇TcpService ,本地端口隨意定義一個,保持和demo填寫的端口號一致,demo裏面的ip是你主機的ip地址,cmd下ipconfig查看
代碼
一、初始化
public TcpManager initSocket(final String ip, final String port) {
this.ip=ip;
this.port=port;
/* 開啓讀寫線程*/
threadStatus =true;
new ReadThread().start();
if (socket == null && connectThread == null) {
connectThread = new Thread(new Runnable() {
@Override
public void run() {
socket = new Socket();
try {
/*超時時間爲2秒*/
socket.connect(new InetSocketAddress(ip, Integer.valueOf(port)), 2000);
/*連接成功的話 發送心跳包*/
if (socket.isConnected()) {
inputStream = socket.getInputStream();
dis = new DataInputStream(inputStream);
/*因爲Toast是要運行在主線程的 這裏是子線程 所以需要到主線程哪裏去顯示toast*/
Log.e(TAG, "服務連接成功");
/*發送連接成功的消息*/
if(onSocketStatusListener!=null)
onSocketStatusListener.onConnectSuccess();
/*發送心跳數據*/
sendBeatData();
}
} catch (IOException e) {
e.printStackTrace();
if (e instanceof SocketTimeoutException) {
Log.e(TAG,"連接超時,正在重連");
releaseSocket();
} else if (e instanceof NoRouteToHostException) {
Log.e(TAG,"該地址不存在,請檢查");
} else if (e instanceof ConnectException) {
Log.e(TAG,"連接異常或被拒絕,請檢查");
}
}
}
});
/*啓動連接線程*/
connectThread.start();
}
return this;
}
二、發送數據
public void sendData(final String data) {
if (socket != null && socket.isConnected()) {
/*發送指令*/
new Thread(new Runnable() {
@Override
public void run() {
try {
outputStream = socket.getOutputStream();
if (outputStream != null) {
outputStream.write((data).getBytes("UTF-8"));
outputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} else {
Log.e(TAG,"socket連接錯誤,請重試");
}
}
三、讀取數據
private class ReadThread extends Thread {
@Override
public void run() {
super.run();
//判斷進程是否在運行,更安全的結束進程
while (threadStatus) {
if (inputStream != null) {
try {
rcvLength = dis.read(buff);
if (rcvLength > 0) {
rcvMsg = new String(buff, 0, rcvLength, "GBK");
//接收到數據,切換主線程,顯示數據
handler.post(new Runnable() {
@Override
public void run() {
if(onReceiveDataListener!=null){
onReceiveDataListener.onReceiveData(rcvMsg);
}
}
});
}
} catch (Exception e) {
Log.e(TAG,"接收總控數據異常");
}
}
}
}
}
四、全部代碼
package com.yufs.testsocket.tcp;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Timer;
import java.util.TimerTask;
public class TcpManager {
private static final String TAG = TcpManager.class.getSimpleName();
/*socket*/
private Socket socket;
/*連接線程*/
private Thread connectThread;
/* 發送輸出流*/
private OutputStream outputStream;
/* 讀寫輸入流*/
private InputStream inputStream;
private DataInputStream dis;
/* 線程狀態,安全結束線程*/
private boolean threadStatus =false;
/* 讀取保存進數組*/
byte buff[] = new byte[1024*1024*2];
private String ip;
private String port;
private Handler handler = new Handler(Looper.getMainLooper());
/*默認重連*/
private boolean isReConnect = true;
/*倒計時Timer發送心跳包*/
private Timer timer;
private TimerTask task;
/* 心跳週期(s)*/
private int heartCycle = 30;
/*接收數據長度*/
private int rcvLength;
/*接收數據*/
private String rcvMsg;
private TcpManager(){}
private static TcpManager instance;
public static synchronized TcpManager getInstance(){
if(instance==null){
synchronized (TcpManager.class){
instance=new TcpManager();
}
}
return instance;
}
public TcpManager initSocket(final String ip, final String port) {
this.ip=ip;
this.port=port;
/* 開啓讀寫線程*/
threadStatus =true;
new ReadThread().start();
if (socket == null && connectThread == null) {
connectThread = new Thread(new Runnable() {
@Override
public void run() {
socket = new Socket();
try {
/*超時時間爲2秒*/
socket.connect(new InetSocketAddress(ip, Integer.valueOf(port)), 2000);
/*連接成功的話 發送心跳包*/
if (socket.isConnected()) {
inputStream = socket.getInputStream();
dis = new DataInputStream(inputStream);
/*因爲Toast是要運行在主線程的 這裏是子線程 所以需要到主線程哪裏去顯示toast*/
Log.e(TAG, "服務連接成功");
/*發送連接成功的消息*/
if(onSocketStatusListener!=null)
onSocketStatusListener.onConnectSuccess();
/*發送心跳數據*/
sendBeatData();
}
} catch (IOException e) {
e.printStackTrace();
if (e instanceof SocketTimeoutException) {
Log.e(TAG,"連接超時,正在重連");
releaseSocket();
} else if (e instanceof NoRouteToHostException) {
Log.e(TAG,"該地址不存在,請檢查");
} else if (e instanceof ConnectException) {
Log.e(TAG,"連接異常或被拒絕,請檢查");
}
}
}
});
/*啓動連接線程*/
connectThread.start();
}
return this;
}
/*發送數據*/
public void sendData(final String data) {
if (socket != null && socket.isConnected()) {
/*發送指令*/
new Thread(new Runnable() {
@Override
public void run() {
try {
outputStream = socket.getOutputStream();
if (outputStream != null) {
outputStream.write((data).getBytes("UTF-8"));
outputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} else {
Log.e(TAG,"socket連接錯誤,請重試");
}
}
/*定時發送數據*/
private void sendBeatData() {
if (timer == null) {
timer = new Timer();
}
if (task == null) {
task = new TimerTask() {
@Override
public void run() {
try {
outputStream = socket.getOutputStream();
Log.i(TAG,"發送心跳包");
/*這裏的編碼方式根據你的需求去改*/
outputStream.write(("test\n").getBytes("UTF-8"));
outputStream.flush();
} catch (Exception e) {
/*發送失敗說明socket斷開了或者出現了其他錯誤*/
Log.e(TAG,"連接斷開,正在重連");
/*重連*/
releaseSocket();
e.printStackTrace();
}
}
};
}
timer.schedule(task, 0, 1000*heartCycle);
}
/*釋放資源*/
private void releaseSocket(){
if (task != null) {
task.cancel();
task = null;
}
if (timer != null) {
timer.purge();
timer.cancel();
timer = null;
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
outputStream = null;
}
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
inputStream=null;
}
if(dis!=null){
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
dis=null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
socket = null;
}
if (connectThread != null) {
connectThread = null;
}
/*重新初始化socket*/
if (isReConnect) {
initSocket(ip,port);
}
}
private class ReadThread extends Thread {
@Override
public void run() {
super.run();
//判斷進程是否在運行,更安全的結束進程
while (threadStatus) {
if (inputStream != null) {
try {
rcvLength = dis.read(buff);
if (rcvLength > 0) {
rcvMsg = new String(buff, 0, rcvLength, "GBK");
//接收到數據,切換主線程,顯示數據
handler.post(new Runnable() {
@Override
public void run() {
if(onReceiveDataListener!=null){
onReceiveDataListener.onReceiveData(rcvMsg);
}
}
});
}
} catch (Exception e) {
Log.e(TAG,"接收總控數據異常");
}
}
}
}
}
public interface OnSocketStatusListener{
void onConnectSuccess();
}
public OnSocketStatusListener onSocketStatusListener;
public void setOnSocketStatusListener(OnSocketStatusListener onSocketStatusListener) {
this.onSocketStatusListener = onSocketStatusListener;
}
public interface OnReceiveDataListener{
void onReceiveData(String str);
}
public OnReceiveDataListener onReceiveDataListener;
public void setOnReceiveDataListener(OnReceiveDataListener onReceiveDataListener) {
this.onReceiveDataListener = onReceiveDataListener;
}
}
總體來說分爲三步:連接、發送、線程監聽數據,這裏連接狀態和接收數據使用了監聽事件,可以考慮使用EventBus。整個代碼寫在一個工具類裏面,裏面還有很多需要優化的地方。不如說將這些寫在服務裏面,服務的生命週期與界面綁定。還有數據的讀取這一方面好像有長度限制,超過一定長度,它就分爲兩次或三次,這個問題待解決,不過應對小數據的通信是沒有問題的。