接口自动化测试(二)--socket模块

介绍本次自动化测试中,网络模块的实现思路

Posted by Nela on September 26, 2018

接口自动化测试(二)–socket模块

socket模块

需求

  • 连接服务端
  • 读数据
  • 写数据
  • 处理数据粘包

由于程序轻量,采用BIO方式

BIO特点

  1. inputStream read
  2. socket.accept

两个方法都是线程阻塞式。

读写线程通讯

由于write和read均不能触发在主线程。

思路是将读写线程分开,并创建一个工作线程,统一由线程池开启。通过handler机制传递消息。

由于handler是队列管理,所以不存在消息顺序错乱等情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 Runnable mWriteTask = new Runnable() {
        @SuppressLint("HandlerLeak")
        @Override
        public void run() {
            Looper.prepare();
            mWriteHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SERVICE_SEND_COMMAND:
                            Bundle bundle = msg.getData();
                            String data = bundle.getString("sendData");
                            Log.d("WriteTask", data.toString());
                            mTcpUtils.sentData2Server(data);
                            break;
                    }
                }
            };
            Looper.loop();
        }
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Runnable mReadTask = new Runnable() {
        @Override
        public void run() {
            mTcpUtils = new TcpUtils();
            Boolean connectResult = mTcpUtils.connectService(mHost, mPort);
            if (connectResult) {
                boolean flag = true;
                while (flag) {
                    String content = mTcpUtils.getDataFromService();
                    if (content != null) {
                        Log.d("ReadTask: ", content.toString());
                        Message message = buildMessage(SERVICE_RECEIVE_COMMAND, "ReceiveData", content.toString());
                        mWorkHandler.sendMessage(message);
                        // mWorkHandler.sendMessage();
                        // mWriteTask.sendMessage();
                    }
                }
            } else {
                Log.d(TAG, "连接失败");
            }
        }
    };

工作线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Runnable mWorkTask = new Runnable() {
        @SuppressLint("HandlerLeak")
        @Override
        public void run() {
            Looper.prepare();
            mWorkHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    CommandUtils commandUtils = new CommandUtils();
                    switch (msg.what) {
                        case SERVICE_RECEIVE_COMMAND:
                        ....
                    }
                }
            };
            Looper.loop();
        }
    };

线程池:

1
2
3
4
5
public void startTask(Runnable myTask) {
    mExecutor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<Runnable>(5));
    mExecutor.execute(myTask);
}

判读连接是否断开

方案: 轮询,发送一段数据,根据返回值,判断socket是否断开。

粘包处理

因为,数据是以流的形式传递的,所以不存在数据”包”。那么包就是我们自行进行装包和拆包的。

处理数据粘包的几种方案

  1. 可在头部约定数据包大小关流
  2. 约定有效数据的结尾字段

根据 /r/n 处理数据粘包

假设我们发送的命令以 /r/n 结尾为一个命令。

1.阻塞读流 2.定义缓冲区,将读到的数据放入缓冲区 3.判断缓冲区有无一个完整命令如果有则依次return

这里我们简单处理/r/n 就是readLine

同样在发包时增加 /r/n 并替换所有数据包内/r/n为 空格

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
79
80
81
82
83
84
public class TcpUtils {

    private final int CONNECT_TIME_OUT = 30000;

    public final  String TAG=TcpUtils.class.getSimpleName();

    private Socket client;

    private InputStream mInputStream;
    private InputStreamReader mInputStreamReader;
    private BufferedReader mBufferedReader;

    public Boolean connectService(final String adress, final int port) {
        try {
            client = new Socket(adress, port);
            client.setSoTimeout(CONNECT_TIME_OUT);
            if(isConnected()){
                initReader();
                return true;
            }else {
                return false;
            }

        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    private void initReader() throws IOException {
        mInputStream = client.getInputStream();
        mInputStreamReader = new InputStreamReader(mInputStream);
        mBufferedReader = new BufferedReader(mInputStreamReader);
    }

    //判断是否连接
    public boolean isConnected(){
        try{
            client.sendUrgentData(0xFF);
            return true;
        }catch(Exception e){
            return false;
        }
    }


    //阻塞方法
    public String getDataFromService() {
        String content = null;
        try {
            if (mInputStream != null) {
                content = mBufferedReader.readLine();
            } else {
                Log.d(TAG, "InputStream  获取失败");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return content;
        }
        return content;
    }

    public void sentData2Server(String data) {
        if(TextUtils.isEmpty(data)){
            return;
        }
         data=data.replaceAll("\r\n","");
        try {
            OutputStream outputStream = client.getOutputStream();
            if (outputStream != null) {
                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
                data = data + "\r\n";
                writer.write(data);
                writer.flush();
                outputStream.flush();
            } else {

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

NIO

在编写之前,再次了解到NIO相关技术。 使用新的方法处理输入输出, 和input outPut 的区别在于,提供了map 方法,如果传统是面向流,那么新io 则是面向块。 nio 最关键的部分是由阻塞读流,改为读状态,而读取状态不是真正的占用CPU。只有读流写流是占用cpu的而且时间很短。所以定义NIO为非阻塞。

参考文章

https://www.cnblogs.com/QG-whz/p/5537447.html https://www.jianshu.com/p/3ff7065bb38b http://guguzhang.iteye.com/blog/2253218