OkSocket与Android的简单使用

一个Android轻量级Socket通讯框架,既OkHttp后又一力作.
框架开源地址及Demo演示: https://github.com/xuuhaoo/OkSocket
欢迎star,fork,Issue交流


OkSocket简介

  • OkSocket是一款基于阻塞式传统Socket的一款Socket客户端整体解决方案.你可以使用它进行基于Tcp协议的Socket通讯.就是我们所说的长连接.
  • 对通讯协议几乎无限制,可以使用PB,可以使用JSON,可以使用XML.只要可以序列化成Byte数组的对象都可以传输.
  • 兼容所有语言写的Socket服务端,解决了Tcp通讯中头疼的粘包拆包问题,断线重连问题,心跳保持问题,分片发送,重定向连接等问题.
  • 针对 手机 < - > 服务器 , 手机< - > 手机 间都可以进行tcp通讯,手机间通讯俗称点对点通讯,可以很好的支持.
  • OkSocket还支持单工和全双工通讯.适配各种复杂业务场景.分为 客户端(OkSocketClient) 服务端(OkSocketServer)具体的继承和依赖方法在下面.
  • 如果需要看demo程序,可以去https://github.com/xuuhaoo/OkSocket地址进行clone.之后直接run起来就可以了.Demo会自动和OkSocket编写的EchoServer进行连接通讯,让使用者更好地了解使用方法
  • OkSocket旨在让更多不熟悉socket和tcp协议的朋友可以专注于业务开发而不是底层协议的开发和学习.

Demo程序截图

  • 1.简单的调用示例(建立连接,断开连接,发送数据,接收数据)
  • 2.复杂的调用示例(简单示例中的内容,断线重连,心跳,重定向)
  • 3.OkServer的Android端使用(可以进行点对点通讯,当然也可以部署在云端服务器用作SocketServer)


    首页
    简单示例
    复杂示例

Maven配置

  • OkSocket 支持 JCenter 仓库
allprojects {
    repositories {
        jcenter()
    }
}
  • 在Module的build.gradle文件中添加依赖配置
dependencies {
    //基础的 OkSocket 功能集成包.您的Socket开发无论是客户端还是Java,都需要此包 (必须集成)
    api 'com.tonystark.android:socket:4.1.0'
    //如果您需要使用 OkSocketServer 功能在客户端或者Java程序,您还需要依赖下面的Server插件包和上面的一起依赖.
    api 'com.tonystark.android:socket-server:4.1.0'
}

如果您是 Android 请关注下权限

  • 在AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

混淆配置

  • 请避免混淆OkSocket,在Proguard混淆文件中增加以下配置:
-dontwarn com.xuhao.didi.socket.client.**
-dontwarn com.xuhao.didi.socket.common.**
-dontwarn com.xuhao.didi.socket.server.**
-dontwarn com.xuhao.didi.core.**

-keep class com.xuhao.didi.socket.client.** { *; }
-keep class com.xuhao.didi.socket.common.** { *; }
-keep class com.xuhao.didi.socket.server.** { *; }
-keep class com.xuhao.didi.core.** { *; }

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
-keep class com.xuhao.didi.socket.client.sdk.client.OkSocketOptions$* {
    *;
}
-keep class com.xuhao.didi.socket.server.impl.OkServerOptions$* {
    *;
}

回声服务器

  • 该回声服务器是专门为初学者调试 OkSocket 库部属的一台服务器,初学者可以将项目中的 app 安装到手机上,点击 Connect 按钮即可,该服务器仅为熟悉通讯方式和解析方式使用.不能作为商用服务器. 不用时应及时断开,保证有限的资源最大化利用

公网IP:104.238.184.237
公网Port:8080

开始一个简单的长连接

  • OkSocket 会默认对每一个 Open 的新通道做缓存管理,仅在第一次调用 Open 方法时创建 ConnectionManager 管理器,之后调用者可以通过获取到该ConnectionManager的引用,继续调用相关方法
  • ConnectionManager 主要负责该地址的套接字连接断开发送消息等操作.
//连接参数设置(IP,端口号),这也是一个连接的唯一标识,不同连接,该参数中的两个值至少有其一不一样
ConnectionInfo info = new ConnectionInfo("104.238.184.237", 8080);
//调用OkSocket,开启这次连接的通道,调用通道的连接方法进行连接.
OkSocket.open(info).connect();

有回调的长连接

  • 注册该通道的监听器,每个 Connection 通道中的监听器互相隔离,因此如果一个项目连接了多个 Socket 连接需要在每个 Connection 注册自己的连接监听器,连接监听器是该 OkSocket 与用户交互的唯一途径
