hessian协议解析--hessian序列化

一,前言

本文主要介绍hessian的解析协议,通过理解hessian协议以便于知道它的优劣,从而更好的运用它。Hessian序列化的协议可以在官方文档上找到 Hessian 2.0 Serialization Protocol

二,基本类型序列化

我们先从最简单的看起,int是如何序列化的。首先我们看官方文档定义。

           # 32-bit signed integer
int        ::= 'I' b3 b2 b1 b0
           ::= [x80-xbf]             # -x10 to x3f  -16到63之间
           ::= [xc0-xcf] b0          # -x800 to x7ff  -2048到2047之间
           ::= [xd0-xd7] b1 b0       # -x40000 to x3ffff  -262144到262143之间

其实有了定义之后就容易实现了,我们看代码实现:

class Hessian2Output
public void writeInt(int value)
  throws IOException
{
  int offset = _offset;
  byte []buffer = _buffer;
  //扩容
  if (SIZE <= offset + 16) {
    flushBuffer();
    offset = _offset;
  }
  if (INT_DIRECT_MIN <= value && value <= INT_DIRECT_MAX)
  //值在-x10到x3f之间,加上BC_INT_ZERO(0x90),就在区间[x80-xbf]上了。
    buffer[offset++] = (byte) (value + BC_INT_ZERO);
  else if (INT_BYTE_MIN <= value && value <= INT_BYTE_MAX) {
  //值在-0x800到0x7ff之间,左移8位加上BC_INT_BYTE_ZERO(0xc8),第一个字节就在区间[xc0-xcf]上了。
    buffer[offset++] = (byte) (BC_INT_BYTE_ZERO + (value >> 8));
    buffer[offset++] = (byte) (value);
  }
  else if (INT_SHORT_MIN <= value && value <= INT_SHORT_MAX) {
  //值在-x40000到x3ffff之间,左移16位加上BC_INT_SHORT_ZERO(0xd4),第一个字节就在区间[xd0-xd7]上了。
    buffer[offset++] = (byte) (BC_INT_SHORT_ZERO + (value >> 16));
    buffer[offset++] = (byte) (value >> 8);
    buffer[offset++] = (byte) (value);
  }
  else {
    buffer[offset++] = (byte) ('I');
    buffer[offset++] = (byte) (value >> 24);
    buffer[offset++] = (byte) (value >> 16);
    buffer[offset++] = (byte) (value >> 8);
    buffer[offset++] = (byte) (value);
  }

  _offset = offset;
}

同理,Long和Double也是如此,就不一一解释了。Boolean类型用’T’’F’来表示。

三,对象序列化

3,1 String序列化

String是我们最常用的对象,下面我们看看它的结构定义。

           # UTF-8 encoded character string split into 64k chunks
string     ::= x52 b1 b0 <utf8-data> string  # non-final chunk
           ::= 'S' b1 b0 <utf8-data>         # string of length
                                             #  0-65535
           ::= [x00-x1f] <utf8-data>         # string of length
                                             #  0-31
           ::= [x30-x34] <utf8-data>         # string of length
                                             #  0-1023

从上面我们看到,当字符串小于65535的时候,会生成一个结束数据块,否者,一直遍历循环截取String,生成一个大小为65535的。
下面我们看源码实现:

