Netty TCP通讯拆包、粘包处理

Netty-粘包,半包和使用Json方式传输数据 文章目录JSON和ProtoBuf序列化背景详解粘包和拆包粘包和半包半包问题的实践案例运行结果半包粘包解决JSON协议通信codeJson序列化与反序列化实践案例JsonMsgJsonMsgDemoJSON传输至服务器端的实践案例code客户端实践案例 JSON和ProtoBuf序列化 背景 在开发一些远程过程调用(RPC)的程序时,通常会涉及对象的序列化/反序列化的问题, 例如一个对象从客户端... 阅读详情

tcp是个流协议,所谓流,就是没有界限的一串数据。tcp底层并不了解上层业务的具体含义,它会根据tcp缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被tcp拆分为多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送。这就是所谓的tcp拆包/粘包问题。

拆包:通常是由于发送较大长度数的据超出了自定义长度,或者超出了相关网络传输协议的长度限制,发送的一包数据被拆分为多次发送。

粘包:由于前后包之间的发送间隔过短,造成接收端将多条数据当做同一包数据读取出来。例子如下,

channel.writeAndFlush(sMsg);
channel.writeAndFlush(sMsg);
channel.writeAndFlush(sMsg);

连续多个发送,其实是发送了多个包,对方应该把其看成是多个消息。但是因为发送的过快,对方几乎一定会把其当作一个包来处理。看成是发送了一条消息。这个就发生了粘包。

netty中解决方案:(注意事项请看文章最后)

1)LineBasedFrameDecoder行分割解码

SocketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024);

SocketChannel.pipeline().addLast(new StringDecoder());

LineBaseFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有"\n"或者"\r\n",如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。

StringDecoder的功能非常简单,就是将接受到的对象转换成字符串,然后继续调用后面的Handler。


LineBasedFrameDecoder+StringDecoder的组合就是按行切换的文本解码器,它被设计用来支持TCP的粘包和拆包。

 

2)DelimiterBasedFrameDecoder自定义分隔符

// 创建分隔符缓冲对象$_作为分割符
ByteBuf byteBuf = Unpooled.copiedBuffer("$_".getBytes());
/**
 * 第一个参数:单条消息的最大长度,当达到最大长度仍然找不到分隔符抛异常,防止由于异常码流缺失分隔符号导致的内存溢出
 * 第二个参数:分隔符缓冲对象
 */
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,byteBuf));
socketChannel.pipeline().addLast(new StringDecoder());

DelimiterBasedFrameDecoder还可以设置对自定义分割付的处理,如下:

ByteBuf delemiter= Unpooled.buffer();
delemiter.writeBytes("$##$".getBytes());//自定义分隔符
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(65535, true, true,delemiter));

//netty实现
DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, boolean failFast,ByteBuf delimiter)

maxLength:
表示一行最大的长度,如果超过这个长度依然没有检测到\n或者\r\n,将会抛出TooLongFrameException

failFast:
与maxLength联合使用,表示超过maxLength后,抛出TooLongFrameException的时机。如果为true,则超出maxLength后立即抛出TooLongFrameException,不继续进行解码;如果为false,则等到完整的消息被解码后,再抛出TooLongFrameException异常。

stripDelimiter:
解码后的消息是否去除分隔符。

delimiters:
分隔符。我们需要先将分割符,写入到ByteBuf中,然后当做参数传入。

需要注意的是,netty并没有提供一个DelimiterBasedFrameDecoder对应的编码器实现(笔者没有找到),因此在发送端需要自行编码,添加分隔符。

 

3)FixedLengthFrameDecoder定长,即发送接受固定长度的包。感觉不大适合我的业务,暂时不考虑使用。

 

注意事项:

1、编码格式的设置

//字符串编解码器获取环境默认编码格式
pipeline.addLast(
    new StringDecoder(),
    new StringEncoder()
);

 //指定字符串编解码器编码格式为UTF-8
pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));

