精华内容
下载资源
问答
  • 易语言自定义协议头源码,自定义协议头,取关联文件路径,文件关联,撤消文件关联
  • 易语言自定义协议头源码
  • 易语言源码易语言自定义协议头源码.rar
  • Android_Socket_客户端和服务器端,支持多张图片同时传送,自定义协议头
  • void CDXClientEngine::SetHeaderL(RHTTPHeaders aHeaders, TDesC8 aHdrField, const TDesC8& aHdrValue){ RStringF valStr = iSession.StringPool().OpenFStringL(aHdrValue); HBufC8 * pFi


    void CDXClientEngine::SetHeaderL(RHTTPHeaders aHeaders, TDesC8 aHdrField,
            const TDesC8& aHdrValue)
    {
        RStringF valStr = iSession.StringPool().OpenFStringL(aHdrValue);
        HBufC8 * pField = aHdrField.AllocL();
        RStringF rHdrField = iSession.StringPool().OpenFStringL(*pField);
        CleanupClosePushL(valStr);
        THTTPHdrVal val(valStr);
        aHeaders.SetFieldL(rHdrField, val);
        CleanupStack::PopAndDestroy(); // valStr
        rHdrField.Close();   
        delete pField;
    }


    调用方法:
    SetHeaderL(hdr, _L8("sessionID"), aSessionID);

    展开全文
  • 如何自定义协议

    2020-12-28 08:51:41
    何为自定义协议,其实是相对标准协议来说的,这里主要针对的是应用层协议;常见的标准的应用层协议如http、ftp、smtp等,如果我们在网络通信的过程中不去使用这些标准协议,那就需要自定义协议,比如我们常用的RPC...

    前言

    何为自定义协议,其实是相对标准协议来说的,这里主要针对的是应用层协议;常见的标准的应用层协议如http、ftp、smtp等,如果我们在网络通信的过程中不去使用这些标准协议,那就需要自定义协议,比如我们常用的RPC框架(dubbo,thrift),分布式缓存(redis,memcached)等都是自定义协议;本文就来讲讲如何去自定义私有协议,在此之前我们先考虑一下为什么要自定义协议。

    为什么要自定义协议

    直接使用标准的协议好处是显而易见的,我个人理解的几点优点:

    • 既然是标准协议说明已经成为了标准,这样很多系统就可以直接对接,无缝集成;
    • 协议最重要的一点就是编码解码,标准协议往往有现成的编码解码包,直接拿来使用,减少开发时间;
    • 有很多围绕标准协议的第三方测试工具,可以很方便的进行测试;

    既然有这么多优点那我们为什么还要去自定义协议,大致出于以下几点考虑:

    • 既然是标准协议,往往兼顾的东西比较多,导致协议数据相对来说比较大,这样可能在一些追求性能,流量的系统中不能容忍;
    • 标准协议有很多,没有哪一种协议可以适用任何场景中,所以如果在某个场景中还没有既定的标准协议,这时候会有各种私有协议;
    • 自定义协议只要双方约定好数据结构就行,不具有通用性,理论上来说会更加安全一点,当然现在很多标准协议都有安全版本,比如https,sftp等等;

    以上只是个人的一点理解,欢迎大家补充;关于如何去自定义协议,其实可以去多参考一些主流的标准协议或者私有协议,其实有很多共同点可以去借鉴;下面先简单看看那些主流的协议;

    主流协议

    下面分别看看一些主流的标准协议或者私有协议都是如何去定义自己的数据结构的,对我们有非常好的借鉴意义;

    http协议

    http协议大家最熟悉不过了,全称叫超文本传输协议,整个请求报文可以分为三个部分分别是:请求行,请求报头,请求正文;

    • 请求行
    GET /test.html HTTP/1.1 (CRLF换行)
    
    • 请求报头
    Accept-Encoding: gzip, deflate
    Content-Length: 38 
    Content-Encoding: gzip
    ...
    

    请求包头有很多,每一个代表了各自的含义,这边就不一一列出,我们这里更加关注整个报文的结构;

    • 请求正文
      这个只有在POST请求的时候才有正文,里面存放业务数据,比如常见的json文本串;具体正文的长度可以根据消息头中的Content-Length来决定;

    dubbo协议

    dubbo协议格式可以直接参考官网提供的如下图片:
    image
    看上图其实整个协议数据包也大致分为两个部分:固定部分和可变部分,或者叫消息头和消息体;
    固定部分一共是4+8+4=16个字节,具体如下所示:

    header{
        Magic High       = 8bit;  //魔数高位
        Magic Low        = 8bit;  //魔数低位
        Req/Res          = 1bit;  //标识是请求或响应
        2 Way            = 1bit;  //标记是否期望从服务器返回值
        Event            = 1bit;  //标识是否是事件消息
        Serialization ID = 5bit;  //标识序列化类型
        Status           = 8bit;  //标识响应的状态
        Request ID       = 64bit; //标识唯一请求
        Data Length      = 32bit; //序列化后的内容长度
    }
    

    可变部分根据固定部分中的Data Length来确定长度;

    redis协议

    Redis的客户端与服务端采用叫做 **RESP(Redis Serialization Protocol)**的网络通信协议交换数据,相对来说还是比较简单的,以下是这个协议的一般形式:

    *< 参数数量 > CR LF
    $< 参数 1 的字节数量 > CR LF
    < 参数 1 的数据 > CR LF
    ...
    $< 参数 N 的字节数量 > CR LF
    < 参数 N 的数据 > CR LF
    

    以上大致介绍了三种比较有代表性的协议,虽然说每种协议都有各自的使用场景,但是如果我们自己去定义协议,还是有一些相通的东西;

    如何自定义协议

    下面我们重点看看去自定义协议有哪些需要我们关注的点,以下是本人根据自己的理解整理了如下关注点:

    • 完整的数据包
    • 协议号
    • 消息头标识
    • 业务数据
    • 预留字段

    下面分别逐一详细介绍:

    完整的数据包

    我们平时经常讲数据包,但是TCP其实只有流的概念,并没有数据包的概念;那很重要的一点就是我们的程序怎么知道现在的业务数据已经接受全部接收完了,可以作为一个完整的数据包去处理了,如果不去做处理的话就会出现我们常说的半包和粘包问题;主流的的处理方式大致有这么两种:

    • 在消息头部加上数据包长度描述,比如在http协议和dubbo协议中出现的dataLength字段;
    • 用特殊的字符串作为数据包的结尾,这样我们在接受数据的时候接受到预定的特殊字符串就表示数据包完整了;

    协议号

    可能不同的协议有不同的叫法,我这里把它叫做协议号,个人理解就是根据这个协议号,服务器端知道去执行什么逻辑;比如http协议请求行中的/test.html,dubbo协议中的服务名+版本号,redis中的具体要执行什么key;

    消息头标识

    这个是否需要还是要看各自的场景,比如redis协议足够简单,无需任何标识,所有的东西都是双端约定好的;但是其他很多协议还是有一些需要的,除了上面说到的可以在消息头中指定dataLength,其实还有很多其他的东西可以指定比如:

    • 业务数据格式:文本格式,json格式,html格式等等;
    • 压缩格式:可能为了追求流量包大小对数据包进行压缩,gzip、deflater、snappy等;
    • 加密算法:可能需要对我的业务数据进行加密处理,保证业务数据的安全性AES、DES等;

    业务数据

    业务数据往往在整个数据包中是最大的,同时也是大小可变的部分;我们上面所做的这些其实都是在为业务数据服务,业务数据需要在网络传输,最重要的一点就是序列化,一般就以下两种方式:

    • 文本方式:序列化文本文档text,或者json串,xml格式等;
    • 二进制方式:常见的比如protobuf,thrift,kyro等;

    预留字段

    是否需要预留字段这个得看情况,比如http协议整个消息头是可变的,每一行一个标识,知道读取到空行,表示消息头结束下面就是正文了,可以理解为http使用了两种方式来保证完整包,消息头使用特殊字符结尾,正文使用在消息头中指定dataLength;这种方式其实它的整个扩展性是非常好的;
    另外一种像dubbo这样,其实它的头部相当于已经固定好了16个字节,这种情况下是否可以预留几个字节防止后面的变更;

    总结

    自定义协议其实在我们真正的工作中还是很少能接触到的,更多的其实还是去实现业务,但是我们系统无时无刻不在和各种应用层协议打交道,如果我们了解了各种协议,在系统出现问题时可以做抓包分析;另外像我们常用的数据库中间件、缓存中间件等,都需要对协议都充分的了解,然后去实现代理。

    感谢关注

    可以关注微信公众号「回滚吧代码」,第一时间阅读,文章持续更新;专注Java源码、架构、算法和面试。

    展开全文
  • 自定义协议

    2013-10-18 18:57:34
    自定义协议的头文件 .h中 @protocol NSDelegate @optional //可选 - (void)OnOption:(NSString *)pStr; @required //必须 - (void)OnRequired:(NSString *)pStr; @end   @interface ...

    在自定义协议的头文件 .h中

    @protocol NSDelegate <NSObject>
    @optional  //可选

    - (void)OnOption:(NSString *)pStr;

    @required //必须
    - (void)OnRequired:(NSString *)pStr;
    @end

     

    @interface NSClass :NSObject

    id<BSDelegate> delegate;

    @end

     

    @property(assian) id<BSDelegate> delegate;


    在自定义协议的实现文件.m中

    @synthesize delegate;

     在制定协议实现文件中调用协议

    如果委托方法是可实现的,可以在调用委托方法的用respondsToSelector来判断是否已经实现了委托方法。

    例如:

    [delegate respondsToSelector@selector(OnOption:)]

    然后调用。

    如果是必须实现的则直接调用:

    [delegate OnRequired:@"test"]


    其它类中声明和使用协议

    @interface UIMyView : UIViewController<BSDelegate>

          NSClass *m_pClass;

    @end


    UIMyView 的.m文件中制定委托并实现相关委托方法

    m_pClass.delegate = self;

    再实现委托方法:

    - (void)OnOption:(NSString *)pStr

    {
    }

    - (void)OnRequire:(NSString *)pStr

    {
    }

    展开全文
  • netty自定义协议

    2016-07-15 15:39:17
    netty实现自定义协议通信,形如: /** * socketHeader定义 40 + 4+ 100 +4 * 序列号 40bytes 不足前面补0 * 来源 4bytes * 指令 8bytes 不足前面补0 形如0001, 0000:心跳 * 长度 4bytes */ 心跳连接 运行 ...
  • ![图片说明](https://img-ask.csdn.net/upload/202003/24/1585035030_975869.png) 以上协议头,如何用java封装?
  • Netty实现自定义协议

    千次阅读 2020-01-06 17:36:00
    关于协议,使用最为广泛的是HTTP协议,但是在一些服务交互领域,其使用则相对较少,主要原因有三方面: HTTP协议会携带诸如header和cookie等信息,其本身对字节的利用率也较低,这使得HTTP协议比较臃肿,在承载相同...

    关于协议,使用最为广泛的是HTTP协议,但是在一些服务交互领域,其使用则相对较少,主要原因有三方面:

    • HTTP协议会携带诸如header和cookie等信息,其本身对字节的利用率也较低,这使得HTTP协议比较臃肿,在承载相同信息的情况下,HTTP协议将需要发送更多的数据包;
    • HTTP协议是基于TCP的短连接,其在每次请求和响应的时候都需要进行三次握手和四次挥手,由于服务的交互设计一般都要求能够承载高并发的请求,因而HTTP协议这种频繁的握手和挥手动作会极大的影响服务之间交互的效率;
    • 服务之间往往有一些根据其自身业务特性所独有的需求,而HTTP协议无法很好的服务于这些业务需求。

           基于上面的原因,一般的服务之间进行交互时都会使用自定义协议,常见的框架,诸如dubbo,kafka,zookeeper都实现了符合其自身业务需求的协议,本文主要讲解如何使用Netty实现一款自定义的协议。

    1. 协议规定

           所谓协议,其本质其实就是定义了一个将数据转换为字节,或者将字节转换为数据的一个规范。一款自定义协议,其一般包含两个部分:消息头和消息体。消息头的长度一般是固定的,或者说是可确定的,其定义了此次消息的一些公有信息,比如当前服务的版本,消息的sessionId,消息的类型等等;消息体则主要是此次消息所需要发送的内容,一般在消息头的最后一定的字节中保存了当前消息的消息体的长度。下面是我们为当前自定义协议所做的一些规定:

    名称字段字节数描述
    魔数magicNumber4一个固定的数字,一般用于指定当前字节序列是当前类型的协议,比如Java生成的class文件起始就使用0xCAFEBABE作为其标识符,对于本服务,这里将其定义为0x1314
    主版本号mainVersion1当前服务器版本代码的主版本号
    次版本号subVersion1当前服务器版本的次版本号
    修订版本号modifyVersion1当前服务器版本的修订版本号
    会话idsessionId8当前请求的会话id,用于将请求和响应串联到一起
    消息类型messageType1请求:1,表示当前是一个请求消息;响应:2,表示当前是一个响应消息;Ping:3,表示当前是一个Ping消息;Pong:4,表示当前是一个Pong消息;Empty:5,表示当前是一个空消息,该消息不会写入数据管道中;
    附加数据attachments不定附加消息是字符串类型的键值对来表示的,这里首先使用2个字节记录键值对的个数,然后对于每个键和值,都首先使用4个字节记录其长度,然后是具体的数据,其形式如:键值对个数+键长度+键数据+值长度+值数据...
    消息体长度length4字节记录了消息体的长度
    消息体body不定消息体,服务之间交互所发送或接收的数据,其长度有前面的length指定

           上述协议定义中,我们除了定义常用的请求和响应消息类型以外,还定义了Ping和Pong消息。Ping和Pong消息的作用一般是,在服务处于闲置状态达到一定时长,比如2s时,客户端服务会向服务端发送一个Ping消息,则会返回一个Pong消息,这样才表示客户端与服务端的连接是完好的。如果服务端没有返回相应的消息,客户端就会关闭与服务端的连接或者是重新建立与服务端的连接。这样的优点在于可以防止突然会产生的客户端与服务端的大量交互。

    2. 协议实现

           通过上面的定义其实我们可以发现,所谓协议,就是定义了一个规范,基于这个规范,我们可以将消息转换为相应的字节流,然后经由TCP传输到目标服务,目标服务则也基于该规范将字节流转换为相应的消息,这样就达到了相互交流的目的。这里面最重要的主要是如何基于该规范将消息转换为字节流或者将字节流转换为消息。这一方面,Netty为我们提供了ByteToMessageDecoderMessageToByteEncoder用于进行消息和字节流的相互转换。首先我们定义了如下消息实体:

    public class Message {
      private int magicNumber;
      private byte mainVersion;
      private byte subVersion;
      private byte modifyVersion;
      private String sessionId;
     
      private MessageTypeEnum messageType;
      private Map<String, String> attachments = new HashMap<>();
      private String body;
     
      public Map<String, String> getAttachments() {
        return Collections.unmodifiableMap(attachments);
      }
     
      public void setAttachments(Map<String, String> attachments) {
        this.attachments.clear();
        if (null != attachments) {
          this.attachments.putAll(attachments);
        }
      }
     
      public void addAttachment(String key, String value) {
        attachments.put(key, value);
      }
     
      // getter and setter...
    }
    

           上述消息中,我们将协议中所规定的各个字段都进行了定义,并且定义了一个标志消息类型的枚举MessageTypeEnum,如下是该枚举的源码:

    public enum MessageTypeEnum {
      REQUEST((byte)1), RESPONSE((byte)2), PING((byte)3), PONG((byte)4), EMPTY((byte)5);
     
      private byte type;
     
      MessageTypeEnum(byte type) {
        this.type = type;
      }
     
      public int getType() {
        return type;
      }
     
      public static MessageTypeEnum get(byte type) {
        for (MessageTypeEnum value : values()) {
          if (value.type == type) {
            return value;
          }
        }
     
        throw new RuntimeException("unsupported type: " + type);
      }
    }
    

    上述主要是定义了描述自定义协议相关的实体属性,对于消息的编码,本质就是依据上述协议方式将消息实体转换为字节流,如下是转换字节流的代码:

    public class MessageEncoder extends MessageToByteEncoder<Message> {
     
      @Override
      protected void encode(ChannelHandlerContext ctx, Message message, ByteBuf out) {
        // 这里会判断消息类型是不是EMPTY类型,如果是EMPTY类型,则表示当前消息不需要写入到管道中
        if (message.getMessageType() != MessageTypeEnum.EMPTY) {
          out.writeInt(Constants.MAGIC_NUMBER);	// 写入当前的魔数
          out.writeByte(Constants.MAIN_VERSION);	// 写入当前的主版本号
          out.writeByte(Constants.SUB_VERSION);	// 写入当前的次版本号
          out.writeByte(Constants.MODIFY_VERSION);	// 写入当前的修订版本号
          if (!StringUtils.hasText(message.getSessionId())) {
            // 生成一个sessionId,并将其写入到字节序列中
            String sessionId = SessionIdGenerator.generate();
            message.setSessionId(sessionId);
            out.writeCharSequence(sessionId, Charset.defaultCharset());
          }
     
          out.writeByte(message.getMessageType().getType());	// 写入当前消息的类型
          out.writeShort(message.getAttachments().size());	// 写入当前消息的附加参数数量
          message.getAttachments().forEach((key, value) -> {
            Charset charset = Charset.defaultCharset();
            out.writeInt(key.length());	// 写入键的长度
            out.writeCharSequence(key, charset);	// 写入键数据
            out.writeInt(value.length());	// 希尔值的长度
            out.writeCharSequence(value, charset);	// 写入值数据
          });
     
          if (null == message.getBody()) {
            out.writeInt(0);	// 如果消息体为空,则写入0,表示消息体长度为0
          } else {
            out.writeInt(message.getBody().length());
            out.writeCharSequence(message.getBody(), Charset.defaultCharset());
          }
        }
      }
    }
    

       对于消息的解码,其过程与上面的消息编码方式基本一致,主要是基于协议所规定的将字节流数据转换为消息实体数据。如下是其转换过程:

    
    public class MessageDecoder extends ByteToMessageDecoder {
     
      @Override
      protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) throws Exception {
        Message message = new Message();
        message.setMagicNumber(byteBuf.readInt());  // 读取魔数
        message.setMainVersion(byteBuf.readByte()); // 读取主版本号
        message.setSubVersion(byteBuf.readByte()); // 读取次版本号
        message.setModifyVersion(byteBuf.readByte());	// 读取修订版本号
        CharSequence sessionId = byteBuf.readCharSequence(
            Constants.SESSION_ID_LENGTH, Charset.defaultCharset());	// 读取sessionId
        message.setSessionId((String)sessionId);
     
        message.setMessageType(MessageTypeEnum.get(byteBuf.readByte()));	// 读取当前的消息类型
        short attachmentSize = byteBuf.readShort();	// 读取附件长度
        for (short i = 0; i < attachmentSize; i++) {
          int keyLength = byteBuf.readInt();	// 读取键长度和数据
          CharSequence key = byteBuf.readCharSequence(keyLength, Charset.defaultCharset());
          int valueLength = byteBuf.readInt();	// 读取值长度和数据
          CharSequence value = byteBuf.readCharSequence(valueLength, Charset.defaultCharset());
          message.addAttachment(key.toString(), value.toString());
        }
     
        int bodyLength = byteBuf.readInt();	// 读取消息体长度和数据
        CharSequence body = byteBuf.readCharSequence(bodyLength, Charset.defaultCharset());
        message.setBody(body.toString());
        out.add(message);
      }
    }

           如此,我们自定义消息与字节流的相互转换工作已经完成。对于消息的处理,主要是要根据消息的不同类型,对消息进行相应的处理,比如对于request类型消息,要写入响应数据,对于ping消息,要写入pong消息作为回应。下面我们通过定义Netty handler的方式实现对消息的处理:

    // 服务端消息处理器
    public class ServerMessageHandler extends SimpleChannelInboundHandler<Message> {
     
      // 获取一个消息处理器工厂类实例
      private MessageResolverFactory resolverFactory = MessageResolverFactory.getInstance();
     
      @Override
      protected void channelRead0(ChannelHandlerContext ctx, Message message) throws Exception {
        Resolver resolver = resolverFactory.getMessageResolver(message);	// 获取消息处理器
        Message result = resolver.resolve(message);	// 对消息进行处理并获取响应数据
        ctx.writeAndFlush(result);	// 将响应数据写入到处理器中
      }
     
      @Override
      public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        resolverFactory.registerResolver(new RequestMessageResolver());	// 注册request消息处理器
        resolverFactory.registerResolver(new ResponseMessageResolver());// 注册response消息处理器
        resolverFactory.registerResolver(new PingMessageResolver());	// 注册ping消息处理器
        resolverFactory.registerResolver(new PongMessageResolver());	// 注册pong消息处理器
      }
    }
    
    // 客户端消息处理器
    public class ClientMessageHandler extends ServerMessageHandler {
     
      // 创建一个线程,模拟用户发送消息
      private ExecutorService executor = Executors.newSingleThreadExecutor();
     
      @Override
      public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 对于客户端,在建立连接之后,在一个独立线程中模拟用户发送数据给服务端
        executor.execute(new MessageSender(ctx));
      }
     
      /**
       * 这里userEventTriggered()主要是在一些用户事件触发时被调用,这里我们定义的事件是进行心跳检测的
       * ping和pong消息,当前触发器会在指定的触发器指定的时间返回内如果客户端没有被读取消息或者没有写入
       * 消息到管道,则会触发当前方法
       */
      @Override
      public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
          IdleStateEvent event = (IdleStateEvent) evt;
          if (event.state() == IdleState.READER_IDLE) {
            // 一定时间内,当前服务没有发生读取事件,也即没有消息发送到当前服务来时,
            // 其会发送一个Ping消息到服务器,以等待其响应Pong消息
            Message message = new Message();
            message.setMessageType(MessageTypeEnum.PING);
            ctx.writeAndFlush(message);
          } else if (event.state() == IdleState.WRITER_IDLE) {
            // 如果当前服务在指定时间内没有写入消息到管道,则关闭当前管道
            ctx.close();
          }
        }
      }
     
      private static final class MessageSender implements Runnable {
     
        private static final AtomicLong counter = new AtomicLong(1);
        private volatile ChannelHandlerContext ctx;
     
        public MessageSender(ChannelHandlerContext ctx) {
          this.ctx = ctx;
        }
     
        @Override
        public void run() {
          try {
            while (true) {
              // 模拟随机发送消息的过程
              TimeUnit.SECONDS.sleep(new Random().nextInt(3));
              Message message = new Message();
              message.setMessageType(MessageTypeEnum.REQUEST);
              message.setBody("this is my " + counter.getAndIncrement() + " message.");
              message.addAttachment("name", "xufeng");
              ctx.writeAndFlush(message);
            }
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }
    

     上述代码中,由于客户端和服务端需要处理的消息类型是完全一样的,因而客户端处理类继承了服务端处理类。但是对于客户端而言,其还需要定时向服务端发送心跳消息,用于检测客户端与服务器的连接是否健在,因而客户端还会实现userEventTriggered()方法,在该方法中定时向服务器发送心跳消息。userEventTriggered()方法主要是在客户端被闲置一定时间后,其会根据其读取或者写入消息的限制时长来选择性的触发读取或写入事件。

            上述实现中,我们看到,对于具体类型消息的处理,我们是通过一个工厂类来获取对应的消息处理器,然后处理相应的消息,下面我们该工厂类的代码:

    public final class MessageResolverFactory {
     
      // 创建一个工厂类实例
      private static final MessageResolverFactory resolverFactory = new MessageResolverFactory();
      private static final List<Resolver> resolvers = new CopyOnWriteArrayList<>();
     
      private MessageResolverFactory() {}
     
      // 使用单例模式实例化当前工厂类实例
      public static MessageResolverFactory getInstance() {
        return resolverFactory;
      }
     
      public void registerResolver(Resolver resolver) {
        resolvers.add(resolver);
      }
     
      // 根据解码后的消息,在工厂类处理器中查找可以处理当前消息的处理器
      public Resolver getMessageResolver(Message message) {
        for (Resolver resolver : resolvers) {
          if (resolver.support(message)) {
            return resolver;
          }
        }
     
        throw new RuntimeException("cannot find resolver, message type: " + message.getMessageType());
      }
     
    }
    

           上述工厂类比较简单,主要就是通过单例模式获取一个工厂类实例,然后提供一个根据具体消息来查找其对应的处理器的方法。下面我们来看看各个消息处理器的代码:

    // request类型的消息
    public class RequestMessageResolver implements Resolver {
     
      private static final AtomicInteger counter = new AtomicInteger(1);
     
      @Override
      public boolean support(Message message) {
        return message.getMessageType() == MessageTypeEnum.REQUEST;
      }
     
      @Override
      public Message resolve(Message message) {
        // 接收到request消息之后,对消息进行处理,这里主要是将其打印出来
        int index = counter.getAndIncrement();
        System.out.println("[trx: " + message.getSessionId() + "]"
            + index + ". receive request: " + message.getBody());
        System.out.println("[trx: " + message.getSessionId() + "]"
            + index + ". attachments: " + message.getAttachments());
     
        // 处理完成后,生成一个响应消息返回
        Message response = new Message();
        response.setMessageType(MessageTypeEnum.RESPONSE);
        response.setBody("nice to meet you too!");
        response.addAttachment("name", "xufeng");
        response.addAttachment("hometown", "wuhan");
        return response;
      }
    }
    
    // 响应消息处理器
    public class ResponseMessageResolver implements Resolver {
     
      private static final AtomicInteger counter = new AtomicInteger(1);
     
      @Override
      public boolean support(Message message) {
        return message.getMessageType() == MessageTypeEnum.RESPONSE;
      }
     
      @Override
      public Message resolve(Message message) {
        // 接收到对方服务的响应消息之后,对响应消息进行处理,这里主要是将其打印出来
        int index = counter.getAndIncrement();
        System.out.println("[trx: " + message.getSessionId() + "]"
            + index + ". receive response: " + message.getBody());
        System.out.println("[trx: " + message.getSessionId() + "]"
            + index + ". attachments: " + message.getAttachments());
     
        // 响应消息不需要向对方服务再发送响应,因而这里写入一个空消息
        Message empty = new Message();
        empty.setMessageType(MessageTypeEnum.EMPTY);
        return empty;
      }
    }
    
    
    // ping消息处理器
    public class PingMessageResolver implements Resolver {
     
      @Override
      public boolean support(Message message) {
        return message.getMessageType() == MessageTypeEnum.PING;
      }
     
      @Override
      public Message resolve(Message message) {
        // 接收到ping消息后,返回一个pong消息返回
        System.out.println("receive ping message: " + System.currentTimeMillis());
        Message pong = new Message();
        pong.setMessageType(MessageTypeEnum.PONG);
        return pong;
      }
    }
    
    
    // pong消息处理器
    public class PongMessageResolver implements Resolver {
     
      @Override
      public boolean support(Message message) {
        return message.getMessageType() == MessageTypeEnum.PONG;
      }
     
      @Override
      public Message resolve(Message message) {
        // 接收到pong消息后,不需要进行处理,直接返回一个空的message
        System.out.println("receive pong message: " + System.currentTimeMillis());
        Message empty = new Message();
        empty.setMessageType(MessageTypeEnum.EMPTY);
        return empty;
      }
    }
    

    如此,对于自定义协议的消息处理过程已经完成,下面则是使用用Netty实现的客户端与服务端代码:

    
    // 服务端
    public class Server {
     
      public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
          ServerBootstrap bootstrap = new ServerBootstrap();
          bootstrap.group(bossGroup, workerGroup)
              .channel(NioServerSocketChannel.class)
              .option(ChannelOption.SO_BACKLOG, 1024)
              .handler(new LoggingHandler(LogLevel.INFO))
              .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                  ChannelPipeline pipeline = ch.pipeline();	
                  // 添加用于处理粘包和拆包问题的处理器
                  pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));
                  pipeline.addLast(new LengthFieldPrepender(4));
                  // 添加自定义协议消息的编码和解码处理器
                  pipeline.addLast(new MessageEncoder());
                  pipeline.addLast(new MessageDecoder());
                  // 添加具体的消息处理器
                  pipeline.addLast(new ServerMessageHandler());
                }
              });
     
          ChannelFuture future = bootstrap.bind(8585).sync();
          future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
          bossGroup.shutdownGracefully();
          workerGroup.shutdownGracefully();
        }
      }
    }
    
    public class Client {
      public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        try {
          bootstrap.group(group)
              .channel(NioSocketChannel.class)
              .option(ChannelOption.TCP_NODELAY, Boolean.TRUE)
              .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                  ChannelPipeline pipeline = ch.pipeline();
                  // 添加用于解决粘包和拆包问题的处理器
                  pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));
                  pipeline.addLast(new LengthFieldPrepender(4));
                  // 添加用于进行心跳检测的处理器
                  pipeline.addLast(new IdleStateHandler(1, 2, 0));
                  // 添加用于根据自定义协议将消息与字节流进行相互转换的处理器
                  pipeline.addLast(new MessageEncoder());
                  pipeline.addLast(new MessageDecoder());
                  // 添加客户端消息处理器
                  pipeline.addLast(new ClientMessageHandler());
                }
              });
     
          ChannelFuture future = bootstrap.connect("127.0.0.1", 8585).sync();
          future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
          group.shutdownGracefully();
        }
      }
    }
    

    运行上述代码之后,我们可以看到客户端和服务器分别打印了如下数据:

    // 客户端
    receive pong message: 1555123429356
    [trx: d05024d2]1. receive response: nice to meet you too!
    [trx: d05024d2]1. attachments: {hometown=wuhan, name=xufeng}
    [trx: 66ee1438]2. receive response: nice to meet you too!
    
    
    // 服务器
    receive ping message: 1555123432279
    [trx: f582444f]4. receive request: this is my 4 message.
    [trx: f582444f]4. attachments: {name=xufeng}

    3. 小结

           本文首先将自定义协议与HTTP协议进行了对比,阐述了自定义协议的一些优点。然后定义了一份自定义协议,并且讲解了协议中各个字节的含义。最后通过Netty对自定义协议进行了实现,并且实现了基于自定义协议的心跳功能。

    转载于:https://my.oschina.net/zhangxufeng/blog/3043768

    展开全文
  • 各位大神,我现在要写Socket自定义协议 跟服务器交互,要通过probuff 或者 messagepack 序列化。能不能提供C# 或者c/c++ 的代码解释一下。数据包 = 包头 + 包体长度+包体。谢谢各位大神!
  • java自定义协议

    2020-02-26 18:02:03
    在设计 C-S 结构的应用时,我们一般需要使用一种客户端C与服务端S的通信协议。现在常用的协议有HTTP,XML-RPC,SOAP等,当然,现在XML在很多场景下都用JSON替代了,这个不赘述,有些时候,我们需要设计一些自有协议,...
  • 浏览器自定义协议检测

    千次阅读 2015-03-06 22:09:41
    桌面客户端通过向操作系统注册私有自定义协议的方式,可以在web页面中以url的方式的调起桌面客户端。 我们常见的协议是http:// https:// ftp://等 当我们的桌面客户端注册了某个自定义协议,之后,web页面就可以...
  • Netty实现自定义协议和源码分析

    千次阅读 2018-05-23 10:55:25
    本篇 主要讲的是自定义协议是如何实现的,以及自定义协议中会出现的问题和Netty是如何支持的。 分为4个部分 |– 粘包 拆包 数据包不全 和解决方案 |– 代码实现 |– ByteToMessageDecoder的源码分析 |– ...
  • 让基于CEF的应用支持自定义协议

    万次阅读 2016-06-16 14:32:06
    基于CEF开发浏览器或Native与Web混合的富客户端,可以支持自定义协议,当点击网页上的自定义协议链接时,调用系统注册的协议处理程序
  • 在上节课中我们介绍了如何使用网络协议解决 TCP 拆包/粘包的底层问题,本节课我们将在此基础上继续讨论如何设计一个高效、可扩展、易维护的自定义通信协议,以及如何使用 Netty 实现自定义通信协议。 通信协议设计 ...
  • 这是一种简便的设置请求的方法: + (void)load {  [NSURLProtocol registerClass:self]; } + (BOOL)canInitWithRequest:(NSURLRequest *)request {    if ...
  • Netty自定义协议开发

    千次阅读 2015-12-24 17:23:06
    自定义协议格式%1$8s%2$4s%3$4s%4$8s%5$16s%6$32s%7$8s类似:AAAAAAAA0001000011001001usernameusernamesession1session1session1session100000023参考代码package com.phei.netty.codec.custom;import java.util....
  • 利用RHSocketKit构建自定义协议通信

    千次阅读 2016-04-18 21:11:23
    利用RHSocketKit构建自定义协议通信 在网络传输中,tcp/ip协议只是网络的基础,分别属于传输层和网络层。我们各种应用之间的通信需要依赖各种各样的协议,是建立在tcp/ip之上的,比如典型的http协议。 在开发过程中...
  • 最近在做一个浏览器的项目中遇到一个跳转到自定义协议的连接上。 如:tencent://message/?uin=123456789&Site=qq&Menu=yes 类这种协议是如何工作的呢? 将连接进行拆分: tencent://协议头 message地址...
  • TCP只是一个可靠传输的通信管道,上层协议要你自己定的,通俗来说...自定义协议的核心有两个: 1. 控制码 2. 流程控制 用Socket进行通信,发送的数据包一定是有结构的,类似于:数据+数据长度+数据
  • QQ-自定义协议说明

    2014-04-24 08:27:17
    QQ-增加自定义协议接收客户端用户名 QQ-系统在线用户改变时,更新客户端列表
  • netty4 实现自定义协议开发

    千次阅读 热门讨论 2017-02-25 00:13:31
    netty4实现自定义协议开发本示例使用的是最新netty4.1.8 参考excmples (securechat) 消息格式定义 编码器 解码器 示例指令 源码 本示例协议头部使用12个字节来定义具体如下: 一个字节的标记和类型,一个字节的...
  • Netty自定义协议半包编解码

    千次阅读 2018-06-28 16:30:49
    1.自定义协议客户端与服务器收发逻辑 Netty中自带了多种编解码器,在项目中更常用的是自定义协议进行通信,此时需要自行处理 半包问题,通过继承ByteToMessageDecoder可以方便的解决这个问题。 服务器代码: ...
  • 利用Netty构建自定义协议的通信

    千次阅读 2018-06-14 22:34:21
    在开发过程中,有时候我们需要构建一些适应自己业务的应用层协议,Netty作为一个非常优秀的网络通信框架,可以帮助我们完成自定义协议的通信。一般而言,我们制定的协议需要两个部分:Header : 协议头部,放置一些...
  • iOS自定义协议中的byte使用

    千次阅读 2018-11-07 12:00:19
    在一些需要兼容多端的开发的时候,可能会遇到使用自定义协议,比如与硬件通信或者使用socket的时候,接受的数据是按约定协议的字节数组,写数据的时候也需要按照协议来发送数据,这时就涉及到byte数组的的写入和解析(很久...
  • Mina自定义协议-实现数据交互

    千次阅读 2016-06-20 11:12:05
    本文主要现实mina的自定义协议,并且实现服务器和客户端的简单数据交互。   "mina协议的自定义"可参考本博Mina相关文章。   正题,所需要的基础类: 抽象协议类 请求协议 响应协议 (需要...
  • Netty自定义协议的粘包和拆包处理

    千次阅读 2017-02-17 20:35:25
    Netty自定义协议的粘包和拆包处理

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 102,789
精华内容 41,115
关键字:

自定义协议头