class Hessian2Output
public void writeString(String value)
   throws IOException
 {
   ....//略     扩容
   if (value == null) {
     buffer[offset++] = (byte) 'N';
     _offset = offset;
   } else {
     int length = value.length();
     int strOffset = 0;

     while (length > 0x8000) {
       int sublen = 0x8000; //长度为32768,这里是由于一个unicode字符为2byte
       ...//略 扩容

       // chunk can't end in high surrogate
       char tail = value.charAt(strOffset + sublen - 1);
       //块不能以高位结尾
       if (0xd800 <= tail && tail <= 0xdbff)
         sublen--;
       buffer[offset + 0] = (byte) BC_STRING_CHUNK;
       buffer[offset + 1] = (byte) (sublen >> 8);
       buffer[offset + 2] = (byte) (sublen);
       //3个字节,R字符串长度
       _offset = offset + 3;
       printString(value, strOffset, sublen);
       length -= sublen;
       strOffset += sublen;
     }
     ...//略 扩容,这里代码太不规范,一直重复
     if (length <= STRING_DIRECT_MAX) {
       //直接写入字符串长度
       buffer[offset++] = (byte) (BC_STRING_DIRECT + length);
     }
     else if (length <= STRING_SHORT_MAX) {
       //高位与0x30相加,地位取length的低位
       buffer[offset++] = (byte) (BC_STRING_SHORT + (length >> 8));
       buffer[offset++] = (byte) (length);
     }
     else {
       buffer[offset++] = (byte) ('S');
       buffer[offset++] = (byte) (length >> 8);
       buffer[offset++] = (byte) (length);
     }

     _offset = offset;

     printString(value, strOffset, length);
   }
 }

3,2 date序列化

其实日期也是个相对时间,相对1970年的差值而已,也就是序列化的为整形而已。

           # time in UTC encoded as 64-bit long milliseconds since
           #  epoch
date       ::= x4a b7 b6 b5 b4 b3 b2 b1 b0
           ::= x4b b3 b2 b1 b0       # minutes since epoch

我们看到,0x4a开头的日期是带秒的,0x4b开头的日期是不带秒的。接下来看代码实现:

    public void writeUTCDate(long time)
  throws IOException
{
  if (SIZE < _offset + 32)
    flushBuffer();

  int offset = _offset;
  byte []buffer = _buffer;
  //除以60000秒
  if (time % 60000L == 0) {
    // compact date ::= x65 b3 b2 b1 b0

    long minutes = time / 60000L;

    if ((minutes >> 31) == 0 || (minutes >> 31) == -1) {
      buffer[offset++] = (byte) BC_DATE_MINUTE;
      buffer[offset++] = ((byte) (minutes >> 24));
      buffer[offset++] = ((byte) (minutes >> 16));
      buffer[offset++] = ((byte) (minutes >> 8));
      buffer[offset++] = ((byte) (minutes >> 0));

      _offset = offset;
      return;
    }
  }

  buffer[offset++] = (byte) BC_DATE;
  buffer[offset++] = ((byte) (time >> 56));
  buffer[offset++] = ((byte) (time >> 48));
  buffer[offset++] = ((byte) (time >> 40));
  buffer[offset++] = ((byte) (time >> 32));
  buffer[offset++] = ((byte) (time >> 24));
  buffer[offset++] = ((byte) (time >> 16));
  buffer[offset++] = ((byte) (time >> 8));
  buffer[offset++] = ((byte) (time));

  _offset = offset;
}

3.3自定义类定义序列化

序列化类规则:”C”+类名+字段长度+字段名。
对象规则:”O”+类定义引用+字段值 或者 类定义引用值小于16,(0x60+引用值)+字段值。

class-def  ::= 'C' string int string*

object     ::= 'O' int value*
           ::= [x60-x6f] value*

对象序列化需要注意,如果以前已经序列化过了,再次序列化时,会添加一个引用标志,如下:

ref ::= x51 int

引用仅仅引用list,map和对象。

public void writeObject(Object obj, AbstractHessianOutput out)
    throws IOException
  {
    //对象引用
    if (out.addRef(obj)) {
      return;
    }
    Class<?> cl = obj.getClass();

    //'C'和类名
    int ref = out.writeObjectBegin(cl.getName());

    //类名引用
    if (ref >= 0) {
      writeInstance(obj, out);
    }
    else if (ref == -1) {
      //字段长度,字段名
      writeDefinition20(out);
      out.writeObjectBegin(cl.getName());
      //字段序列化
      writeInstance(obj, out);
    }
    else {
      writeObject10(obj, out);
    }
  }