2、自定义分隔符和解码的添加顺序是,先添加自定义解码器,然后再添加StringDecoder,否则分割无效。

//先使用DelimiterBasedFrameDecoder解码,以自定义的字符作为分割符
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(65535, true, true,delemiter));
//解码为UTF-8字符串
socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));

 

实际开发代码示例:

以下示例时结合业务需求写的,有些地方不需要,请自行删除,仅供参考。

package com.groot.CPMasterController.netty.tcp.server;

import com.groot.CPMasterController.netty.tcp.entity.TCPConst;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * description:
 * author:groot
 * date: 2019-4-10 12:07
 **/
@Component
@PropertySource(value="classpath:config.properties")
@Slf4j
public class TcpServer {

    //boss事件轮询线程组
    //处理Accept连接事件的线程,这里线程数设置为1即可,netty处理链接事件默认为单线程,过度设置反而浪费cpu资源
    private EventLoopGroup boss = new NioEventLoopGroup(1);
    //worker事件轮询线程组
    //处理hadnler的工作线程,其实也就是处理IO读写 。线程数据默认为 CPU 核心数乘以2
    private EventLoopGroup worker = new NioEventLoopGroup();

    @Autowired
    TCPServerChannelInitializer TCPServerChannelInitializer;

    @Value("${netty.tcp.server.port}")
    private Integer port;

    //与客户端建立连接后得到的通道对象
    private Channel channel;

    /**
     * @Author groot
     * @Date 2019/4/13 12:46
     * @Param
     * @return
     * @Description 存储所有client的channel
     **/

//    public static Map<String, Channel> clientTotalMap = new ConcurrentHashMap<String, Channel>();

    /**
     * @Author groot
     * @Date 2019/4/13 12:46
     * @Param key 链接身份,Value channel队列
     * @return
     * @Description 分类型存储业务所需channel
     **/
    public static Map<String, Set<Channel>> clientTypeMap = new ConcurrentHashMap<>();

    /**
     * @Author groot
     * @Date 2019/4/13 12:46
     * @Param []
     * @return io.netty.channel.ChannelFuture
     * @Description 开启Netty tcp server服务
     **/

