1 Star 13 Fork 3

OS-Android / TcpLib

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

TcpLibApp

License API

介绍

安卓 Java tcp提炼封装工具, 目前已支持一台手机建立多个端口监听服务器且使用各自的报文处理规则,一个手机对多个端口服务器进行连接且使用各自的报文处理规则。

更新

V1.2.0 (2023-09-27)

  • 优化为自定义缓存区,提高缓存区解析速率的同时避免发生集合修改异常;
  • 配置中增加缓存区上限大小设置,不建议设置过大,大小控制在64KB-128kb即可,注意缓存区解析速度不要低于传输速度避免导致缓存区超出后不在接收;
  • 增加一组UDP工具。

V1.1.5 (2022-12-05)

  • 增加缓存区方法“removeFrameToHeader(byte[] header)”移除缓存区与给定头匹配的字节前所有字节,将匹配头置于缓存区首位;
  • 修改缓存区继承至“ArrayList”,提高缓存区解析速率;
  • 修改客户端和服务器工具类发送数据方式,避免发送数据顺序错乱问题。

V1.1.1 (2022-02-17)

  • 缓冲区列表对象增加写操作时的线程锁,避免出现集合修改错误。
  • 缓冲区为避免方法函数错用,以将常规方法函数进行删除标记。

一、项目介绍

  1. TcpLib aar资源项目,需要引入的资源包项目,aar资源已申请联网权限。 现已支持jitpack引入。
  2. TcpService 为APP类型,服务端演示程序。
  3. tcpclient 为APP类型,客户端演示程序。

二、工程引入工具包

com.android.tools.build:gradle:7.0.0以下版本,工程的build.gradle文件添加

allprojects {
    repositories {
        google()
        mavenCentral()

        //jitpack 仓库
        maven { url 'https://jitpack.io' }
    }
}

com.android.tools.build:gradle:7.0.0及以上版本,在工程的 settings.gradle 文件添加

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()

        //jitpack 仓库
        maven {
            url 'https://jitpack.io'
        }
    }
}

APP的build.gradle文件添加

dependencies {
    ...
    //TCP工具
    implementation 'com.gitee.osard.TcpLib:TcpLib:1.2.0'
    //UDP工具
    implementation 'com.gitee.osard.TcpLib:UdpLib:1.2.0'
    
    //核心消息通讯
    implementation 'org.greenrobot:eventbus:3.3.1'
}

三、配置debug模式

在application下注册debug模式,可以打印更多log日志。

//TCP服务设定debug模式,在debug下打印log日志
TcpLibConfig.getInstance()
        .setDebugMode(BuildConfig.DEBUG)
        //设置连接断开后缓存区数据继续保留的时间,单位:分钟,超出此时间后缓存数据扔未处理完时将会被自动清理。
        .setRetentionTime(30);

四、重写服务报文接收及发送处理

- 接收报文处理

此为简单示例 ,也可以定义带报文头、报文尾、数据验证等的处理方式,具体规则完全由自己定义。bufferQueue处理一帧报文后需要在队列中移除这一帧报文数据。 必须实现接口 TcpBaseDataDispose

public class DataDispose implements TcpBaseDataDispose {

    private final static String TAG = DataDispose.class.getSimpleName();

    @Override
    public void dispose(ByteQueueList bufferQueue, int servicePort, String clientAddress) {
        byte[] b = bufferQueue.copyAndRemove(bufferQueue.size());
        //todo 按照解析后的指令分发事件
        EventBus.getDefault().post(new TcpServiceReceiveDataEvent(servicePort, clientAddress, new String(b)));
    }
}

- 发送报文处理

此为简单示例 ,也可以定义带报文头、报文尾、数据验证等的处理方式,具体规则完全由自己定义。 必须实现接口 TcpBaseDataGenerate

public class DataGenerate implements TcpBaseDataGenerate {

    @Override
    public byte[] generate(Object content) {
        //仅作为参考,不推荐此做法,缓冲区为10kb,请做好报文头和报文尾区分,避免缓冲区读取不完整
        if (content instanceof byte[]) {
            return (byte[]) content;
        } else if (content instanceof String) {
            return ((String) content).getBytes(Charset.forName("UTF-8"));
        } else {
            return content.toString().getBytes(Charset.forName("UTF-8"));
        }
    }
}

五、服务端的使用

- 服务端启动,需提供启动的端口号以及报文的处理和生成实现类

