基于Netty的Android长连接实现

学习 Android(六)Netty通信实战 在 Android 开发中,Netty 是一个基于 Java NIO 的高性能、异步事件驱动的网络应用框架,主要用于构建需要高效网络通信的客户端或服务端。尽管 Android 开发中常见的 HTTP 请求通常使用 OkHttp 或 Retrofit,但在某些复杂场景下,Netty 能提供更底层的控制和更高的性能。接下来跟着文章一起学习一下 Netty 。异步非阻塞模型协议高度可定制零拷贝优化高拓展性上述大致介绍了一下 Netty 的相关知识,为了对其有进一步的了解,本文将实现一个简单的实时通信功能来进一步的 阅读详情

0. 传送门

https://github.com/jeanpeng/Netty4Android.git (欢迎star)

1. 协议的确定

  • 协议头——4字节,标识协议体的长度
  • 协议体——包括4字节扩展信息+传参,其中扩展信息里包括协议加密类型,传参为key,value键值对类型的数据,数据格式为4字节key长度+key+4字节value长度+value

2.协议加解密

  • 客户端随机生成16位密钥,通过RSA非对称加密方式将密钥同步给服务器
  • 密钥同步后,使用该密钥通过AES对称加密的方式对协议进行加密,然后进行数据传输

3.协议封装和解封装

package com.**.nettylib.netty.codec;

import com.**.nettylib.constant.NetworkConfig;
import com.**.nettylib.netty.Request;
import com.**.nettylib.netty.util.AESUtils;
import com.**.nettylib.netty.util.ExtendInfo;
import com.**.nettylib.netty.util.GZIPUtils;
import com.**.nettylib.netty.util.KeyManager;
import com.**.nettylib.netty.util.MsgEncryptType;
import com.**.nettylib.netty.util.ProtocolUtil;
import com.**.nettylib.netty.util.RSAUtils;

import java.io.ByteArrayOutputStream;
import java.util.Map;
import java.util.Set;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * Created by james on 2018/7/30.
 * 加密协议组包,支持处理粘包和半包
 */
public class MessageEncryptEncoder extends MessageToByteEncoder<Request> {

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Request request, ByteBuf byteBuf) throws Exception {
        byteBuf.writeBytes(encodeResponse(request));
    }

    public byte[] encodeResponse(Request request) {
        try {
            Map<String, String> properties = request.toMap();
            Set<Map.Entry<String, String>> propertiesSet = properties.entrySet();
            ByteArrayOutputStream bodyStream = new ByteArrayOutputStream();
            for (Map.Entry<String, String> property : propertiesSet) {
                String propertyName = property.getKey();
                String propertyValue = property.getValue();
                bodyStream.write(ProtocolUtil.stringToByteArray(propertyName, "utf-8"));
                bodyStream.write(ProtocolUtil.stringToByteArray(propertyValue, "utf-8"));
            }
            byte[] bodyBytes = bodyStream.toByteArray();// 数据部分
            bodyStream.close();

            ByteArrayOutputStream responseStream = new ByteArrayOutputStream();
            ExtendInfo ext = request.getExt();
            byte[] extBytes = ext.toExtBytes();
            int msgEncryptType = ext.getMsgEncryptType();
            if (MsgEncryptType.NONO == msgEncryptType) {
                // do noting
            } else if (MsgEncryptType.RSA == msgEncryptType) {
                bodyBytes = doEncodeOfRSA(bodyBytes);
            } else if (MsgEncryptType.AES == msgEncryptType) {
                bodyBytes = doEncodeOfAES(bodyBytes);
            }
            responseStream.write(ProtocolUtil.intToByteArray(bodyBytes.length + extBytes.length));
            responseStream.write(extBytes);
            responseStream.write(bodyBytes);
            byte[] responseBytes = responseStream.toByteArray();// 整个响应包
            responseStream.close();
            return responseBytes;
        } catch (Exception e) {
            return null;
        }
    }

    private byte[] doEncodeOfRSA(byte[] bodyBytes) {
        // 先压缩,再加密
        return RSAUtils.publicEncrypt(GZIPUtils.compress(bodyBytes), NetworkConfig.RSA_PUBLIC_KEY);
    }

    private byte[] doEncodeOfAES(byte[] bodyBytes) {
        String password = KeyManager.aesKey;
        // 先压缩,再加密
        return AESUtils.encrypt(GZIPUtils.compress(bodyBytes), password);
    }
}

