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.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

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