参考hessian序列化的例子:

class Car {
  String color;
  String model;
}

out.writeObject(new Car("red", "corvette"));
out.writeObject(new Car("green", "civic"));

---

C                        # object definition (#0)
  x0b example.Car        # type is example.Car
  x92                    # two fields
  x05 color              # color field name
  x05 model              # model field name

O                        # object def (long form)
  x90                    # object definition #0
  x03 red                # color field value
  x08 corvette           # model field value

x60                      # object def #0 (short form)
  x05 green              # color field value
  x05 civic              # model field value

四,集合序列化

4.1 数组序列化

我们看它的协议:

list ::= x55 type value* 'Z'   # variable-length list
     ::= 'V' type int value*   # fixed-length list
     ::= x57 value* 'Z'        # variable-length untyped list
     ::= x58 int value*        # fixed-length untyped list
     ::= [x70-77] type value*  # fixed-length typed list
     ::= [x78-7f] value*       # fixed-length untyped list

协议本身很简单:总共分为6种情况。
变长list
固定长度list
变长无类型list
固定长度无类型list
固定长度(小于等于7)有类型list
固定长度(小于等于7)无类型list

下面我看看int[]的解析:

V                    # fixed length, typed list
  x04 [int           # encoding of int[] type
  x92                # length = 2
  x90                # integer 0
  x91                # integer 1
BasicSerializer
case INTEGER_ARRAY:
{
  if (out.addRef(obj))
    return;

  int []data = (int []) obj;
  //写对象头
  boolean hasEnd = out.writeListBegin(data.length, "[int");
  //写数据
  for (int i = 0; i < data.length; i++)
    out.writeInt(data[i]);
  //写结尾
  if (hasEnd)
out.writeListEnd();

  break;
}

4.2 Map序列化

我们看它的协议:

map        ::= M type (value value)* Z

这个就更简单了,M开头,接着是类型,然后是值,最后以Z结尾。代码如下:

MapSerializer
public void writeObject(Object obj, AbstractHessianOutput out)
throws IOException
  {
    if (out.addRef(obj))
      return;

    Map map = (Map) obj;

    Class cl = obj.getClass();

    if (cl.equals(HashMap.class)
    || ! _isSendJavaType
    || ! (obj instanceof java.io.Serializable))
      out.writeMapBegin(null);
    else
      out.writeMapBegin(obj.getClass().getName());

    Iterator iter = map.entrySet().iterator();
    while (iter.hasNext()) {
      Map.Entry entry = (Map.Entry) iter.next();

      out.writeObject(entry.getKey());
      out.writeObject(entry.getValue());
    }
    out.writeMapEnd();
  }

五,结尾

hessian序列化以开头一个字节做映射,根据不同的编号映射到不同的类型定义,下面是总体规则:

x00 - x1f    # utf-8 string length 0-32
x20 - x2f    # binary data length 0-16
x30 - x33    # utf-8 string length 0-1023
x34 - x37    # binary data length 0-1023
x38 - x3f    # three-octet compact long (-x40000 to x3ffff)
x40          # reserved (expansion/escape)
x41          # 8-bit binary data non-final chunk ('A')
x42          # 8-bit binary data final chunk ('B')
x43          # object type definition ('C')
x44          # 64-bit IEEE encoded double ('D')
x45          # reserved
x46          # boolean false ('F')
x47          # reserved
x48          # untyped map ('H')
x49          # 32-bit signed integer ('I')
x4a          # 64-bit UTC millisecond date
x4b          # 32-bit UTC minute date
x4c          # 64-bit signed long integer ('L')
x4d          # map with type ('M')
x4e          # null ('N')
x4f          # object instance ('O')
x50          # reserved
x51          # reference to map/list/object - integer ('Q')
x52          # utf-8 string non-final chunk ('R')
x53          # utf-8 string final chunk ('S')
x54          # boolean true ('T')
x55          # variable-length list/vector ('U')
x56          # fixed-length list/vector ('V')
x57          # variable-length untyped list/vector ('W')
x58          # fixed-length untyped list/vector ('X')
x59          # long encoded as 32-bit int ('Y')
x5a          # list/map terminator ('Z')
x5b          # double 0.0
x5c          # double 1.0
x5d          # double represented as byte (-128.0 to 127.0)
x5e          # double represented as short (-32768.0 to 327676.0)
x5f          # double represented as float
x60 - x6f    # object with direct type
x70 - x77    # fixed list with direct length
x78 - x7f    # fixed untyped list with direct length
x80 - xbf    # one-octet compact int (-x10 to x3f, x90 is 0)
xc0 - xcf    # two-octet compact int (-x800 to x7ff)
xd0 - xd7    # three-octet compact int (-x40000 to x3ffff)
xd8 - xef    # one-octet compact long (-x8 to xf, xe0 is 0)
xf0 - xff    # two-octet compact long (-x800 to x7ff, xf8 is 0)

本文介绍了hessian的序列化机制,而反序列化是如何实现的呢,下文见。

GC调优总结

一,概述

twitter的一位工程师说过一句话,“Biggest threat to responsiveness in the JVM is the garbage collector”,可见垃圾收集器的重要性。下面,我将总结一下GC调优的方方面面,希望以后在这里能少走弯路。

二,监控命令

1,jdk命令

1.1 jps 进程状态信息

jps [options] [hostid]
-q 不输出类名、Jar名和传入main方法的参数
-m 输出传入main方法的参数
-l 输出main类或Jar的全限名
-v 输出传入JVM的参数

1.2 jstack 线程堆栈信息

jstack [option] pid
-l long listings,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况
-m mixed mode,不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法)