//连接参数设置(IP,端口号),这也是一个连接的唯一标识,不同连接,该参数中的两个值至少有其一不一样
ConnectionInfo info = new ConnectionInfo("104.238.184.237", 8080);
//调用OkSocket,开启这次连接的通道,拿到通道Manager
IConnectionManager manager = OkSocket.open(info);
//注册Socket行为监听器,SocketActionAdapter是回调的Simple类,其他回调方法请参阅类文档
manager.registerReceiver(new SocketActionAdapter(){
    @Override
    public void onSocketConnectionSuccess(ConnectionInfo info, String action) {
     Toast.makeText(context, "连接成功", LENGTH_SHORT).show();
    }
});
//调用通道进行连接
manager.connect();

可配置的长连接

  • 获得 OkSocketOptions 的行为属于比较高级的 OkSocket 调用方法,每个 Connection 将会对应一个 OkSocketOptions,如果第一次调用 Open 时未指定 OkSocketOptions,OkSocket将会使用默认的配置对象,默认配置请见文档下方的高级调用说明
//连接参数设置(IP,端口号),这也是一个连接的唯一标识,不同连接,该参数中的两个值至少有其一不一样
ConnectionInfo info = new ConnectionInfo("104.238.184.237", 8080);
//调用OkSocket,开启这次连接的通道,拿到通道Manager
IConnectionManager manager = OkSocket.open(info);
//获得当前连接通道的参配对象
OkSocketOptions options= manager.getOption();
//基于当前参配对象构建一个参配建造者类
OkSocketOptions.Builder builder = new OkSocketOptions.Builder(options);
//修改参配设置(其他参配请参阅类文档)
builder.setSinglePackageBytes(size);
//建造一个新的参配对象并且付给通道
manager.option(builder.build());
//调用通道进行连接
manager.connect();

如何发送数据

//类A:
//...定义将要发送的数据结构体...
public class TestSendData implements ISendable {
    private String str = "";

    public TestSendData() {
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("cmd", 14);
            jsonObject.put("data", "{x:2,y:1}");
            str = jsonObject.toString();
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public byte[] parse() {
        //根据服务器的解析规则,构建byte数组
        byte[] body = str.getBytes(Charset.defaultCharset());
        ByteBuffer bb = ByteBuffer.allocate(4 + body.length);
        bb.order(ByteOrder.BIG_ENDIAN);
        bb.putInt(body.length);
        bb.put(body);
        return bb.array();
    }
}

//类B:
private IConnectionManager mManager;
//...省略连接及设置回调的代码...
@Override
public void onSocketConnectionSuccess(ConnectionInfo info, String action) {
     //连接成功其他操作...
     //链式编程调用
     OkSocket.open(info)
        .send(new TestSendData());
}

如何接收数据

  • OkSocket客户端接收服务器数据是要求一定格式的,客户端的OkSocketOptions提供了接口来修改默认的服务器返回的包头解析规则.请看下图为默认的包头包体解析规则


    payload_explain_en_压缩.png
  • 如上图包头中的内容为4个字节长度的int型,该int值标识了包体数据区的长度,这就是默认的头解析.
  • 如果需要自定义头请按照如下方法

注意:
Header :指的是数据协议中定长部分,该部分应该包含后面不定长的Body(Payload)的字节长度.
Body(Payload) :指的是不定长部分,通常是数据区:

//设置自定义解析头
OkSocketOptions.Builder okOptionsBuilder = new OkSocketOptions.Builder(mOkOptions);
okOptionsBuilder.setReaderProtocol(new IReaderProtocol() {
    @Override
    public int getHeaderLength() {
        / *
         * 返回不能为零或负数的报文头长度(字节数)。
         * 您返回的值应符合服务器文档中的报文头的固定长度值(字节数),可能需要与后台同学商定
         * /
        return / *固定报文头的长度(字节数)* /;
    }

    @Override
    public int getBodyLength(byte[] header, ByteOrder byteOrder) {
     / *
         * 体长也称为有效载荷长度,
         * 该值应从作为函数输入参数的header中读取。
         * 从报文头数据header中解析有效负载长度时,最好注意参数中的byteOrder。
         * 我们强烈建议您使用java.nio.ByteBuffer来做到这一点。
         * 你需要返回有效载荷的长度,并且返回的长度中不应该包含报文头的固定长度
         * /
        return /*有效负载长度(字节数),固定报文头长度(字节数)除外*/;
    }
});
//将新的修改后的参配设置给连接管理器
mManager.option(okOptionsBuilder.build());


//...正确设置解析头之后...
@Override
public void onSocketReadResponse(ConnectionInfo info, String action, OriginalData data) {
    //遵循以上规则,这个回调才可以正常收到服务器返回的数据,数据在OriginalData中,为byte[]数组,该数组数据已经处理过字节序问题,直接放入ByteBuffer中即可使用
}

如何保持心跳

//类A:
//...定义心跳管理器需要的心跳数据类型...
public class PulseData implements IPulseSendable {
    private String str = "pulse";