    public void start() {
        try {
            //启动类
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boss, worker)//组配置,初始化ServerBootstrap的线程组
                    .channel(NioServerSocketChannel.class)///构造channel通道工厂//bossGroup的通道,只是负责连接
                    .childHandler(TCPServerChannelInitializer)//设置通道处理者ChannelHandler////workerGroup的处理器
                    .option(ChannelOption.SO_BACKLOG, 1024)//socket参数,当服务器请求处理程全满时,用于临时存放已完成三次握手请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
                    .childOption(ChannelOption.SO_KEEPALIVE, true)//启用心跳保活机制,tcp,默认2小时发一次心跳
                    .childOption(ChannelOption.TCP_NODELAY, true)//2019年4月15日新增 TCP无延迟
                    .handler(new LoggingHandler(LogLevel.INFO));//2019年4月15日新增  日志级别info
            //Future:异步任务的生命周期,可用来获取任务结果
//        ChannelFuture channelFuture1 = serverBootstrap.bind(port).syncUninterruptibly();//绑定端口,开启监听,同步等待
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();//绑定端口,开启监听,同步等待
            if (channelFuture != null && channelFuture.isSuccess()) {
                channel = channelFuture.channel();//获取通道
                log.info("Netty tcp server start success, port = {}", port);
            } else {
                log.error("Netty tcp server start fail");
            }
            channelFuture.channel().closeFuture().sync();// 监听服务器关闭监听
        } catch (InterruptedException e) {
            log.error("Netty tcp server start Exception e:"+e);
        }finally {
            boss.shutdownGracefully(); //关闭EventLoopGroup,释放掉所有资源包括创建的线程
            worker.shutdownGracefully();
        }

    }

    /**
     * @Author groot
     * @Date 2019/4/13 12:46
     * @Param []
     * @return void
     * @Description 停止Netty tcp server服务
     **/
    @PreDestroy
    public void destroy() {
        if (channel != null) {
            channel.close();
        }
        try {
            Future<?> future = worker.shutdownGracefully().await();
            if (!future.isSuccess()) {
                log.error("netty tcp workerGroup shutdown fail, {}", future.cause());
            }
            Future<?> future1 = boss.shutdownGracefully().await();
            if (!future1.isSuccess()) {
                log.error("netty tcp bossGroup shutdown fail, {}", future1.cause());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("Netty tcp server shutdown success");
    }


    /**
     * @Author groot
     * @Date 2019/5/8 14:34
     * @Param [identity, msg] 链接身份,消息
     * @return void
     * @Description 通过
     **/
    public static void sendMsg(String identity,String msg) {
        send(identity, msg,true);
    }

    /**
     * @Author groot
     * @Date 2019/5/8 14:34
     * @Param [identity, msg] 链接身份,消息
     * @return void
     * @Description 通过
     **/
    public static void sendHeart(String identity,String msg) {
        send(identity, msg,false);
    }


    /**
     * @Author groot
     * @Date 2019/5/17 15:38
     * @Param [identity, msg,endFlag] endFlag是否添加结束符
     * @return void
     * @Description 发送
     **/
    public static void send(String identity, String msg,boolean endFlag) {
        //log.info("sendMsg to:{},msg:{}",identity,msg);
        if(StringUtils.isEmpty(identity) || StringUtils.isEmpty(msg))return;
        StringBuffer sMsg = new StringBuffer(msg);
        if(endFlag){
            sMsg.append(TCPConst.MARK_END);//拼接消息截止符
        }
        Set<Channel> channels = TcpServer.clientTypeMap.get(identity);
        if(channels!=null && !channels.isEmpty()){//如果有client链接
            //遍历发送消息
            for (Channel channel:channels){
                channel.writeAndFlush(sMsg).syncUninterruptibly();
            }
        }
    }

    // 这个注解表示在spring boot依赖注入完成后执行一次该方法,但对方法有很严格的要求
    @PostConstruct()
    public void init() {
        //需要开启一个新的线程来执行netty server 服务器
        new Thread(new Runnable() {
            public void run() {
                start();
            }
        }).start();

    }

}

 

package com.groot.CPMasterController.netty.tcp.server;

import com.alibaba.fastjson.JSON;
import com.groot.CPMasterController.common.utils.TimeUtil;
import com.groot.CPMasterController.control.service.ipml.GameControlService;
import com.groot.CPMasterController.netty.tcp.entity.TCPConst;
import com.groot.CPMasterController.netty.websocket.WebSocketServer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.net.SocketAddress;
import java.util.*;

/**
 * description:
 * author:groot
 * date: 2019-4-10 15:49
 **/
@Component
@ChannelHandler.Sharable
@Slf4j
public class TCPServerChannelHandler extends SimpleChannelInboundHandler<Object> {

    @Resource
    GameControlService gameControlService;

    //msg拼包
    private StringBuffer msgBuffer = new StringBuffer();

    /**
     * 拿到传过来的msg数据,开始处理
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("Netty tcp server receive data : " + msg);
        //tcp监听收到消息,解析消息内容
        try {
            if(null==msg)return;
            String sMsg = msg.toString();
            if (TCPConst.MARK_HEART.equals(sMsg)){//心跳
                //TODO 心跳暂时无需处理
//                log.info("Netty tcp server receive heart : " + msg);
            }else{
//                spellPackage(ctx, sMsg);//手动解决拆包粘包
                //netty自定义分隔符解析
                dealMsg(ctx,sMsg);
            }
        } catch (Exception e) {
            log.error("Netty tcp server channelRead0 Exception e:{}",e);
        }

    }

    /**
     * @Author groot
     * @Date 2019/5/17 9:21
     * @Param [ctx, msg]
     * @return void
     * @Description TCP拼包
     **/
    private void spellPackage(ChannelHandlerContext ctx, String msg) {
        if(StringUtils.isNotBlank(msg)){
            synchronized (msgBuffer) {
                StringBuffer msgbf = new StringBuffer(msg);
                String sMsg = msgBuffer.append(msgbf).toString();
                if(sMsg.contains(TCPConst.MARK_HEART)){//包含心跳
                    sMsg=sMsg.replaceAll(TCPConst.MARK_HEART,"");//去心跳
                }
                if(sMsg.contains(TCPConst.MARK_END)){//包含结束符
                    msgBuffer.setLength(0);//清空缓存 需要重新拼接
                    String[] splitMsg = sMsg.trim().split(TCPConst.MARK_END);
                    int arrLen = splitMsg.length;
                    if(arrLen ==1){
                        dealMsg(ctx,splitMsg[0]);
                    }else if(arrLen >1){
                        for(int i = 0; i< arrLen; i++){
                            if(i==0){
                                dealMsg(ctx,splitMsg[0]);
                            }else if(i==arrLen-1){
                                if(msg.endsWith(TCPConst.MARK_END)) {//最后一条是完整的
                                    dealMsg(ctx,splitMsg[i]);
                                }else {//最后一条结尾不是结束符 继续拼接
                                    msgBuffer.append(splitMsg[i]);//只拼接最后一条不执行
                                }
                            }else {
                                dealMsg(ctx,splitMsg[i]);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * @Author groot
     * @Date 2019/5/16 17:23
     * @Param [ctx, msg]
     * @return void
     * @Description 处理消息
     **/
    private void dealMsg(ChannelHandlerContext ctx,String msg) {
        log.info("dealMsg :::{}",msg);
        gameControlService.dealMassage( ctx,  msg);
    }

    /**
     * 活跃的、有效的通道
     * 第一次连接成功后进入的方法
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        log.info("tcp client " + getRemoteAddress(ctx) + " connect success");
    }

    /**
     * 不活动的通道
     * 连接丢失后执行的方法(client端可据此实现断线重连)
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //删除Channel Map中的失效Client
        log.error("检测到不活跃的通道-- ip:{} ,即将删除", getRemoteAddress(ctx));
        removeChannel(ctx.channel());//安全删除channel
        ctx.close();
    }

    /**
     * 异常处理
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        //发生异常,关闭连接
        log.error(" ip:{} -- 的通道发生异常,即将断开连接", getRemoteAddress(ctx));
        removeChannel(ctx.channel());//安全删除channel
        ctx.close();//再次建议close  内部解析的错误已经在channelRead0中捕获 所以这里的异常 应该是在连接出现异常时出现
    }


    /**
     * 心跳机制,超时处理
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */
    /*@Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        String socketString = ctx.channel().remoteAddress().toString();
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                log.info("Client: " + socketString + " READER_IDLE 读超时");
                ctx.disconnect();//断开
            } else if (event.state() == IdleState.WRITER_IDLE) {
                log.info("Client: " + socketString + " WRITER_IDLE 写超时");
                ctx.disconnect();
            } else if (event.state() == IdleState.ALL_IDLE) {
                log.info("Client: " + socketString + " ALL_IDLE 总超时");
                ctx.disconnect();
            }
        }
    }*/

    /**
     * @Author groot
     * @Date  15:07
     * @Param [id, channel]
     * @return void
     * @Description 添加业务channel
     **/
    public static void addChannel(String id,Channel channel){
        synchronized (TcpServer.clientTypeMap) {
            if(TcpServer.clientTypeMap.get(id)!=null){
                TcpServer.clientTypeMap.get(id).add(channel);
            }else {
                Set<Channel> channels = new HashSet<>();
                channels.add(channel);
                TcpServer.clientTypeMap.put(id,channels);
            }
        }
        log.info("addChannel -- 业务channel size:{}",getNumberOfClients());
    }


    /**
     * @Author groot
     * @Date 2019/5/8 13:34
     * @Param [channel]
     * @return void
     * @Description 安全移除业务channel
     **/
    private void removeChannel(Channel channel){
        synchronized (TcpServer.clientTypeMap) {
            if(!TcpServer.clientTypeMap.isEmpty()){
                Set<String> keys = TcpServer.clientTypeMap.keySet();
                for (String key:keys){
                    Set<Channel> channels = TcpServer.clientTypeMap.get(key);
                    //判断包含channel
                    if(channels!=null && channels.contains(channel)){
                        //获取ip
                        SocketAddress socketAddress = channel.remoteAddress();
                        //删除channel
                        channels.remove(channel);
                        //获取通知web端
                        String clientName="";
                        switch (key){
                            case TCPConst.ID_CPTIMER:
                                clientName =TCPConst.ID_CPTIMER_NAME;
                                break;
                            case TCPConst.ID_DEVCTRL:
                                clientName =TCPConst.ID_DEVCTRL_NAME;
                                break;
                            case TCPConst.ID_GPSCTRL:
                                clientName =TCPConst.ID_GPSCTRL_NAME;
                                break;
                            default:
                                clientName =TCPConst.ID_UNKNOWN_NAME;
                                break;
                        }
                        warnMsgToWeb(socketAddress.toString().replaceAll("/",""), clientName);
                    }
                }
            }
        }
    }

    /**
     * @Author groot
     * @Date 2019/5/17 13:43
     * @Param [socketAddress, clientName]
     * @return void
     * @Description 提示web连接断开
     **/
    private void warnMsgToWeb(String ip, String clientName) {
        Map<String,Object> map=new HashMap<>();
        map.put("type","warn");
        map.put("name",clientName);
        map.put("ip",ip);
        map.put("msg","断开连接");
        map.put("time", TimeUtil.dateFormat(new Date()));
        WebSocketServer.sendInfo(JSON.toJSONString(map),null);
    }

    /**
     * @Author groot
     * @Date 2019/5/8 14:00
     * @Param []
     * @return int
     * @Description 获取当前channel连接数
     **/
    public static int getNumberOfClients(){
        int count = 0;
        if(TcpServer.clientTypeMap!=null && !TcpServer.clientTypeMap.isEmpty()){
            for (Set<Channel> channels: TcpServer.clientTypeMap.values()) count += channels.size();
        }
        return count;
    }

    /**
     * 获取client对象:ip+port
     *
     * @param ctx
     * @return
     */
    public String getRemoteAddress(ChannelHandlerContext ctx) {
        String socketString = ctx.channel().remoteAddress().toString();
        return socketString;
    }

    /**
     * 获取client的ip
     *
     * @param ctx
     * @return
     */
    public String getIPString(ChannelHandlerContext ctx) {
        String socketString = ctx.channel().remoteAddress().toString();
        int colonAt = socketString.indexOf(":");
        String ipString = socketString.substring(1, colonAt);
        return ipString;
    }
}
package com.groot.CPMasterController.netty.tcp.server;

import com.groot.CPMasterController.netty.tcp.entity.TCPConst;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * description: 通道初始化,主要用于设置各种Handler
 * author:groot
 * date: 2019-4-10 14:55
 **/
@Component
public class TCPServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    TCPServerChannelHandler TCPServerChannelHandler;

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
//        ChannelPipeline pipeline = socketChannel.pipeline();
        //IdleStateHandler心跳机制,如果超时触发Handle中userEventTrigger()方法
//        pipeline.addLast("idleStateHandler",new IdleStateHandler(15, 0, 0, TimeUnit.MINUTES));
        //字符串编解码器获取环境默认编码格式 ,如UTF-8
//        pipeline.addLast(
//                new StringDecoder(),
//                new StringEncoder()
//        );
        //指定字符串编解码器编码格式为UTF-8
//        pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
//        pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));

        ByteBuf delemiter= Unpooled.buffer();
        delemiter.writeBytes(TCPConst.MARK_END.getBytes());//自定义分隔符
        /**
         * 第一个参数:单条消息的最大长度,当达到最大长度仍然找不到分隔符抛异常,防止由于异常码流缺失分隔符号导致的内存溢出
         * 第二个参数:分隔符缓冲对象
         */
        //先使用DelimiterBasedFrameDecoder解码,以自定义的字符作为分割符
        socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(65535, true, true,delemiter));
        //解码为UTF-8字符串
        socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        //编码为UTF-8字符串
        socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));

        //自定义Handler
        socketChannel.pipeline().addLast("serverChannelHandler", TCPServerChannelHandler);
    }
}

 

Java基于TCP(Socket)协议的网络语音聊天 阅读详情

相关推荐

怎么用Netty开发IM即时通讯聊天软件

例如\r\n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包。也就是说,Netty是一个基于NIO的客户、服务器端编程框架,使用Netty可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。①客户端在发送数据包的时候,每个包都固定长度。如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包,也就是将一个大的包拆分为多个小包进行发送。...

蔚可云的博客 683

netty中 解决服务器到客户端传输数据 中文乱码问题

//编码格式 arg0.pipeline().addLast(new StringEncoder(Charset.forName("GBK"))); //解码格式 arg0.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8"))); ch.pipeline().addLast(new StringEncoder(Cha...

已被格式化的叔叔 1万+

Netty数据处理:拆包、组包实现

1.前言 最近一直找第三方产品对接,目前已经集成了40多款产品和20多种传感器,实现了不同协议下的针对粘包、半包数据的拆包、组包处理。 2.为什么会出现粘包与半包数据 TCP通讯在物联网数据通讯中,目前仍然占据着绝大部分的市场,相比UDP来说,它更加可靠。因为TCP通讯是以二进制流的方式进行,中间是没有间隙的,如果数据传输频率过快则会出现粘包现象。在Netty机制里,传输的数据是存储在ByteBuf中,当我们在接收数据的时候,有可能会一次读取多个包的数据,也可能数据包长...

谁念西风独自凉 5254

netty系列之:netty中常用的字符串编码解码器

文章目录简介netty中的字符串编码解码器不同平台的换行符字符串编码的实现总结 简介 字符串是我们程序中最常用到的消息格式,也是最简单的消息格式,但是正因为字符串string太过简单,不能附加更多的信息,所以在netty中选择的是使用byteBuf作为最底层的消息传递载体。 虽然底层使用的ByteBuf,但是对于程序员来说,还是希望能够使用这种最简单的字符串格式,那么有什么简单的方法吗? netty中的字符串编码解码器 为了解决在netty的channel中传递字符串的问题,netty提供了针对于字符串的编

程序那些事 1733

软件工程实验报告--用Visio绘制数据流图、ER图、状态图、软件结构图、程序流程图、系统流程图

用Visio绘制数据流图、ER图、状态图、软件结构图、程序流程图、系统流程图

m0_73814332的博客 4162

kaggle社区LLM Classification Finetuning

预测用户在一个由大型语言模型(LLMs)驱动的聊天机器人之间的正面交锋中会偏好哪些回应。获得来自聊天机器人竞技场(Chatbot Arena)的对话数据集,其中不同的LLMs对用户的提示生成答案。通过开发一个胜出的机器学习模型,帮助改进聊天机器人与人类的互动方式,并确保它们更好地符合人类的偏好。实际上是进行一个序列的分类。

m0_73542617的博客 1923

Netty(五)之心跳机制与重连

文章目标 1)实现客户端和服务端的心跳 2)心跳多少次没有应答断开处理 3)客户端宕机通知服务端 4)服务端宕机客户端重连 运行代码下载(亲测有效) 链接:https://pan.baidu.com/s/1YaCQisOfovSCm5xV1XWtlA 提取码:ecc2 复制这段内容后打开百度网盘手机App,操作更方便哦 前提 在 Netty(一)之hellowor...

