EasySocket一款轻量级的Android端Socket框架

源码地址:https://github.com/jiusetian/EasySocket

EasySocket是一个轻量级的Android端Socket框架,初衷是希望使Socket编程变得更加简单、快捷,该框架可快速实现客户端和服务端之间的TCP长连接通讯,兼容于各种消息协议,非常灵活自由,框架的优势是可以兼容所有的消息协议和快速实现Socket的消息回调功能

EasySocket主要特点:

  • 采用链式调用一键发送和接收数据,根据自己的需求配置参数,简单易用,灵活性高

  • EasySocket实现了包括TCP的连接和断开、数据的发送和接收、心跳保活、重连机制等功能

  • 消息协议自己定义,只需要实现简单的接口即可使用,所以兼容性很强,框架已经帮你实现Socket编程的各种功能,同时也解决了Socket编程的各种问题,比如分包、粘包、重连等等

  • EasySocket只需简单的配置即可实现心跳检测功能

  • 方便实现和管理多个Socket连接

  • EasySocket可以实现消息的回调功能,传统的Socket通信客户端是无法识别服务端的应答消息是对应哪个请求消息的,该框架可以实现Socket请求消息和应答消息的一一对应

一、EasySocket的Android Studio配置

所需权限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

       关于混淆

      -keep class com.easysocket.entity.basemsg.** { *; }

      -keep class com.easysocket.entity.OriginReadData

Gradle配置

1、在根目录的build.gradle文件中添加配置

allprojects {
    repositories {
        ...
	maven { url 'https://jitpack.io' }
    }
}

2、Module的build.gradle文件中添加依赖配置

dependencies { 

    implementation 'com.github.jiusetian:EasySocket:最新版本号' 

}

Server端设置

测试项目,可以用项目中自带的服务端模块socket_server,使用它要先设置socket_server为Application,步骤如下

1.在Android studio 应用选择中打开Edit Configurations,如下

       

 

2.点击+号选择Application

  

   3、填好几个信息,如下图,其中server是应用名称,可以随意取,Main class是socket_server中MainClass类的路径名,Working directory 是MainClass所在的包路径,然后是对应的module,选择socket_server确认即可

 

  4.运行server,如下图所示,然后服务端代码就已经跑起来了,把项目配置中的IP地址改为自己的本地IP就可以测试了

        

 

 

二、EasySocket的基本使用

       1、创建Socket连接

一般在项目的Application中对EasySocket进行全局化配置,下面是一个最简单的配置

    /**
     * 初始化EasySocket
     */
    private void initEasySocket() {
        // socket配置
        EasySocketOptions options = new EasySocketOptions.Builder()
                // 主机地址,请填写自己的IP地址,以getString的方式是为了隐藏作者自己的IP地址
                .setSocketAddress(new SocketAddress(getResources().getString(R.string.local_ip), 9999))
                // 定义消息协议,方便解决 socket黏包、分包的问题
                .setReaderProtocol(new DefaultMessageProtocol())
                .build();

        // 初始化
        EasySocket.getInstance()
                .options(options) // 项目配置
                .createConnection(this);// 创建一个socket连接
    }

      这里主要设置了IP和端口,其他的配置参数都使用了默认值,然后创建一个Socket连接

      2、注册Socket的监听

      定义一个socket行为的监听器,如下

  /**
     * socket行为监听
     */
    private ISocketActionListener socketActionListener = new SocketActionListener() {
        /**
         * socket连接成功
         * @param socketAddress
         */
        @Override
        public void onSocketConnSuccess(SocketAddress socketAddress) {
            LogUtil.d("---> 连接成功");
            controlConnect.setText("socket已连接,点击断开连接");
            isConnected = true;
        }

        /**
         * socket连接失败
         * @param socketAddress
         * @param isNeedReconnect 是否需要重连
         */
        @Override
        public void onSocketConnFail(SocketAddress socketAddress, boolean isNeedReconnect) {
            controlConnect.setText("socket连接被断开,点击进行连接");
            isConnected = false;
        }

        /**
         * socket断开连接
         * @param socketAddress
         * @param isNeedReconnect 是否需要重连
         */
        @Override
        public void onSocketDisconnect(SocketAddress socketAddress, boolean isNeedReconnect) {
            LogUtil.d("---> socket断开连接,是否需要重连:" + isNeedReconnect);
            controlConnect.setText("socket连接被断开,点击进行连接");
            isConnected = false;
        }

        /**
         * socket接收的数据
         * @param socketAddress
         * @param readData
         */
        @Override
        public void onSocketResponse(SocketAddress socketAddress, String readData) {
            LogUtil.d("SocketActionListener收到数据-->" + readData);
        }

        @Override
        public void onSocketResponse(SocketAddress socketAddress, OriginReadData originReadData) {
            super.onSocketResponse(socketAddress, originReadData);
            LogUtil.d("SocketActionListener收到数据-->" + originReadData.getBodyString());
        }
    };