package com.**.nettylib.netty.codec;

import com.**.nettylib.netty.util.AESUtils;
import com.**.nettylib.netty.util.ExtendInfo;
import com.**.nettylib.netty.util.GZIPUtils;
import com.**.nettylib.netty.util.KeyManager;
import com.**.nettylib.netty.util.MsgEncryptType;
import com.**.nettylib.netty.util.ProtocolUtil;

import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

/**
 * Created by james on 2018/7/30.
 * 加密协议拆包,支持处理粘包和半包
 */
public class MessageEncryptDecoder extends ByteToMessageDecoder {
    public static String skey;

    private byte[] remainingBytes;

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        ByteBuf currBB = null;
        if(remainingBytes == null) {
            currBB = msg;
        }else {
            byte[] tb = new byte[remainingBytes.length + msg.readableBytes()];
            System.arraycopy(remainingBytes, 0, tb, 0, remainingBytes.length);
            byte[] vb = new byte[msg.readableBytes()];
            msg.readBytes(vb);
            System.arraycopy(vb, 0, tb, remainingBytes.length, vb.length);
            currBB = Unpooled.copiedBuffer(tb);
        }
        while(currBB.readableBytes() > 0) {
            if(!doDecode(ctx, currBB, out)) {
                break;
            }
        }
        if(currBB.readableBytes() > 0) {
            remainingBytes = new byte[currBB.readableBytes()];
            currBB.readBytes(remainingBytes);
        }else {
            remainingBytes = null;
        }
    }

    /**
     * @Title:doDecode
     * @param ctx
     * @param msg
     * @param out
     * @return boolean
     */
    private boolean doDecode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out)  throws Exception{
        if(msg.readableBytes() < 4)
            return false;
        msg.markReaderIndex();
        int len = msg.readInt();
        if(msg.readableBytes() < len) {
            msg.resetReaderIndex();
            return false;
        }
        byte[] extBytes = new byte[4];
        msg.readBytes(extBytes);
        ExtendInfo ext = new ExtendInfo(extBytes);
        int msgEncryptType = ext.getMsgEncryptType();

        byte[] body = new byte[len-4];
        msg.readBytes(body);
        Map<String, String> properties = decodeProperties(body,msgEncryptType);
        out.add(properties);
        if(msg.readableBytes() > 0)
            return true;
        return false;
    }

    private Map<String, String> decodeProperties(byte[] bodyBytes,int msgEncryptType) throws Exception {
        if (MsgEncryptType.NONO == msgEncryptType) {
            // do noting
        } else if (MsgEncryptType.AES == msgEncryptType) {
            bodyBytes = doDecodeOfAES(bodyBytes);
        }

        StringBuilder builder = new StringBuilder();
        for (byte b : bodyBytes) {
            builder.append(b).append(',');
        }
        try {
            Map<String, String> properties = new HashMap<String, String>();
            ByteArrayInputStream in = new ByteArrayInputStream(bodyBytes);
            in.skip(0);
            while (in.available() > 0) {
                String propertyName = ProtocolUtil.readString(in, "utf-8");
                String propertyValue = ProtocolUtil.readString(in, "utf-8");
                properties.put(propertyName, propertyValue);
            }
            return properties;
        } catch (Exception e) {
            throw new Exception("DoDecode exception, Received:byte[" + bodyBytes.length + "]{" + builder + "}");
        }
    }

    private byte[] doDecodeOfAES(byte[] bodyBytes) {
        String password = KeyManager.aesKey;
        return GZIPUtils.uncompress(AESUtils.decrypt(bodyBytes, password));
    }
}

4.发包和收包的对应关系确定

在协议体的传参部分,参数中加入自增长的sn,即sequence num,表示消息的序列号,服务器回包的时候也带上该sn,之后客户端可以通过sn来对应发包和回包