1.3 jmap 堆内存使用状况

jmap [option] pid

jmap -permstat pid
打印进程的类加载器和类加载器加载的持久代对象信息,输出:类加载器名称、对象是否存活(不可靠)、对象地址、父类加载器、已加载的类大小等信息

jmap -heap pid
查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况。

jmap -histo[:live] pid 
查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象 (注意:执行此语句会造成Full GC)

jmap -dump:format=b,file=dumpFile pid

1.4 jstat 统计监测工具

jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
vmidJava虚拟机ID,在Linux/Unix系统上一般就是进程IDinterval是采样时间间隔。count是采样数目。
root@ubuntu:/# jstat -gc 21711 250 4
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
192.0  192.0   64.0   0.0    6144.0   1854.9   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649
192.0  192.0   64.0   0.0    6144.0   1972.2   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649
192.0  192.0   64.0   0.0    6144.0   1972.2   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649
192.0  192.0   64.0   0.0    6144.0   2109.7   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649

S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used)
EC、EU:Eden区容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年轻代GC次数和GC耗时
FGC、FGCT:Full GC次数和Full GC耗时
GCT:GC总耗时

2, 系统命令

2.1 top

参数: 
H Show all threads by process
1 显示各个CPU的运行情况

关键指标:

  1. us%
    用户进程CPU使用(us)消耗:正常 65%-70%
    过高,表示应用消耗了大部分的CPU。原因通常是大量计算或GC导致。
  2. sy%:
    内核CPU使用(sy)消耗:正常 30%-35%
    过高,表示OS花费了大量时间在进行线程切换. 原因通常是线程启动过多,并都处于不断阻塞状态或线程状态不断在变化。

top命令可以和jstack结合用

top -H –p javaid 
查看某个进程的线程,找到最用cpu最高的线程后,
printf '0x%x\n' tid  
转换线程id为16进制
jstack -l javaid | grep 16进制tid    

2.2 vmstat


