存档在 2015年12月

Java BlockingQueue

2015年12月24日

BlockingQueue,如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒.同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作.
使用BlockingQueue的关键技术点如下:
1.BlockingQueue定义的常用方法如下:

  • 1)add(anObject):把anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则报异常
  • 2)offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.
  • 3)put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
  • 4)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null
  • 5)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止

2.BlockingQueue有四个具体的实现类,根据不同需求,选择不同的实现类

  • 1)ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的.
  • 2)LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的
  • 3)PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序.
  • 4)SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的.

3.LinkedBlockingQueue和ArrayBlockingQueue比较

  • 1)它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue.

本例实现一个篮子程序,不过这个篮子中最多能放得苹果数不是1,可以随意指定。当篮子满时,生产者进入等待状态,当篮子空时,消费者等待。

import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.BlockingQueue;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
public class BlockingQueueTest {  
  
    /** *//** 
     * 定义装苹果的篮子 
     */  
    public static class Basket{  
        // 篮子,能够容纳3个苹果   
        BlockingQueue basket = new ArrayBlockingQueue< String>(3);  
          
        // 生产苹果,放入篮子   
        public void produce() throws InterruptedException{  
            // put方法放入一个苹果,若basket满了,等到basket有位置   
            basket.put("An apple");  
        }  
        // 消费苹果,从篮子中取走   
        public String consume() throws InterruptedException{  
            // get方法取出一个苹果,若basket为空,等到basket有苹果为止   
            return basket.take();  
        }  
    }  
    // 测试方法   
    public static void testBasket() {  
        // 建立一个装苹果的篮子   
        final Basket basket = new Basket();  
        // 定义苹果生产者   
        class Producer implements Runnable {  
            public void run() {  
                try {  
                    while (true) {  
                        // 生产苹果   
                        System.out.println("生产者准备生产苹果:"   
                                + System.currentTimeMillis());  
                        basket.produce();  
                        System.out.println("生产者生产苹果完毕:"   
                                + System.currentTimeMillis());  
                        // 休眠300ms   
                        Thread.sleep(300);  
                    }  
                } catch (InterruptedException ex) {  
                }  
            }  
        }  
        // 定义苹果消费者   
        class Consumer implements Runnable {  
            public void run() {  
                try {  
                    while (true) {  
                        // 消费苹果   
                        System.out.println("消费者准备消费苹果:"   
                                + System.currentTimeMillis());  
                        basket.consume();  
                        System.out.println("消费者消费苹果完毕:"   
                                + System.currentTimeMillis());  
                        // 休眠1000ms   
                        Thread.sleep(1000);  
                    }  
                } catch (InterruptedException ex) {  
                }  
            }  
        }  
          
        ExecutorService service = Executors.newCachedThreadPool();  
        Producer producer = new Producer();  
        Consumer consumer = new Consumer();  
        service.submit(producer);  
        service.submit(consumer);  
        // 程序运行5s后,所有任务停止   
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
        }  
        service.shutdownNow();  
    }  
  
    public static void main(String[] args) {  
        BlockingQueueTest.testBasket();  
    }  
}  

结果

生产者准备生产苹果:1356400720843
生产者生产苹果完毕:1356400720843
消费者准备消费苹果:1356400720843
消费者消费苹果完毕:1356400720859
生产者准备生产苹果:1356400721156
生产者生产苹果完毕:1356400721156
生产者准备生产苹果:1356400721468
生产者生产苹果完毕:1356400721484
生产者准备生产苹果:1356400721796
生产者生产苹果完毕:1356400721812
消费者准备消费苹果:1356400721859
消费者消费苹果完毕:1356400721875
生产者准备生产苹果:1356400722125
生产者生产苹果完毕:1356400722140
生产者准备生产苹果:1356400722468
消费者准备消费苹果:1356400722890
消费者消费苹果完毕:1356400722906
生产者生产苹果完毕:1356400722921
生产者准备生产苹果:1356400723234
消费者准备消费苹果:1356400723921
生产者生产苹果完毕:1356400723937
消费者消费苹果完毕:1356400723953
生产者准备生产苹果:1356400724250
消费者准备消费苹果:1356400724968
生产者生产苹果完毕:1356400724984
消费者消费苹果完毕:1356400724984
生产者准备生产苹果:1356400725296

