• QQ
  • nahooten@sina.com
  • 常州市九洲新世界花苑15-2

Android

Android 一套完备的 Socket 办理计划

原创内容,转载请注明原文网址: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对象分析及脱壳详细示例