android开发:Socket对象使用心跳机制实现服务器与客户端通信

  • 简介

Socket本质上就是Java封装了传输层上的TCP协议(注:UDP用的是DatagramSocket类)。要实现Socket的传输,需要构建客户端和服务器端。另外,传输的数据可以是字符串和字节。字符串传输主要用于简单的应用,比较复杂的应用(比如Java和C++进行通信),往往需要构建自己的应用层规则(类似于应用层协议),并用字节来传输。

  • Socket案例

现实这个功能非常简单,创建一个Socket对象,如:Socket s = new Socket(HOST_ADDR,HOST_PORT); HOST_ADDR是服务器的IP地址,HOST_PORT是服务器的端口,一般我们会选择8000以上的端口,避免端口冲突.

当客户端与服务端产生了对应的Socket之后,程序无需再区分服务器与客户端,而是通过各自的Socket进行通信.Socket提供了2个方法来获取输入流和输出流.

  • InputStream getInputStream():返回该Socket对象对应的输入流,让程序通过该方法从Socket中取出数据.
  • OutputStream getOutputStream(): 返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据.

现在我们来看看android从服务端读取数据的方法:

InputStream is = s.getInputStream();
byte[] buff = new byte[1024 * 4];
int len = -1;

while (!s.isClosed() && !s.isInputShutdown() && is_start && (len = is.read(buff)) != -1){
    Log.i(TAG,">>>>>>reading..........");

    String msg = new String(Arrays.copyOf(buff, len)).trim();
    if (msg!=null){
        Log.i(TAG,">>>>>>read:"+msg);

        Message m = mHandler.obtainMessage();
        m.what = 1;
        Bundle b = new Bundle();
        b.putString("msg",msg);
        m.setData(b);
        mHandler.sendMessage(m);
    }

}
is.close();

上一段代码就是使用了死循环来实现读取服务器发送过来的数据,当有数据到达的时候,我们使用了一个Handler来发送消息,用来通知UI,告诉它什么消息,把消息显示出来.

现在我们在来实现一段给服务器发送消息的代码:

try {
    if(!s.isClosed() && !s.isOutputShutdown()){
        OutputStream out = s.getOutputStream();
        msg += "\r\n";
        out.write(msg.getBytes("utf-8"));
        out.flush();
        
    }
}catch (IOException e){
    e.printStackTrace();
    
}

这段代码,我们先获取输出流,然后编辑消息msg是一个String文本对象,然后通过OutputStream对象的write的方法把数据发送出去.

 

以下是整个实例的代码:

package com.test.testnetwork;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    private final static String TAG = "NetWorkLog";
    private TextView text;
    private FloatingActionButton fab;
    private Context mContext;

    EditText editText;
    Button but;
    WeakReference<Socket> mSocket;//使用弱引用来保存对象

    //处理消息
    Handler mHandler = new Handler(){
        public void handleMessage(Message msg) {

            Bundle b = msg.getData();

            switch (msg.what){
                case 1:
                    String str = b.getString("msg");
                    text.setText(str+"\n"+text.getText());
                    Log.d(TAG,"msg:"+str);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        fab = (FloatingActionButton) findViewById(R.id.fab);

        mContext = this;
        text = (TextView)findViewById(R.id.text);
        but = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.editText);

        init3();

    }

    /**
     * app初始化入口
     */
    private void init3() {
        //启动Socket的按钮,在应用中,我们一般会写在服务类中
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(mSocket == null){
                    initSocket();
                }else {
                    releaseLastSocket(mSocket);
                }
            }
        });

        //给服务器发送消息的按钮
        but.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String msg = editText.getText().toString();
                sendMsg(msg);
            }
        });
    }

    /**
     * 初始化Socket
     */
    private void initSocket(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Socket s = new Socket(HOST_ADDR,HOST_PORT);
                    mSocket = new WeakReference<Socket>(s);//加到弱引用
                    mReadThread = new ReadThread(s);
                    mReadThread.start();
                    mHandler.postDelayed(HeartBeatRunnable, TAKT_TIME);//初始化成功后,就准备发送心跳包

                } catch (UnknownHostException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * 心跳线程,每隔一段时间与服务器通信,发送的数据是:1,也可以制定你们想要的数据
     */
    private Runnable HeartBeatRunnable = new Runnable() {
        @Override
        public void run() {
            if(System.currentTimeMillis() - lastTime >= TAKT_TIME){
                boolean status = sendMsg("1");
                if(!status){
                    mHandler.removeCallbacks(HeartBeatRunnable);
                    releaseLastSocket(mSocket);
                    mReadThread.release();//释放线程
                    initSocket();//重建线程
                }
                mHandler.postDelayed(HeartBeatRunnable, TAKT_TIME);//继续发送心跳包
                Log.d(TAG,"HeartBeatRunnable");
            }
        }
    };


    private ReadThread mReadThread;//消息读取县城
    private long lastTime = 0L;     //最后一次发送时间
    private static final int TAKT_TIME = 3000;  //间隔时间
    private static final String HOST_ADDR = "192.168.1.205";    //服务器IP地址
    private static final int HOST_PORT = 30003; //端口号

    /**
     * 向服务端发送数据
     * @param msg
     * @return
     */
    public boolean sendMsg(String msg) {
        if(null == mSocket || null == mSocket.get()){
            return false;
        }

        Socket s = mSocket.get();

        try {
            if(!s.isClosed() && !s.isOutputShutdown()){
                OutputStream out = s.getOutputStream();
                msg += "\r\n";
                out.write(msg.getBytes("utf-8"));
                out.flush();
                lastTime = System.currentTimeMillis();//记录最后发送时间
            }
        }catch (IOException e){
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 创建一个线程用来监听服务器发送过来的消息
     */
    class ReadThread extends Thread{
        private WeakReference<Socket> mWeakSocket;
        private boolean is_start = true;

        public ReadThread(Socket s){
            mWeakSocket = new WeakReference<Socket>(s);
        }

        private void release(){
            is_start = false;
            releaseLastSocket(mWeakSocket);
        }
        @Override
        public void run() {
            super.run();

            Socket s = mWeakSocket.get();
            if(s != null){
                try {
                    InputStream is = s.getInputStream();
                    byte[] buff = new byte[1024 * 4];
                    int len = -1;

                    while (!s.isClosed() && !s.isInputShutdown() && is_start && (len = is.read(buff)) != -1){
                        Log.i(TAG,">>>>>>reading..........");

                        String msg = new String(Arrays.copyOf(buff, len)).trim();
                        if (msg!=null){
                            Log.i(TAG,">>>>>>read:"+msg);

                            Message m = mHandler.obtainMessage();
                            m.what = 1;
                            Bundle b = new Bundle();
                            b.putString("msg",msg);
                            m.setData(b);
                            mHandler.sendMessage(m);
                        }

                    }
                    is.close();

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

            }


        }
    }


    /**
     * 释放Socket
     * @param s WeakReference中的socket对象
     */
    private void releaseLastSocket(WeakReference<Socket> s) {
        if(null != s){
            Socket skt = s.get();
            if(!skt.isClosed()){
                try {
                    skt.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            skt = null;
            mSocket = null;
        }
    }



    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

代码解析:

首先我们在initSocket()方法中初始化Socket对象,然后通过Handler对象实现了一个线程延迟发送心跳机制的代码,在HeartBeatRunnable方法中我们对时间进行判断,并且在代码执行完毕后继续发送一个延迟消息,就这样我们实现了按间隔时间向服务器发送存活状态的功能.

最后说一下服务端,服务端可以用很多语言去实现,这里我用的是易语言,方便快速一点.

也可以使用java去实现一个简单的服务端代码:

public static void Socketer() {

try {
ServerSocket serverSocket = new ServerSocket(30003);
while (true) {
Socket socket = serverSocket.accept();

OutputStream out = socket.getOutputStream();
System.out.println(“addr:”+socket.getLocalSocketAddress());

out.write(“欢迎您使用”.getBytes(“UTF-8″));

out.close();
socket.close();

}

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

以下是效果图:

 

 

QQ截图20160630153619

 

易语言服务端的代码:

.版本 2
.支持库 iconv

.程序集 窗口程序集1
.程序集变量 kh, 文本型

.子程序 _服务器1_数据到达
.局部变量 a, 文本型

a = 删首尾空 (到文本 (服务器1.取回数据 ()))
.判断开始 (到整数 (a) = 1)
‘ 心跳机制发送的消息
.默认
服务器1.发送数据 (kh, 编码转换 (到字节集 (“信息已收到,请稍后”), #编码_GBK, #编码_UTF_8, ), )
.判断结束
.子程序 _服务器1_客户进入

kh = 服务器1.取回客户 ()
服务器1.发送数据 (kh, 编码转换 (到字节集 (“欢迎您使用”), #编码_GBK, #编码_UTF_8, ), )
.子程序 _服务器1_客户离开

服务器1.发送数据 (kh, 编码转换 (到字节集 (“拜拜”), #编码_GBK, #编码_UTF_8, ), )
.子程序 _按钮1_被单击
.局部变量 a, 字节集

按钮1.禁止 = 真
a = 编码转换 (到字节集 (编辑框1.内容), #编码_GBK, #编码_UTF_8, )
按钮1.禁止 = 假

下载地址:http://pan.baidu.com/s/1o7NwnRO

Leave a Comment

 
Copyright © 2008-2021 lanxinbase.com Rights Reserved. | 粤ICP备14086738号-3 |