CBeann的博客 2873

[Python]:热血传奇-Wzl,Wzx的解析

首先我们要了解,wzl与wzx的对应关系,wzx里面存的是每张二进制图片数据在wzl中的偏移量,所以取到wzx中的偏移数据就可以取到wzl每张图片数据,其实就这么简单。结构体为 [Byte][Byte][Byte][Byte][short][short][short][short][int]-读取44至48字节代表为,[wzx中偏移值总数量] [int类型] [4字节]-根据在wzx中取得的偏移值总数量,每张图片偏移值在wzl中取图片数据。2.[取wzl中图片偏据]:根据wzx中取得数据对[

qq_36138506的博客 3447

TCP粘包/拆包 分包

TCP的粘包和拆包问题往往出现在基于TCP协议的通讯中,比如RPC框架、Netty等。如果你的简历中写了类似的技术或者你所面试的公司使用了相关的技术,被问到该面试的几率会非常高。 今天这篇文章就带大家详细了解一下TCP的粘包和拆包以及解决方案。 什么是粘包? 在学习粘包之前,先纠正一下读音,很多视频教程中将“粘”读作“nián”。经过调研,个人更倾向于读“zhān bāo”。 如果在百度百...

清崎教练 780

CPU也能跑大模型?手把手教你用vLLM离线部署Qwen2.5-7B-Instruct(附性能优化技巧)

本文详细介绍了如何在CPU服务器上高效部署Qwen2.5-7B-Instruct模型,通过vLLM框架和一系列优化技巧实现离线推理。内容包括环境配置、模型加载优化、性能调优及生产环境部署方案,帮助开发者在资源有限的情况下也能运行大语言模型。

weixin_28691741的博客 492

Proteus仿真实战:用STM32F103C8T6输入捕获模式精准测量PWM频率(附完整代码)

本文详细解析了如何在Proteus仿真环境中,利用STM32F103C8T6的输入捕获模式精准测量PWM频率。文章从输入捕获的工作原理、Proteus电路搭建、模块化代码实现到调试优化,提供了完整的实战指南和代码示例,帮助开发者深入理解并掌握这一关键技术。

weixin_29158047的博客 257

BIO NIO 粘包与拆包

BIO NIO 粘包与拆包动机什么是粘包与拆包那为什么我们经常把粘包拆包问题与NIO一起谈在TCP相关的API中没有packet相关的概念NIO模式 与粘包拆包 动机 Netty 提供了大量的开箱即用的组件, 这些组件对使用者透明了很多技术实现细节, 粘包拆包就是其中很有趣的部分. 本人在使用LengthFieldBasedFrameDecoder(在自定义通讯协议的场景下经常被使用: 作为TCP数据包 -> 业务数据包的拆包解析器)时对这个组件的参数还有用法产生了困惑,查阅了一些资料也阅读了源码…写

fanduifandui的博客 759

《拉依达的嵌入式\驱动面试宝典》—计算机硬件篇(一)

《拉依达的嵌入式\驱动面试宝典》 整合了所有我认为可能遇到的技术面试问题。所有问题及其答案均是我个人学习后查阅资料总结,每个回答都做了仔细的分析,在嵌入式及其驱动面试相关问题解析做到全网独一无二。学习或者转行到嵌入式、驱动开发领域学习的不二之选

拉依达不拉跨的博客 768

面试题:聊聊TCP的粘包、拆包以及解决方案

TCP的粘包和拆包问题往往出现在基于TCP协议的通讯中,比如RPC框架、Netty等。如果你的简历中写了类似的技术或者你所面试的公司使用了相关的技术,被问到该面试的几率会非常高。 今天这篇文章就带大家详细了解一下TCP的粘包和拆包以及解决方案。 什么是粘包? 在学习粘包之前,先纠正一下读音,很多视频教程中将“粘”读作“nián”。经过调研,个人更倾向于读“zhān bāo”。 如果在百度百科上搜索“粘包”,对应的读音便是“zhān bāo”,语义解释为:网络技术术语。指TCP协议中,发送方发送的若干

qq_34874784的博客 1007

静态编译Qt5源码(Kylin-Desktop-V10-SP1-2203-LoongArch64+qt5.15.16)

之前的文档,记录的是通用情况,本次笔者记录下Qt5的静态编译过程,供有需要的小伙伴参考使用的操作系统是Kylin-Desktop-V10-SP1-General-Release-2203-LoongArch64,处理器是龙芯3A5000,架构是loongarch。

aggs1990的专栏 1402

ParaView :在paraview创建渲染视图

ParaView :在paraview创建渲染视图

希望我的博客,能帮上你解决学习中工作中所遇到的问题 1306

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

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

choutang6247的博客 760

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)