package com.**.nettylib.netty;

import android.content.Context;
import android.content.SharedPreferences;

import com.**.nettylib.netty.codec.MessageDecoder;
import com.**.nettylib.netty.util.SecurityUtil;
import com.**.nettylib.netty.util.ExtendInfo;
import com.**.nettylib.netty.util.KeyManager;
import com.**.nettylib.netty.util.MsgEncryptType;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import io.netty.util.internal.StringUtil;

public class Request {
    private String tag = "netty";
    private String serviceName;
    private Map<String, String> reqInfo;
    private String cs;
    private int sn;
    private ExtendInfo ext;
    private ResponseListener callback;

    public Request(String serviceName, Map<String, String> reqInfo, ResponseListener callback, String tag) {
        this(serviceName, reqInfo, callback);
        if (tag != null && !(tag.trim().equals(""))) {
            this.tag = tag;
        }
    }

    public Request(String serviceName, Map<String, String> reqInfo, ResponseListener callback) {
        this.serviceName = serviceName;
        ext = new ExtendInfo();
        if (serviceName.equals("syncAesKey")) {
            this.reqInfo = new HashMap<>();
            String key = createKey(16);
            this.reqInfo.put("aesKey", key);
            KeyManager.aesKey = key;
            ext.setMsgEncryptType(MsgEncryptType.RSA);
        } else {
            this.reqInfo = reqInfo;
            //公共参数
            this.reqInfo.put("version", "1.01");
            this.reqInfo.put("userid", getUserIDFromSDCard());
            this.reqInfo.put("sessionid", getSessionIDFromSDCard());
            ext.setMsgEncryptType(MsgEncryptType.AES);
        }
        this.cs = getSign(serviceName, reqInfo);
        this.sn = NettyClient.getInstance().getSN();
        this.callback = callback;
    }

    public int getSN() {
        return this.sn;
    }

    public String getServiceName() {
        return this.serviceName;
    }

    public String getTag() {
        return this.tag;
    }

    public boolean isDefaultTag(){
        return this.tag.equals("netty");
    }

    public String getReqInfoMd5() {
        return md5(map2String(this.reqInfo));
    }

    public ExtendInfo getExt() {
        return this.ext;
    }

    public ResponseListener getCallback() {
        return this.callback;
    }

    private String getSign(String method, Map<String, String> reqInfo) {
        if (StringUtil.isNullOrEmpty(MessageDecoder.skey)) {
            return "noskey";
        } else {
            String params = map2String(reqInfo);
            String str = method + params + "0x4Plfd8b65O3Lkm" + MessageDecoder.skey;
            if (method.equals("iappay")) {
                str = method + params + "0x4Plfd8b65O3Lkm" + MessageDecoder.skey;
            }
            return md5(str);
        }
    }

    private String map2String(Map<String, String> reqInfo) {
        String params = "";
        for (Map.Entry<String, String> entry : reqInfo.entrySet()) {
            if (!StringUtil.isNullOrEmpty(params)) {
                params += "&";
            }
            params += entry.getKey() + "=" + entry.getValue();
        }
        return params;
    }

    private String md5(String str) {
        if (StringUtil.isNullOrEmpty(str)) {
            return "";
        }

        return SecurityUtil.md5(str);
    }

    //转成Map,用于组包
    public Map<String, String> toMap() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("serviceName", this.serviceName);
        map.put("reqInfo", map2String(this.reqInfo));
        map.put("cs", this.cs);
        map.put("sn", String.valueOf(this.sn));