    @Override
    public byte[] parse() {
        byte[] body = str.getBytes(Charset.defaultCharset());
        ByteBuffer bb = ByteBuffer.allocate(4 + body.length);
        bb.order(ByteOrder.BIG_ENDIAN);
        bb.putInt(body.length);
        bb.put(body);
        return bb.array();
    }
}

//类B:
private IConnectionManager mManager;
private PulseData mPulseData = new PulseData;
//...省略连接及设置回调的代码...
@Override
public void onSocketConnectionSuccess(ConnectionInfo info, String action) {
     //连接成功其他操作...
     //链式编程调用,给心跳管理器设置心跳数据,一个连接只有一个心跳管理器,因此数据只用设置一次,如果断开请再次设置.
     OkSocket.open(info)
        .getPulseManager()
        .setPulseSendable(mPulseData)//只需要设置一次,下一次可以直接调用pulse()
        .pulse();//开始心跳,开始心跳后,心跳管理器会自动进行心跳触发
}

心跳接收到了之后需要进行喂狗

  • 因为我们的客户端需要知道服务器收到了此次心跳,因此服务器在收到心跳后需要进行应答,我们收到此次心跳应答后,需要进行本地的喂狗操作,否则当超过一定次数的心跳发送,未得到喂狗操作后,狗将会将此次连接断开重连.
//定义成员变量
private IConnectionManager mManager;
//当客户端收到消息后
@Override
public void onSocketReadResponse(ConnectionInfo info, String action, OriginalData data) {
    if(mManager != null && 是心跳返回包){//是否是心跳返回包,需要解析服务器返回的数据才可知道
        //喂狗操作
        mManager.getPulseManager().feed();
    }
}

如何手动触发一次心跳,在任何时间

//定义成员变量
private IConnectionManager mManager;
//...在任意地方...
mManager = OkSocket.open(info);
if(mManager != null){
    PulseManager pulseManager = mManager.getPulseManager();
    //手动触发一次心跳(主要用于一些需要手动控制触发时机的场景)
    pulseManager.trigger();
}

OkSocket参配选项及回调说明

  • OkSocketOptions

    • Socket通讯模式mIOThreadMode
    • 连接是否管理保存isConnectionHolden
    • 写入字节序mWriteOrder
    • 读取字节序mReadByteOrder
    • 头字节协议mHeaderProtocol
    • 发送单个数据包的总长度mSendSinglePackageBytes
    • 单次读取的缓存字节长度mReadSingleTimeBufferBytes
    • 脉搏频率间隔毫秒数mPulseFrequency
    • 脉搏最大丢失次数(狗的失喂次数)mPulseFeedLoseTimes
    • 后台存活时间(分钟)mBackgroundLiveMinute
    • 连接超时时间(秒)mConnectTimeoutSecond
    • 最大读取数据的兆数(MB)mMaxReadDataMB
    • 重新连接管理器mReconnectionManager
  • ISocketActionListener

    • Socket读写线程启动后回调onSocketIOThreadStart
    • Socket读写线程关闭后回调onSocketIOThreadShutdown
    • Socket连接状态由连接->断开回调onSocketDisconnection
    • Socket连接成功回调onSocketConnectionSuccess
    • Socket连接失败回调onSocketConnectionFailed
    • Socket从服务器读取到字节回调onSocketReadResponse
    • Socket写给服务器字节后回调onSocketWriteResponse
    • 发送心跳后的回调onPulseSend
   Copyright [2017] [徐昊]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 3,406评论 3 7
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 127,576评论 18 546
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 74,608评论 12 116
  • iPhone的标准推荐是CFNetwork 库编程,其封装好的开源库是 cocoa AsyncSocket库,用它...
    Ethan_Struggle阅读 1,041评论 2 11
  • 洛桑陀美上师:《和谐中的圆满》—【恶趣之苦】 关于三恶道的痛苦 , 尤其是地狱道的痛苦,这方面我们要好好的去...
    祥云_17ec阅读 17评论 0 0