本文由作者FreddyChen原创分享,为了更好的体现文章价值,引用时有少许改动,感谢原作者。 1、写在前面 一直想写一篇关于im即时通讯分享的文章,无奈工作太忙,很难抽出时间。今天终于从公司离职了,打算好好休息几天再重新找工作,趁时间空闲,决定静下心来写一篇文章,毕竟从前辈那里学到了很多东西。 工作了五年半,这三四年来一直在做社交相关的项目,有直播、即时通讯、短视频分享、社区论坛等产品...

hellojackjiang2011的博客 4318

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Netty实战——构建高性能、可扩展的网络应用

近期我利用 Netty 开发了一个即时通讯(IM)产品,在此分享一些对 Netty 的总结,以深化对其的理解。Netty 是一个被广泛使用的网络框架,主要用于处理 TCP 连接。让我们首先了解一下官方对 Netty 的定义。在物联网开发中,服务端与设备端之间通常通过自定义 TCP 协议进行通信。(也有使用MQTT(Message Queuing Telemetry Transport))而在 Java 中,Netty 是一个常用的选择用于实现 TCP 通信。在这篇文章中,我将介绍如何利用 Netty 实现客

纪大侠博客 1042

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Netty面试题

Netty高频面试题。

golove666的博客 2843

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

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