        return map;
    }

    private static String getUserIDFromSDCard() {
        String result = null;
        SharedPreferences preference = NettyClient.getInstance().getContext().getSharedPreferences("USERINFO", Context.MODE_PRIVATE);
        result = preference.getString("USERID", "");
        return result;
    }

    private static String getSessionIDFromSDCard() {
        String result = null;
        SharedPreferences preference = NettyClient.getInstance().getContext().getSharedPreferences("USERINFO", Context.MODE_PRIVATE);
        result = preference.getString("SESSIONID", "");
        return result;
    }


    /**
     * 随机生成指定位数秘钥
     *
     * @param KeyLength
     * @return
     */
    public String createKey(int KeyLength) {
        String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random = new Random();
        StringBuffer Keysb = new StringBuffer();
        for (int i = 0; i < KeyLength; i++)    //生成指定位数的随机秘钥字符串
        {
            int number = random.nextInt(base.length());
            Keysb.append(base.charAt(number));
        }
        return Keysb.toString();
    }
}

5.心跳机制

通过IdleStateHandler设置写空闲时间,间隔一定时间(15s)给服务器发送心跳包来维持连接

package com.**.nettylib.netty;


import com.**.nettylib.constant.NetworkConfig;
import com.**.nettylib.netty.codec.MessageDecoder;
import com.**.nettylib.netty.codec.MessageEncoder;
import com.**.nettylib.netty.codec.MessageEncryptDecoder;
import com.**.nettylib.netty.codec.MessageEncryptEncoder;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * Created by james on 2018/6/20.
 */

public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {

    private NettyListener listener;

    public NettyClientInitializer(NettyListener listener) {
        if(listener == null){
            throw new IllegalArgumentException("listener == null ");
        }
        this.listener = listener;
    }

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
//        SslContext sslCtx = SSLContext.getDefault()
//                .createSSLEngine(InsecureTrustManagerFactory.INSTANCE).build();

        ChannelPipeline pipeline = ch.pipeline();
//        pipeline.addLast(sslCtx.newHandler(ch.alloc()));    // 开启SSL
        pipeline.addLast(new LoggingHandler(LogLevel.INFO));    // 开启日志,可以设置日志等级
        pipeline.addLast("IdleStateHandler", new IdleStateHandler(NetworkConfig.READ_IDLE_TIME_SECOND, NetworkConfig.HEART_BEAT_TIME, 0));
        pipeline.addLast("StringDecoder", new MessageEncryptDecoder());//解码器 这里要与服务器保持一致
        pipeline.addLast("StringEncoder", new MessageEncryptEncoder());//编码器 这里要与服务器保持一致
        pipeline.addLast(new NettyClientHandler(listener));
    }
}
package com.**.nettylib.netty;

import android.util.Log;

import com.**.nettylib.netty.util.LogUtils;

import java.util.HashMap;
import java.util.Map;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

/**
 * Created by james on 2018/6/20.
 */

public class NettyClientHandler extends SimpleChannelInboundHandler<Map> {
    private static final String TAG = NettyClientHandler.class.getName();
    private NettyListener listener;

    public NettyClientHandler(NettyListener listener) {
        this.listener = listener;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        NettyClient.getInstance().setConnectStatus(true);
        listener.onServiceStatusConnectChanged(NettyListener.STATUS_CONNECT_SUCCESS);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        NettyClient.getInstance().setConnectStatus(false);
        listener.onServiceStatusConnectChanged(NettyListener.STATUS_CONNECT_CLOSED);
        NettyClient.getInstance().reconnect();
    }


    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Map map) throws Exception {
        LogUtils.logError(TAG, "thread == " + Thread.currentThread().getName());
        listener.onMessageResponse(map);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.WRITER_IDLE){
                // 发送密钥成功后才开始心跳
                if(NettyClient.getInstance().isSendAesKeyFinish()){
                    try{
                        Request request = new Request("heartbeat", new HashMap<String, String>(), new ResponseListener() {
                            @Override
                            public void onSuccess(String data) {
                            }

                            @Override
                            public void onFail(int errcode) {
                            }

                        });
                        ctx.channel().writeAndFlush(request);
                    } catch (Exception e){
                    }
                }
            }else if(event.state() == IdleState.READER_IDLE){
                // 响应超时进行网络重连
                NettyClient.getInstance().setConnectStatus(false);
                NettyClient.getInstance().reconnect();
            }
        }
    }
}

6.网络重连机制

  • 服务器主动断开后重连
  • 监听网络断开恢复可用后进行重连
  • 服务器响应超时后重连
  • sendMsg时客户端连接状态为断开且手机网络连接正常时重连