关键指标:

  • r(等待和正在运行队列的进程数) 数大于CPU个数, 则有可能出现CPU瓶颈
  • b(等待IO的进程数) 经常过高, 则io(网络IO/文件IO)消耗严重。
  • 通过应当结合CPU利用率和CPU Load average来判断性能问题。
  • 如果每个CPU的平均load值大于5(load/cpu count)则存在严重的性能问题(无论CPU利用率如何)。

2.3 iostat

查看各硬盘IO负载信息

确定 IO瓶颈重要指标在于 r/s、 w/s 及 rkB/s、 wkB/s,前者为 tps, 后者为吞吐量。
IO 操作对时间消耗可从 util% 看出,如将近100%表示 io 请求(tps)过多。
await 远远大于 svctm, 说明等待的系统IO处理的队列太长, 则会导致响应时间变慢。

2.4 pidstat

各进程/线程对CPU利用率

三,JDK配置参数

1,内存参数



-Xms –Xmx -XX:PermSize -XX:MaxPermSize 最好设置成一样,防止“堆震荡”
-XX:SurvivorRatio :设置年轻代中Eden区与Survivor区的大小比值

2,日志参数

四,性能诊断

OOM

对象未释放

  • 查看大对象

Full GC频繁

对象占用时间太长

  • 查看大对象

CMS

promotion failed,concurrent mode failure

  • 如为内存用完的情况,则dump内存分析;
  • 如为cms gc碎片问题,暂时只能定时执行下jmap –histo:live;

StackOverFlow

打印线程栈

CPU高负荷

  • 查看线程争用,上下文切换
  • 查看线程死锁

五,分析工具

六,参考资料

1,http://bluedavy.me/--淘宝林昊对GC调优的分享

2,JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解

记一次netty异常处理

前言

在用netty的时候,大家都遇到了哪些坑呢?又是如何解决的呢?

下面,我给大家讲讲这段时间我们在遇到的问题,也算是给自己个总结和教训吧。

背景

在3月底的时候,我们跟合作方开发了一个程序,他们是服务端,我们是客户端。

我们之间有2个接口,按照不同的方式进行通讯:

  • 接口1:服务端每隔5秒钟发送一次数据,客户端(我们)只接收数据

  • 接口2:服务端第一次发全量数据,我们收到数据后返回给他们当前数据的id码,他们收到id码后等待数据发生变化再返给我们数据,然后我们接到数据后再发给他们id码,一直循环。

第一个坑

当时接口2运行一段时间后,大概几天吧,就收到串包的数据了。然后我们就找合作方,他们查看了日志后说他们那边发送没问题。

当时老大就说,既然他们说没问题。我们就要自证清白。于是我们就加了几行代码,将接口2接收到的每个报文的数据都写到文件中去了。

过了几天,问题又复现了,我们将问题反馈给合作方。他们看了他们的日志后依然说没问题,并且发给我们了。我看了一眼他们的日志,发现了问题。原来是多个线程同时向一个通道中写入数据…

第二个坑

第一个坑改好后,我们优化了代码,接受速率比以前有了很大的提升。
但是没过多久,又出问题了,我们发现隔一端时间(也是几天吧)之后,接口1又每收到数据了。检查了我们的代码后,本着怀疑他人的态度,我们认为是合作方的问题。注:当时程序在现场,跑一次要花半天时间,没有用wireshark查看数据包是否发过来。 反馈给合作方之后,他们也没查出所以然来。

于是我们就出了个解决方案。当在5分钟之内没收到数据的时候,链接自动断开重连。就这样,这个问题就算暂时告一段落了。

第三个坑

过了段时间,我们优化了代码程序,使得数据接收频率更快了。
但是,程序只跑了一天,CPU就到了100%了。于是现场的技术人员给我们发堆栈信息,分析了后,发现存在大量的Nio Worker线程。如图:

于是我们判断重连有问题,仔细检查了代码后,并没有发现问题。于是将ClientBootstrap改成仅第一次连接初始化,以后重连的时候不初始化。
程序运行一天后,CPU没有出现100%了,但是数据又接收不到了。