int port = 50000;
TcpLibService.getInstance()
                .bindService(port, TcpDataBuilder.builder(new DataGenerate(), new DataDispose()));

- 服务端关闭,关闭时需提供启动的端口号

int port = 50000;
TcpLibService.getInstance().close(port);

1. 服务端的启动、客户端事件处理

在任意对象下,创建实例时,以下以activity为例

  • 注册EventBus
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EventBus.getDefault().register(this);
        ...

    }
  • 接收EventBus事件
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void eventFun(TcpBaseEvent et) {
        //服务端的事件在com.mjsoftking.tcplib.event.service包下
        //todo 自行处理的报文数据分发事件,
        if (et instanceof TcpServiceReceiveDataEvent) {
            TcpServiceReceiveDataEvent event = (TcpServiceReceiveDataEvent) et;
            Log.e(TAG, "服务端端口: " + event.getServicePort() + ", 地址: " + event.getAddress() + ", 接收到数据: " + event.getMessage());

            TcpLibService.getInstance().sendMessage(event.getServicePort(), event.getAddress(), "shou dao xiao xi");
        }
        //todo 服务启动成功
        else if (et instanceof TcpServiceBindSuccessEvent) {
            Log.w(TAG, String.format("服务器启动成功,端口:%d", et.getServicePort()));
        }
        //todo 服务启动失败
        else if (et instanceof TcpServiceBindFailEvent) {
            Log.w(TAG, String.format("服务器启动失败,端口:%d", et.getServicePort()));
        }
        //todo 服务关闭
        else if (et instanceof TcpServiceCloseEvent) {
            Log.w(TAG, String.format("服务器已关闭,端口:%d", et.getServicePort()));
        }
        //todo 客户端上线
        else if (et instanceof TcpClientConnectEvent) {
            Log.w(TAG, String.format("新客户端连接,服务端口:%d, 客户端地址:%s", et.getServicePort(), et.getAddress()));
        }
        //todo 客户端下线
        else if (et instanceof TcpClientDisconnectEvent) {
            Log.w(TAG, String.format("客户端连接断开,服务端口:%d, 客户端地址:%s", et.getServicePort(), et.getAddress()));
        }
        //todo 服务端发送消息事件
        else if (et instanceof TcpServiceSendMessageEvent) {
            TcpServiceSendMessageEvent event = (TcpServiceSendMessageEvent) et;
            Log.w(TAG, String.format("服务端发送消息,服务端口:%d, 客户端地址:%s,发送消息内容:%s", event.getServicePort(), event.getAddress(), event.getContent().toString());
        }
    }
  • 注销EventBus
 @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

2. 服务端向客户端发送消息

int port = 50000;//服务端启动服务的端口
String address = "127.0.0.1:1233"; //服务端收到客户端连接事件时的地址 (ip:port)形式。
Object content = "数据";//此参数会进入TcpBaseDataGenerate 实现内,根据具体业务定义数据类型
TcpLibService.getInstance().sendMessage(port, address, content);

3. 服务端其他api

TcpLibService提供以下api

获取指定端口服务器是否在运行
boolean isRun(int port){}
获取指定端口服务器的在线客户端数量,在线客户端数;-1:服务器未启动,反之为在线数量
int getOnlineClientCount(int port){}
获取指定端口服务器的在线客户端,返回:null:服务器未启动,反之为在线客户端的ip:port形式列表,此内容可以直接在服务器向其发送数据
List<String> getOnlineClient(int port){}
关闭指定端口服务器下的客户端连接
void closeClient(int port, String address) {}

六、客户端的使用

- 客户端启动,需提供IP和端口号以及报文的处理和生成实现类

Sting address = "127.0.0.1";
int port = 50000;
TcpLibClient.getInstance()
                   .connect(address, port ),
                            TcpDataBuilder.builder(new ClientDataGenerate(), new ClientDataDispose()));

- 客户端关闭,关闭时需提供IP和端口号

Sting address = "127.0.0.1";
int port = 50000;
TcpLibClient.getInstance()
                    .close(address, port);

1. 客户端的启动、客户端事件处理