7.超时重发/重复消息过滤机制

维护一个请求缓存列表,当发送消息的时候将消息暂存在消息列表中,如果消息发送成功则移除,如果失败则待网络重连成功后再发送

package com.**.nettylib.netty;

import com.**.nettylib.netty.util.LogUtils;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;


/**
 * 管理请求
 */
public class RequestManager {
    private static RequestManager sRequestManager = new RequestManager();
    private CopyOnWriteArrayList<Request> mCachedRequestList = new CopyOnWriteArrayList<>();
    private List<String> mNoCacheServiceList = new ArrayList<>();

    private RequestManager() {
        mNoCacheServiceList.add("syncAesKey");
        mNoCacheServiceList.add("sessionlogin");
    }

    public static RequestManager getInstance() {
        return sRequestManager;
    }

    /**
     * 缓存需要重发的请求
     *
     * @param request
     */
    public void add(Request request) {
        if (request != null) {
            if (isCacheRequest(request)) {
                // 判断sn是否已经存在,过滤重复请求
                for (Request r : mCachedRequestList) {
                    if (r.getSN() == request.getSN()) {
                        return;
                    }
                }
                mCachedRequestList.add(request);
            }
        }
    }

    /**
     * 是否重发
     *
     * @param request
     * @return
     */
    private boolean isCacheRequest(Request request) {
        String serviceName = request.getServiceName();
        return serviceName.startsWith("query") && !mNoCacheServiceList.contains(serviceName);
    }

    public void removeBySN(int sn) {
        Iterator<Request> it = mCachedRequestList.iterator();
        while (it.hasNext()) {
            Request request = it.next();
            if (request.getSN() == sn) {
                LogUtils.logError("NettyClient", "移除缓存请求:" + request.getServiceName());
                mCachedRequestList.remove(request);
                break;
            }
        }
    }


    /**
     * 通过tag移除请求
     */
    public void removeByTag(String tag) {
        Iterator<Request> it = mCachedRequestList.iterator();
        while (it.hasNext()) {
            Request request = it.next();
            if (request.getTag().equals(tag)) {
                LogUtils.logError("NettyClient", "通过Tag:" + tag + "移除缓存请求:" + request.getServiceName());
                mCachedRequestList.remove(request);
            }
        }
    }


    /**
     * 请求重发
     */
    public void resend() {
        for (Request request : mCachedRequestList) {
            LogUtils.logError("NettyClient", "重发缓存请求:" + request.getServiceName());
            NettyClient.getInstance().sendMessage(request);
        }
    }

    /**
     * 是否重复的查询请求
     */
    public boolean isRepeatRequest(Request request) {
        if (request.getServiceName().startsWith("query") && !request.isDefaultTag()) {
            for (Request req : mCachedRequestList) {
                if (req.getTag().equals(request.getTag())
                        && req.getServiceName().equals(request.getServiceName())
                        && req.getReqInfoMd5().equals(request.getReqInfoMd5())) {
                    LogUtils.logError("NettyClient", "重复的查询请求:tag = " + request.getTag() + ",serviceName = " + request.getServiceName());
                    return true;
                }
            }
        }
        return false;
    }
}


Android端使用Netty框架实现TCP/IP通信 Android端采用Netty实现长连接通信最近项目需要 阅读详情

相关推荐

基于Netty实现的Android 消息推送(即时通信)的解决方案

根据Netty框架实现消息推送(即时聊天)功能. Netty框架,TCP长连接,心跳,阻塞消息队列,线程池处理消息发送, 基于Google ProtoBuf自定义的消息协议, TCP粘包/拆包.... 客户端通过TCP连接到服务器,并建立TCP长连接;当服务器端收到新消息后通过TCP连接推送给客户端, 即消息传递方式: 客户端A -> 服务器 -> 客户端B. 不说了,直接上核心代码吧:

gary0502的专栏 7158

Netty——实现Android客户端长连接