当时左思又想不得其解。模拟Nio Worker线程变多的情况也模拟不出来。
后来又看了一眼堆栈信息。突然忘记了一件重要的事情,线程都阻塞在了

at org.jboss.netty.buffer.DynamicChannelBuffer.ensureWritableBytes(DynamicChannelBuffer.java:81)

这一行,这是为啥?于是打开源码,一探究竟:

79 int minNewCapacity = writerIndex() + minWritableBytes;
80 while (newCapacity < minNewCapacity) {
81     newCapacity <<= 1;
82 }

原来这里有个死循环,当minNewCapacity为int的最大值时,newCapacity是无论如何也不会比它大了。参考Netty BUG https://github.com/netty/netty/issues/258。

当ClientBootstrap每次都初始化时,由于handler引用的是同一个,所以每个现场都会被halt在死循环上,线程运行多了,自然CPU100%了。

第四个坑

这个坑是同事找到的,当时同事不理解netty的deocde方法的执行时机。于是问我,我就回答了netty每次收到消息后都会触发一次decode方法。 他就问我,deocde方法每次只处理一次消息实体是不是不对,要是decode里面传递了多个消息实体,那后面的怎么办。我一看,果然会存在这样的问题,这都是前人留下的坑…虽然我们的消息实体一般很长,但也会存在短的,这个在一般情况下不会发生。

最后

这次遇到了这么些问题,感觉自己在分析问题上还是没有完全掌控。这跟现场太远,不好跟踪bug,也跟自己对netty的掌握程度有关。所以还需要好好理解netty的机制。
还有就是,不要完全相信以前的代码。。。

计算机网络协议(3)-传输层

TCP

一,报文结构

  • TCP的包是没有IP地址的,那是IP层上的。但是有源端口和目标端口。
  • Window又叫Advertised-Window,也就是滑动窗口(Sliding Window)
  • TCP Flag ,也就是包的类型,主要是用于操控TCP的状态机的。

    SYN 同步序号
    FIN 发送方完成数据发送
    RST 复位连接
    PSH 尽可能的将数据送往接收进程
    

二,建立与关闭连接


  • 关于建连接时SYN超时
    在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻售,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才会把断开这个连接。

  • TIME_WAIT
    TIME_WAIT状态也称2MSL(报文段最大生存时间)等待状态。
    从TIME_WAIT状态到CLOSED状态,有一个超时设置,这个超时设置是 2*MSL(RFC793定义了MSL为2分钟,Linux设置成了30s)

三,可靠数据传输

1,MSS

最大报文段长度表示TCP传往另一端的最大数据库的长度。当一个连接建立的时候,连接的双发都要告知各自的MSS。

报文段越大允许每个报文段传送的数据就越多,相对IP和TCP首部就有更高的利用率。

2,序号

用来解决网络包乱序(reordering)问题

3,确认号

用来解决不丢包的问题

4,滑动窗口

TCP头里有一个字段叫Window,又叫Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

5,超时重传

根据RTT(Round Trip Time)设置RTO(Retransmission TimeOut)。

6,快速重传

如果发送方连续收到3次相同的ack,就立即重传。

7,Nagle算法

延迟小分组的发送

四,拥塞控制

1,慢启动

  1. 连接建好的开始先初始化cwnd = 1,表明可以传一个MSS大小的数据。
  2. 每当收到一个ACK,cwnd++; 呈线性上升
  3. 每当过了一个RTT,cwnd = cwnd*2; 呈指数让升
  4. 还有一个ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入“拥塞避免算法”

2,拥塞避免

一般来说ssthresh的值是65535,单位是字节,当cwnd达到这个值时后

  1. 收到一个ACK时,cwnd = cwnd + 1/cwnd
  2. 当每过一个RTT时,cwnd = cwnd + 1