在任意对象下,创建实例时,以下以activity为例

  • 注册EventBus
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EventBus.getDefault().register(this);
        ...

    }
  • 接收EventBus事件
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void eventFun(TcpBaseEvent et) {
        //客户端的事件在com.mjsoftking.tcplib.event.client包下
        //todo 自行处理的报文数据分发事件
        if (et instanceof TcpClientReceiveDataEvent) {
            TcpClientReceiveDataEvent event = (TcpClientReceiveDataEvent) et;
            Log.w(TAG, "服务端端口: " + event.getServicePort() + ", 服务端地址: " + event.getAddress() + ", 接收到数据: " + event.getMessage());
        }
        //todo 连接服务成功
        else if (et instanceof TcpServiceConnectSuccessEvent) {
            Log.w(TAG, "连接服务成功,服务端端口: " + et.getServicePort() + ", 服务端地址: " + et.getAddress());
        }
        //todo 连接服务失败
        else if (et instanceof TcpServiceConnectFailEvent) {
           Log.w(TAG, "连接服务失败,服务端端口: " + et.getServicePort() + ", 服务端地址: " + et.getAddress());
        }
        //todo 连接关闭
        else if (et instanceof TcpServiceDisconnectEvent) {
           Log.w(TAG, "连接关闭,服务端端口: " + et.getServicePort() + ", 服务端地址: " + et.getAddress());
        }
        //todo 客户端发送消息事件
        else if (et instanceof TcpClientSendMessageEvent) {
            TcpClientSendMessageEvent event = (TcpClientSendMessageEvent) et;
            Log.w(TAG, String.format("客户端发送消息,服务端口:%d, 服务端地址:%s,发送消息内容:%s", event.getServicePort(), event.getAddress(), event.getContent().toString();
        }
    }
  • 注销EventBus
 @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

2. 客户端向服务端发送消息

int port = 50000;//服务端启动服务的端口
String address = "127.0.0.1"; //服务端IP地址。
Object content = "数据";//此参数会进入TcpBaseDataGenerate 实现内,根据具体业务定义数据类型
TcpLibClient.getInstance().sendMessage(address, port, content);

3. 服务端其他api

TcpLibClient提供以下api

获取是否指定的服务器处于连接状态
boolean boolean isConnect(String ipAddress, int port) {}
关闭与指定服务器的连接
void close(String ipAddress, int port)

七、比较复杂的报文解析处理

- 报文处理类

/**
 * 用途:报文结构类
 * <p>
 * 完整报文包含:
 * 4位报文头长
 * 4位指令长
 * 4位数据长度
 * 不定位数据长度
 * 1位签名长度
 * 4位报文尾长
 */
public class Datagram {

    /**
     * 报文头,0xDD,0xDD,0xDD,0xDD
     */
    public final static byte[] HEADER = new byte[]{(byte) 0xDD, (byte) 0xDD, (byte) 0xDD, (byte) 0xDD};
    /**
     * 报文尾,0xFF,0xFF,0xFF,0xFF
     */
    public final static byte[] FOOTER = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
    /**
     * 命令,预留4位
     */
    private byte[] command;
    /**
     * 数据长度,预留4位
     */
    private byte[] length;
    /**
     * 数据
     */
    private byte[] data;
    /**
     * 签名 1位,数据部分的和 &0xFF 的值
     */
    private byte sign;

    /**
     * 不建议使用,供序列化用
     */
    @Deprecated
    public Datagram() {
    }

    /**
     * 生成发送的数据
     *
     * @param command 4位长度命令
     * @param data    有效数据
     */
    public Datagram(byte[] command, byte[] data) {
        this.command = command;
        this.length = dataLengthBytes(data.length);
        this.data = data;
        sign();
    }

    /**
     * 将收取到的完整报文解析回原始数据
     *
     * @param fullData 完整一帧数据
     */
    public Datagram(byte[] fullData) {
        this.command = new byte[]{fullData[4], fullData[5], fullData[6], fullData[7]};
        this.length = new byte[]{fullData[8], fullData[9], fullData[10], fullData[11]};
        int dataLength = dataLength();
        this.data = new byte[dataLength];
        System.arraycopy(fullData, 12, this.data, 0, dataLength);
        this.sign = fullData[12 + dataLength];
    }

    /**
     * 获取数据有效长度
     */
    public static int dataLength(byte[] lengthBytes) {
        //取得数据长度
        return new BigInteger(lengthBytes).intValue();
    }

    public byte[] getCommand() {
        return command;
    }

    public byte[] getLength() {
        return length;
    }

    public byte[] getData() {
        return data;
    }

    public byte getSign() {
        return sign;
    }

    /**
     * 4位表示的data长度
     *
     * @param len
     * @return
     */
    public byte[] dataLengthBytes(int len) {
        byte[] buffer = new byte[4];
        buffer[0] = (byte) (len >>> 24);
        buffer[1] = (byte) (len >>> 16);
        buffer[2] = (byte) (len >>> 8);
        buffer[3] = (byte) (len);
        return buffer;
    }

    /**
     * 获取数据有效长度
     */
    public int dataLength() {
        //取得数据长度
        return dataLength(length);
    }

    /**
     * 签名
     */
    private void sign() {
        long mSum = 0;
        for (int i = 0; i < data.length; ++i) {
            mSum += (long) data[i];
        }
        sign = (byte) (mSum & 0xff);
    }

    /**
     * 求和签名验证
     */
    public boolean checkSign() {
        long mSum = 0;
        for (int i = 0; i < data.length; ++i) {
            mSum += (long) data[i];
        }
        return sign == (byte) (mSum & 0xff);
    }

    /**
     * 取得完整数据报文
     * <p>
     * 4位报文头长
     * 4位指令长
     * 4位数据长度
     * 不定位数据长度
     * 1位签名长度
     * 4位报文尾长
     */
    public byte[] fullData() {
        byte[] buffer = new byte[4 + 4 + 4 + dataLength() + 1 + 4];
        System.arraycopy(HEADER, 0, buffer, 0, HEADER.length);
        System.arraycopy(command, 0, buffer, 4, command.length);
        System.arraycopy(length, 0, buffer, 8, length.length);
        System.arraycopy(data, 0, buffer, 12, data.length);
        buffer[12 + data.length] = sign;
        System.arraycopy(FOOTER, 0, buffer, 12 + data.length + 1, FOOTER.length);
        return buffer;
    }
}

- 报文解析类的处理方案

public class TcpServiceDispose implements TcpBaseDataDispose {

    TcpServiceDispose() {
    }

    public static synchronized TcpServiceDispose creteObject() {
        return new TcpServiceDispose();
    }

    @Override
    public void dispose(ByteQueueList bufferQueue, int servicePort, String address) {
        //缓冲区数据长度必须满足无数据大小的整包长度,方可计算
        if (bufferQueue.size() < (4 + 4 + 4 + 1 + 4)) {
            return;
        }
        //验证报文头是否匹配
        if (!Arrays.equals(Datagram.HEADER, bufferQueue.copy(4))) {
            //不匹配时移除首位byte
            bufferQueue.removeFirstFrame();
            return;
        }
        //读取数据的长度
        int dataLength = Datagram.dataLength(new byte[]{
                bufferQueue.get(8),
                bufferQueue.get(9),
                bufferQueue.get(10),
                bufferQueue.get(11)});
        //报文的完整长度
        int length = 4 + 4 + 4 + dataLength + 1 + 4;
        //取出报文并移除队列
        byte[] bytes = bufferQueue.copyAndRemove(length);
        //验证报文尾
        if (!Arrays.equals(Datagram.FOOTER, new byte[]{
                bufferQueue.get(4 + 4 + 4 + dataLength + 1),
                bufferQueue.get(4 + 4 + 4 + dataLength + 1 + 1),
                bufferQueue.get(4 + 4 + 4 + dataLength + 1 + 2),
                bufferQueue.get(4 + 4 + 4 + dataLength + 1 + 3)
        })) {
            //todo 报文无效
            return;
        }

        Datagram datagram = new Datagram(bytes);
        if (!datagram.checkSign()) {
            //签名校验失败
            return;
        }
        //todo 根据命令做事件分发,以及针对命令对数据做处理

        //命令 byte[4]
        datagram.getCommand();
        //实际数据 byte[n]
        datagram.getData();

        。。。
    }
}

License

Copyright 2021 mjsoftking

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.
Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS

简介

安卓 Java tcp提炼封装工具, 服务端支持一台手机建立多个端口监听服务器且使用各自的报文处理规则,客户端支持一个手机对多个端口服务器进行连接且使用各自的报文处理规则 展开 收起
Android
Apache-2.0
取消

发行版 (2)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
Android
1
https://gitee.com/osard/TcpLib.git
git@gitee.com:osard/TcpLib.git
osard
TcpLib
TcpLib
master

搜索帮助

14c37bed 8189591 565d56ea 8189591