注册监听

        //监听socket相关行为
        EasySocket.getInstance().subscribeSocketAction(socketActionListener);

3、发送Socket消息

演示发送一个消息

    /**
     * 发送一个的消息,
     */
    private void sendTestMessage() {
        TestMessage testMessage = new TestMessage();
        testMessage.setMsgId("test_msg");
        testMessage.setFrom("android");
        // 发送
        EasySocket.getInstance().upMessage(testMessage.pack());
    }

 在App中点击“发送消息”,执行结果如下:

可以看到我们注册的监听器监收到了服务器的响应消息

     4、断开Socket连接

//断开当前的Socket连接,参数false表示当前断开不需要自动重连
EasySocket.getInstance().disconnect(false);

//连接Socket
EasySocket.getInstance().connect();

5、销毁连接

EasySocket.getInstance().destroyConnection();

 destroyConnection()代表销毁整个连接状态,跟disconnect()不一样,如果此时要进行socket连接需要重新创建一个连接,而disconnect()要重新连接直接调用connect()即可

 

三、EasySocket消息协议的定义

       之前严格定义了消息结构必须是Header+Body,收到很多网友的反馈,很多人想让框架兼容所有的消息格式,其实兼容是可以做到的,我不做消息格式的打包,直接发送即可,接收也是每次socket读取的数据算一个消息。不建议这么做,因为这种形式如果消息出现黏包或分包的时候,就会出现解析错误

        其实网络传输中消息协议的格式一般采用:“消息头+消息体”,EasySocket消息格式的基本框架也是如此,即 Header+Body,header一般保存消息的长度、类型等信息,body一般保存消息体

         每个人的协议可能都不一样,所以我们也不可能去统一格式,EasySocket提供了一个协议接口,方便你自定义协议,如下

/**
 * 消息数据格式
 */
public interface IMessageProtocol {

    /**
     * 获取包头的长度
     */
    int getHeaderLength();

    /**
     * 获取数据包体的长度,根据协议这个长度应该写在包头中,在读取数据时用到
     */
    int getBodyLength(byte[] header, ByteOrder byteOrder);

}

            实现自己的消息协议需要实现接口的两个方法,定义好包头的长度和消息体的长度,可以参考框架默认的消息协议,如下

/**
 * Author:Alex
 * Date:2019/5/31
 * Note:默认的消息协议,header为4个字节,用于保存消息体 body的长度
 */
public class DefaultMessageProtocol implements IMessageProtocol {
    @Override
    public int getHeaderLength() {
        return 4; // 包头长度,用来保存body的长度值
    }

    @Override
    public int getBodyLength(byte[] header, ByteOrder byteOrder) {
        if (header == null || header.length < getHeaderLength()) {
            return 0;
        }
        ByteBuffer bb = ByteBuffer.wrap(header);
        bb.order(byteOrder);
        return bb.getInt(); // body的长度以int的形式保存在 header
    }
}

           定义好消息协议,需要在初始化配置EasySocket的时候进行设置:setReaderProtocol(IMessageProtocol readerProtocol),这样就可以使用你定义的消息协议了

 

四、EasySocket启动心跳机制