关于长连接的一些介绍 长连接的应用场景非常的广泛,比如监控系统,IM系统,即时报价系统,推送服务等等。像这些场景都是比较注重实时性,如果每次发送数据都要进行一次DNS解析,建立连接的过程肯定是极其影响体验。 长连接的维护必然需要一套机制来控制。比如 HTTP/1.0 通过在 header 头中添加 Connection:Keep-Alive参数,如果当前请求需要保活则添加该参数作为标识,否则服务端就不会保持该连接的状态,发送完数据之后就关闭连接。HTTP/1.1以后 Keep-Alive 是默认打开的。

qq_44946212的博客 1952

开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现。

转载请注明出处:https://juejin.im/post/5c97ae12e51d45580b681b0b 写在前面 一直想写一篇关于im即时通讯分享的文章,无奈工作太忙,很难抽出时间。今天终于从公司离职了,打算好好休息几天再重新找工作,趁时间空闲,决定静下心来写一篇文章,毕竟从前辈那里学到了很多东西。工作了五年半,这三四年来一直在做社交相关的项目,有 直播、 即时通讯、 短视频分享、 社区论...

FreddyChen的博客 6678

Android 长连接Netty

netty准备工作netty 准备工作 依赖: implementation 'io.netty:netty-all:4.1.36.Final' compileOnly "org.projectlombok:lombok:1.16.18" implementation 'org.glassfish:javax.annotation:10.0-b28' 导入jar包: libs–>fastjson-1.2.49.jar *下方有demo下载获取 n

weixin_45680654的博客 719

开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现

下面是辛苦给大家整理的学习路线《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!这些内容对你有帮助,可以扫码获取!!(备注:Android)**下面是辛苦给大家整理的学习路线[外链图片转存中…(img-vjE26CL7-1712408729429)]《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

2401_84132723的博客 1315

Android使用Netty框架的推送实现

1、android端demo中的NettyClientBootstrap类中host、Constants.setClientId、LoginMsg需要自行修改为服务的ip地址、连接编号、登录信息 2、服务端demo中PushServer中的push方法,如果更改了Constants.setClientId的命名方式,则需要更改push方法中获取channel的方式 3、Android端demo请在Android Studio中运行,我的环境是3.0.0.1 4、服务端demo请在Intellij idea中运行 5、源代码直接是不能实现连接和推送的,要修改android端demo中服务的ip地址,连接到服务端ip(本机ip) 6、需要修改的地方,添加了TODO标识,全局搜索,并在适当的地方修改内容即可

Android 长连接初体验(基于netty)

前言 众所周知,推送和 IM 在 Android 应用中很常见,但真正自己去实现的比较少,我们大多会去选择第三方提供的成熟方案,如极光推送、云信等,因为移动网络具有不确定性,因此自己实现一套稳定的方案会耗费很多精力,这对于小公司来说是得不偿失的。 推送和 IM 我们平时用的很多,但真正了解原理的不多,真正动手实现过的不多。推送和 IM 本质上都是长连接,无非是业务方向不同,因此我们以下统称为长连...

weixin_33946605的博客 708

基于Netty二次封装的Android链路通讯库,用以快速开发高性能.zip

快应用开发

Java Netty长连接实现Android推送

Java Netty长连接实现Android推送,之前自己写socket来实现长连接发现有点不靠谱,正好同学公司在用netty推荐我使用这个,一试还真不错,现在分享出来给自己参考!

Spring of Han的专栏 7131

制造业与工业知识图谱应用

在全球数字化转型背景下,制造业面临创新周期缩短、质量管控精细化等挑战。知识图谱技术通过结构化表示工业知识,为智能制造提供关键技术支撑。工业知识图谱具有专业性强、多源异构数据融合、实时性要求高等特点,其构建流程涵盖本体设计、知识抽取与整合等环节。研究显示,知识图谱与大模型结合可提升工业场景的智能化水平,在设备故障诊断、供应链优化等领域具有显著应用价值。该技术有望成为制造业数字化转型的核心驱动力。

分享一些感兴趣的技术方面 1432

告别理论纠结!用ADS和Sigrity玩转电源完整性:从PDN阻抗优化到芯片供电仿真全流程

