Android编程:双工tcp客户端
本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明.
环境:
主机:WIN10
开发环境:Android Studio 2.2 Preview 3
说明:
在<<Android编程:UDP客户端和TCP客户端>>介绍了编写TCP客户端方法。先对此代码进行优化,实现以下功能:
- 双工通信。发送一个线程,接收一个线程,不占用UI主线程
- 发送线程在发送完成后会自锁。当有新的任务需要发送,再唤醒发送线程。
- 可以新建连接,断开连接
- 当接收到数据,用观察者机制推送数据给其他模块
- 实时检测与服务器连接是否保持。如果检测到断开连接,锁住接收线程
- 本模块为单例模式独立运行
发送线程同步机制与《Android编程:双工udp客户端》保持一致。
源码:
TdpClinet.Java
package com.bazhangkeji.classroom.net;
import android.util.Log;
import com.bazhangkeji.classroom.Config;
import com.bazhangkeji.classroom.common.Crc16;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TcpClient extends Observable implements Config, Protocol, Runnable {
private static final String TAG_LOG = "TcpClient";
private static final int MAX_FRAME_LENGTH = 2048;
private static final int CONNECT_TIME_OUT = 3000;
private static TcpClient tcpClient;
private boolean isConnected = false;
private Socket tcpSocket;
private InputStream inputSteam;
private OutputStream outputStream;
private String serverIp = "";
private int serverPort;
private List<NetSendParameter> sendListCache;
private List<NetSendParameter> sendList;
private ReadWriteLock sendListCacheLock;
// 线程锁:当前发送线程全部发送完成后就自锁等待
private final byte[] lockSendThread = new byte[0];
// 线程锁:当前不需要侦听则自锁等待
private final byte[] lockReceiveThread = new byte[0];
public static TcpClient getInstance() {
if (tcpClient == null) {
tcpClient = new TcpClient();
new Thread(TcpClient.getInstance()).start();
}
return tcpClient;
}
private TcpClient()
{
sendListCache = new ArrayList<>();
sendList = new ArrayList<>();
sendListCacheLock = new ReentrantReadWriteLock();
new Thread(new SendThread()).start();
}
/**
* 连接服务器
* @param ip: 服务器ip
* @param port: 服务器端口
*/
public synchronized void makeConnect(String ip, int port) {
if (isConnected) {
if (this.serverIp.equals(ip) && this.serverPort == port) {
return;
}
}
serverIp = ip;
serverPort = port;
new Thread(new MakeConnect()).start();
}
/**
* 关闭连接
*/
public void close() {
if (isConnected) {
new Thread(new CloseConnect()).start();
}
}
/**
* 当前是否连接.
* @return 连接返回true,失败返回false
*/
public boolean isConnected() {
return isConnected;
}
/**
* 得到服务器ip
* @return 服务器ip
*/
public String getServerIp() {
return serverIp;
}
/**
* 得到服务器端口
* @return 服务器端口
*/
public int getServerPort() {
return serverPort;
}
public void send(NetSendParameter netSendParameter) {
if (isConnected) {
sendListCacheLock.writeLock().lock();
sendListCache.add(netSendParameter);
sendListCacheLock.writeLock().unlock();
unlockSendThread();
}
}
private void unlockSendThread() {
synchronized (lockSendThread) {
lockSendThread.notifyAll();
}
}
@Override
public void run() {
byte[] bufferReceive = new byte[MAX_FRAME_LENGTH];
DatagramPacket receiveFrame = new DatagramPacket(bufferReceive, MAX_FRAME_LENGTH);
while (true) {
if (!isConnected) {
lockReceiveThread();
}
try {
int length = inputSteam.read(receiveFrame.getData());
if (length > 0) {
receiveFrame.setLength(length);
if (FilterFrame.filter(receiveFrame)) {
setChanged();
notifyObservers(receiveFrame);
}
} else {
if (!tcpSocket.isClosed()) {
Log.i(TAG_LOG, "closed!!!!");
tcpSocketClose();
}
if (!tcpSocket.isConnected()) {
Log.i(TAG_LOG, "disconnect!!!!");
}
}
} catch (IOException e) {
e.printStackTrace();
tcpSocketClose();
}
}
}
private void lockReceiveThread() {
synchronized (lockReceiveThread) {
try {
lockReceiveThread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private class MakeConnect implements Runnable {
@Override
public void run() {
if (isConnected) {
tcpSocketClose();
}
try {
// tcpSocket = new Socket(serverIp, serverPort);
tcpSocket = new Socket();
SocketAddress address = new InetSocketAddress(serverIp, serverPort);
tcpSocket.connect(address, CONNECT_TIME_OUT);
inputSteam = tcpSocket.getInputStream();
outputStream = tcpSocket.getOutputStream();
isConnected = true;
unlockReceiveThread();
} catch (IOException e) {
e.printStackTrace();
}
}
private void unlockReceiveThread() {
synchronized (lockReceiveThread) {
lockReceiveThread.notifyAll();
}
}
}
private void tcpSocketClose() {
isConnected = false;
try {
tcpSocket.close();
Log.i(TAG_LOG, "断开连接");
} catch (IOException e) {
e.printStackTrace();
}
}
private class CloseConnect implements Runnable {
@Override
public void run() {
if (isConnected) {
tcpSocketClose();
}
}
}
private class SendThread implements Runnable {
@Override
public void run() {
while (true) {
if (!isConnected) {
clearCache();
sendList.clear();
lockThread();
}
copyCache();
if (!sendList.isEmpty()) {
sendFrame();
sendList.clear();
} else {
lockThread();
}
}
}
private void clearCache() {
sendListCacheLock.writeLock().lock();
sendListCache.clear();
sendListCacheLock.writeLock().unlock();
}
private void lockThread() {
synchronized (lockSendThread) {
try {
lockSendThread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void copyCache() {
sendListCacheLock.readLock().lock();
if (sendListCache.isEmpty()) {
sendListCacheLock.readLock().unlock();
} else {
for (NetSendParameter parameter : sendListCache) {
sendList.add(parameter);
}
sendListCacheLock.readLock().unlock();
clearCache();
}
}
private void sendFrame() {
for (NetSendParameter parameter: sendList) {
send(parameter);
}
}
private void send(NetSendParameter netSendParameter) {
byte[] arr = new byte[MAX_FRAME_LENGTH];
int j = 0;
arr[j++] = (byte)(FRAME_HEAD >> 8);
arr[j++] = (byte)FRAME_HEAD;
arr[j++] = (byte)PROTOCOL_VERSION_CODE;
arr[j++] = (byte)(netSendParameter.cmd >> 8);
arr[j++] = (byte)netSendParameter.cmd;
if (netSendParameter.frameIndex == 0) {
int frameIndex = FrameIndex.getInstance().get();
arr[j++] = (byte)(frameIndex >> 8);
arr[j++] = (byte)frameIndex;
FrameIndex.getInstance().increment();
} else {
arr[j++] = (byte) (netSendParameter.frameIndex >> 8);
arr[j++] = (byte) netSendParameter.frameIndex;
}
// 报文长度
arr[j++] = (byte)(netSendParameter.length >> 8);
arr[j++] = (byte)netSendParameter.length;
int crc = Crc16.calc(netSendParameter.frameBody, 0, netSendParameter.length);
arr[j++] = (byte)(crc >> 8);
arr[j++] = (byte)crc;
// 正文
for (int i = 0; i < netSendParameter.length; i++) {
arr[j++] = netSendParameter.frameBody[i];
}
try {
outputStream.write(arr, 0, j);
} catch (IOException e) {
e.printStackTrace();
tcpSocketClose();
}
}
}
}
测试代码:
新建连接:
TcpClient.getInstance().makeConnect("115.28.86.171", 21801);
NetSendParameter parameter = new NetSendParameter();
parameter.cmd = 3;
TcpClient.getInstance().send(parameter);