粘包问题分析与对策

2015年12月21日

  TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
  出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据(图1所示)。
5
图1
201110201111447650
图2
6
图3
  粘包情况有两种,一种是粘在一起的包都是完整的数据包(图1、图2所示),另一种情况是粘在一起的包有不完整的包(图3所示),此处假设用户接收缓冲区长度为m个字节。
  不是所有的粘包现象都需要处理,若传输的数据为不带结构的连续流数据(如文件传输),则不必把粘连的包分开(简称分包)。但在实际工程应用中,传输的数据一般为带结构的数据,这时就需要做分包处理。
  在处理定长结构数据的粘包问题时,分包算法比较简单;在处理不定长结构数据的粘包问题时,分包算法就比较复杂。特别是如图3所示的粘包情况,由于一包数据内容被分在了两个连续的接收包中,处理起来难度较大。实际工程应用中应尽量避免出现粘包现象。
  为了避免粘包现象,可采取以下几种措施。一是对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;二是对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;三是由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。
  以上提到的三种措施,都有其不足之处。第一种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。
  一种比较周全的对策是:接收方创建一预处理线程,对接收到的数据包进行预处理,将粘连的包分开。

Protobuf指南[转]

2015年12月20日

l  定义一个消息(message)类型

l  标量值类型

l  Optional 的字段及默认值

l  枚举

l  使用其他消息类型

l  嵌套类型

l  更新一个消息类型

l  扩展

l  包(package)

l  定义服务(service)

l  选项(option)

 

本指南描述了怎样使用protocolbuffer语言来构造你的protocol buffer数据,包括.proto文件语法以及怎样生成.proto文件的数据访问类。

本文是一个参考指南——如果要查看如何使用本文中描述的多个特性的循序渐进的例子,请在http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html中查找需要的语言的教程。

l  定义一个消息类型

先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;

  optional int32 result_per_page = 3;

}

SearchRequest消息格式有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。

Ø  指定字段类型

在上面的例子中,所有字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型。

Ø   分配标识号

正如上述文件格式,在消息定义中,每个字段都有唯一的一个标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改 变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

最小的标识号可以从1开始,最大到229 – 1, or 536,870,911。不可以使用其中的[19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。

Ø  指定字段规则

所指定的消息字段修饰符必须是如下之一:

²  required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的;

²  optional:消息格式中该字段可以有0个或1个值(不超过1个)。

²  repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于java中的List。

由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。如:

repeated int32 samples = 4 [packed=true];

required是永久性的:在将一个字段标识为required的时候,应该特别小心。如果在某些情况下不想写入或者发送一个required的 字段,将原始该字段修饰符更改为optional可能会遇到问题——旧版本的使用者会认为不含该字段的消息是不完整的,从而可能会无目的的拒绝解析。在这 种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google的一些工程师得出了一个结论:使用required弊多于利;他们更 愿意使用optional和repeated而不是required。当然,这个观点并不具有普遍性。

Ø   添加更多消息类型

在一个.proto文件中可以定义多个消息类型。在定义多个相关的消息的时候,这一点特别有用——例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;

  optional int32 result_per_page = 3;

}

message SearchResponse {

 …

}

Ø  添加注释

向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式,如:

message SearchRequest {

  required string query = 1;

  optional int32 page_number = 2;// 最终返回的页数

  optional int32 result_per_page = 3;// 每页返回的结果数

}

Ø  从.proto文件生成了什么?

当用protocolbuffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

²  对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。

²  对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。

²  对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。

你可以从如下的文档链接中获取每种语言更多API。http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html

l  标量数值类型

一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:

.proto类型 Java 类型 C++类型 备注
double double double
float float float
int32 int int32 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。
int64 long int64 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64。
uint32 int[1] uint32 Uses variable-length encoding.
uint64 long[1] uint64 Uses variable-length encoding.
sint32 int int32 使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。
sint64 long int64 使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。
fixed32 int[1] uint32 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。
fixed64 long[1] uint64 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。
sfixed32 int int32 总是4个字节。
sfixed64 long int64 总是8个字节。
bool boolean bool
string String string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。
bytes ByteString string 可能包含任意顺序的字节数据。

你可以在文章http://code.google.com/apis/protocolbuffers/docs/encoding.html 中,找到更多“序列化消息时各种类型如何编码”的信息。

» 阅读更多: Protobuf指南[转]

TCP粘包、多包和少包, 断包

2015年12月17日

TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。

UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
由于TCP无消息保护边界, 需要在消息接收端处理消息边界问题。也就是为什么我们以前使用UDP没有此问题。反而使用TCP后,出现少包的现象。

粘包的分析
上面说了原理,但可能有人使用TCP通信会出现多包/少包,而一些人不会。那么我们具体分析一下,少包,多包的情况。
正常情况,发送及时每消息发送,接收也不繁忙,及时处理掉消息。像UDP一样.
1
发送粘包,多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包. 这种情况和客户端处理繁忙,接收缓存区积压,用户一次从接收缓存区多个数据包的接收端处理一样。
2
发送粘包或接收缓存区积压,但用户缓冲区大于接收缓存区数据包总大小。此时需要考虑处理一次处理多数据包的情况,但每个数据包都是完整的。3
发送粘包或接收缓存区积压, 用户缓存区不是数据包大小的整数倍。此时需要考虑处理一次处理多数据包的情况,同时也需要考虑数据包不完整。
4

两种情况下会发生粘包:
1.发送端需要等缓冲区满才发送出去,造成粘包;
2.接收方不及时接收缓冲区的包,造成多个包接收。

不是所有的粘包都需要处理。 我们先列举一下:
1.连续的数据流不需要处理。如一个在线视频,它是一个连续不断的流, 不需要考虑分包。
2.每发一个消息,建一次连接的情况。
3.发送端使用了TCP强制数据立即传送的操作指令push。
4.udp传输
如果用socket编写编程的话, 可参考下面的资料:
Grizzly: http://grizzly.java.net/nonav/docs/docbkx2.0/html/coreframework-samples.html User Guide 第二章的样例:解析收到的消息。
xSocket:http://xsocket.sourceforge.net/core/tutorial/V2/TutorialCore.htm 第 18 节。
Netty: http://netty.io/docs/3.2.6.Final/api/org/jboss/netty/handler/codec/frame/FrameDecoder.html FrameDecoder 的 API 文档。Netty 抽象了一个“消息桢解码器”的类来处理这些。
Mina 2:http://mina.apache.org/chapter-11-codec-filter.html
Mina 2:如果En文不好的话, 可参考http://freemart.iteye.com/blog/836654。 它在判断包是否完整时,有个小缺陷,它没使用IOBuffer的prefixedDataAvailable。但注释写的比较好。
把官网上的代码,也在这展示一下。

public class ImageResponseDecoder extends CumulativeProtocolDecoder {
/** 
* 返回值的解释: 
* 1、false, 继续接收下一批数据,有两种情形,如缓冲区数据刚刚就是一个完整消息,或不够一条消息时。如果不够一条消息,那么会将下一批数据和剩余消息进行合并
* 2、true, 当缓冲区的消息多于一条消息时,剩余消息会再会推送至doDecode
*/ 
	protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)throws Exception {
		//	发送数据时,头四个字节记录了消息的长度。 此方法会读四个字节,并和实现流长度对比。返回前,将流reset.
		if (in.prefixedDataAvailable(4)) {
			int length = in.getInt();
			byte [] bytes =newbyte[length];
			in.get(bytes);
			ByteArrayInputStream bais =new ByteArrayInputStream(bytes);
			BufferedImage image = ImageIO.read(bais);
			out.write(image);
			return true;//如果读取内容后还粘了包,系统会自动处理。
		}else{
			returnfalse;//继续接收数据,以待数据完整
		} 
	}
}