本文详细介绍了如何利用ADS和Sigrity工具进行电源完整性(PI)分析,从PDN阻抗优化到芯片供电仿真的全流程。通过频域、直流和时域分析,工程师可以精确评估电源网络性能,优化设计以避免电压波动和系统失效。文章还提供了ADS和Sigrity PowerDC的实战操作指南,帮助读者掌握现代PI设计的核心技术。

weixin_33749242的博客 310

Netty长链接在Android端的应用

前言部分 说到长链接的技术,我们首先都会想到netty这个框架,也是目前使用最广泛的长链接框架,由于该框架使用简单性能稳定,自然也是项目的首先,一般来说长链接的话,可能是做即时通讯会用到的比较多,因为要随时监控是否有新的信息发送过来。其实长链接的应用范围是很广泛的,我们平时也是一直都在使用,不过我们没有很留意而已。常见的推送就是通过长链接来实现实时的接受后台的消息。起初我的项目中也是使用的推送来完...

鲨鱼不会飞的博客 1176

是德频谱分析仪相位噪声测量实战指南:从原理到精准操作

本文详细介绍了是德频谱分析仪在相位噪声测量中的实战操作指南,从基础概念到高级技巧全面解析。通过5G基站时钟测量等实际案例,展示如何利用是德N9030B等设备精准测量相位噪声,并分享设备预热、参数设置、环境干扰排除等关键操作要点,帮助工程师提升测量精度和效率。

jenkins8butler的博客 229

android netty导入_Netty在Android中使用

引用netty介绍文档中的一句话:Netty适合用来开发高性能长连接的客户端或服务器其次netty基于NIO,并做了封装与优化以及一些高级算法,使得使用起来相比原生NIO更加容易,性能更好timg.jpg1.使用方法1.1建立连接//进行初始化NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(); //初始化线程组Bootst...

weixin_29166013的博客 1005

LAMMPS实操系列(一): 大量FCC-CoCrCuFeNi高熵合金建模与最稳定结构筛选

高熵合金(High-entropy alloys,HEA),是由五种或五种以上等量或大约等量金属形成的合金。由于高熵合金可能具有许多优秀的物理化学性质,因此在材料科学领域受到广泛关注

Ceshigo_CD的博客 3257

TSMC65nm工艺下Virtuoso仿真gm-id曲线的保姆级教程(含ADE设置详解)

本文提供了一份在TSMC 65nm工艺下使用Cadence Virtuoso绘制gm-id曲线的详细教程。内容涵盖从理解gm/id方法学、搭建仿真环境、深度解析ADE设置,到运行DC仿真、提取数据并生成对数坐标曲线的完整流程。教程重点解析了Virtuoso ADE中的关键设置与常见避坑点,旨在帮助模拟集成电路设计初学者快速掌握这一核心设计工具,为高性能电路设计奠定基础。

grpc6streamer的博客 1171

揭开netty神秘面纱_Android 长连接初体验(基于netty)

前言众所周知,推送和 IM 在 Android 应用中很常见,但真正自己去实现的比较少,我们大多会去选择第三方提供的成熟方案,如极光推送、云信等,因为移动网络具有不确定性,因此自己实现一套稳定的方案会耗费很多精力,这对于小公司来说是得不偿失的。推送和 IM 我们平时用的很多,但真正了解原理的不多,真正动手实现过的不多。推送和 IM 本质上都是长连接,无非是业务方向不同,因此我们以下统称为长连接。今...

weixin_39986171的博客 366

18650电池组流体直冷热管理仿真:COMSOL多物理场耦合建模路线与实战策略

在动力电池与储能系统的热管理设计中,多物理场耦合仿真已成为平衡性能与成本的关键工具。液冷方案的换热极限促使行业转向制冷剂直冷技术,而两相流与电池产热的强耦合特性,使得基于COMSOL的数值建模成为评估系统温升与流动分配的重要手段。工程实践中,电化学-热-流场全耦合模型与集总电池-流场简化模型各有适用场景,前者揭示电池内部微观机理,后者则以高计算效率支撑包级方案迭代。通过合理简化几何、标定内阻与熵热系数、设置等效换热边界,仿真能够准确预测直冷系统在快充或大倍率放电下的温度均匀性与压降特性,从而指导流道优化与制

