Android Socket开发

socket的原理和介绍,网上一搜一大把,但是在我开发遇到问题时候却鲜有博客能搜索到,
在此就不多花时间了,给个传送门

在开发过程中,对socker数据流的包装,格式的协商制定,很重要,
毕竟是跨平台通信,两端其中一端没有处理好数据都会出现问题,而且也不好调试

socket编程

记录下android端简单的socket代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public abstract class BaseSocket {

private final String TAG = "BaseSocket";
// 主线程Handler
// 用于将从服务器获取的消息显示出来
protected Handler mMainHandler;
// Socket变量
protected Socket socket;
// 此处直接采用线程池进行线程管理,而没有一个个开线程
protected ExecutorService mThreadPool;
// 输入流对象
protected InputStream is;
// 输出流对象
protected OutputStream os;

protected String ip;
protected int port;

public BaseSocket(Handler handler, String ip, int port)
{
mMainHandler = handler;
this.ip = ip;
this.port = port;
mThreadPool = Executors.newCachedThreadPool();
}

public boolean connect()
{
// 利用线程池直接开启一个线程 & 执行该线程
mThreadPool.execute(new Runnable() {
@Override
public void run()
{
try {
// 创建Socket对象 & 指定服务端的IP 及 端口号
while (socket == null || !socket.isConnected()) {
socket = new Socket();
socket.setTcpNoDelay(true);
SocketAddress socAddress = new InetSocketAddress(ip, port);
socket.connect(socAddress, 5000);
}
Log.d(TAG, "socket connect=>" + socket.isConnected());

} catch (UnknownHostException e) {
Log.d(TAG, "connect exception=>" + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "connect exception=>" + e.getMessage());
}

recvMessage();
}
});
return true;
}

protected abstract void recvMessage();

protected abstract void sendMessage(String content);


public void close()
{
try {
if (socket != null) {
if (!socket.isOutputShutdown()) {
socket.shutdownOutput();
}
if (!socket.isInputShutdown()) {
socket.shutdownInput();
}
socket = null;
Log.d(TAG, "socket close");
}
} catch (Exception e) {
Log.d(TAG, "close exception=>" + e.getMessage());
}
}
}

由于业务需求对socket的使用不止一处,这里简单的封装基类

下面是一个具体的业务实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class SocketClient extends BaseSocket {

private final String TAG = "SocketClient";

public SocketClient(Handler handler, String ip, int port)
{
super(handler, ip, port);
}

@Override
protected void recvMessage()
{
try {
//创建输入流对象InputStream
is = socket.getInputStream();
int length;
byte[] inputByte = new byte[60];
while ((length = is.read(inputByte, 0, inputByte.length)) > 0) {
String result = new String(inputByte, 0, length, "UTF-8");
Log.e(TAG, "socket recv=>content:" + result);
//通过与服务端协商的数据流格式来解析,根据解析后的不同指令来更新UI
// Message msg = Message.obtain();
// msg.what = STATE_FROM_SERVER_ERR;
// msg.obj = value;
// mMainHandler.sendMessage(msg);
}
} catch (IOException e) {

Log.d(TAG, "recv exception=>" + e.getMessage());
}catch (NullPointerException e)
{
Log.d(TAG, "recv exception=>" + e.getMessage());
}
finally {
close();
}
}

@Override
public void sendMessage(final String content)
{
if (socket == null || !socket.isConnected()) {
Log.d(TAG, "socket reconnect...");
//重连
connect();
}
// 利用线程池直接开启一个线程 & 执行该线程
mThreadPool.execute(new Runnable() {
@Override
public void run()
{
try {
os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
byte[] buf = content.getBytes("UTF-8");
dos.write(buf, 0, buf.length);
dos.flush();
Log.d(TAG, "socket send=>content:" + content);

} catch (IOException e) {
Log.d(TAG, "send exception=>" + e.getMessage());
}catch (Exception e)
{
Log.d(TAG, "send exception=>" + e.getMessage());
}
}
});
}
}

通信过程中,如果服务端socket异常关闭,客户端将会出现各种异常崩溃,
所以socket编程中对于异常的捕获处理也很重要,
这里我直接捕获异常,没做处理,是因为产品需求上逻辑,
最后都会调用close方法,释放socket链接。
不同的业务需求有不同的处理方法。

Socket开发中可能遇到的问题

  1. socket通信请放在工作线程中,如果通信频率很高,最好使用线程池来管理

  2. 当socket长连接建立成功后,如果手机屏幕关闭,只要过很短的时间,
    android系统就会将socket服务挂起,这种行为应该是出于节电考虑的,
    但体验会下去很多,因为总是自动断开。
    另外当我们的手机通过数据线连接电脑调试的时候,
    手机熄灭屏幕后,socket服务是不会被自动挂起的,
    似乎在调试模式下,手机不会自动进入节电模式,
    但当连接数据线充电的的话,手机在熄屏后还是会将socket服务挂起的。
    如果希望手机在熄屏后不将socket服务挂起,可以通过PowerManager设置电源模式,
    使cpu不进入节电模式,当然了,可以做个计时器,
    也别让cpu全马力输出太久(这点未验证过,以后业务上遇到再研究)

  3. 两端可以协商好使用心跳包来确认双方是否存活(短链接可能没必要)

  4. 自动拆包问题。在发送数据时如果发送的数据被转化为byte后,
    长度超过1448时,发送的数据将被拆分。
    解决自动拆包问题,一是约定好数据包长度,二是在数据包里面写入信息的总长度,
    比如,可以将发送的第一位设置为“$”,表示数据的开头,
    接下来后四位用来存放数据的总长度,之后的部分用来存放发送数据,
    这样,接收端在接收到数据后就可以通过解析出数据的总长度来确认是否已经接收完全,
    如果不全再继续接收即可。(关于TCP和1448,可以参考传送门

  5. 自动粘包问题。发送数据过长时会有自动拆包的问题,
    当发送数据过短或者发送的数据之间时间间隔很短的时候,还会发生自动粘包的问题。
    自动粘包就是指本来是两个或者多个包中的数据同时合并到了一个包中,
    这时候,容易出现解析不全的情况。
    解决粘包问题同样可以使用上面提到的方法,只不过反过来了,
    当发现接收到的数据比通过首部一至五位计算出的长度要长时,
    可以通过截取的方法,分段计算。

  6. 别以为自动拆包和自动粘包是互斥的,它们也有可能同时出现。
    首先发生粘包,而粘包的长度超过了1448,然后就会发生拆包。
    处理方法也不难,接收端首先处理粘包,
    当处理到最后一个包时会发现最后一个包不完整,将他保存下来,
    与接下来接收到的数据进行拼接,然后接续处理粘包就可以了,
    调用流程就是:处理粘包、处理拆包、处理粘包。。。就可以了。

后续遇到问题再补充。。。

坚持原创分享,您的支持将鼓励我继续创作!