Socket的连接监听一般用心跳包去检测,EasySocket启动心跳机制非常简单, 下面是实例代码

    // 启动心跳检测功能
    private void startHeartbeat() {
        // 心跳实例
        ClientHeartBeat clientHeartBeat = new ClientHeartBeat();
        clientHeartBeat.setMsgId("heart_beat");
        clientHeartBeat.setFrom("client");
        EasySocket.getInstance().startHeartBeat(clientHeartBeat.pack(), new HeartManager.HeartbeatListener() {
            // 用于判断当前收到的信息是否为服务器心跳,根据自己的实际情况实现
            @Override
            public boolean isServerHeartbeat(OriginReadData orginReadData) {
                try {
                    String s =orginReadData.getBodyString();
                    JSONObject jsonObject = new JSONObject(s);
                    if ("heart_beat".equals(jsonObject.getString("msgId"))) {
                        LogUtil.d("---> 收到服务端心跳");
                        return true;
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                return false;
            }
        });
    }

启动心跳机制关键要定义一个心跳包作为Socket发送给服务端的心跳,然后实现接口用来识别收到的消息是否为服务器的心跳包,根据自己的实际情况来实现

 

五、EasySocket的请求回调功能

EasySocket的最大特点是实现了消息的回调功能,即当发送一个带有回调标识的消息给服务器的时候,我们可以准确地接收到这个消息的响应。启用回调功能需要用户实现如下步骤:

1.自定义CallbackIDFactory

因为回调消息需要携带一个消息的唯一标识,这里我们称之为callbackId,所以要想实现这个功能,你必须告诉框架你的callbackID是怎么获取的,如Demo中所示

/**
 * Author:枪花
 * Date:2020/3/20
 * note:根据自己的实际情况实现
 */
public class CallbackIDFactoryImpl extends CallbackIDFactory {

    /**
     * @param
     * @return
     */
    @Override
    public String getCallbackID(OriginReadData data) {
        try {
            JSONObject body = new JSONObject(data.getBodyString());
            String callbackId = body.getString("callbackId");
            return callbackId;
        } catch (JSONException e) {
            e.printStackTrace();
            return null;
        }
    }
}

因为示例中的消息体是json格式,所以可以通过key-value的形式来获取callbackID的值,当然不限制于任何格式,只要你实现这个方法并返回你的callbackID就可以了

 

2.配置CallbackIDFactory

定义好了CallbakcIdKeyFactory,需要进行配置,如下

        // socket配置
        EasySocketOptions options = new EasySocketOptions.Builder()
                // 主机地址,请填写自己的IP地址,以getString的方式是为了隐藏作者自己的IP地址
                .setSocketAddress(new SocketAddress(getResources().getString(R.string.local_ip), 9999))
                // 配置自己实现的CallbackIDFactory
                .setCallbackIDFactory(new CallbackKeyFactoryImpl())
                .setHeartbeatFreq(10 * 1000) // 心跳间隔
                .build();

启用回调功能的时候,需要将定义好的CallbaCKIDFactory通过setCallbackIDFactory方法配置上去

 

3.自定义回调功能的消息

自定义有回调功能的消息需要继承SuperCallbackSender并实现pack方法,默认每当创建一个回到消息都会生成一个20位数的随机字符串作为callbackID,本项目示例中消息都是以json格式来实现的,当然你的消息格式不一定是json,可以是任何其他形式,你只要实现pack方法,将callbackID打包进去即可, 示例如下

public class CallbackSender extends SuperCallbackSender {

    private String msgId;
    private String from;

    public String getMsgId() {
        return msgId;
    }

    public void setMsgId(String msgId) {
        this.msgId = msgId;
    }

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }


    @Override
    public byte[] pack() {
        byte[] body = new Gson().toJson(this).getBytes();
        // 如果没有设置消息协议,则直接发送消息
        if (EasySocket.getInstance().getOptions().getMessageProtocol() == null) {
            return body;
        }
        ByteBuffer bb = ByteBuffer.allocate(body.length + 4);
        bb.putInt(body.length);
        bb.put(body);
        return bb.array();
    }
}

// 回调消息的父类
public abstract class SuperCallbackSender extends SuperSender {

    private String callbackId;

    public SuperCallbackSender() {
        generateCallbackId();
    }

    public String getCallbackId() {
        return callbackId;
    }

    /**
     * 根据自己的协议打包消息
     *
     * @return
     */
    public abstract byte[] pack();

    /**
     * 随机生成一个回调标识 CallbackId,在消息发送前执行,CallbackId作为消息的唯一标识一起传给服务器,服务器反馈
     * 当前消息的时候也是携带同样的CallbackId给客户端,用以识别
     */
    public void generateCallbackId() {
        callbackId= Utils.getRandomChar(20);
    }
}

 

4.服务器方面的配合使用

回调功能需要服务器方面的配合,因为回调消息携带了唯一标识即callbackId,所以服务器在响应消息的时候,需要将这个callbackId的值返回给客户端

如果以上方面都准备好的话,那么就可以实现Socket的回调功能了,示例如下


    /**
     * 发送一个有回调的消息
     */
    private void sendCallbackMsg() {

        CallbackSender sender = new CallbackSender();
        sender.setMsgId("callback_msg");
        sender.setFrom("我来自android");
        EasySocket.getInstance().upCallbackMessage(sender)
                .onCallBack(new SimpleCallBack(sender.getCallbackId()) {
                    @Override
                    public void onResponse(OriginReadData data) {
                        LogUtil.d("Socket应答消息-->" + data.getBodyString());
                        Toast.makeText(MainActivity.this, "应答消息:" + data.getBodyString(), Toast.LENGTH_LONG).show();
                    }
                    
                    @Override
                    public void onError(Exception e) {
                        super.onError(e);
                        e.printStackTrace();
                    }
                });
    }

执行结果如下:

可以看到,在发送的回调方法中收到了此消息的反馈

另外还封装了一个带进度框的请求,非常实用,使用方法如下:

    // 有进度条的消息
    private void sendProgressMsg() {

        // 进度条接口
        IProgressDialog progressDialog = new IProgressDialog() {
            @Override
            public Dialog getDialog() {
                ProgressDialog dialog = new ProgressDialog(MainActivity.this);
                dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                dialog.setTitle("正在加载...");
                return dialog;
            }
        };
        CallbackSender sender = new CallbackSender();
        sender.setFrom("android");
        sender.setMsgId("delay_msg");
        EasySocket.getInstance()
                .upCallbackMessage(sender)
                .onCallBack(new ProgressDialogCallBack(progressDialog, true, true, sender.getCallbackId()) {
                    @Override
                    public void onResponse(OriginReadData data) {
                        LogUtil.d("进度条回调消息-->" + data.getBodyString());
                    }

                    @Override
                    public void onError(Exception e) {
                        super.onError(e);
                        e.printStackTrace();
                    }
                });
    }

以上演示了EasySocket的基本使用方法,欢迎start,项目地址:https://github.com/jiusetian/EasySocket

六、EasySocket的配置信息说明(EasySocketOptions)

    /**
     * 是否调试模式
     */
    private static boolean isDebug = true;
    /**
     * 主机地址
     */
    private SocketAddress socketAddress;
    /**
     * 备用主机地址
     */
    private SocketAddress backupAddress;
    /**
     * 写入Socket管道的字节序
     */
    private ByteOrder writeOrder;
    /**
     * 从Socket读取字节时的字节序
     */
    private ByteOrder readOrder;
    /**
     * 从socket读取数据时遵从的数据包结构协议,在业务层进行定义
     */
    private IMessageProtocol messageProtocol;
    /**
     * 写数据时单个数据包的最大值
     */
    private int maxWriteBytes;
    /**
     * 读数据时单次读取最大缓存值,数值越大效率越高,但是系统消耗也越大
     */
    private int maxReadBytes;
    /**
     * 心跳频率/毫秒
     */
    private long heartbeatFreq;
    /**
     * 心跳最大的丢失次数,大于这个数据,将断开socket连接
     */
    private int maxHeartbeatLoseTimes;
    /**
     * 连接超时时间(毫秒)
     */
    private int connectTimeout;
    /**
     * 服务器返回数据的最大值(单位Mb),防止客户端内存溢出
     */
    private int maxResponseDataMb;
    /**
     * socket重连管理器
     */
    private AbsReconnection reconnectionManager;
    /**
     * 安全套接字相关配置
     */
    private SocketSSLConfig easySSLConfig;
    /**
     * socket工厂
     */
    private SocketFactory socketFactory;
    /**
     * 实现回调功能需要callbackID,而callbackID是保存在发送消息和应答消息中的,此工厂用来获取socket消息中
     * 保存callbackID值的key,比如json格式中的key-value中的key
     */
    private CallbakcKeyFactory callbakcKeyFactory;
    /**
     * 请求超时时间,单位毫秒
     */
    private long requestTimeout;
    /**
     * 是否开启请求超时检测
     */
    private boolean isOpenRequestTimeout;

    /**
     * IO字符流的编码方式,默认utf-8
     */
    private String charsetName;

GitHub代码的Demo中还有socket服务端的测试代码,大家可以用本地IP地址对本框架进行测试,欢迎点评交流


版权声明:本文为liuxingrong666原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。