再总结一下处理流程: 就发送数据时,包开始写入消息长度n, 当接收到的缓存区数据m,各处理流程如下:
1)若n小于m,则表明数据流包含多包数据,从其头部截取n个字节存入临时缓冲区,剩余部分数据依此继续循环处理,直至结束。或n大于m
2)若n=m,则表明数据流内容恰好是一完整结构数据,直接将其存入临时缓冲区即可。
3)若n大于m,则表明数据流内容尚不够构成一完整结构数据,需留待与下一包数据合并后再行处理。
参考
http://blog.csdn.net/binghuazh/article/details/4222516
http://www.cnblogs.com/alon/archive/2009/04/16/1437600.html
http://hi.baidu.com/chongerfeia/blog/item/b1e572f631dd7e28bd310965.html
http://freemart.iteye.com/blog/836654
http://blianchen.blog.163.com/blog/static/1310562992010101891522100/
http://mina.apache.org/chapter-11-codec-filter.html

eclipse gradle配置与使用

2015年12月16日
  • 1.上Grandle官网下载Gradle,地址:http://www.gradle.org/downloads
  • 2.下载完毕,直接把zip解压,放到你电脑的软件安装目录下,例如:D:\Program Files
  • 3.我的电脑设置环境变量,如果你下载了all,需要添加GRADLE_HOME指向gradle的安装目录的跟目录,例如:D:\Program Files\gradle-2.0。然后需要在Path环境变量中加上gradle的bin目录,例如:D:\Program Files\gradle-2.0\bin

  • 4.以上步骤设置完毕,打开cmd,运行gradle -v就可以看到gradle的版本信息,标志配置完成。

  • 5.打开eclipse,Help–>Install new software,输入http://dist.springsource.com/release/TOOLS/gradle

  • 如果提示找不到地址,估计是被墙掉了,需要翻墙。如果找得到地址,选现实出来需要安装的项,然后一直按下一步,安装完毕,重启eclipse就可以了。
  • » 阅读更多: eclipse gradle配置与使用

Qt中将TableView里的数据导出到Excel

2015年12月15日

Qt中将TableView里的数据导出到Excel的解决方案网络上不太多,我这里综合各路资源,给大家提供参考。
将Excel文件当成是一个数据库
使用MS的ODBC或ADO都可以将Excel文件当做一个数据库,那么我们只需要使用下面这个DSN连接串去创建并连接至该Excel文件:

QString dsn = QString("DRIVER={Microsoft Excel Driver (*.xls)};DSN=''; FIRSTROWHASNAMES=1;;CREATE_DB=/"%1/";DBQ=%2").

                  arg(excelFilePath).arg(excelFilePath);

2. 将Excel的工作表(sheet)当成是一个数据库表
可以使用SQL语句“CREATE TABLE” 去创建一个工作表。
3. 向表中插入数据
使用SQL的“INSERT”语句插入数据。
4. Unicode支持
是的,列名和数据都支持Unicode。
先看看图片,再直接提供代码,代码很简单,一看便懂。
QQ图片201512151313182
代码下载链接:

  Excel.rar (6.1 MiB, 344 hits)

Android Application Architecture 安卓APP架构[译]

2015年12月11日

本文介绍了文章作者从事了几年android应用的开发,经历2次架构变革,第一次集成了RxJava第二次集成了MVP,并将RxJava与MVP完美结合,实现了低耦合,代码简单,测试方便的架构。

» 阅读更多: Android Application Architecture 安卓APP架构[译]

MyEclipse迁移后出错解决

2015年12月6日

新装系统,将原来系统里的MyEclipse迁移过来,出现如下错误 A Java RunTime Environment (JRE) or Java Development Kit (JDK) must be available in order to run Eclipse.

解决办法:
首先查看环境变量是否正确JAVA_HOME,CLASSPATH, Path,如果没问题的话。就直接修改安装目录下的myeclipse.ini文件,将其中的-vm修改成你当前系统安装的jdk路径。

-vm
D:\Program Files\Java\jdk1.7.0_75\bin\javaw.exe