weixin_33585822的博客 313

Android 通过 NSD 服务 & Netty(断线重连、心跳、黏包处理) 实现两个 Android 系统端的长连接通讯

引言 近期需求,通过手机App端取号机(含叫号通知功能),实时连接 另一台 Android 广告机用于播放当前被叫到的号数。 这里有两种Android 机 一台「基于Sunmi版的可出小票的Android机」 一台「Android系统 盒子连接上大屏电视机」 总之要做的就是连接两台机子互相通讯,一台发送数据 一台处理展示。最终可实现两端互发! 本篇目录 1.寻找适合的通讯方式...

thisfeng 3937

GPU服务器维修全流程:从检测到解锁

《GPU服务器维修维保全流程指南》摘要:本文系统介绍GPU服务器从检测到解锁的完整维修流程。检测环节包含物理检查(氧化层、散热、PCB板)和电子诊断(ECC报错率、电源波动等);核心维修技术涵盖PCB板修复(微孔镀铜、BGA返修)和芯片级维修(成功率78-92%);解锁验证阶段包括性能解锁(VBIOS重写)和72小时稳定性测试。文末提供故障速查表及维保建议,强调定期除尘可降低38%故障率,推荐环境粉尘浓度控制在≤0.3mg/m³。

2401_89353317的博客 889

SAP Fiori Launchpad 上看不到任何 tile 应该怎么办?

中心UI组件是一个框架,为所有SAPFiori应用程序提供公共基础设施SAPFiorilaunchpad是所有SAPFioriUI的基础,并为SAPFiori应用程序提供基本功能,如登录,界面大小,应用程序之间的导航,和基于角色的应用程序目录。ABAP前端服务器包含所有基础设施组件,用于为客户端生成SAPFiori应用程序特定的UI,并与SAPBusinessSuite后端系统通信。SAPFiori应用程序的处理过程如下首先,客户端加载SAPFiori应用程序的界面。...

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 939

Netty_Android 使用netty与服务器保持长连接腾讯

Netty_Android 项目地址:https://github.com/zhangYanGitHub/Netty_Demo 采用netty-4.1.16.Final 于服务器保持长连接进行通讯 主要思路为: 开启一个service 初始化Netty连接 service 类 private NetworkReceiver receiver; public static...

张俨的博客 1万+

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Netty 4.0在Android 上的使用,长连接断线自动重连

转自 Netty 4.0在Android 上的使用,长连接断线自动重连(原文) 1、最近使用Netty 异步通讯框架 ,在使用的过程中发现如果当网络断开的时候会出现无法检测的现象; 影响长链接断开的原因主要有三种:1.服务停止,2.本地网线断开,3.公网或者局域网中交换机断开; 在使用的过程中发现在服务停止或者本地网络断开的时候netty的@ChannelHandler

jcy2sls的专栏 6327

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

【基于netty的安卓实例】以及开发中的若干问题

本周末学习netty长连接,实现了一个netty安卓客户端与服务器连接并发送消息的实例,代码位置:https://git.oschina.net/lpt20... 主要实现: 引入netty jar包 建立连接客户端代码 > 建立连接 public class HelloClientInitializer extends C...

weixin_33755554的博客 194

android 心跳连接 长连接 简书,GitHub - ciyixi/NettyChat: 基于Netty+TCP+Protobuf实现的Android IM库,包含Protobuf序列化、TCP拆...

使用方式1. 依赖im_lib库,implementation project(':im_lib')2. 自定义IMSEventListener,实现OnEventListener,重写对应的方法配置参数3. 自定义IMSConnectStatusListener,实现IMSConnectStatusCallback,重现对应的方法监听ims连接状态4. 调用IMSClientInterface....

weixin_32516005的博客 228
上一篇: JAVA LinkedBlockingQueue阻塞队列
下一篇: Netty 粘包、半包解决
jamespengo
博客等级 码龄16年 83粉丝 48原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值