原创内容,转载请注明原文网址:http://homeqin.cn/a/wenzhangboke/jishutiandi/Android/2019/0930/666.html
名目地点,稀饭点一个 star:
AndroidSocket
写在前面:
在上上周的时候,写了一篇文章:
在 Android 上,一个手机App外包完备的 UDP 通讯模块应该是怎样的?
文中分析了在 Android 端,一个完备的 UDP 模块应该考虑哪些方面。固然了文中末了也提到了,UDP 的应用本身就有少许范围性,好比发送数据的大小有限制,属于不行靠和谈,可能丢包。并且它是一对多发送的和谈等等...要是能将这个模块能加入 TCP Socket 增补,那就对照完善办理了 Android 上端到端的通讯。下面就来看看怎么去做。
整体步骤流程
先来说一下整体的步骤思绪吧:
发送 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();
}
开始进行少许初始化操纵,App开发培训筹办线程池,纪录工具初始的光阴等等。
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 Socket 端,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 开启一个心跳线程,每隔断五秒,就去播送一个 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 Socket 来发送和汲取消息了。
细节处分
接下来即是少许细节处分了,好比我们的 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);
}
开始会每隔两秒,就给对方发送一个 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 密集型啊,来开启差别的线程通道。这个就波及线程的常识了。
上篇:上一篇:Android运用socket即时通信的实现
下篇:下一篇:ZjDroid对象分析及脱壳详细示例