weixin_34383618的博客 687

Netty知多少?-----第一篇

初次接触netty,很多迷茫,也很多坑,踩了,自己记下,分享到博客,以免后来人继续踩坑,喜欢就点赞或评论,因为刚开始写博客,谢谢了。 我假如你已经了解netty的一些知识,比如他的NIO是多么的好,ByteBuf是多么的好,所以你才选择用此框架,那么,就我和一起开始学习netty吧。(不过即使你不知道netty有多好,但是既然你来看这篇博客,就说明你想了解netty,那我们不说屁话,开始吧。) ...

sinat_38723491的博客 291

Android Netty 使用

Android中使用Netty指南--Kotlin

Mahjong的博客 966

第十章 Netty

10.1 IO模型 10.2 Netty原理源码 10.3 Netty应用 10.1 IO模型 我觉得这篇IO讲得太好了,我自己再解释一遍就是暴殄天物 Reactor设计模式 所有IO操作都由NIO单线程完成,然后通过这个线程分配handler处理请求。 这样不会每有新的请求就会创建新的线程。有的线程存活但空闲,不给这些任务分配线程也节约了IO线程空间 10.2 Netty原理源码 一 原理 Netty主要基于主从Reactor多线程模型 Netty执行流程图 主从线程池具体介绍 1 Netty 抽象2组

weixin_40503364的博客 432

Netty学习笔记

Netty、BIO、NIO学习笔记总结

倩mys的博客 793

java netty适合做什么_什么是Netty?

Netty简介Netty是由JBoss开发,基于Java NIO的一个高性能通信框架。之前几篇文章介绍了Java NIO的一些基本的概念和API。但在实际的网络开发中,其实很少使用Java NIO原生的API。主要有以下原因:原生API使用单线程模型,不能很好利用多核优势,如果自己去写多线程结合起来比较麻烦;原生API是直接使用的IO数据,没有做任何封装处理,对数据的编解码、TCP的粘包和拆包、客...

weixin_39947908的博客 329
上一篇: maven 打包跳过单元测试
下一篇: 公众号开发--java保存用户信息
grootblockchain
博客等级 码龄11年 55粉丝 101原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值