3,快速恢复

3个重复的Ack会进入快速恢复算法,进入Fast Recovery之前,cwnd 和 sshthresh已被更新:

  • cwnd = cwnd /2
  • sshthresh = cwnd

算法描述:

  1. cwnd = sshthresh + 3 * MSS (3的意思是确认有3个数据包被收到了)
  2. 重传Duplicated ACKs指定的数据包
  3. 如果再收到 duplicated Acks,那么cwnd = cwnd +1
  4. 如果收到了新的Ack,那么,cwnd = sshthresh ,然后就进入了拥塞避免的算法了。

UDP

1,报文结构

2,优势

  • 实时发送,由应用程序精确控制发送时间
  • 无需连接建立,发送效率高
  • 无连接状态,连接开销下,可创建更多连接
  • 分组首部开销小,tcp首部20字节,udp只需8字节

参考资料

计算机网络协议(1)-概览


本文介绍时将采用5层因特网协议。

一,应用层

应用层是网络应用程序及他们的应用层协议存留的地方。位于应用层信息分组称为报文(message)。

二,传输层

因特网的传输层在应用程序之间传送应用报文。传输层分组成为报文段(segment)。

三,网络层

因特网的网络层负责将成为数据报的网络层分组从一台主机移动到另一台主机。网络层分组称为包(packet)。

四,链路层

因特网的网络层通过源和目的地之间的一系列路由器路由数据报。链路层分组称为帧(frame)。

五,物理层

参考资料

  • 《计算机网络-自顶向下方法》—陈鸣译

SQL问题 找出连续日期

今天在公司遇到一个有趣的sql问题,觉得挺有趣的,那就分享下。

问题

有如下数据,找出其中日期(rq字段)连续性大于等于3的日期。
图片1

即,结果为如下图所示:
图片2

思考

最开始的时候,我想这至少得写存储过程吧,先排序这个表,然后再从前向后取,当遇到连续性大于2的就保留下来,以此类推,直到读取完毕。

后来,百度了下,发现了一个更好的方法—-“关于数字的经典SQL编程:连续范围问题”

思路

在那篇文章中,题主用的是整形,我们可以类推下,整形和时间其实差不多。

第一步,查找每行的与rownum的差值

在这里我们用系统时间来确定相差的天数,保证了每个时间都有唯一的对标,然后减去rownum。

select rq, floor(rq - sysdate) - rownum  as diff ,rownum from tmptable

执行结果如下图所示,我们发现,只要时间是连续的,那么他们的差值(diff字段)一定相等。
图片3

第二步,根据分组查找大于等于3的差值

这个简单,使用havaing搞定,贴sql就行了,不多说了。

select diff
  from (select floor(rq - sysdate) - rownum as diff from tmptable t) aa
having count(*) > 2
 group by diff

图片3

第三步,根据差值查找出所有的行

差值都有了,这就好办啦,我们直接使用第一步的sql和第二步的sql进行关联查询,结果就出来啦。

select t.rq
  from 
  -- 查找每行的与rownum的差值
        (select rq, floor(rq - sysdate) - rownum as diff from tmptable) t,
  -- 根据分组查找大于等于3的差值
       (select diff
          from (select floor(rq - sysdate) - rownum as diff from tmptable t) aa
        having count(*) > 2
         group by diff) cg
 where t.diff = cg.diff
 order by t.rq ;

好了,结果就是最开始要求的数据。

总结

在连续性这个问题上,只要我们找到了与连续性相关的字段(rownum),然后在这个字段上进行处理,就可以得到我们想要的结果了。

从这里我们可以看到,如果需要做某件事的时候,先找是否有与它相关联的东西,如果有,那么我们可以先从相关联的的东西上入手,然后再用死方法一步一步来。

java核心系列(十三)—java并发编程

并发编程概览

并发编程

1,容器

2,协作

3,变量同步

4,内存模型

5,参考资料