Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 15843 articles
Browse latest View live

做实际的测试

$
0
0

做实际的测试我经历过两种公司的风格,一种开发测试界限明显,多数时候测试给开发打下手,转测试之前开发围着测试转;第二种没有什么开发测试的分工,程序员从头干到尾,从需求分析干到处理线上问题。我不想在这里分析优劣,我想说的是,不论什么样的形式,项目阶段中测试的环节是很实际、很重要的。这也是被许多程序员低估的步骤。都在说设计,都在谈用户体验,但是测试呢?设计再精良的东西,如果满是bug,还是白搭。很多人都愿意写程序,不愿意做测试,多数人觉得单纯的测试比单纯的开发发展空间小多了。但是不可否认的是,测试这一项活动,从来都有着举足轻重的作用,不论是什么样的角色去完成。抛开那些冠冕堂皇的话,我总结了几个实际、好用,或者说土鳖,但是成本不高的测试方法。

写Main方法跑代码逻辑

我记得以前在一个项目组里面,我写的一些类,顺手写了main方法来完成开发阶段的初步验证,但是很快被一些有经验的工程师要求删掉。事实上直到今天,我也没有觉得那样有多么不妥。作为领导当然很愿意看到一个独立的测试代码包,大大小小的mock,批量执行起来齐刷刷的绿条。可是从实际的角度出发,这样的方式也有不少局限性。其中一条,就是不能和源代码放在足够近的位置。写起来远没有一个main方法来的便捷简明。有人在说可维护性,没错,可是大部分情况下测试代码能够同步更新的产品真是不多,再者写得好的main方法一样可以做到同步更新。很多情况下我们做出一些东西以后,代码就趋于稳定,在开发阶段写main方法快速阅读和获得结果反馈,是很有价值的。

Debug大法

以前我在讲Java多线程程序的测试的时候提到过它,看起来很土,但是在开发阶段跑通逻辑是非常有效的。本身测试用例的构造往往很耗费精力,但是在debug模式下可以任意执行、修改代码逻辑,改变变量取值。很容易遍历到希望的逻辑分支。Debug不是正儿八经的测试方法,同时被很多专业测试人员所不齿,他们还是更喜欢花大量的精力去构造各种奇葩的测试用例,条件组合。其实debug的好处远不止测试和问题定位,还可以帮助熟悉代码逻辑等等。

浏览器行为模拟工具

界面测试本来就不像后台代码那么容易完成。但是一些脚本工具,比如iMacros就很好用,可以很方便地录制和修改脚本,然后批量执行。很多情况下我们其实不需要天天喊多少自动化多少持续集成,对于完备稳定的脚本集合,就是手动跑一遍也很方便。这些80%的重复劳动其实很容易做,很容易就可以砍掉堆人的成本。以前用过一些公司级别的庞大的浏览器模拟测试工具,看起来很强大,但是却笨重而且容易出问题。

Apache Bench

我把 AB这个工具单列一行是因为它用起来实在太方便了,特别是对于请求速率准确性要求不高的情形。我还把它用来当做性能测试的时候提供请求压力的工具。和Load Runner比起来,就像是游击队和正规军一样的区别。

用编译脚本偷梁换柱

对于一些独立的、清晰的远程调用的类,可以在编译打包的时候,用一个触发本地调用的mock类替换掉,相同的类、方法定义和路径,但是做的事情却大不相同。免去了那些复杂的mock技术,还把远程的调用放到本地来跟踪。

后门参数

这个东西用起来要小心,但是对于一些不容易重现、构建的环境中,如果预留一些后门参数,帮助清除缓存、收集信息、打印日志,或者是在发布以后短时间内在生产环境上主要功能的冒烟测试,是很有价值的。顺便提一提强大的 BTrace工具,对于无法在本地或者beta环境中重现的线上问题,它是一把能够窥探代码执行的利器。

最后,我想说的是,对于不喜欢测试的工程师,这样的想法是可以理解的,但是必须通过约束自己的行为,保证各个阶段软件的质量。这是不可变的东西,我们可以做得更好、玩得更转的,也就是那些五花八门的测试技术,但是测试更需要责任心和周全的考虑,技术能帮忙简化测试上的问题,而没有态度则什么也做不成。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

分享到:
你可能也喜欢:

(转)Kafka部署与代码实例

$
0
0

转自: http://shift-alt-ctrl.iteye.com/blog/1930791

  kafka作为分布式日志收集或系统监控服务,我们有必要在合适的场合使用它。kafka的部署包括zookeeper环境/kafka环境,同时还需要进行一些配置操作.接下来介绍如何使用kafka.

    我们使用3个zookeeper实例构建zk集群,使用2个kafka broker构建kafka集群.

    其中kafka为0.8V,zookeeper为3.4.5V

 

一.Zookeeper集群构建

    我们有3个zk实例,分别为zk-0,zk-1,zk-2;如果你仅仅是测试使用,可以使用1个zk实例.

     1) zk-0

    调整配置文件:

Php代码   收藏代码
  1. clientPort=2181  
  2. server.0=127.0.0.1:2888:3888  
  3. server.1=127.0.0.1:2889:3889  
  4. server.2=127.0.0.1:2890:3890  
  5. ##只需要修改上述配置,其他配置保留默认值  

    启动zookeeper

Java代码   收藏代码
  1. ./zkServer.sh start  

     2) zk-1

    调整配置文件(其他配置和zk-0一只):

Php代码   收藏代码
  1. clientPort=2182  
  2. ##只需要修改上述配置,其他配置保留默认值  

    启动zookeeper

 

Java代码   收藏代码
  1. ./zkServer.sh start  

     3) zk-2

    调整配置文件(其他配置和zk-0一只):

Php代码   收藏代码
  1. clientPort=2183  
  2. ##只需要修改上述配置,其他配置保留默认值  

    启动zookeeper

 

Java代码   收藏代码
  1. ./zkServer.sh start  

  

二. Kafka集群构建

    因为Broker配置文件涉及到zookeeper的相关约定,因此我们先展示broker配置文件.我们使用2个kafka broker来构建这个集群环境,分别为kafka-0,kafka-1.

     1) kafka-0

    在config目录下修改配置文件为:

Java代码   收藏代码
  1. broker.id=0  
  2. port=9092  
  3. num.network.threads=2  
  4. num.io.threads=2  
  5. socket.send.buffer.bytes=1048576  
  6. socket.receive.buffer.bytes=1048576  
  7. socket.request.max.bytes=104857600  
  8. log.dir=./logs  
  9. num.partitions=2  
  10. log.flush.interval.messages=10000  
  11. log.flush.interval.ms=1000  
  12. log.retention.hours=168  
  13. #log.retention.bytes=1073741824  
  14. log.segment.bytes=536870912  
  15. ##replication机制,让每个topic的partitions在kafka-cluster中备份2个  
  16. ##用来提高cluster的容错能力..  
  17. default.replication.factor=1  
  18. log.cleanup.interval.mins=10  
  19. zookeeper.connect=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183  
  20. zookeeper.connection.timeout.ms=1000000  

    因为kafka用scala语言编写,因此运行kafka需要首先准备scala相关环境。

Java代码   收藏代码
  1. > cd kafka-0  
  2. > ./sbt update  
  3. > ./sbt package  
  4. > ./sbt assembly-package-dependency   

    其中最后一条指令执行有可能出现异常,暂且不管。 启动kafka broker:

Java代码   收藏代码
  1. > JMS_PORT=9997 bin/kafka-server-start.sh config/server.properties &  

    因为zookeeper环境已经正常运行了,我们无需通过kafka来挂载启动zookeeper.如果你的一台机器上部署了多个kafka broker,你需要声明JMS_PORT.

     2) kafka-1

Java代码   收藏代码
  1. broker.id=1  
  2. port=9093  
  3. ##其他配置和kafka-0保持一致  

    然后和kafka-0一样执行打包命令,然后启动此broker.

Java代码   收藏代码
  1. > JMS_PORT=9998 bin/kafka-server-start.sh config/server.properties &  

    仍然可以通过如下指令查看topic的"partition"/"replicas"的分布和存活情况.

Java代码   收藏代码
  1. > bin/kafka-list-topic.sh --zookeeper localhost:2181  
  2. topic: my-replicated-topic  partition: 0    leader: 2   replicas: 1,2,0 isr: 2  
  3. topic: test partition: 0    leader: 0   replicas: 0 isr: 0   

    到目前为止环境已经OK了,那我们就开始展示编程实例吧。[ 配置参数详解]

 

三.项目准备

    项目基于maven构建,不得不说kafka java客户端实在是太糟糕了;构建环境会遇到很多麻烦。建议参考如下pom.xml;其中各个依赖包必须版本协调一致。如果kafka client的版本和kafka server的版本不一致,将会有很多异常,比如"broker id not exists"等;因为kafka从0.7升级到0.8之后(正名为2.8.0),client与server通讯的protocol已经改变.

Java代码   收藏代码
  1. <dependencies>  
  2.     <dependency>  
  3.         <groupId>log4j</groupId>  
  4.         <artifactId>log4j</artifactId>  
  5.         <version>1.2.14</version>  
  6.     </dependency>  
  7.     <dependency>  
  8.         <groupId>org.apache.kafka</groupId>  
  9.         <artifactId>kafka_2.8.2</artifactId>  
  10.         <version>0.8.0</version>  
  11.         <exclusions>  
  12.             <exclusion>  
  13.                 <groupId>log4j</groupId>  
  14.                 <artifactId>log4j</artifactId>  
  15.             </exclusion>  
  16.         </exclusions>  
  17.     </dependency>  
  18.     <dependency>  
  19.         <groupId>org.scala-lang</groupId>  
  20.         <artifactId>scala-library</artifactId>  
  21.         <version>2.8.2</version>  
  22.     </dependency>  
  23.     <dependency>  
  24.         <groupId>com.yammer.metrics</groupId>  
  25.         <artifactId>metrics-core</artifactId>  
  26.         <version>2.2.0</version>  
  27.     </dependency>  
  28.     <dependency>  
  29.         <groupId>com.101tec</groupId>  
  30.         <artifactId>zkclient</artifactId>  
  31.         <version>0.3</version>  
  32.     </dependency>  
  33. </dependencies>  

 

四.Producer端代码

     1) producer.properties文件:此文件放在/resources目录下

Java代码   收藏代码
  1. #partitioner.class=  
  2. ##broker列表可以为kafka server的子集,因为producer需要从broker中获取metadata  
  3. ##尽管每个broker都可以提供metadata,此处还是建议,将所有broker都列举出来  
  4. ##此值,我们可以在spring中注入过来  
  5. ##metadata.broker.list=127.0.0.1:9092,127.0.0.1:9093  
  6. ##,127.0.0.1:9093  
  7. ##同步,建议为async  
  8. producer.type=sync  
  9. compression.codec=0  
  10. serializer.class=kafka.serializer.StringEncoder  
  11. ##在producer.type=async时有效  
  12. #batch.num.messages=100  

     2) KafkaProducerClient.java代码样例

Java代码   收藏代码
  1. import java.util.ArrayList;  
  2. import java.util.Collection;  
  3. import java.util.List;  
  4. import java.util.Properties;  
  5.   
  6. import kafka.javaapi.producer.Producer;  
  7. import kafka.producer.KeyedMessage;  
  8. import kafka.producer.ProducerConfig;  
  9.   
  10. /** 
  11.  * User: guanqing-liu 
  12.  */  
  13. public class KafkaProducerClient {  
  14.   
  15.     private Producer<String, String> inner;  
  16.       
  17.     private String brokerList;//for metadata discovery,spring setter  
  18.     private String location = "kafka-producer.properties";//spring setter  
  19.       
  20.     private String defaultTopic;//spring setter  
  21.   
  22.     public void setBrokerList(String brokerList) {  
  23.         this.brokerList = brokerList;  
  24.     }  
  25.   
  26.     public void setLocation(String location) {  
  27.         this.location = location;  
  28.     }  
  29.   
  30.     public void setDefaultTopic(String defaultTopic) {  
  31.         this.defaultTopic = defaultTopic;  
  32.     }  
  33.   
  34.     public KafkaProducerClient(){}  
  35.       
  36.     public void init() throws Exception {  
  37.         Properties properties = new Properties();  
  38.         properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(location));  
  39.           
  40.           
  41.         if(brokerList != null) {  
  42.             properties.put("metadata.broker.list", brokerList);  
  43.         }  
  44.   
  45.         ProducerConfig config = new ProducerConfig(properties);  
  46.         inner = new Producer<String, String>(config);  
  47.     }  
  48.   
  49.     public void send(String message){  
  50.         send(defaultTopic,message);  
  51.     }  
  52.       
  53.     public void send(Collection<String> messages){  
  54.         send(defaultTopic,messages);  
  55.     }  
  56.       
  57.     public void send(String topicName, String message) {  
  58.         if (topicName == null || message == null) {  
  59.             return;  
  60.         }  
  61.         KeyedMessage<String, String> km = new KeyedMessage<String, String>(topicName,message);  
  62.         inner.send(km);  
  63.     }  
  64.   
  65.     public void send(String topicName, Collection<String> messages) {  
  66.         if (topicName == null || messages == null) {  
  67.             return;  
  68.         }  
  69.         if (messages.isEmpty()) {  
  70.             return;  
  71.         }  
  72.         List<KeyedMessage<String, String>> kms = new ArrayList<KeyedMessage<String, String>>();  
  73.         int i= 0;  
  74.         for (String entry : messages) {  
  75.             KeyedMessage<String, String> km = new KeyedMessage<String, String>(topicName,entry);  
  76.             kms.add(km);  
  77.             i++;  
  78.             if(i % 20 == 0){  
  79.                 inner.send(kms);  
  80.                 kms.clear();  
  81.             }  
  82.         }  
  83.           
  84.         if(!kms.isEmpty()){  
  85.             inner.send(kms);  
  86.         }  
  87.     }  
  88.   
  89.     public void close() {  
  90.         inner.close();  
  91.     }  
  92.   
  93.     /** 
  94.      * @param args 
  95.      */  
  96.     public static void main(String[] args) {  
  97.         KafkaProducerClient producer = null;  
  98.         try {  
  99.             producer = new KafkaProducerClient();  
  100.             //producer.setBrokerList("");  
  101.             int i = 0;  
  102.             while (true) {  
  103.                 producer.send("test-topic", "this is a sample" + i);  
  104.                 i++;  
  105.                 Thread.sleep(2000);  
  106.             }  
  107.         } catch (Exception e) {  
  108.             e.printStackTrace();  
  109.         } finally {  
  110.             if (producer != null) {  
  111.                 producer.close();  
  112.             }  
  113.         }  
  114.   
  115.     }  
  116.   
  117. }  

     3) spring配置

Java代码   收藏代码
  1. <bean id="kafkaProducerClient" class="com.test.kafka.KafkaProducerClient" init-method="init" destroy-method="close">  
  2.     <property name="zkConnect" value="${zookeeper_cluster}"></property>  
  3.     <property name="defaultTopic" value="${kafka_topic}"></property>  
  4. </bean>  

 

五.Consumer端

     1) consumer.properties:文件位于/resources目录下

Java代码   收藏代码
  1. ## 此值可以配置,也可以通过spring注入  
  2. ##zookeeper.connect=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183  
  3. ##,127.0.0.1:2182,127.0.0.1:2183  
  4. # timeout in ms for connecting to zookeeper  
  5. zookeeper.connectiontimeout.ms=1000000  
  6. #consumer group id  
  7. group.id=test-group  
  8. #consumer timeout  
  9. #consumer.timeout.ms=5000  
  10. auto.commit.enable=true  
  11. auto.commit.interval.ms=60000  

     2) KafkaConsumerClient.java代码样例

Java代码   收藏代码
  1. package com.test.kafka;  
  2. import java.nio.ByteBuffer;  
  3. import java.nio.CharBuffer;  
  4. import java.nio.charset.Charset;  
  5. import java.util.HashMap;  
  6. import java.util.List;  
  7. import java.util.Map;  
  8. import java.util.Properties;  
  9. import java.util.concurrent.ExecutorService;  
  10. import java.util.concurrent.Executors;  
  11.   
  12. import kafka.consumer.Consumer;  
  13. import kafka.consumer.ConsumerConfig;  
  14. import kafka.consumer.ConsumerIterator;  
  15. import kafka.consumer.KafkaStream;  
  16. import kafka.javaapi.consumer.ConsumerConnector;  
  17. import kafka.message.Message;  
  18. import kafka.message.MessageAndMetadata;  
  19.   
  20. /** 
  21.  * User: guanqing-liu  
  22.  */  
  23. public class KafkaConsumerClient {  
  24.   
  25.     private String groupid; //can be setting by spring  
  26.     private String zkConnect;//can be setting by spring  
  27.     private String location = "kafka-consumer.properties";//配置文件位置  
  28.     private String topic;  
  29.     private int partitionsNum = 1;  
  30.     private MessageExecutor executor; //message listener  
  31.     private ExecutorService threadPool;  
  32.       
  33.     private ConsumerConnector connector;  
  34.       
  35.     private Charset charset = Charset.forName("utf8");  
  36.   
  37.     public void setGroupid(String groupid) {  
  38.         this.groupid = groupid;  
  39.     }  
  40.   
  41.     public void setZkConnect(String zkConnect) {  
  42.         this.zkConnect = zkConnect;  
  43.     }  
  44.   
  45.     public void setLocation(String location) {  
  46.         this.location = location;  
  47.     }  
  48.   
  49.     public void setTopic(String topic) {  
  50.         this.topic = topic;  
  51.     }  
  52.   
  53.     public void setPartitionsNum(int partitionsNum) {  
  54.         this.partitionsNum = partitionsNum;  
  55.     }  
  56.   
  57.     public void setExecutor(MessageExecutor executor) {  
  58.         this.executor = executor;  
  59.     }  
  60.   
  61.     public KafkaConsumerClient() {}  
  62.   
  63.     //init consumer,and start connection and listener  
  64.     public void init() throws Exception {  
  65.         if(executor == null){  
  66.             throw new RuntimeException("KafkaConsumer,exectuor cant be null!");  
  67.         }  
  68.         Properties properties = new Properties();  
  69.         properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(location));  
  70.           
  71.         if(groupid != null){  
  72.             properties.put("groupid", groupid);  
  73.         }  
  74.         if(zkConnect != null){  
  75.             properties.put("zookeeper.connect", zkConnect);  
  76.         }  
  77.         ConsumerConfig config = new ConsumerConfig(properties);  
  78.   
  79.         connector = Consumer.createJavaConsumerConnector(config);  
  80.         Map<String, Integer> topics = new HashMap<String, Integer>();  
  81.         topics.put(topic, partitionsNum);  
  82.         Map<String, List<KafkaStream<byte[], byte[]>>> streams = connector.createMessageStreams(topics);  
  83.         List<KafkaStream<byte[], byte[]>> partitions = streams.get(topic);  
  84.         threadPool = Executors.newFixedThreadPool(partitionsNum * 2);  
  85.           
  86.         //start  
  87.         for (KafkaStream<byte[], byte[]> partition : partitions) {  
  88.             threadPool.execute(new MessageRunner(partition));  
  89.         }  
  90.     }  
  91.   
  92.     public void close() {  
  93.         try {  
  94.             threadPool.shutdownNow();  
  95.         } catch (Exception e) {  
  96.             //  
  97.         } finally {  
  98.             connector.shutdown();  
  99.         }  
  100.   
  101.     }  
  102.   
  103.     class MessageRunner implements Runnable {  
  104.         private KafkaStream<byte[], byte[]> partition;  
  105.   
  106.         MessageRunner(KafkaStream<byte[], byte[]> partition) {  
  107.             this.partition = partition;  
  108.         }  
  109.   
  110.         public void run() {  
  111.             ConsumerIterator<byte[], byte[]> it = partition.iterator();  
  112.             while (it.hasNext()) {  
  113.                 // connector.commitOffsets();手动提交offset,当autocommit.enable=false时使用  
  114.                 MessageAndMetadata<byte[], byte[]> item = it.next();  
  115.                 try{  
  116.                     executor.execute(new String(item.message(),charset));// UTF-8,注意异常  
  117.                 }catch(Exception e){  
  118.                     //  
  119.                 }  
  120.             }  
  121.         }  
  122.           
  123.         public String getContent(Message message){  
  124.             ByteBuffer buffer = message.payload();  
  125.             if (buffer.remaining() == 0) {  
  126.                 return null;  
  127.             }  
  128.             CharBuffer charBuffer = charset.decode(buffer);  
  129.             return charBuffer.toString();  
  130.         }  
  131.     }  
  132.   
  133.     public static interface MessageExecutor {  
  134.   
  135.         public void execute(String message);  
  136.     }  
  137.   
  138.     /** 
  139.      * @param args 
  140.      */  
  141.     public static void main(String[] args) {  
  142.         KafkaConsumerClient consumer = null;  
  143.         try {  
  144.             MessageExecutor executor = new MessageExecutor() {  
  145.   
  146.                 public void execute(String message) {  
  147.                     System.out.println(message);  
  148.                 }  
  149.             };  
  150.             consumer = new KafkaConsumerClient();  
  151.               
  152.             consumer.setTopic("test-topic");  
  153.             consumer.setPartitionsNum(2);  
  154.             consumer.setExecutor(executor);  
  155.             consumer.init();  
  156.         } catch (Exception e) {  
  157.             e.printStackTrace();  
  158.         } finally {  
  159.              if(consumer != null){  
  160.                  consumer.close();  
  161.              }  
  162.         }  
  163.   
  164.     }  
  165.   
  166. }  

    3) spring配置(略)

 

    需要提醒的是,上述LogConsumer类中,没有太多的关注异常情况,必须在MessageExecutor.execute()方法中抛出异常时的情况.

    在测试时,建议优先启动consumer,然后再启动producer,这样可以实时的观测到最新的消息。



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



分布式消息系统:Kafka

$
0
0

Kafka是分布式发布-订阅消息系统。它最初由LinkedIn公司开发,之后成为Apache项目的一部分。Kafka是一个分布式的,可划分的,冗余备份的持久性的日志服务。它主要用于处理活跃的流式数据。

在大数据系统中,常常会碰到一个问题,整个大数据是由各个子系统组成,数据需要在各个子系统中高性能,低延迟的不停流转。传统的企业消息系统并不是非常适合大规模的数据处理。为了已在同时搞定在线应用(消息)和离线应用(数据文件,日志)Kafka就出现了。Kafka可以起到两个作用:

  1. 降低系统组网复杂度。
  2. 降低编程复杂度,各个子系统不在是相互协商接口,各个子系统类似插口插在插座上,Kafka承担高速数据总线的作用。

Kafka主要特点:

  1. 同时为发布和订阅提供高吞吐量。据了解,Kafka每秒可以生产约25万消息(50 MB),每秒处理55万消息(110 MB)。
  2. 可进行持久化操作。将消息持久化到磁盘,因此可用于批量消费,例如ETL,以及实时应用程序。通过将数据持久化到硬盘以及replication防止数据丢失。
  3. 分布式系统,易于向外扩展。所有的producer、broker和consumer都会有多个,均为分布式的。无需停机即可扩展机器。
  4. 消息被处理的状态是在consumer端维护,而不是由server端维护。当失败时能自动平衡。
  5. 支持online和offline的场景。

Kayka的架构:

kafka

Kayka的整体架构非常简单,是显式分布式架构,producer、broker(kafka)和consumer都可以有多个。Producer,consumer实现Kafka注册的接口,数据从producer发送到broker,broker承担一个中间缓存和分发的作用。broker分发注册到系统中的consumer。broker的作用类似于缓存,即活跃的数据和离线处理系统之间的缓存。客户端和服务器端的通信,是基于简单,高性能,且与编程语言无关的TCP协议。几个基本概念:

  1. Topic:特指Kafka处理的消息源(feeds of messages)的不同分类。
  2. Partition:Topic物理上的分组,一个topic可以分为多个partition,每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset)。
  3. Message:消息,是通信的基本单位,每个producer可以向一个topic(主题)发布一些消息。
  4. Producers:消息和数据生产者,向Kafka的一个topic发布消息的过程叫做producers。
  5. Consumers:消息和数据消费者,订阅topics并处理其发布的消息的过程叫做consumers。
  6. Broker:缓存代理,Kafa集群中的一台或多台服务器统称为broker。

消息发送的流程:

message

  1. Producer根据指定的partition方法(round-robin、hash等),将消息发布到指定topic的partition里面
  2. kafka集群接收到Producer发过来的消息后,将其持久化到硬盘,并保留消息指定时长(可配置),而不关注消息是否被消费。
  3. Consumer从kafka集群pull数据,并控制获取消息的offset

Kayka的设计:

1、吞吐量

高吞吐是kafka需要实现的核心目标之一,为此kafka做了以下一些设计:

  1. 数据磁盘持久化:消息不在内存中cache,直接写入到磁盘,充分利用磁盘的顺序读写性能
  2. zero-copy:减少IO操作步骤
  3. 数据批量发送
  4. 数据压缩
  5. Topic划分为多个partition,提高parallelism

负载均衡

  1. producer根据用户指定的算法,将消息发送到指定的partition
  2. 存在多个partiiton,每个partition有自己的replica,每个replica分布在不同的Broker节点上
  3. 多个partition需要选取出lead partition,lead partition负责读写,并由zookeeper负责fail over
  4. 通过zookeeper管理broker与consumer的动态加入与离开

拉取系统

由于kafka broker会持久化数据,broker没有内存压力,因此,consumer非常适合采取pull的方式消费数据,具有以下几点好处:

  1. 简化kafka设计
  2. consumer根据消费能力自主控制消息拉取速度
  3. consumer根据自身情况自主选择消费模式,例如批量,重复消费,从尾端开始消费等

可扩展性

当需要增加broker结点时,新增的broker会向zookeeper注册,而producer及consumer会根据注册在zookeeper上的watcher感知这些变化,并及时作出调整。

Kayka的应用场景:

1.消息队列

比起大多数的消息系统来说,Kafka有更好的吞吐量,内置的分区,冗余及容错性,这让Kafka成为了一个很好的大规模消息处理应用的解决方案。消息系统一般吞吐量相对较低,但是需要更小的端到端延时,并尝尝依赖于Kafka提供的强大的持久性保障。在这个领域,Kafka足以媲美传统消息系统,如 ActiveMRRabbitMQ

2.行为跟踪

Kafka的另一个应用场景是跟踪用户浏览页面、搜索及其他行为,以发布-订阅的模式实时记录到对应的topic里。那么这些结果被订阅者拿到后,就可以做进一步的实时处理,或实时监控,或放到hadoop/离线数据仓库里处理。

3.元信息监控

作为操作记录的监控模块来使用,即汇集记录一些操作信息,可以理解为运维性质的数据监控吧。

4.日志收集

日志收集方面,其实开源产品有很多,包括Scribe、Apache Flume。很多人使用Kafka代替日志聚合(log aggregation)。日志聚合一般来说是从服务器上收集日志文件,然后放到一个集中的位置(文件服务器或HDFS)进行处理。然而Kafka忽略掉文件的细节,将其更清晰地抽象成一个个日志或事件的消息流。这就让Kafka处理过程延迟更低,更容易支持多数据源和分布式数据处理。比起以日志为中心的系统比如Scribe或者Flume来说,Kafka提供同样高效的性能和因为复制导致的更高的耐用性保证,以及更低的端到端延迟。

5.流处理

这个场景可能比较多,也很好理解。保存收集流数据,以提供之后对接的Storm或其他流式计算框架进行处理。很多用户会将那些从原始topic来的数据进行阶段性处理,汇总,扩充或者以其他的方式转换到新的topic下再继续后面的处理。例如一个文章推荐的处理流程,可能是先从RSS数据源中抓取文章的内容,然后将其丢入一个叫做“文章”的topic中;后续操作可能是需要对这个内容进行清理,比如回复正常数据或者删除重复数据,最后再将内容匹配的结果返还给用户。这就在一个独立的topic之外,产生了一系列的实时数据处理的流程。 StromSamza是非常著名的实现这种类型数据转换的框架。

6.事件源

事件源是一种应用程序设计的方式,该方式的状态转移被记录为按时间顺序排序的记录序列。Kafka可以存储大量的日志数据,这使得它成为一个对这种方式的应用来说绝佳的后台。比如动态汇总(News feed)。

7.持久性日志(commit log)

Kafka可以为一种外部的持久性日志的分布式系统提供服务。这种日志可以在节点间备份数据,并为故障节点数据回复提供一种重新同步的机制。Kafka中日志压缩功能为这种用法提供了条件。在这种用法中,Kafka类似于Apache BookKeeper项目。

Kayka的设计要点:

1、直接使用linux 文件系统的cache,来高效缓存数据。

2、采用linux Zero-Copy提高发送性能。传统的数据发送需要发送4次上下文切换,采用sendfile系统调用之后,数据直接在内核态交换,系统上下文切换减少为2次。根据测试结果,可以提高60%的数据发送性能。Zero-Copy详细的技术细节可以参考:https://www.ibm.com/developerworks/linux/library/j-zerocopy/

3、数据在磁盘上存取代价为O(1)。kafka以topic来进行消息管理,每个topic包含多个part(ition),每个part对应一个逻辑log,有多个segment组成。每个segment中存储多条消息(见下图),消息id由其逻辑位置决定,即从消息id可直接定位到消息的存储位置,避免id到位置的额外映射。每个part在内存中对应一个index,记录每个segment中的第一条消息偏移。发布者发到某个topic的消息会被均匀的分布到多个part上(随机或根据用户指定的回调函数进行分布),broker收到发布消息往对应part的最后一个segment上添加该消息,当某个segment上的消息条数达到配置值或消息发布时间超过阈值时,segment上的消息会被flush到磁盘,只有flush到磁盘上的消息订阅者才能订阅到,segment达到一定的大小后将不会再往该segment写数据,broker会创建新的segment。

4、显式分布式,即所有的producer、broker和consumer都会有多个,均为分布式的。Producer和broker之间没有负载均衡机制。broker和consumer之间利用zookeeper进行负载均衡。所有broker和consumer都会在zookeeper中进行注册,且zookeeper会保存他们的一些元数据信息。如果某个broker和consumer发生了变化,所有其他的broker和consumer都会得到通知。

参考资料:

[原创]zepto打造一款移动端划屏插件 - wingkun

$
0
0

最近忙着将项目内的jquery 2换成zepto

因为不想引用过多的zepto包,所以花了点时间

zepto真的精简了许多,源代码看着真舒服

 

正好项目内需要一个划屏插件,就用zepto写了一个

逻辑其实很简单,但没想到测试时,在老版本android设备浏览器上的touchmove有许多bug

做兼容倒是搞了一阵

效果图

样式1

样式2

 

调用

正常情况下应该是后台生成的html代码,但还是写了一套操作tab页的方法

调用简便如下:

<link rel="stylesheet" href="kslider.css" type="text/css"/>
<script type="text/javascript" src="http://zeptojs.com/zepto.js"></script>
<script type="text/javascript" src="zepto.kslider.js"></script>
<script type="text/javascript">
var k;

$(function () {

/*
参数:config

change:tab页变更事件
参数e: 当前页码
tick:自动滚动间隔时间毫秒 (不设置则不自动滚动)
maxWidth:容器最大宽度 (默认有100%)
minWidth:容器最小宽度 (默认有100%)
className:样式类名
"ks_wt_1" 标题栏-方形 (默认)
"ks_wt_2" 标题栏-小圆形
或者你自定义的类名

*/

k = $("#divs1").slider({ change: function (e) { console.log(e); }, maxWidth: 360, minWidth: 300 });
//js添加一页并且跳转到第4页
k.add("标题", "内容").tab(3);
//删除页
//k.remove(0);

//小圆形按钮标题 每隔3秒自动滚动 myimg:自己写的css类,控制里面图片大小
$("#divs2").slider({ maxWidth: 300, className: "ks_wt_2 myimg", tick: 3000 });
});
</script>

html

<div id="divs1" class="kslider">
<ul class="ks_wt">
<li class="ks_t2">标题1</li>
<li>标题2</li>
<li>标题3</li>
</ul>
<div class="ks_dbox ks_ts">
<div class="ks_warp">
<ul>
<li>text1</li>
<li>text1</li>
<li>text1</li>
<li>text1</li>
<li>text1</li>
<li>text1</li>
<li>text1</li>
<li>text1</li>
</ul>
</div>
<div class="ks_warp">
<img src="img/img1.jpg" />
</div>
<div class="ks_warp">
<ul>
<li>text3</li>
<li>text3</li>
<li>text3</li>
<li>text3</li>
<li>text3</li>
<li>text3</li>
</ul>
</div>
</div>
</div>

具体代码

css

/*
kslider.css
lxk 2014.08.14
www.cnblogs.com/wingkun
*/


body{margin:0px;text-align:center;font:12px 微软雅黑;}

.kslider{width:100%;overflow:hidden;margin:0 auto;background:#f0f0f0;}

.kslider .ks_warp{width:100%;}
.kslider .ks_ts{-webkit-transition:500ms;}
.kslider .ks_dbox{width:100%;display:-webkit-box;text-align:left;}

.kslider .ks_wt{display:-webkit-box;margin:0px;padding:0px;-webkit-box-pack:center;}
.kslider .ks_wt li{text-align:center;list-style:none;background: -webkit-linear-gradient(top, #AAAAAA 0%,#979797 100%);color: #fff;}

.ks_wt_1 .ks_wt li{-webkit-box-flex:1;height:35px;line-height:35px;border-right:solid 1px #BBB;}
.ks_wt_2 .ks_wt li{background:-webkit-linear-gradient(top, #e7e7e7 0%,#dfdfdf 100%);text-indent: 20px;height:10px;width:10px;overflow:hidden; border-radius:100%;margin:5px;}

.ks_wt_1 .ks_wt .ks_t2{background:-webkit-linear-gradient(top, #e7e7e7 0%,#dfdfdf 100%); color:#000;}
.ks_wt_2 .ks_wt .ks_t2{background: -webkit-linear-gradient(top, #AAAAAA 0%,#979797 100%); -webkit-animation:kt2 500ms linear;}


@-webkit-keyframes kt2
{
0%{-webkit-transform:scale(1);}
100%{-webkit-transform:scale(1.5);}
}

js

/*
zepto.kslider.js
lxk 2014.08.14
www.cnblogs.com/wingkun
*/

(function ($) {
/*
参数:config

change:tab页变更事件
参数e: 当前页码
tick:自动滚动间隔时间毫秒 (不设置则不自动滚动)
maxWidth:容器最大宽度 (默认有100%)
minWidth:容器最小宽度 (默认有100%)
className:样式类名
"ks_wt_1" 标题栏-方形 (默认)
"ks_wt_2" 标题栏-小圆形
或者你自定义的类名

*/
$.fn.slider = function (config) {

config = $.extend({}, { className: "ks_wt_1" }, config);

var b = $(this), tw, timer,
target = b.find(".ks_dbox"),
title = b.find(".ks_wt"),
m = { initX: 0, initY: 0, startX: 0, endX: 0, startY: 0, canmove: false },
currentTab = 0;

b.toggleClass(config.className,true);
if (config.maxWidth) b.css({ maxWidth: config.maxWidth });
if (config.minWidth) b.css({ mixWidth: config.minWidth });

title.on("click", function (e) {
if (e.target == this) return;
toTab($(e.target).index());
});

b.on("touchstart", function (e) {
var et = e.touches[0];
if ($(et.target).closest(".ks_dbox").length != 0) {
m.canmove = true, m.initX = m.startX = et.pageX;
m.initY = et.pageY;
clearTimer();
}

}).on("touchmove", function (e) {

var et = e.touches[0];
if (m.canmove && Math.abs(et.pageY - m.initY) / Math.abs(et.pageX - m.initX) < 0.6) {
// if (m.canmove && Math.abs(et.pageX - m.startX) > 10) {
target.removeClass("ks_ts").css("-webkit-transform", "translate3d(" + (m.endX += et.pageX - m.startX) + "px,0,0)");
m.startX = et.pageX;
e.preventDefault();
}
}).on("touchend", function (e) {
if (!m.canmove) return;
target.toggleClass("ks_ts", true);

tw = target.width();
//是否超过了边界
var bl = false, current = Math.abs(m.endX / tw);

if (m.endX > 0) {
current = m.endX = 0;
bl = true;
}
else if (m.endX < -tw * (target.children().length - 1)) {
current = target.children().length - 1;
bl = true;
}

if (!bl) {
if (m.endX % tw != 0) {
//target.css("transform", "translate(" + (m.endX = -tw*Math.abs(Math.round(m.endX/tw))) + "px,0px)");
var str = parseInt((current + "").split(".")[1][0]);

if (e.changedTouches[0].pageX > m.initX) {
//往右
current = str <= 9 ? Math.floor(Math.abs(current)) : Math.abs(Math.round(m.endX / tw));
} else {
//往左
current = str >= 1 ? Math.floor(Math.abs(current)) + 1 : Math.abs(Math.round(m.endX / tw));
}
}
}
toTab(current);
setTimer();
m.canmove = false;
});

var move = function (i) {
target.css("-webkit-transform", "translate3d(" + (m.endX = i) + "px,0,0)");
}

var setIndex = function (i) {
return i < 0 ? 0 : i >= target.children().length ? target.children().length - 1 : i;
}

var toTab = function (i) {
i = setIndex(i), tw = target.width();
move(-tw * i), toTitle(i);
if (currentTab != i && config.change) {
config.change(i);
}
currentTab = i
}

var toTitle = function (i) {
if (title.length == 0) return;
title.children().toggleClass("ks_t2", false).eq(i).toggleClass("ks_t2", true);
}

var setTimer = function () {
if (!config.tick) return;
if (timer) clearTimer();
timer = setInterval(function () {
toTab(currentTab >= target.children().length - 1 ? 0 : currentTab + 1);
}, config.tick)
}

var clearTimer = function () {
clearInterval(timer);
timer = null;
}

setTimer();

return {
add: function (t, c) {
//添加tab
title.append("<li>" + t + "</li>");
target.append("<div class=\"ks_warp\">" + c + "</div>");
return this;
},
remove: function (i) {
//移除tab
if (title.children().length == 1) return;
i = setIndex(i);
title.children().eq(i).remove();
target.children().eq(i).remove();
if (i == currentTab) toTab(0);
return this;
}, tab: function (i) {
//设置或者获取当前tab
return i ? toTab(i) : currentTab;
}
}
}
})(Zepto);

其他

  • demo里面只引用了基础的zepto,其实移动端他的touch.js也是非常有必要的,引用了之后可以将代码内的click换成zepto的tap事件

  地址: https://github.com/madrobby/zepto/blob/master/src/touch.js#files

  • 容器用的box布局,内部html样式要注意一下
  • 只支持大部分webkit内核浏览器
  • 测试需要在移动设备上进行,电脑上需要chrome,F12打开,在控制台旁边,伪装环境,如下图:

 

  发布匆忙,如有纰漏麻烦大家指出哦,demo下载: 这里

 

本人工作之余,闲暇时间好多……顺便来寻求下兼职!

asp.net/js/jquery/html5/css3/移动前端 经验丰富

(坐标[长沙],行业[彩票业] -- 如果有需要)

求多多支持!


本文链接: [原创]zepto打造一款移动端划屏插件,转载请注明。

什么是堆和栈,它们在哪儿?

$
0
0

   英文原文:What and where are the stack and heap?

   问题描述

  编程语言书籍中经常解释值类型被创建在栈上,引用类型被创建在堆上,但是并没有本质上解释这堆和栈是什么。我仅有高级语言编程经验,没有看过对此更清晰的解释。我的意思是我理解什么是栈,但是它们到底是什么,在哪儿呢(站在实际的计算机物理内存的角度上看)?

  1. 在通常情况下由操作系统(OS)和语言的运行时(runtime)控制吗?
  2. 它们的作用范围是什么?
  3. 它们的大小由什么决定?
  4. 哪个更快?

  答案一

  栈是为执行线程留出的内存空间。当函数被调用的时候,栈顶为局部变量和一些 bookkeeping 数据预留块。当函数执行完毕,块就没有用了,可能在下次的函数调用的时候再被使用。栈通常用后进先出(LIFO)的方式预留空间;因此最近的保留块(reserved block)通常最先被释放。这么做可以使跟踪堆栈变的简单;从栈中释放块(free block)只不过是指针的偏移而已。

  堆(heap)是为动态分配预留的内存空间。和栈不一样,从堆上分配和重新分配块没有固定模式;你可以在任何时候分配和释放它。这样使得跟踪哪部分堆已经被分配和被释放变的异常复杂;有许多定制的堆分配策略用来为不同的使用模式下调整堆的性能。

  每一个线程都有一个栈,但是每一个应用程序通常都只有一个堆(尽管为不同类型分配内存使用多个堆的情况也是有的)。

  直接回答你的问题: 1. 当线程创建的时候,操作系统(OS)为每一个系统级(system-level)的线程分配栈。通常情况下,操作系统通过调用语言的运行时(runtime)去为应用程序分配堆。 2. 栈附属于线程,因此当线程结束时栈被回收。堆通常通过运行时在应用程序启动时被分配,当应用程序(进程)退出时被回收。 3. 当线程被创建的时候,设置栈的大小。在应用程序启动的时候,设置堆的大小,但是可以在需要的时候扩展(分配器向操作系统申请更多的内存)。 4. 栈比堆要快,因为它存取模式使它可以轻松的分配和重新分配内存(指针/整型只是进行简单的递增或者递减运算),然而堆在分配和释放的时候有更多的复杂的 bookkeeping 参与。另外,在栈上的每个字节频繁的被复用也就意味着它可能映射到处理器缓存中,所以很快(译者注:局部性原理)。

  答案二

  Stack:

  1. 和堆一样存储在计算机 RAM 中。
  2. 在栈上创建变量的时候会扩展,并且会自动回收。
  3. 相比堆而言在栈上分配要快的多。
  4. 用数据结构中的栈实现。
  5. 存储局部数据,返回地址,用做参数传递。
  6. 当用栈过多时可导致栈溢出(无穷次(大量的)的递归调用,或者大量的内存分配)。
  7. 在栈上的数据可以直接访问(不是非要使用指针访问)。
  8. 如果你在编译之前精确的知道你需要分配数据的大小并且不是太大的时候,可以使用栈。
  9. 当你程序启动时决定栈的容量上限。

  Heap:

  1. 和栈一样存储在计算机RAM。
  2. 在堆上的变量必须要手动释放,不存在作用域的问题。数据可用 delete, delete[] 或者 free 来释放。
  3. 相比在栈上分配内存要慢。
  4. 通过程序按需分配。
  5. 大量的分配和释放可造成内存碎片。
  6. 在 C++ 中,在堆上创建数的据使用指针访问,用 new 或者 malloc 分配内存。
  7. 如果申请的缓冲区过大的话,可能申请失败。
  8. 在运行期间你不知道会需要多大的数据或者你需要分配大量的内存的时候,建议你使用堆。
  9. 可能造成内存泄露。

  举例:

int foo()
{
char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
bool b = true; // Allocated on the stack.
if(b)
{
//Create 500 bytes on the stack
char buffer[500];

//Create 500 bytes on the heap
pBuffer = new char[500];

}//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;

  答案三

  堆和栈是两种内存分配的两个统称。可能有很多种不同的实现方式,但是实现要符合几个基本的概念:

  1.对栈而言,栈中的新加数据项放在其他数据的顶部,移除时你也只能移除最顶部的数据(不能越位获取)。

khqDF

  2.对堆而言,数据项位置没有固定的顺序。你可以以任何顺序插入和删除,因为他们没有“顶部”数据这一概念。

E5QTV

  上面上个图片很好的描述了堆和栈分配内存的方式。

  在通常情况下由操作系统(OS)和语言的运行时(runtime)控制吗?

  如前所述,堆和栈是一个统称,可以有很多的实现方式。计算机程序通常有一个栈叫做 调用栈,用来存储当前函数调用相关的信息(比如:主调函数的地址,局部变量),因为函数调用之后需要返回给主调函数。栈通过扩展和收缩来承载信息。实际上,程序不是由运行时来控制的,它由编程语言、操作系统甚至是系统架构来决定。

  堆是在任何内存中动态和随机分配的(内存的)统称;也就是无序的。内存通常由操作系统分配,通过应用程序调用 API 接口去实现分配。在管理动态分配内存上会有一些额外的开销,不过这由操作系统来处理。

  它们的作用范围是什么?

  调用栈是一个低层次的概念,就程序而言,它和“作用范围”没什么关系。如果你反汇编一些代码,你就会看到指针引用堆栈部分。就高级语言而言,语言有它自己的范围规则。一旦函数返回,函数中的局部变量会直接直接释放。你的编程语言就是依据这个工作的。

  在堆中,也很难去定义。作用范围是由操作系统限定的,但是你的编程语言可能增加它自己的一些规则,去限定堆在应用程序中的范围。体系架构和操作系统是使用虚拟地址的,然后由处理器翻译到实际的物理地址中,还有页面错误等等。它们记录那个页面属于那个应用程序。不过你不用关心这些,因为你仅仅在你的编程语言中分配和释放内存,和一些错误检查(出现分配失败和释放失败的原因)。

  它们的大小由什么决定?

  依旧,依赖于语言,编译器,操作系统和架构。栈通常提前分配好了,因为栈必须是连续的内存块。语言的编译器或者操作系统决定它的大小。不要在栈上存储大块数据,这样可以保证有足够的空间不会溢出,除非出现了无限递归的情况(额,栈溢出了)或者其它不常见了编程决议。

  堆是任何可以动态分配的内存的统称。这要看你怎么看待它了,它的大小是变动的。在现代处理器中和操作系统的工作方式是高度抽象的,因此你在正常情况下不需要担心它实际的大小,除非你必须要使用你还没有分配的内存或者已经释放了的内存。

  哪个更快一些?

  栈更快因为所有的空闲内存都是连续的,因此不需要对空闲内存块通过列表来维护。只是一个简单的指向当前栈顶的指针。编译器通常用一个专门的、快速的寄存器来实现。更重要的一点事是,随后的栈上操作通常集中在一个内存块的附近,这样的话有利于处理器的高速访问(译者注:局部性原理)。

  答案四

  你问题的答案是依赖于实现的,根据不同的编译器和处理器架构而不同。下面简单的解释一下:

  1. 栈和堆都是用来从底层操作系统中获取内存的。
  2. 在多线程环境下每一个线程都可以有他自己完全的独立的栈,但是他们共享堆。并行存取被堆控制而不是栈。

  堆:

  1. 堆包含一个链表来维护已用和空闲的内存块。在堆上新分配(用 new 或者 malloc)内存是从空闲的内存块中找到一些满足要求的合适块。这个操作会更新堆中的块链表。这些元信息也存储在堆上,经常在每个块的头部一个很小区域。
  2. 堆的增加新快通常从地地址向高地址扩展。因此你可以认为堆随着内存分配而不断的增加大小。如果申请的内存大小很小的话,通常从底层操作系统中得到比申请大小要多的内存。
  3. 申请和释放许多小的块可能会产生如下状态:在已用块之间存在很多小的空闲块。进而申请大块内存失败,虽然空闲块的总和足够,但是空闲的小块是零散的,不能满足申请的大小,。这叫做“堆碎片”。
  4. 当旁边有空闲块的已用块被释放时,新的空闲块可能会与相邻的空闲块合并为一个大的空闲块,这样可以有效的减少“堆碎片”的产生。

0Obi0

  栈:

  1. 栈经常与 sp 寄存器(译者注:"stack pointer",了解汇编的朋友应该都知道)一起工作,最初 sp 指向栈顶(栈的高地址)。
  2. CPU 用 push 指令来将数据压栈,用 pop 指令来弹栈。当用 push 压栈时,sp 值减少(向低地址扩展)。当用 pop 弹栈时,sp 值增大。存储和获取数据都是 CPU 寄存器的值。
  3. 当函数被调用时,CPU使用特定的指令把当前的 IP (译者注:“instruction pointer”,是一个寄存器,用来记录 CPU 指令的位置)压栈。即执行代码的地址。CPU 接下来将调用函数地址赋给 IP ,进行调用。当函数返回时,旧的 IP 被弹栈,CPU 继续去函数调用之前的代码。
  4. 当进入函数时,sp 向下扩展,扩展到确保为函数的局部变量留足够大小的空间。如果函数中有一个 32-bit 的局部变量会在栈中留够四字节的空间。当函数返回时,sp 通过返回原来的位置来释放空间。
  5. 如果函数有参数的话,在函数调用之前,会将参数压栈。函数中的代码通过 sp 的当前位置来定位参数并访问它们。
  6. 函数嵌套调用和使用魔法一样,每一次新调用的函数都会分配函数参数,返回值地址、局部变量空间、嵌套调用的活动记录都要被压入栈中。函数返回时,按照正确方式的撤销。
  7. 栈要受到内存块的限制,不断的函数嵌套/为局部变量分配太多的空间,可能会导致栈溢出。当栈中的内存区域都已经被使用完之后继续向下写(低地址),会触发一个 CPU 异常。这个异常接下会通过语言的运行时转成各种类型的栈溢出异常。(译者注:“不同语言的异常提示不同,因此通过语言运行时来转换”我想他表达的是这个含义)

9UshP

  *函数的分配可以用堆来代替栈吗?

  不可以的,函数的活动记录(即局部或者自动变量)被分配在栈上, 这样做不但存储了这些变量,而且可以用来嵌套函数的追踪。

  堆的管理依赖于运行时环境,C 使用 malloc ,C++ 使用 new ,但是很多语言有垃圾回收机制。

  栈是更低层次的特性与处理器架构紧密的结合到一起。当堆不够时可以扩展空间,这不难做到,因为可以有库函数可以调用。但是,扩展栈通常来说是不可能的,因为在栈溢出的时候,执行线程就被操作系统关闭了,这已经太晚了。

  译者注

  关于堆栈的这个帖子,对我来说,收获非常多。我之前看过一些资料,自己写代码的时候也常常思考。就这方面,也和祥子(我的大学舍友,现在北京邮电读研,技术牛人)探讨过多次了。但是终究是一个一个的知识点,这个帖子看完之后,豁然开朗,把知识点终于连接成了一个网。这种感觉,经历过的一定懂得,期间的兴奋不言而喻。

  这个帖子跟帖者不少,我选了评分最高的四个。这四个之间也有一些是重复的观点。个人钟爱第四个回答者,我看的时候,瞬间高潮了,有木有?不过需要一些汇编语言、操作系统、计算机组成原理的的基础,知道那几个寄存器是干什么的,要知道计算机的流水线指令工作机制,保护/恢复现场等概念。三个回复者都涉及到了操作系统中虚拟内存;在比较速度的时候,大家一定要在脑中对“局部性原理”和计算机高速缓存有一个概念。

  如果你把这篇文章看懂了,我相信你收获的不只是堆和栈,你会理解的更多!

  兴奋之余,有几点还是要强调的,翻译没有逐字逐词翻译,大部分是通过我个人的知识积累和对回帖者的意图揣测而来的。请大家不要咬文嚼字,逐个推敲,我们的目的在于技术交流,不是么?达到这一目的就够了。

  下面是一些不确定点:

  1. 我没有听过 bookkeeping data 这种说法,故没有翻译。从上下文理解来看,可以想成是用来寄存器值?函数参数?返回地址?如果有了解具体含义的朋友,烦请告知。
  2. 栈和堆栈是一回事,英文表达是 stack,堆是 heap。
  3. 调用栈的概念,我是第一次听说,不太熟悉。大家可以去查查资料研究一下。

  以上,送给大家,本文结束。

Linux系统查看内存使用率

$
0
0
Linux下看内存和CPU使用率一般都用top命令,但是实际在用的时候,用top查看出来的内存占用率都非常高,如:
  Mem:   4086496k total, 4034428k used,    52068k free,   112620k buffers
  Swap: 4192956k total,   799952k used, 3393004k free, 1831700k cached
  top –M看更直观,以M为单位
  接近98.7%,而实际上的应用程序占用的内存往往并没这么多,
  PID USER      PR NI VIRT RES SHR S %CPU %MEM    TIME+ COMMAND
  25801 sybase    15   0 2648m 806m 805m S 1.0 20.2 27:56.96 dataserver
  12084 oracle    16   0 1294m 741m 719m S 0.0 18.6   0:13.50 oracle
  27576 xugy      25   0 986m 210m 1040 S 1.0 5.3 28:51.24 cti
  25587 yaoyang   17   0 1206m 162m 3792 S 0.0 4.1   9:21.14  java
  看%MEM这列的数字,按内存排序后,把前几名加起来,撑死了才不过55%,那剩下的内存都干嘛用了?
  一般的解释是Linux系统下有一种思想,内存不用白不用,占用了就不释放,听上去有点道理,但如果我一定要知道应用程序还能有多少内存可用呢?
  仔细看top关于内存的显示输出,有两个数据buffers和cached,在Linux系统下的buffer指的是磁盘写缓存,而cache则指的是磁盘读缓存。
  (A buffer is something that has yet to be "written" to disk.
  A cache is something that has been "read" from the disk and stored for later use.)
  而这两块是为了提高系统效率而分配的内存,在内存富余的时候, 操作系统将空闲内存利用起来,而有内存需求时,系统会释放这部分的内存供应用程序使用。
  这样,真正应用程序可用的内存就是free+buffer+cache,上面的例子就是:
  52068k + 112620k + 1831700k = 1996388k
  而已用内存则是used-buffer-cache,上面的例子为:
  4034428k - 112620k - 1831700k = 2090108k
  Linux下查看内存还有一个更方便的命令,free:
  $ free
  total       used       free     shared    buffers     cached
  Mem:       4086496    4034044      52452          0     112756    1831564
  -/+ buffers/cache:    2089724    1996772
  Swap:      4192956     799952    3393004
  Mem:这列就是用top命令看到的内存使用情况,而-/+buffers/cache这列就是我们刚刚做的计算结果,used-buffer-cache/free+buffer+cache
  也可以加-m或者-g参数查看按MB或者GB换算的结果。
  $ free -m
  total       used       free     shared    buffers     cached
  Mem:          3990       3906         83          0         90       1786
  -/+ buffers/cache:       2029       1961
  Swap:         4094        781       3312
  这样,真正应用程序的内存使用量就可以得出来了,上面的例子中内存占用率为51.1%。
  例如:
  # free -m
  total       used       free     shared    buffers     cached
  Mem:               4229       2834       1395          0         62       2548
  -/+ buffers/cache:                223        4006    //物理内存使用223M,剩余4006M
  Swap:               8001       0         8001


顺其自然EVO 2014-08-14 09:33 发表评论

HTTP的KeepAlive是开启还是关闭?

$
0
0

1、KeepAlive的概念与优势

        HTTP的KeepAlive就是浏览器和服务端之间保持长连接,这个连接是可以复用的。当客户端发送一次请求,收到相应内容后,这个连接会保持一段时间,在该时间内的第二次就不需要再重新建立连接,就可以直接使用这次的连接来发送请求了,极大的提高了速度。

2、KeepAlive的劣势

        是不是所有网站都应该开启KeepAlive了?答案肯定是不是的。KeepAlive在增加访问效率的同时,也会增加服务器的压力。

3、apache配置KeepAlive

         apache通过配置KeepAlive和KeepAliveTimeout来控制KeepAlive,具体参数如下:

         KeepAlive  On/Off            (可以设置开启On或者是关闭Off)
         KeepAliveTimeout  10    (持久连接保持的时间,到时间会断开链接)

4、案例说明(来源互联网):

         假设 KeepAlive 的超时时间为 10 秒种,服务器每秒处理 50 个独立用户访问,那么系统中Apache的总进程数就是 10 * 50 = 500 个,如果一个进程占用 4M 内存,那么总共会消耗2G内存,所以可以看出,在这种配置中,相当消耗内存,但好处是系统只处理了 50次 TCP 的握手和关闭操作。
         如果关闭 KeepAlive,如果还是每秒50个用户访问,如果用户每次连续的请求数为3个,那么 Apache 的总进程数就是 50 * 3 = 150 个,如果还是每个进程占用 4M 内存,那么总的内存消耗为600M,这种配置能节省大量内存,但是,系统处理了 150 次 TCP 的握手和关闭的操作,因此又会多消耗一些 CPU 资源。

5、总结

        (1)如果内存和CPU都足够,开启和关闭KeepAlive对性能影响不大。
        (2)如果考虑服务器压力,如果是静态页面,大量的调用js或者图片的话,建议开启KeepAlive;如果是动态网页,建议关闭KeepAlive。


作者:Zhao1234567890123456 发表于2014-8-13 23:11:54 原文链接
阅读:56 评论:0 查看评论

分布式服务框架:Zookeeper

$
0
0

Zookeeper是一个高性能,分布式的,开源分布式应用协调服务。它提供了简单原始的功能,分布式应用可以基于它实现更高级的服务,比如同步,配置管理,集群管理,名空间。它被设计为易于编程,使用文件系统目录树作为数据模型。服务端跑在java上,提供java和C的客户端API。Zookeeper是Google的Chubby一个开源的实现,是高有效和可靠的协同工作系统,Zookeeper能够用来leader选举,配置信息维护等,在一个分布式的环境中,需要一个Master实例或存储一些配置信息,确保文件写入的一致性等。

Zookeeper总体结构

Zookeeper服务自身组成一个集群(2n+1个服务允许n个失效)。Zookeeper服务有两个角色,一个是leader,负责写服务和数据同步,剩下的是follower,提供读服务,leader失效后会在follower中重新选举新的leader。

Zookeeper逻辑图如下:

zookeeper

  1. 客户端可以连接到每个server,每个server的数据完全相同。
  2. 每个follower都和leader有连接,接受leader的数据更新操作。
  3. Server记录事务日志和快照到持久存储。
  4. 大多数server可用,整体服务就可用。

ZooKeeper的基本运转流程:

  1. 选举Leader。
  2. 同步数据。
  3. 选举Leader过程中算法有很多,但要达到的选举标准是一致的。
  4. Leader要具有最高的zxid。
  5. 集群中大多数的机器得到响应并follow选出的Leader。

Zookeeper表现为一个分层的文件系统目录树结构(不同于文件系统的是,节点可以有自己的数据,而文件系统中的目录节点只有子节点)。数据模型结构图如下:

node

圆形节点可以含有子节点,多边形节点不能含有子节点。一个节点对应一个应用,节点存储的数据就是应用需要的配置信息。

Zookeeper 特点

  1. 顺序一致性:按照客户端发送请求的顺序更新数据。
  2. 原子性:更新要么成功,要么失败,不会出现部分更新。
  3. 单一性 :无论客户端连接哪个server,都会看到同一个视图。
  4. 可靠性:一旦数据更新成功,将一直保持,直到新的更新。
  5. 及时性:客户端会在一个确定的时间内得到最新的数据。

Zookeeper利于分布式系统开发,它能让分布式系统更加健壮和高效。它的主要优点有:

  1. zookeeper是一个精简的文件系统。这点它和hadoop有点像,但是zookeeper这个文件系统是管理小文件的,而hadoop是管理超大文件的。
  2. zookeeper提供了丰富的“构件”,这些构件可以实现很多协调数据结构和协议的操作。例如:分布式队列、分布式锁以及一组同级节点的“领导者选举”算法。
  3. zookeeper是高可用的,它本身的稳定性是相当之好,分布式集群完全可以依赖zookeeper集群的管理,利用zookeeper避免分布式系统的单点故障的问题。
  4. zookeeper采用了松耦合的交互模式。这点在zookeeper提供分布式锁上表现最为明显,zookeeper可以被用作一个约会机制,让参入的进程不在了解其他进程的(或网络)的情况下能够彼此发现并进行交互,参入的各方甚至不必同时存在,只要在zookeeper留下一条消息,在该进程结束后,另外一个进程还可以读取这条信息,从而解耦了各个节点之间的关系。
  5. zookeeper为集群提供了一个共享存储库,集群可以从这里集中读写共享的信息,避免了每个节点的共享操作编程,减轻了分布式系统的开发难度。
  6. zookeeper的设计采用的是观察者的设计模式,zookeeper主要是负责存储和管理大家关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper 就将负责通知已经在 Zookeeper 上注册的那些观察者做出相应的反应,从而实现集群中类似 Master/Slave 管理模式。

Zookeeper 会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统,如图所示:

zookeeper-1

Zookeeper 这种数据结构有如下这些特点:

  1. 每个子目录项如 NameService 都被称作为 znode,这个 znode 是被它所在的路径唯一标识,如 Server1 这个 znode 的标识为 /NameService/Server1
  2. znode 可以有子节点目录,并且每个 znode 可以存储数据,注意 EPHEMERAL 类型的目录节点不能有子节点目录
  3. znode 是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据
  4. znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,Zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了
  5. znode 的目录名可以自动编号,如 App1 已经存在,再创建的话,将会自动命名为 App2
  6. znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是 Zookeeper 的核心特性,Zookeeper 的很多功能都是基于这个特性实现的,后面在典型的应用场景中会有实例介绍

四种类型的znode:

  1. PERSISTENT-持久化目录节点。客户端与zookeeper断开连接后,该节点依旧存在
  2. PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点。客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  3. EPHEMERAL-临时目录节点。客户端与zookeeper断开连接后,该节点被删除
  4. EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点。客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

ZooKeeper Client Library提供了丰富直观的API供用户程序使用,下面是一些常用的API:

  1. create(path, data, flags): 创建一个ZNode, path是其路径,data是要存储在该ZNode上的数据,flags常用的有: PERSISTEN, PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL
  2. delete(path, version): 删除一个ZNode,可以通过version删除指定的版本, 如果version是-1的话,表示删除所有的版本
  3. exists(path, watch): 判断指定ZNode是否存在,并设置是否Watch这个ZNode。这里如果要设置Watcher的话,Watcher是在创建ZooKeeper实例时指定的,如果要设置特定的Watcher的话,可以调用另一个重载版本的exists(path, watcher)。以下几个带watch参数的API也都类似
  4. getData(path, watch): 读取指定ZNode上的数据,并设置是否watch这个ZNode
  5. setData(path, watch): 更新指定ZNode的数据,并设置是否Watch这个ZNode
  6. getChildren(path, watch): 获取指定ZNode的所有子ZNode的名字,并设置是否Watch这个ZNode
  7. sync(path): 把所有在sync之前的更新操作都进行同步,达到每个请求都在半数以上的ZooKeeper Server上生效。path参数目前没有用
  8. setAcl(path, acl): 设置指定ZNode的Acl信息
  9. getAcl(path): 获取指定ZNode的Acl信息

Zookeeper的应用场景:

1、命名服务

命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等——这些我们都可以统称他们为名字(Name)。其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。

2、配置管理

程序总是需要配置的,如果程序分散部署在多台机器上,要逐个改变配置就变得困难。现在把这些配置全部放到zookeeper上去,保存在 Zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中就好。

zookeeper-2

3、集群管理

所谓集群管理无在乎两点:是否有机器退出和加入、选举master。

对于第一点,所有机器约定在父目录GroupMembers下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个兄弟目录被删除,于是,所有人都知道:它上船了。新机器加入也是类似,所有机器收到通知:新兄弟目录加入,highcount又有了。

对于第二点,我们稍微改变一下,所有机器创建临时顺序编号目录节点,每次选取编号最小的机器作为master就好。

zookeeper-3

4、分布式锁

有了zookeeper的一致性文件系统,锁的问题变得容易。锁服务可以分为两类,一个是保持独占,另一个是控制时序。

对于第一类,我们将zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。厕所有言:来也冲冲,去也冲冲,用完删除掉自己创建的distribute_lock 节点就释放出锁。

对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。

5、队列管理

两种类型的队列:

1、同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。

2、队列按照 FIFO 方式进行入队和出队操作。

第一类,在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。

第二类,和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。

6、负载均衡

这里说的负载均衡是指软负载均衡。在分布式环境中,为了保证高可用性,通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑,其中比较典型的是消息中间件中的生产者,消费者负载均衡。

7、分布式通知/协调

ZooKeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能够收到通知,并作出相应处理。

参考链接:


美国用面部识别技术抓住逃亡14年的通缉犯

$
0
0
美国国务院正在测试用面部识别软件识别护照欺诈,工作人员扫描FBI通缉犯照片时发现一位通缉犯匹配了一位不同名字的人的护照。化名为Kevin Hodges的Neil Stammer当时生活在尼泊尔教英语课,他定时访问尼泊尔美国大使馆更新旅游签证。Stammer于本周被捕,他在1999年受到儿童性虐待和绑架罪指控后一直逃到了尼泊尔。追缉逃犯的FBI特工说,他在尼泊尔过得非常舒服,可能坚信自己十分安全,不会被人发现。






秒变配色高手!怎么都不会错的6条网页设计配色原则

$
0
0

website-pinciple-6-1

身为 网页设计新手的你,是不是还在纠结于你制作的网页找不到一组完美的配色方案?在本教程中我们将与你分享6条肯定会火,并且“错不了”的指导方针,你可以按照这些原则把握最基本的色彩规律。现在我们分享的这些原则都不是规则,你会在你的职业生涯中创造出更多的配色方案。相反,他们是一个起点,是你如何生存在 网页设计领域色彩方向的安全指南。

一、需要配色的是你的画布,而不是你的图片

一个在网页设计中最根本的原则是,无论你花了多少时间创造了一个辉煌的设计,其最终的作用是发挥出内容的核心位置。你的配色方案永远不应该比它呈现的内容的更加“响亮”。你的设计应该是在后台,目的是帮助突出网站的内容。

秒变配色高手!怎么都不会错的6条网页设计配色原则

秒变配色高手!怎么都不会错的6条网页设计配色原则

淡色的画布突出了图像,而明亮的画布反而不能突出你的内容。(别笑,后者是发生在网络上的真实案例^—^)

用Photoshop或者Sketch等软件设计网站的时候,创建设计的过程往往是相互独立的。有些设计单个看起来很不错,也能被你的客户所接受,但是当它真正被设计成网页的时候不适当的配色往往会分散访客的注意力。事实上,网页设计的过程是和内容紧密相连的,很多制作高品质的网页看上去空空荡荡,几乎没有内容。

这是一个伟大的想法:你可以在你的网站上先铺陈出你的内容,用设计软件也好用代码也好,然后在你内容的周围设计你的网页。当然这也是一个特殊情况,如果一个特定风格的图像和照片都能和你的设计和谐的融为一体,那么你的设计配色才算是完美。试想一下,网站的配色对内容而言就像衣服对于人的重要性,对此你必须制定一套完美的并且合身的衣服。

二、选择简单的灰色作为你网站的基调

你可以为你的网站基调选择无数种颜色,不过我建议你采用最简单的颜色,比如白色/浅灰色与深灰色的搭配文字背景。

你可以看看任何热门的网站、模版、主题,白色或浅灰色与深灰色搭配成了大多数的选择,这当然也是有充分理由的。这样的搭配对访客而言提高了你内容的可读性,并且把你的图片突出在最前方。

秒变配色高手!怎么都不会错的6条网页设计配色原则

一般来说,你的文字最好避免使用墨黑色,深灰色一般更容易阅读。我们提供一个比较舒服的文字颜色范围:#333333到#666666。

对于你的背景色,全白色(#FFFFFFF)是可以搭配任何文本的最安全的颜色。如果你想选用其他的背景颜色,我们建议采用#FFFFFF到#CCCCCC

当然,这些颜色的选择都不是固定死的。只不过如果你是新手,以上的配色方案你可以放心使用。

三、只选择一种颜色突出显示

如果你选择好几种不同的色调来,那么你的配色方案绝大多数是有问题的。你颜色用得越多,你的页面就越来越难以控制。所以,在你网页以灰色基调的前提下,你最好只选择一种鲜艳的颜色来作为你想要突出的事物,比如标题、菜单、按钮等等。你的高亮颜色可以是蓝色、红色、绿色,等等。

你最好选择与你的基调颜色相关的高亮颜色。打开你的颜色选择器,并单击你彩色方块的中心。

秒变配色高手!怎么都不会错的6条网页设计配色原则

向上或向下移动你的滑块,你可以仔细选择你认为最合适的颜色。

秒变配色高手!怎么都不会错的6条网页设计配色原则

现在,你设计的页面有了三个基本的颜色:背景色、文本色和高亮色。在以后你也可以选择一种以上的高亮色,但现在对于新手的你来说还是选择一种比较合适。你现在已经掌握了基本的配色,如果你有信心,你以后还可以尝试更多种不同的方案。

你刚刚学到:

学会了如何选择“色相”。概括来说,色相是基本色,当你移动滑块,你会看到h值在颜色选择器中的变化。

“H”代表色相,一旦你选择了你的高亮颜色,文本框中显示的就是你当前颜色的色相。

四、如果有疑问,请使用蓝色

如果你对你的高亮色的选择有疑惑的话,不妨使用蓝色。蓝色是一种弹性比较大的颜色,可以和很多种颜色搭配。黄色和紫色也很不错,但是如果使用不当会适得其反。

另一方面,如果你是用蓝色,那么你用错颜色的概率就会很低。如果你正犹豫着不知道用什么颜色好,不妨使用蓝色。比较安全的蓝色包括从H235到H190,从海军蓝到深蓝色。

秒变配色高手!怎么都不会错的6条网页设计配色原则

如果是我的话,我通常选择H205的蓝色,如果你选定了一种颜色作为你的高亮色,那么也请你在其他需要的地方使用这种颜色。如果你的按钮、标题等需要高亮,把它们的颜色也换成同一种蓝色。在下面这个例子中我把白色换成了蓝色。

clip_image007

五、给你的高亮色增加变化

你一旦选择了高亮的颜色,从该点移动滑块来选择接下来的颜色。在你的设计中也需要其他的颜色,高亮色的稍微变化会让你的颜色选择变得简单。

秒变配色高手!怎么都不会错的6条网页设计配色原则

使用这些类型的颜色变化的东西,如:

悬停效果:

秒变配色高手!怎么都不会错的6条网页设计配色原则

边界:

秒变配色高手!怎么都不会错的6条网页设计配色原则

通过突出显示颜色:

秒变配色高手!怎么都不会错的6条网页设计配色原则

渐变:

秒变配色高手!怎么都不会错的6条网页设计配色原则

光影效果:

秒变配色高手!怎么都不会错的6条网页设计配色原则

六、尽量不要使用颜色选择器右上角的颜色

颜色选择器的右上角是一块肥沃的土地。在右上角的颜色就像F1赛车; 他们可以执行出惊人的效果,而且非常诱人,但通常想用好需要大量的经验。如果没有这种经验,他们可能会导致事故的发生,所​​以最好以削弱你的颜色,最好都保持比较淡化的色彩。

这就是为什么在本教程的第三部分,我问你点击的颜色在地图右上角的中心选择您的高亮颜色之前,要确保你有一个比较柔和的颜色拉开序幕。

为了说明这一点,看如果我只是改变了我们设计的色调,到目前为止,会发生什么。

秒变配色高手!怎么都不会错的6条网页设计配色原则

看上去还是听舒服的对吧?但是如果你把颜色调整为选择器右上角的颜色,我们再来看看效果如何:

秒变配色高手!怎么都不会错的6条网页设计配色原则

分分钟亮瞎了访客的双眼!如果你想确保你不烧焦你的访客的视网膜,遵循留出颜色选择器的右上角的格子的一般原则。

饱和度和亮度

当您拖动周围的颜色选择器区域的地图区域,你会看到“S”和“B”的值发生变化,这代表饱和度和亮度。您还可以看到色相号保持不变。所以,你通过改变你原有的色相的饱和度和亮度会产生颜色的变化。

秒变配色高手!怎么都不会错的6条网页设计配色原则

饱和度

是一种生动的色彩表现。例如,认为“我的衬衫中渗透着葡萄酒红”。在一个典型的颜色选择器饱和度是多少白了就是混在你的基地的色调来决定。白色越少,越饱和。

当您拖动到右边的颜色在地图上可以减少白色量,从而增加了饱和度,“S”的值上升。当您拖动到左边向所有的白角,你会减少饱和使“S”的值下降。

秒变配色高手!怎么都不会错的6条网页设计配色原则

亮度

亮度是有多少黑色混合到你的颜色。黑色越少,越亮。

当你拖动滑块向上,减少了黑色的数量,增加亮度,在颜色选择器中的“B”的值上升。

秒变配色高手!怎么都不会错的6条网页设计配色原则

与你原来的色调混合的黑色也被称为创造了“阴影”。这也来源于油漆混合,是黑色油漆与涂料颜色的混合。

当你混合灰色到彩色这被称为创造了色调。如果你调节饱和度和亮度,那么你就创造了一种色调。所以基本上任何时候无论你的饱和度和亮度均小于100%,这就是一个色调。

同样,这个词来源于油漆混合,在其中创建一个灰色的油漆,然后用彩色涂料混合。

单色配色方案

单色的配色方案是,你以一个基本的色调和扩展它的色相,饱和度和亮度。因此,通过采摘一大亮点的颜色和它创造的变化,你实际上创建了一个单色方案。

秒变配色高手!怎么都不会错的6条网页设计配色原则

下一步?

坚持练习对灰度基础的单色配色方案,直到你感到很有信心为止。试着用不同的色调,尝试创建不同的高亮色,看看它是如何改变饱和度和亮度的设置,直到可以使用为止。

当你感觉很舒服,可以增加一个额外的高亮颜色。我建议尝试橙色和蓝色,因为他们往往​​是最简单的二重奏。然后尝试绿色和蓝色,这在我的经验中是第二个最简单的。这两种往往是与客户和访客都会称赞的搭配。

为了推动了你对Web的配色方案的理解,最​好的事情就是抓住自己喜欢颜色,你可以用取样器(浏览器扩展Colorzilla),并用它来​研究经验丰富的设计师是怎么做到的丰富的色彩搭配的。当你在浏览互联网的时候,看到一个很大的配合突破,你可以用颜色取样器看看在页面上使用的颜色的配色方案。你甚至可以尝试每个色相,看看哪些饱和度和亮度水平效果最好吧。另外要注意它的颜色组合的和谐度。

如果有疑问,可以随时回到属于你的“安全第一”的指导方针,这无论如何都是不会错的。

配色好文合集!

经典好文,配色三部曲!
《设计师的配色理论:你真懂颜色了吗?》
《设计师的配色理论:你真懂这些色彩术语吗?》
《设计师的配色理论:创建自己的调色板》

色彩怎么用?看看这些!
《巧用色彩的冲击力!13例大胆用色网页设计赏析》

秒变配色高手!怎么都不会错的6条网页设计配色原则

原文地址:blog.enqoo

【优设网 原创文章 投稿邮箱:2650232288@qq.com】

================ 关于优设网================
“优设网 uisdc.com“是一个分享网页设计、无线端设计以及PS教程的干货网站。
【特色推荐】
设计师需要读的100本书:史上最全的设计师图书导航: http://hao.uisdc.com/book/
设计微博:拥有粉丝量75万的人气微博 @优秀网页设计,欢迎关注获取网页设计资源、下载顶尖设计素材。
设计导航:全球顶尖设计网站推荐,设计师必备导航: http://hao.uisdc.com
———————————————————–
想在手机上、被窝里获取设计教程、 经验分享和各种意想不到的”福利”吗?
添加 优秀网页设计 微信号:【youshege】优设哥的全拼
您也可以通过扫描下方二维码快速添加:

sdcweixin

优设哥向您推荐:

玩转博客!新闻类网站风格设计原则

高端网站都懂的!如何在网页设计中使用留白

设计师如何制定设计原则

设计师应当知道的20大UI设计原则

浅析网页设计中图片的使用基本原则
无觅

页面重绘和回流以及优化

$
0
0

标签:   回流   重绘

在讨论页面重绘、回流之前。需要对页面的呈现流程有些了解,页面是怎么把html结合css等显示到浏览器上的,下面的流程图显示了浏览器对页面的呈现的处理流程。可能不同的浏览器略微会有些不同。但基本上都是类似的。

8_1

1.  浏览器把获取到的HTML代码解析成1个DOM树,HTML中的每个tag都是DOM树中的1个节点,根节点就是我们常用的document对象。DOM树里包含了所有HTML标签,包括display:none隐藏,还有用JS动态添加的元素等。

2. 浏览器把所有样式(用户定义的CSS和用户代理)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式,而FF会去掉_开头的样式。

3、DOM Tree 和样式结构体组合后构建render tree, render tree类似于DOM tree,但区别很大,render tree能识别样式,render tree中每个NODE都有自己的style,而且 render tree不包含隐藏的节点 (比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。注意 visibility:hidden隐藏的元素还是会包含到 render tree中的,因为visibility:hidden 会影响布局(layout),会占有空间。根据CSS2的标准,render tree中的每个节点都称为Box (Box dimensions),理解页面元素为一个具有填充、边距、边框和位置的盒子。

4. 一旦render tree构建完毕后,浏览器就可以根据render tree来绘制页面了。

回流与重绘

1. 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

2. 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

注意:回流必将引起重绘,而重绘不一定会引起回流。


回流何时发生:

当页面布局和几何属性改变时就需要回流。下述情况会发生浏览器回流:

1、添加或者删除可见的DOM元素;

2、元素位置改变;

3、元素尺寸改变——边距、填充、边框、宽度和高度

4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;

5、页面渲染初始化;

6、浏览器窗口尺寸改变——resize事件发生时;


让我们看看下面的代码是如何影响回流和重绘的:

  1. var s = document.body.style;

  2. s.padding ="2px";// 回流+重绘

  3. s.border ="1px solid red";// 再一次 回流+重绘

  4. s.color ="blue";// 再一次重绘

  5. s.backgroundColor ="#ccc";// 再一次 重绘

  6. s.fontSize ="14px";// 再一次 回流+重绘

  7. // 添加node,再一次 回流+重绘

  8. document.body.appendChild(document.createTextNode('abc!'));

说到这里大家都知道回流比重绘的代价要更高,回流的花销跟render tree有多少节点需要重新构建有关系,假设你直接操作body,比如在body最前面插入1个元素,会导致整个render tree回流,这样代价当然会比较高,但如果是指body后面插入1个元素,则不会影响前面元素的回流。


聪明的浏览器

从上个实例代码中可以看到几行简单的JS代码就引起了6次左右的回流、重绘。而且我们也知道回流的花销也不小,如果每句JS操作都去回流重绘的话,浏览器可能就会受不了。所以很多浏览器都会优化这些操作,浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

虽然有了浏览器的优化,但有时候我们写的一些代码可能会强制浏览器提前flush队列,这样浏览器的优化可能就起不到作用了。当你请求向浏览器请求一些 style信息的时候,就会让浏览器flush队列,比如:

1. offsetTop, offsetLeft, offsetWidth, offsetHeight

2. scrollTop/Left/Width/Height

3. clientTop/Left/Width/Height

4. width,height

5. 请求了getComputedStyle(), 或者 IE的 currentStyle

当你请求上面的一些属性的时候,浏览器为了给你最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。

如何减少回流、重绘

减少回流、重绘其实就是需要减少对render tree的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略。具体方法有:

1. 直接改变className,如果动态改变样式,则使用cssText(考虑没有优化的浏览器)

  1. // 不好的写法

  2. var left =1;

  3. var top =1;

  4. el.style.left = left +"px";

  5. el.style.top = top +"px";// 比较好的写法

  6. el.className +=" className1";

  7. // 比较好的写法

  8. el.style.cssText +=";

  9. left:" + left + "px;

  10. top:" + top + "px;";

2. 让要操作的元素进行”离线处理”,处理完后一起更新

a) 使用DocumentFragment进行缓存操作,引发一次回流和重绘;
b) 使用display:none技术,只引发两次回流和重绘;
c) 使用cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;


3.不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存

  1. // 别这样写,大哥

  2. for(循环){

  3. el.style.left = el.offsetLeft +5+"px";

  4. el.style.top = el.offsetTop +5+"px";

  5. }

  6. // 这样写好点

  7. var left = el.offsetLeft,

  8. top = el.offsetTop,

  9. s = el.style;

  10. for(循环){

  11. left +=10;

  12. top +=10;

  13. s.left = left +"px";

  14. s.top = top +"px";

  15. }

4. 让元素脱离动画流,减少回流的Render Tree的规模

  1. $("#block1").animate({left:50});

  2. $("#block2").animate({marginLeft:50});

实例测试

最后用2个工具对上面的理论进行一些测试,分别是:dynaTrace(测试ie),Speed Tracer(测试Chrome)。

第一个测试代码不改变元素的规则,大小,位置。只改变颜色,所以不存在回流,仅测试重绘,代码如下:

  1. <body>

  2. &l

    您可能还对下面的文章感兴趣:

    1. 浏览器的重绘(repaints)与重排(reflows) [2014-08-15 13:52:56]
    2. 浏览器的重绘[repaints]与重排[reflows] [2012-11-13 13:46:27]

Mina、Netty、Twisted一起学:实现简单的TCP服务器

$
0
0

MINA、Netty、Twisted为什么放在一起学习?首先,不妨先看一下他们官方网站对其的介绍:

MINA:

Apache MINA is a network application framework which helps users develop high performance and high scalability network applications easily. It provides an abstract event-driven asynchronous API over various transports such as TCP/IP and UDP/IP via Java NIO.

Netty:

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

Twisted:

Twisted is an event-driven networking engine written in Python and licensed under the open source MIT license.

(Twisted官网的文案不专业啊,居然不写asynchronous)

从上面简短的介绍中,就可以发现它们的共同特点:event-driven以及asynchronous。它们都是 事件驱动、异步的网络编程框架。由此可见,它们之间的共同点还是很明显的。所以我这里将这三个框架放在一起,实现相同的功能,不但可以用少量的精力学三样东西,而且还可以对它们之间进行各方面的对比。

其中MINA和Netty是基于Java语言的,而Twisted是Python语言的。不过语言不是重点,重点的是理念。

使用传统的BIO(Blocking IO/阻塞IO)进行网络编程时,进行网络IO读写时都会阻塞当前线程,如果实现一个TCP服务器,都需要对每个客户端连接开启一个线程,而很多线程可能都在傻傻的阻塞住等待读写数据,系统资源消耗大。

而NIO(Non-Blocking IO/非阻塞IO)或AIO(Asynchronous IO/异步IO)则是通过IO多路复用技术实现,不需要为每个连接创建一个线程,其底层实现是通过操作系统的一些特性如select、pool、epoll、iocp等,这些都不是本文的重点。这三个网络框架都是基于此实现。

下面分别用这三个框架实现一个最简单的TCP服务器。当接受到客户端发过来的字符串后,向客户端回写一个字符串作为响应。

Mina:

public class TcpServer {

	public static void main(String[] args) throws IOException {
		IoAcceptor acceptor = new NioSocketAcceptor();
		acceptor.setHandler(new TcpServerHandle());
		acceptor.bind(new InetSocketAddress(8080));
	}

}

class TcpServerHandle extends IoHandlerAdapter {
	
	@Override
	public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
		cause.printStackTrace();
	}

	// 接收到新的数据
	@Override
	public void messageReceived(IoSession session, Object message) throws Exception {
		
		// 接收客户端的数据
		IoBuffer ioBuffer = (IoBuffer) message;
		byte[] byteArray = new byte[ioBuffer.limit()];
		ioBuffer.get(byteArray, 0, ioBuffer.limit());
		System.out.println("messageReceived:" + new String(byteArray, "UTF-8"));
		// 发送到客户端
		byte[] responseByteArray = "你好".getBytes("UTF-8");
		IoBuffer responseIoBuffer = IoBuffer.allocate(responseByteArray.length);
		responseIoBuffer.put(responseByteArray);
		responseIoBuffer.flip();
		session.write(responseIoBuffer);
	}

	@Override
	public void sessionCreated(IoSession session) throws Exception {
		System.out.println("sessionCreated");
	}
	@Override
	public void sessionClosed(IoSession session) throws Exception {
		System.out.println("sessionClosed");
	}
}

Netty:

public class TcpServer {

	public static void main(String[] args) throws InterruptedException {
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workerGroup)
					.channel(NioServerSocketChannel.class)
					.childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						public void initChannel(SocketChannel ch)
								throws Exception {
							ch.pipeline().addLast(new TcpServerHandler());
						}
					}).option(ChannelOption.SO_BACKLOG, 128)
					.childOption(ChannelOption.SO_KEEPALIVE, true);
			ChannelFuture f = b.bind(8080).sync();
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}

}

class TcpServerHandler extends ChannelInboundHandlerAdapter {

	// 接收到新的数据
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
		try {
			// 接收客户端的数据
			ByteBuf in = (ByteBuf) msg;
			System.out.println("channelRead:" + in.toString(CharsetUtil.UTF_8));
			// 发送到客户端
			byte[] responseByteArray = "你好".getBytes("UTF-8");
			ByteBuf out = ctx.alloc().buffer(responseByteArray.length);
			out.writeBytes(responseByteArray);
			ctx.writeAndFlush(out);
		} finally {
			ReferenceCountUtil.release(msg);
		}
	}
	@Override
	public void channelActive(ChannelHandlerContext ctx) {
		System.out.println("channelActive");
	}
	@Override
	public void channelInactive(ChannelHandlerContext ctx){
		System.out.println("channelInactive");
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}

Twisted:

# -*- coding:utf-8 –*-

from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet import reactor

class TcpServerHandle(Protocol):
    
    # 新的连接建立
    def connectionMade(self):
        print 'connectionMade'
    # 连接断开
    def connectionLost(self, reason):
        print 'connectionLost'
    # 接收到新数据
    def dataReceived(self, data):
        print 'dataReceived', data
        self.transport.write('你好')

factory = Factory()
factory.protocol = TcpServerHandle
reactor.listenTCP(8080, factory)
reactor.run()

上面的代码可以看出,这三个框架实现的TCP服务器,在连接建立、接收到客户端传来的数据、连接关闭时,都会触发某个事件。例如接收到客户端传来的数据时,MINA会触发事件调用messageReceived,Netty会调用channelRead,Twisted会调用dataReceived。编写代码时,只需要继承一个类并重写响应的方法即可。这就是event-driven事件驱动。

下面是Java写的一个TCP客户端用作测试,客户端没有使用这三个框架,也没有使用NIO,只是一个普通的BIO的TCP客户端。

TCP在建立连接到关闭连接的过程中,可以多次进行发送和接收数据。下面的客户端发送了两个字符串到服务器并两次获取服务器回应的数据,之间通过Thread.sleep(5000)间隔5秒。

public class TcpClient {
	public static void main(String[] args) throws IOException, InterruptedException {
		Socket socket = null;
		OutputStream out = null;
		InputStream in = null;
		try{
			socket = new Socket("localhost", 8080);      
			out = socket.getOutputStream();
			in = socket.getInputStream();
		    // 请求服务器
		    out.write("第一次请求".getBytes("UTF-8"));
		    out.flush();
		    // 获取服务器响应,输出
		    byte[] byteArray = new byte[1024];
		    int length = in.read(byteArray);
		    System.out.println(new String(byteArray, 0, length, "UTF-8"));
		    Thread.sleep(5000);
		    // 再次请求服务器
		    out.write("第二次请求".getBytes("UTF-8"));
		    out.flush();
		    // 再次获取服务器响应,输出
		    byteArray = new byte[1024];
		    length = in.read(byteArray);
		    System.out.println(new String(byteArray, 0, length, "UTF-8"));
		} finally {
			// 关闭连接
			in.close();
			out.close();
			socket.close();
		}
		
	}

}

用客户端分别测试上面三个TCP服务器:

MINA服务器输出结果:

sessionCreated
messageReceived:第一次请求
messageReceived:第二次请求
sessionClosed

Netty服务器输出结果:

channelActive
channelRead:第一次请求
channelRead:第二次请求
channelInactive

Twisted服务器输出结果:

connectionMade
dataReceived: 第一次请求
dataReceived: 第二次请求
connectionLost


作者:叉叉哥   转载请注明出处: http://blog.csdn.net/xiao__gui/article/details/38581355




作者:xiao__gui 发表于2014-8-15 10:53:19 原文链接
阅读:6 评论:0 查看评论

苹果将 iCloud 中国数据转存中国电信云服务,对用户有什么影响?

$
0
0
除了访问变快,几乎没有影响。

首先有一点必须要清楚,那就是 iCloud 数据原本就没有存放在苹果自己的数据中心,而是在微软的 Azure 和亚马逊的 Amazon Web Services (AWS) 上。现在苹果把中国账号的数据转存到中国电信的云上,其性质并没有发生变化。这一点苹果也没有隐瞒,在文档里明确表示,自己使用一些第三方存储服务商(using third-party storage services, such as Amazon S3 and Windows Azure)。

同时,中国用户没有必要担心放在中国电信的数据会被老大哥轻易看到。原因很简单,苹果自然也不希望数据被微软和亚马逊得到,因此所有数据都通过 AES 128bit 加密,对中国电信自然也不例外(如果有变化,那么 iCloud 的服务条款就要更改了,而目前还没有这方面的消息)。当然,这里我用了轻易一词,因为凡事无例外,再安全的加密也有被破解的可能。

其次,对于目前排名第一答案的担心,也不用过于敏感,君不见,Dropbox、Google Drive、One Drive 这些国外的云存储服务都已经被老大哥拒之门外了,而他们没有一个把数据放在国内。一句话:是福不是祸,是祸躲不过。如果苹果的 iCloud Drive 也因为某些原因让老大哥不爽,那么无论数据是在国内还是国外,都在劫难逃,并没有本质区别。

— 完 —
本文作者: 知乎用户(登录查看详情)

【知乎日报】 你都看到这啦,快来点我嘛 Σ(▼□▼メ)

此问题还有 15 个回答,查看全部。
延伸阅读:
同作为云存储,iCloud 和 Dropbox 你更看好哪个?为什么?
你认为 iCloud 和现有在线存储服务的最大区别会在哪里?

互联网流量超出路由器上限 或致全球断网

$
0
0

互联网流量超出路由器上限 未来数周或断网

    BI中文站 8月13日报道

    如果你近期遭遇互联网连接问题,不用气恼,因为不只你一人遭遇该问题。

    原因是边界网关路由协议(以下简称“BGP”)的路由表变得过于庞大,以致于一些旧的路由器无法承受,从而导致互联网连接不稳定。

    BGP是运行于TCP上的一种自治系统的路由协议,主要功能是和其它的自治系统交换网络信息,即允许路由器共享互联网地图(map of the internet)。

    但当BGP的路由表变得过于庞大,以致于一些旧路由器的内存无法承受,就会导致路由器无法正常处理互联网流量。

互联网流量超出路由器上限 未来数周或断网

    据悉,这些旧路由器只能处理512000条IPv4路由。据互联网流量管理公司Dyn称,全球路由数量已于近期达到该上限。换言之,互联网地图内的路由数量过于庞大,导致旧路由器无法承受。

    该结果意味着什么呢?答案是一些互联网服务可能出现中断。北美网络运营商集团(NANOG)周二称,AT&T、Comcast、Sprint和Verizon等运营商已经遭遇严重的网络性能问题。而且在未来数周内,这一局面仍将继续。

    多家互联网服务提供商也证实,该问题确实影响了他们的服务。但Dyn公司称,该问题只是暂时的,并不会对互联网构成威胁,只要替换旧的路由器即可解决。(谭燃)

利用Java SE 8流处理数据II(译)

$
0
0
利用Java SE 8流处理数据
-- 结合Stream API的高级操作去表示富数据处理查询

本文是 Java Magazine 201405/06刊中的一篇文章,也是文章系列"利用Java SE 8流处理数据"中的第二篇,它基于flatMap()和collect()介绍了Java流的高级用法(2014.08.15最后更新)

在本系列的第一篇文章中,你看到了Java流让你能够使用与数据库操作相似的方法去处理集合。作为一个复习,清单1的例子展示了如何使用Stream API去求得大交易的金额之和。我们组建了一个管道,它由中间操作(filter和map)与最终操作(reduce)构成,图1形象地展示它。
清单1
int sumExpensive =
        transactions.stream()
        .filter(t -> t.getValue() > 1000)
        .map(Transaction::getValue)
        .reduce(0, Integer::sum);
图1

然而在系列的第一部分中,并没有研究这两个方法:
flatMap:这是一个中间操作,它允许将一个"map"和一个"flatten"操作结合在一起
collect:这是一个最终操作,它依据不同的方式,将流中的元素归集为一个结果。
这两个方法对于表达更为复杂的查询是十分有用的。例如,你可以将flatMap和collect结合起来,生成代表一个文字流中每个字母出现的次数的Map对象,如清单2所示。如果第一次看到这段代码觉得很惊奇时,但请不要担心。本文的目的就是要解释并探究这两个方法更多的细节。
清单2
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.*;

Stream<String> words = Stream.of("Java", "Magazine", "is", "the", "best");
Map<String, Long> letterToCount =
        words.map(w -> w.split(""))
        .flatMap(Arrays::stream)
        .collect(groupingBy(identity(), counting()));
清单2中的代码将会生成如清单3示的结果。棒极了,不是吗?让我们开始探究flatMap和collect方法是如何工作的。
清单3
[a:4, b:1, e:3, g:1, h:1, i:2, ..]

flatMap方法
假设你想找出文件中所有独一唯二的字。你会怎么做呢?
你可能认为这很简单;我们可以Files.lines(),在前面的文章中已见过了这个方法,因为它会返回一个包含文件中所有行的流。然后我们就可以使用map方法将每一行拆分成字,最后再使用distinct方法去除重复的字。第一次尝试得到的代码可能如清单4所示。
清单4
Files.lines(Paths.get("stuff.txt"))
        .map(line -> line.split("\\s+")) // Stream<String[]>
        .distinct() // Stream<String[]>
        .forEach(System.out::println);
不幸的是,这段程序并不十分正确。如果运行它,会得到令人生疑的结果,与下面的输出有些类似:
[Ljava.lang.String;@7cca494b
[Ljava.lang.String;@7ba4f24f
...
我们的第一次尝试确实打印出了代表几个流对象的字符串。那发生了什么呢?该方法的问题是,传给map方法的Lambda表达式返回的是文件中每一行的String数组(String[])。而我们真正想要的是一个表示文字的流的Stream<String>对象。
幸运的是,对于该问题有一个解决方案,就是使用flatMap方法。让我们一步一步地看看如何得到正确的解决方法。
首先,我们需要字的流,而不是数组的流。有一个名为Arrays.stream()的方法,它将使用一个数组作为参数,并生成一个流。请看清单5中的例子。
清单5
String[] arrayOfWords = {"Java", "Magazine"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
让我们在前面的流管道中使用该方法,看看会发生什么(见清单6)。这个方案依然行不通。那是因为我们最终得到的是一组流的流(准确地说,就是Stream<Stream<String>>)。确切地是,我们首先将每一行转换为一个字的数组,然后使用方法Arrays.stream()将每一个数组转换成一个流。
清单6
Files.lines(Paths.get("stuff.txt"))
       .map(line -> line.split("\\s+")) // Stream<String[]>
       .map(Arrays::stream) // Stream<Stream<String>>
       .distinct() // Stream<Stream<String>>
       .forEach(System.out::println);
我们使用flatMap()方法去解决这个问题,如清单7所示。使用flatMap()方法能够用流中的内容,而不是流去替换每一个生成的数组。换言之,通过map(Arrays::stream)方法生成的全部独立的流被合并或"扁平化"为一个流。图2形象地展示了使用flatMap()方法的效果。
清单7
Files.lines(Paths.get("stuff.txt"))
       .map(line -> line.split("\\s+")) // Stream<String[]>
       .flatMap(Arrays::stream) // Stream<String>
       .distinct() // Stream<String>
       .forEach(System.out::println);
本质上,flatMap让你可以使用其它流去替换另一个流中的每个元素,然后再将所有生成的流连合并为一个流。
请注意,flatMap()是一个通用的模式,在使用Optaional或CompletableFuture时,你还会看到它。

collect方法
现在让我们看看collect方法的更多细节。在本系列的第一篇文章中你所看到的方法,要么返回另一个流(即,这些方法是中间操作),要么返回一个值,例如一个boolean,一个int,或一个Optional对象(即,这些方法是最终操作)。
collect就是一个最终方法,但它有点儿不同,因为你可以用它将一个Stream对象转为一个List对象。例如,为了得到一个包含有所有高金额交易ID的列表,你可以使用像清单8那样的代码。
清单8
import static java.util.stream.Collectors.*;

List<Integer> expensiveTransactionsIds =
        transactions.stream()
        .filter(t -> t.getValue() > 1000)
        .map(Transaction::getId)
        .collect(toList());
传递给collect方法的参数就是一个类型为java.util.stream.Collector的对象。这个Collector对象是干什么的?本质上看,它描述了如何按照需要去收集流中的元素,再将它们生成为一个最终结果。之前用到的工厂方法Collector.toList()会返回一个Collector对象,它描述了如何将一个Stream对象归集为一个List对象。而且,Collctors内建有有许多相似的方法。例如,使用toSet()方法可以将一个Stream对象转化为一个Set对象,它会删除所有重复的元素。清单9中的代码展示了如何生成一个仅仅包含高金额交易所在城市的Set对象。(注意:在后面的例子中,我们假设Collectors类中的工厂方法都已通过语句import static java.util.stream.Collectors.*被静态引入了)
清单9
Set<String> cities =
        transactions.stream()
        .filter(t -> t.getValue() > 1000)
        .map(Transaction::getCity)
        .collect(toSet());
注意,无法保证会返回何种类型的Set对象。但是,通过使用toCollection(),你可以进行更多的控制。例如,若你想得到一个HashSet,可以传一个构造器给toCollection方法(见清单10)。
清单10
Set<String> cities =
        transactions.stream()
        .filter(t -> t.getValue() > 1000)
        .map(Transaction::getCity)
        .collect(toCollection(HashSet::new));
然而,这并不是你能用collect和Collector所做的全部事情。实际上,这只是你能用它们所做的事情中的极小部分。下面是一些你所能表达的查询的例子:
将交易按货币分类,并计算每种货币的交易金额之和(返回一个Map<Currency, Integer>对象)
将交易划分成两组:高金额交易和非高金额交易(返回一个Map<Boolean, List<Transaction>>对象)
创建多层分组,例如先按交易发生的城市分组,再进一步按它们是否为高金额交易进行分组(返回一个Map<String, Map<Boolean, List<Transaction>>>)
兴奋吗?很好。让我们看看,你是如何使用Stream API和Collector来表达上述查询的。我们首先从一个简单的例子开始,这个例子要对这个流进行"总结":计算它的平均值,最大值和最小值。然后我们再看看如何表达简单的分组,最后,再看看如何将Collector组合起来去创建更为强大的查询,例如多层分组。
总结。让我们用一些简单的例子来热身一下。在之前的文章中,你已经看到如何使用reduce方法去计算流中元素的数量,最小值,最大值和平均值,以及如何使用基本数据类型元素的流。有一些预定义的Collector类也能让你完成那些功能。例如,可以使用counting()方法去计算元素的数量,如清单11所示。
清单11
long howManyTransactions = transactions.stream().collect(counting());
你可以使用summingDouble(),summingInt()和summingLong()分别对流中元素类型为Double,Int或Long的属性求和。在清单12中,我们计算出了所有交易的金额之和。
清单12
int totalValue = transactions.stream().collect(summingInt(Transaction::getValue));
类似的,使用averagingDouble(),averagingInt()和averagingLong()去计算平均值,如清单13所示。
清单13
double average = transactions.stream().collect(averagingInt(Transaction::getValue));
另外,使用maxBy()和minBy()方法,可以计算出流中值最大和最小的元素。但这里有一个问题:你需要为流中元素定义一个顺序,以能够对它们进行比较。这就是为什么maxBy()和minBy()方法使用使用一个Comparator对象作为参数,图3表明了这一点。
图3

在清单14的例子中,我们使用了静态方法comparing(),它将传入的函数作为参数,从中生成一个Comparator对象。该函数用于从流的元素中解析出用于进行比较的关键字。在这个例子中,通过使用交易金额作为比较的关键字,我们找到了那笔最高金额的交易。
清单14
Optional<Transaction> highestTransaction =
        transactions.stream()
        .collect(maxBy(comparing(Transaction::getValue)));
还有一个reducing()方法,由它产生的Collector对象会把流中的所有元素归集在一起,对它们重复的应用同一个操作,直到产生结果。该方法与之前看过的reduce()方法在原理上一样的。例如,清单15展示了使用了基于reducing()方法的另一种方式去计算所有交易的金额之和。
清单15
int totalValue = transactions.stream().collect(reducing(0, Transaction::getValue, Integer::sum));
reducing()方法使用三个参数:
初始值(如果流为空,则返回它);此处,该值为0。
应用于流中每个元素的函数对象;此处,该函数会解析出每笔交易的金额。
将两个由解析函数生成的金额合并在一起的方法;此处,我们只是把金额加起来。
你可能会说,"等等,使用其它的流方法,如reduce(),max()和min(),我已经可以做到这些了。那么,你为什么还要给我看这些方法呢?"后面,你将会看到我们将Collector结合起来去构建更为复杂的查询(例如,对加法平均数进行分组),所以,这也能更易于理解这些内建的Collector。
分组。这是一个普通的数据库查询操作,它使用属性去数据进行分组。例如,你也许想按币种对一组交易进行分组。若你使用如清单16所示的代码,通过显式的遍历去表达这个查询,那会是很痛苦的。
清单16
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap< >();
for(Transaction transaction : transactions) {
    Currency currency = transaction.getCurrency();
    List<Transaction> transactionsForCurrency =
    transactionsByCurrencies.get(currency);

    if (transactionsForCurrency == null) {
        transactionsForCurrency = new ArrayList<>();
        transactionsByCurrencies.put(currency, transactionsForCurrency);
    }
    transactionsForCurrency.add(transaction);
}
你首先需要创建一个Map对象,它将收集所有的交易记录。然后,你需要遍历所有的交易记录,并解析出每笔交易的币种。在将交易记录使用一个值插入Map中之前,需要先检查一下,这个List是否已经创建过了,等等。
真是令人汗颜啊,因为我们想要是"按币种对交易进行分组"。为什么不得不涉及这么多代码呢?有好消息:有一个称为groupingBy()的Collector,它允许我们以简洁的方式来表达这个例子。我们可以使用清单17中的例子来表达这个相同的查询,现在代码的阅读更接近问题语句了。
清单17
Map<Currency, List<Transaction>> transactionsByCurrencies =
        transactions.stream().collect(groupingBy(Transaction::getCurrency));
工厂方法groupingBy()使用一个函数对象作为参数,该函数会解析出用于分类交易记录的关键字。我们称为这个函数为分类函数。在此处,为了按币种对交易进行分组,我们传入一个方法引用,Transaction::getCurrency。图4演示了这个分组操作。
图4

分割。有一个称为partitioningBy()的工厂方法,它可被视为一种特殊的groupingBy()方法。它使用一个谓语作为参数(该参数返回一个boolean值),然后根据元素是否满足这个谓语对它们进行分组。换言之,它将组成流的交易分割成了结构Map<Boolean, List<Transaction>>。例如,如若你想将交易分割成两组--低廉的和昂贵的--你就可以像清单18那样去使用partitioningBy()产生的Collector。此例中的Lambda表达式,t->t.getValue() > 1000,就是一个将交易分成低廉和昂贵的谓语。
清单18
Map<Boolean, List<Transaction>> partitionedTransactions =
        transactions.stream().collect(partitioningBy(t -> t.getValue() > 1000));
组合Collector。如果你熟悉SQL,你应该知道可以将GROUP BY与函数COUNT()和SUM()一块儿使用,以按币种和交易金额之和进行分组。那么,使用Stream API是否也可以实现相似的功能呢?当然可以。确切地说,有一个重载的groupingBy()方法,它使用另一个Collector作为第二个参数。这个额外的Collector对象用于定义在使用由groupingBy()产生的Collector时如何汇集所有与关键字相关的元素。
好吧,这听起来有些抽象,那么让我们看一个简单的例子。我们想基于每个城市的交易金额之和生成一个城市的Map对象(见清单19)。在此处,我们告诉groupingBy()方法使用getCity()方法作为分类方法。那么,得到的Map结果的Key就为城市。正常地,我们期望对Map中每个键所对应的值,即List<Transaction>对象,使用groupingBy()方法。
清单19
Map<String, Integer> cityToSum =
        transactions.stream().collect(groupingBy(Transaction::getCity,
        summingInt(Transaction::getValue)));
然后,我们却是传入了另一个Collector对象,它由summingInt()方法产生,该方法会将所有与特定城市相关的交易记录的金额加起来。结果,我们得到了一个Map<String, Integer>对象,它将每个城市与它们对应的所有交易的金额之和进行了映射。酷,不是吗?想想这个:基本的groupingBy(Transaction:getCity)方法其实就只是groupingBy(Transaction:getCity, toList())的简写。
让我们看看另一个例子。如果你想生成这样一个Map,它对每个城市与它的最大金额的交易记录进行映射,那要怎么做呢?你可能已经猜到了,我们可以重用前面过的由maxBy()方法产生的Collector,如清单20所示。
清单20
Map<String, Optional<Transaction>> cityToHighestTransaction =
        transactions.stream().collect(groupingBy(Transaction::getCity,
        maxBy(comparing(Transaction::getValue))));
你已经看到Stream API很善于表达,我们正在构建的一些十分有趣的查询都可以写的简洁些。你还能想象出回到从前去遍历地处理一个集合吗?让我们看一个更为复杂的例子,以结束这篇文章。你已看到groupingBy()方法可以将一个Collector对象作为参数,再根据进一步的分类规则去收集流中的元素。因为groupingBy()方法本身得到的也是一个Collector对象,那么通过传入另一个由groupingBy()方法得到的Collector对象,该Collector定义了第二级的分类规范,我们就能够创建多层次分组。
在清单21的代码中,先按城市对交易记录进行分组,再进一步对每个城市中的交易记录按币种进行分组,以得到每个城市中每个币种的所有交易记录的平均金额。图5就形象地展示了这种机制。
清单21
Map<String, Map<Currency, Double>> cityByCurrencyToAverage =
        transactions.stream().collect(groupingBy(Transaction::getCity,
        groupingBy(Transaction::getCurrency,  
        averagingInt(Transaction::getValue))));
图5

创建你自己的Collector。到目前为止,我们展示的全部Collector都实现了接口java.util.stream.Collector。这就意味着,你可以实现自己的Collector,以"定制"归一操作。但是对于这个主题,再写一篇文章可能更合适一些,所以我们不会在本文中讨论这个问题。

结论
在本文中,我们探讨了Stream API中的两个高级:flatMap和collect。它们是可以加到你的兵器库中的工具,可以用来表述丰富的数据处理查询。
特别地,你也已经看到了,collect()方法可被用于归纳,分组和分割操作。另外,这些操作还可能被结合在一起,去构建更为丰富的查询,例如"生产一个两层Map对象,它代表每个城市中每个币种的平均交易金额"。
然而,本文也没有查究到所有的内建Collector实现。请你去看看Collectors类,并试试其它的Collector实现,例如由mapping(),joining()和collectingAndThen(),也许你会发现它们也很有用。


Sha Jiang 2014-08-15 19:57 发表评论

[干货]APP用户引导设计秘籍

$
0
0

yindao1用户引导的目的

用户引导,其主要目的当然是引导用户,开个玩笑啦!一般而言,用户引导的目的主要有两种:用户需求和产品需求。

用户需求

即出于用户自己的需求,这一类引导主要的目的是 为了使产品更加方便用户,让用户能够更加顺畅便利地使用自己的产品。比较极限的例子就是Paper,它的交互基本建立在全手势操作地原则之上,所以在用户初次接触的阶段做了大量的引导设计。一反“ 用户引导都会给用户带来困扰”的印象,根据Stef Miller在User Testing网站上发布的文章《Six UX Lessons Learned from the New Facebook App, Paper》中给出的测试结果,用户非常喜欢Paper的引导设计,因为它在需要用到某项功能的时候恰到好处地出现,帮助无所适从的用户找到方向。用户喜欢这种“及时雨”式的引导方式,105名测试用户给Paper的引导打出了4.47/5的分数。

yindao2

产品需求

即出于产品方向上的思考带来的需求,这一类引导主要的目的是 为了塑造用户的使用习惯,提高特定功能的转化率,或者降低特定功能的PV。比如微信朋友圈,在用户第一次通过长按照相机按钮触发测试功能——发布纯文字朋友圈时,会提醒用户“为内部测试功能,后续版本可能取消”,这就是一种对用户使用习惯的塑造。当然,这种“内部功能”的噱头在当时微信的推广期也有自己的一份功劳,时至今日,相信每天发布纯文字朋友圈的量不会太小,微信真正取消这个功能恐怕会比较困难。

yindao3

用户引导的基本思路

针对不同的目的,设计用户引导时的思路也各有不同。对于用户需求类引导,主要目的是 优化用户体验、帮助用户更好地使用这款应用,那么这个引导本身就需要在体验上多做文章,尽可能地“不打扰”用户,让用户在愉悦的心情中学习到一个又一个方便简单地功能或者操作方式。(此类引导通常都会采用下面说到的非阻断式引导。)

而对于产品需求类引导,为了塑造用户的产品使用习惯,达到PM的目的,通常会采用比较“破坏”用户体验地方式,以保证对用户的 有效引导。之所以可以这样做,是因为不管怎样优化用户引导,突然出现地多余元素都是一种对操作成本的提高,假若过多追求用户体验,结果就是在没有达到目的的同时提高了用户使用成本,最终舍本逐末。所以,在做产品需求类引导时,须谨记,永远记得自己出发的目的。(此类引导通常都会采用下面说到的阻断式引导。)

非阻断式引导

即用户使用过程中闪现地引导元素,不影响用户的操作连贯性,不打断用户的使用。换句话说,引导出现之时,用户仍然是想干什么就干什么,除了一定视觉干扰之外,完全不受到限制。例如Facebook客户端中普遍采用地浮标式引导设计,这种设计利用界面中额外出现地视觉元素吸引注意力,但不会阻碍用户正常的操作,用户完全可以忽视弹出地引导。

yindao4

      此种引导对于用户干扰小,在用户体验上较为优秀, 通常用户需求类引导采用这种方式

阻断式引导

顾名思义,此类引导会阻断用户地正常操作,用户至少需要一步点击来结束引导。例如微信当中第一次进入朋友圈点击相机按钮时弹出地引导,用户必须点击“我知道了”才能关闭此对话框;再比如Tidy中的用户引导,用户需要点击“Got it”才能完成引导过程。这一类阻断式地引导有两个好处:其一,引导阅读率更高,用户几乎不可能错过;其二,适当情况下,阻断式引导可以放入更多的字,充分说明需要解释的问题,例如特定功能的隐性规则。

yindao5

此种引导对用户干扰较大,但是引导效果好, 通常产品需求类引导采用这种方式。但是如果引导的内容较多、较复杂,比如Paper基于产品需求的复杂的交互手势的引导,也会采用这种阻断式引导来保证用户的学习率。

yindao6

用户引导设计要点

不管是阻断式还是非阻断式引导,如下几个要点是必须遵从的。

触发情境自然

如果你的应用在一开始就给出5页10页的使用教程,用户既不会看,也不喜欢,即便看了,也记不住。因为引导的触发情境不对,所以用户并不需要这些引导,记忆这些复杂的信息也是一种很大的压力。所以, 事前引导是不正确的,真正好的引导应当是出现在用户最需要它的时候,即触发情境要是用户刚刚要使用或者刚刚使用完此功能的时候,前者可以是对功能的隐性规则的说明,也可以是对复杂交互的一个教学;后者可以是对用户后续操作地一个预期,降低后续操作地成本。

尽量减少文字

过多的文字让本来就是正常使用流程中的“干扰”——用户引导雪上加霜,茫茫多的文字会给予用户非常大的压力,以至于很多用户在看到长串文字之后下意识地会选择直接跳过。尤其对于移动应用,屏幕空间是无比宝贵的,况且人对图形的记忆比对文字的记忆要来得更简单, 引导最好是展示出来,而不是说出来

增加引导趣味性

避免单纯的说教,增加引导在形式、内容上的趣味性。比如可以给出较为夸张的赞美式反馈,或者是采用较为活泼的动态设计,再或者甚至可以采取比较可爱的动画形象用拟人化、口语化的语言进行引导。这些方法都是通过增加趣味性来降低用户对于“被打扰”这件事的反感,然后让用户在愉悦的心情下快速掌握引导内容。

总结

紧紧抓住引导目的,遵循引导设计的基本思路,选择合适的引导类型,设计过程中注意几大要点,相信优秀的用户引导设计马上就会出炉!

来源: 早读课  作者:陈橙

 


(关注更多人人都是产品经理观点,参与微信互动(微信搜索“人人都是产品经理”或“woshipm”)

sftp服务限制用户登录家目录

$
0
0

 

 

              sftp服务限制用户登录家目录

        sftp和ftp是两种协议是不同的,sftp是ssh内含的协议,只要sshd服务器启动了,它就可用,它本身不需要ftp服务器启动。

1.查看openssh软件版本,想sftp服务用户只能访问特定的文件目录,版本需要4.8以上

1
2
3
4
[root@localhost ftp]# rpm -qa | grep openssh
openssh-server-5.3p1-81.el6_3.x86_64
openssh-5.3p1-81.el6_3.x86_64
openssh-clients-5.3p1-81.el6_3.x86_64

2.新增用户,限制用户只能通过sftp访问

1
[root@localhost ftp]# useradd -m -d /opt/ftp/dave -s /sbin/nologin dave

3.限制用户通过sftp登录进来时只能进入主目录,修改/etc/ssh/sshd_config文件

 

1
[root@localhost ftp]# vim /etc/ssh/sshd_config
1
2
3
4
5
6
7
#Subsystem  sftp    /usr/libexec/openssh/sftp-server
Subsystem sftpinternal-sftp
Match User dave
         ChrootDirectory /opt/ftp/dave
         X11Forwarding no
         AllowTcpForwarding no
         ForceCommand internal-sftp

重启ssh

4.测试访问

 

1
2
3
4
5
root@10.1.1.200:test# sftp -oPort=22 dave@10.1.6.175
Connecting to 10.1.6.175...
dave@10.1.6.175's password:
Read from remote host 10.1.6.175: Connection reset by peer
Couldn't readpacket: Connection reset by peer

发现连接不上,查看日志

 

1
2
3
[root@localhost ftp]# tail /var/log/messages
Jan  6 11:41:41 localhost sshd[4907]: fatal: bad ownership or modes forchroot directory "/opt/ftp/dave"
Jan  6 11:41:41 localhost sshd[4905]: pam_unix(sshd:session): session closed foruser dave

解决方法:

目录权限设置上要遵循2点:

ChrootDirectory设置的目录权限及其所有的上级文件夹权限,属主和属组必须是root;

ChrootDirectory设置的目录权限及其所有的上级文件夹权限,只有属主能拥有写权限,权限最大设置只能是755。

如果不能遵循以上2点,即使是该目录仅属于某个用户,也可能会影响到所有的SFTP用户。

1
2
3
4
5
6
7
8
[root@localhost ftp]# ll
total 4
drwxr-xr-x 3 dave dave 4096 Jan  5 13:06 dave
[root@localhost ftp]# chown root:root dave
[root@localhost ftp]# chmod 755 dave
[root@localhost ftp]# ll
total 4
drwxr-xr-x 3 root root 4096 Jan  5 13:06 dave

然后在测试通过

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@10.1.1.200:test# sftp -oPort=22 dave@10.1.6.175
Connecting to 10.1.6.175...
dave@10.1.6.175's password:
sftp> ls
test 
sftp> cd..
sftp> ls
test 
sftp> cdtest
sftp> ls
1.txt 
sftp> get 1.txt
Fetching /test/1.txt to 1.txt
/test/1.txt

 

可以看到已经限制用户在家目录,同时该用户也不能登录该机器。 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



ssh sftp配置及权限设置

$
0
0

 

 

从技术角度来分析,几个要求:
1、从安全方面看,sftp会更安全一点
2、线上服务器提供在线服务,对用户需要控制,只能让用户在自己的home目录下活动
3、用户只能使用sftp,不能ssh到机器进行操作
 
提供sftp服务,可以用系统自带的internal-sftp,也可以使用vsftpd,这里需求不多,直接选用internal-sftp。
 
限制用户只能在自己的home目录下活动,这里需要使用到chroot,openssh 4.8p1以后都支持chroot,我现在用的是CentOS 6.3,自带的openssh已经是5.3p1,足够了。
 
可以输入:
# ssh -V  
来查看openssh的版本,如果低于4.8p1,需要自行升级安装,不在这里具体介绍了。
 
假设,有一个名为sftp的组,这个组中的用户只能使用sftp,不能使用ssh,且sftp登录后只能在自己的home目录下活动
 
1、创建sftp组
# groupadd sftp  
 
2、创建一个sftp用户,名为mysftp
# useradd -g sftp -s /bin/false mysftp
# passwd mysftp
 
3、sftp组的用户的home目录统一指定到/data/sftp下,按用户名区分,这里先新建一个mysftp目录,然后指定mysftp的home为/data/sftp/mysftp
 
# mkdir -p /data/sftp/mysftp
# usermod -d /data/sftp/mysftp mysftp
 
4、配置sshd_config
编辑 /etc/ssh/sshd_config
 
# vim +132 /etc/ssh/sshd_config  
找到如下这行,并注释掉
Subsystem      sftp    /usr/libexec/openssh/sftp-server  
 
添加如下几行
Subsystem       sftp    internal-sftp  
Match Group sftp  
ChrootDirectory /data/sftp/%u  
ForceCommand    internal-sftp  
AllowTcpForwarding no  
X11Forwarding no  
 
解释一下添加的几行的意思
 
Subsystem       sftp    internal-sftp  
这行指定使用sftp服务使用系统自带的internal-sftp
 
Match Group sftp  
这行用来匹配sftp组的用户,如果要匹配多个组,多个组之间用逗号分割
 
当然,也可以匹配用户
Match User mysftp
这样就可以匹配用户了,多个用户名之间也是用逗号分割,但我们这里按组匹配更灵活和方便
 
ChrootDirectory /data/sftp/%u  
用chroot将用户的根目录指定到/data/sftp/%u,%u代表用户名,这样用户就只能在/data/sftp/%u下活动,chroot的含 义,可以参考这里:http://www.ibm.com/developerworks/cn/linux/l-cn-chroot/
 
ForceCommand    internal-sftp  
指定sftp命令
 
AllowTcpForwarding no  
X11Forwarding no  
这两行,如果不希望该用户能使用端口转发的话就加上,否则删掉
 
5、设定Chroot目录权限
# chown root:sftp /data/sftp/mysftp
# chmod 755 /data/sftp/mysftp
 
错误的目录权限设定会导致在log中出现”fatal: bad ownership or modes for chroot directory XXXXXX”的内容
 
目录的权限设定有两个要点:
1、由ChrootDirectory指定的目录开始一直往上到系统根目录为止的目录拥有者都只能是root
2、由ChrootDirectory指定的目录开始一直往上到系统根目录为止都不可以具有群组写入权限
 
所以遵循以上两个原则
1)我们将/data/sftp/mysftp的所有者设置为了root,所有组设置为sftp
2)我们将/data/sftp/mysftp的权限设置为755,所有者root有写入权限,而所有组sftp无写入权限
 
6、建立SFTP用户登入后可写入的目录
照上面设置后,在重启sshd服务后,用户mysftp已经可以登录,但使用chroot指定根目录后,根应该是无法写入的,所以要新建一个目录供mysftp上传文件。这个目录所有者为mysftp,所有组为sftp,所有者有写入权限,而所有组无写入权限
 
# mkdir /data/sftp/mysftp/upload  
# chown mysftp:sftp /data/sftp/mysftp/upload  
# chmod 755 /data/sftp/mysftp/upload  
 
7、重启sshd服务
 
# service sshd restart  
 
到这里,mysftp已经可以通过sftp客户端登录并可以上传文件到upload目录。
如果还是不能在此目录下上传文件,提示没有权限,检查SElinux是否关闭,可以使用如下指令关闭SElinux
修改/etc/selinux/config文件中的SELINUX="" 为 disabled ,然后重启。或者
# setenforce 0
 
一开始,我以为是权限问题导致的无法上传文件,即使给777权限给文件夹也无法上传文件,关闭SElinux才可以正常上传。
 
 


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Struts2实现文件上传并显示实时进度

$
0
0

        基于浏览器的文件上传,特别是对于通过<input type="file">标签来实现上传的情况, 存在着严重的性能问题,因为用户提交了文件之后,在浏览器把文件上传到服务器的过程中,界面看上去似乎是静止的,如果是小文件还好些,如果不幸需要上传的 是几兆、几十兆甚至上百兆的文件,我相信那是一种非常痛苦的体验,我们中间的很多人应该都有过此种不堪的经历。

      我们为了改善用户界面,通常会在处理量大或者是网络速度较慢的时候,给用户显示一个处理进度,让用户心理有底,增强用户等待结果的耐心,以改善用户体验。

  现在我就针对这个问题给出一个解决方案,我们将实现一个具有监控能力的WEB上传的程序——它不仅把文件上传到服务器,而且"实时地"监视文件上传的实际过程。

解决方案的基本思路是这样的:

  •   在Form提交上传文件同时,使用AJAX周期性地从Action轮询上传状态信息
  •   然后,根据此信息更新进度条和相关文字,及时反映文件传输状态

 

实现一个文件监听类,实现对文件上传进度的实时监听,并将监听结果存放到session中,公前台界面读取。

/*
 * Copyright (c) 2012-2032 Accounting Center of China Aviation(ACCA).
 * All Rights Reserved.
 */
package com.wallet.myWallet.listener;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.ProgressListener;

import com.wallet.myWallet.entity.State;

/**
 * 
 * 
 * @author zhouhua, 2014-7-16
 */
public class FileUploadListener implements ProgressListener {

    // 声明一个HttpSession,目的是把State对象放到这个HttpSession中
    private HttpSession session;

    // 此构造函数由MyJakartaMultiPartRequest.java类parseRequest()方法调用
    public FileUploadListener(HttpServletRequest request) {
        super();
        session = request.getSession();
    }

    public void update(long uploadByte, long fileSizeByte, int fileIndex) {
        if (fileSizeByte == -1) {
            // 如果上传的大小为-1则上传已经完成
            System.out.println("上传文件结束!");
        } else {
            if (session.getAttribute("uploadState") == null) {
                // 如果为空就new一个State对象并设置里面的文本内容
                State state = new State();
                state.setState(uploadByte, fileSizeByte, (fileIndex - 1));
                session.setAttribute("uploadState", state);
            } else {
                // 如果session中有uploadState对象就取出来,然后设置里面文本内容
                State state = (State) session.getAttribute("uploadState");
                state.setState(uploadByte, fileSizeByte, (fileIndex - 1));
            }
        }
    }

}


接下来是一个文件状态类:

/*
 * Copyright (c) 2012-2032 Accounting Center of China Aviation(ACCA).
 * All Rights Reserved.
 */
package com.wallet.myWallet.entity;

import java.text.NumberFormat;
import java.text.SimpleDateFormat;

/**
 * 
 * 
 * @author zhouhua, 2014-7-16
 */
public class State {

    private long uploadByte; // 已经上传的字节数,单位:字节
    private long fileSizeByte; // 所有文件的总长度,单位:字节
    private int fileIndex; // 正在上传第几个文件
    private long startTime; // 开始上传的时间,用于计算上传速度等
    private int percent; // 上传百分比
    private long speed;
    private long time; 
    private static final SimpleDateFormat SIMPLEFORMAT = new SimpleDateFormat("HH:mm:ss");

    public State() {
        startTime = System.currentTimeMillis();
        percent = 0;
        speed=0L;
    }

    // 从State状态类中取得状态的字符串,用字符串的形式拼成XML文件内容
    public synchronized String getStateString() {
        StringBuilder sb = new StringBuilder("<info>");
        sb.append("<uploadByte>" + NumberFormat.getInstance().format(uploadByte/(1024*1024))
 + "</uploadByte>");
   sb.append("<fileSizeByte>" + NumberFormat.getInstance().format(fileSizeByte/(1024*1024))
                + "</fileSizeByte>");
   sb.append("<speed>" + NumberFormat.getInstance().format((speed/(1024*1024))/time)
    + "</speed>");
        sb.append("<fileIndex>" + fileIndex + "</fileIndex>");
        sb.append("<percent>" + percent + "</percent>");
        sb.append("<startTime>" + SIMPLEFORMAT.format(startTime) + "</startTime>");
        sb.append("</info>");
        return sb.toString();
    }

    public synchronized void setState(long uploadByte, long fileSizeByte, int fileIndex) {
        this.uploadByte = uploadByte;
        this.fileSizeByte = fileSizeByte;
        this.fileIndex = fileIndex;
        this.speed=uploadByte-speed;
        this.time=(System.currentTimeMillis()-startTime)/1000;
        if ((Long.valueOf(uploadByte) * 100 / Long.valueOf(fileSizeByte) <= 100)) {
            // 生成当前上传进度的公式,加入判断条件的含义在于不需要重复计算
            percent = (int) (Long.valueOf(uploadByte) * 100 / Long.valueOf(fileSizeByte));
        }
    }
}


如果想通过Struts2监听文件上传的进度,我们需要自己实现Struts2的MultiPartRequest类并将自己的文件上传监听类注入,实现类如下:

/*
 * Copyright (c) 2012-2032 Accounting Center of China Aviation(ACCA).
 * All Rights Reserved.
 */
package com.wallet.myWallet.listener;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;

import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.LocalizedTextUtil;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

/**
 * 
 * 
 * @author zhouhua, 2014-7-16
 */
public class MyJakartaMultiPartRequest implements MultiPartRequest {

    static final Logger LOG = LoggerFactory.getLogger(MyJakartaMultiPartRequest.class);
    // maps parameter name -> List of FileItem objects
    protected Map<String, List<FileItem>> files = new HashMap<String, List<FileItem>>();
    // maps parameter name -> List of param values
    protected Map<String, List<String>> params = new HashMap<String, List<String>>();
    // any errors while processing this request
    protected List<String> errors = new ArrayList<String>();
    protected long maxSize;
    private Locale defaultLocale = Locale.ENGLISH;

    @Inject(StrutsConstants.STRUTS_MULTIPART_MAXSIZE)
    public void setMaxSize(String maxSize) {
        this.maxSize = Long.parseLong(maxSize);
    }

    @Inject
    public void setLocaleProvider(LocaleProvider provider) {
        defaultLocale = provider.getLocale();
    }

    /**
     * Creates a new request wrapper to handle multi-part data using methods adapted
     * from Jason
     * Pell's multipart classes (see class description).
     * 
     * @param saveDir the directory to save off the file
     * @param request the request containing the multipart
     * @throws java.io.IOException is thrown if encoding fails.
     */
    public void parse(HttpServletRequest request, String saveDir) throws IOException {
        try {
            setLocale(request);
            processUpload(request, saveDir);
        } catch (FileUploadBase.SizeLimitExceededException e) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Request exceeded size limit!", e);
            }
            String errorMessage = buildErrorMessage(e,
                    new Object[] { e.getPermittedSize(), e.getActualSize() });
            if (!errors.contains(errorMessage)) {
                errors.add(errorMessage);
            }
        } catch (Exception e) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Unable to parse request", e);
            }
            String errorMessage = buildErrorMessage(e, new Object[] {});
            if (!errors.contains(errorMessage)) {
                errors.add(errorMessage);
            }
        }
    }

    protected void setLocale(HttpServletRequest request) {
        if (defaultLocale == null) {
            defaultLocale = request.getLocale();
        }
    }

    protected String buildErrorMessage(Throwable e, Object[] args) {
        String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName();
        if (LOG.isDebugEnabled()) {
            LOG.debug("Preparing error message for key: [#0]", errorKey);
        }
        return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale,
         e.getMessage(),args);
    }

    private void processUpload(HttpServletRequest request, String saveDir)
        throws FileUploadException, UnsupportedEncodingException {
        for (FileItem item : parseRequest(request, saveDir)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Found item " + item.getFieldName());
            }
            if (item.isFormField()) {
                processNormalFormField(item, request.getCharacterEncoding());
            } else {
                processFileField(item);
            }
        }
    }

    private void processFileField(FileItem item) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Item is a file upload");
        }
        // Skip file uploads that don't have a file name - meaning that no file was selected.
        if (item.getName() == null || item.getName().trim().length() < 1) {
            LOG.debug("No file has been uploaded for the field: " + item.getFieldName());
            return;
        }
        List<FileItem> values;
        if (files.get(item.getFieldName()) != null) {
            values = files.get(item.getFieldName());
        } else {
            values = new ArrayList<FileItem>();
        }
        values.add(item);
        files.put(item.getFieldName(), values);
    }

    private void processNormalFormField(FileItem item, String charset)
        throws UnsupportedEncodingException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Item is a normal form field");
        }
        List<String> values;
        if (params.get(item.getFieldName()) != null) {
            values = params.get(item.getFieldName());
        } else {
            values = new ArrayList<String>();
        }
        // note: see http://jira.opensymphony.com/browse/WW-633
        // basically, in some cases the charset may be null, so
        // we're just going to try to "other" method (no idea if this
        // will work)
        if (charset != null) {
            values.add(item.getString(charset));
        } else {
            values.add(item.getString());
        }
        params.put(item.getFieldName(), values);
        item.delete();
    }

private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir)
        throws FileUploadException {
        DiskFileItemFactory fac = createDiskFileItemFactory(saveDir);
        ServletFileUpload upload = new ServletFileUpload(fac);
        // 设置上传进度的监听
        upload.setProgressListener(new FileUploadListener(servletRequest));
        upload.setSizeMax(maxSize);
        return upload.parseRequest(createRequestContext(servletRequest));
    }

    private DiskFileItemFactory createDiskFileItemFactory(String saveDir) {
        DiskFileItemFactory fac = new DiskFileItemFactory();
        // Make sure that the data is written to file
        fac.setSizeThreshold(0);
        if (saveDir != null) {
            fac.setRepository(new File(saveDir));
        }
        return fac;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames()
     */
    public Enumeration<String> getFileParameterNames() {
        return Collections.enumeration(files.keySet());
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * 
     */
    public String[] getContentType(String fieldName) {
        List<FileItem> items = files.get(fieldName);
        if (items == null) {
            return null;
        }
        List<String> contentTypes = new ArrayList<String>(items.size());
        for (FileItem fileItem : items) {
            contentTypes.add(fileItem.getContentType());
        }
        return contentTypes.toArray(new String[contentTypes.size()]);
    }

    /*
     * (non-Javadoc)
     * 
     * 
     */
    public File[] getFile(String fieldName) {
        List<FileItem> items = files.get(fieldName);
        if (items == null) {
            return null;
        }
        List<File> fileList = new ArrayList<File>(items.size());
        for (FileItem fileItem : items) {
            File storeLocation = ((DiskFileItem) fileItem).getStoreLocation();
            if (fileItem.isInMemory() && storeLocation != null && !storeLocation.exists()) {
                try {
                    storeLocation.createNewFile();
                } catch (IOException e) {
                    if (LOG.isErrorEnabled()) {
                        LOG.error("Cannot write uploaded empty file to disk: "
                                        + storeLocation.getAbsolutePath(), e);
                    }
                }
            }
            fileList.add(storeLocation);
        }
        return fileList.toArray(new File[fileList.size()]);
    }


    public String[] getFileNames(String fieldName) {
        List<FileItem> items = files.get(fieldName);
        if (items == null) {
            return null;
        }
        List<String> fileNames = new ArrayList<String>(items.size());
        for (FileItem fileItem : items) {
            fileNames.add(getCanonicalName(fileItem.getName()));
        }
        return fileNames.toArray(new String[fileNames.size()]);
    }


    public String[] getFilesystemName(String fieldName) {
        List<FileItem> items = files.get(fieldName);
        if (items == null) {
            return null;
        }
        List<String> fileNames = new ArrayList<String>(items.size());
        for (FileItem fileItem : items) {
            fileNames.add(((DiskFileItem) fileItem).getStoreLocation().getName());
        }
        return fileNames.toArray(new String[fileNames.size()]);
    }


    public String getParameter(String name) {
        List<String> v = params.get(name);
        if (v != null && v.size() > 0) {
            return v.get(0);
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames()
     */
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(params.keySet());
    }


    public String[] getParameterValues(String name) {
        List<String> v = params.get(name);
        if (v != null && v.size() > 0) {
            return v.toArray(new String[v.size()]);
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors()
     */
    public List<String> getErrors() {
        return errors;
    }

    /**
     * Returns the canonical name of the given file.
     * 
     * @param filename the given file
     * @return the canonical name of the given file
     */
    private String getCanonicalName(String filename) {
        int forwardSlash = filename.lastIndexOf("/");
        int backwardSlash = filename.lastIndexOf("\\");
        if (forwardSlash != -1 && forwardSlash > backwardSlash) {
            filename = filename.substring(forwardSlash + 1, filename.length());
        } else if (backwardSlash != -1 && backwardSlash >= forwardSlash) {
            filename = filename.substring(backwardSlash + 1, filename.length());
        }
        return filename;
    }

    /**
     * Creates a RequestContext needed by Jakarta Commons Upload.
     * 
     * @param req the request.
     * @return a new request context.
     */
    private RequestContext createRequestContext(final HttpServletRequest req) {
        return new RequestContext() {
            public String getCharacterEncoding() {
                return req.getCharacterEncoding();
            }

            public String getContentType() {
                return req.getContentType();
            }

            public int getContentLength() {
                return req.getContentLength();
            }

            public InputStream getInputStream() throws IOException {
                InputStream in = req.getInputStream();
                if (in == null) {
                    throw new IOException("Missing content in the request");
                }
                return req.getInputStream();
            }
        };
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
     */
    public void cleanUp() {
        Set<String> names = files.keySet();
        for (String name : names) {
            List<FileItem> items = files.get(name);
            for (FileItem item : items) {
                if (LOG.isDebugEnabled()) {
                    String msg = LocalizedTextUtil.findText(this.getClass(),"struts.messages.removing.file", Locale.ENGLISH, "no.message.found",
                            new Object[] { name, item });
                    LOG.debug(msg);
                }
                if (!item.isInMemory()) {
                    item.delete();
                }
            }
        }
    }
}


自己的类实现MultiPartRequest后,需要在Struts.xml文件中进行装配:

<!--demo12,struts2文件上传与下载 --><bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"
		name="parser" class="com.wallet.myWallet.listener.MyJakartaMultiPartRequest"
		scope="default" /><!--demo12,struts2文件上传与下载 --><constant name="struts.multipart.parser" value="parser" />


前面的工作做完后就需要写两个Action,一个用于文件上传,另一个用于读取文件上传进度:

 

文件上传:

/*
 * Copyright (c) 2012-2032 Accounting Center of China Aviation(ACCA).
 * All Rights Reserved.
 */
package com.wallet.myWallet.action;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

import com.wallet.core.action.BaseAction;
import com.wallet.myWallet.entity.DataImportStatus;
import com.wallet.myWallet.entity.FileUploadTools;

/**
 * 
 * 
 * @author zhouhua, 2014-7-15
 */
@Controller
@Scope("prototype")
public class FileUploadAction extends BaseAction<Object> {

    /**  */
    private static final long serialVersionUID = -2091410772080750644L;

    // 声明封装了File上传的FileUploadTools类的实例
    // FileUploadTools类也封装了上传的属性及get和set方法
    private FileUploadTools fileUploadTools = new FileUploadTools();

    private Map jsonMap = new HashMap();

    public Map getJsonMap() {
        return jsonMap;
    }

    public void setJsonMap(Map jsonMap) {
        this.jsonMap = jsonMap;
    }

    public FileUploadTools getFileUploadTools() {
        return fileUploadTools;
    }

    public void setFileUploadTools(FileUploadTools fileUploadTools) {
        this.fileUploadTools = fileUploadTools;
    }

    /**
     * 处理文件上传
     * 
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    public String upload() throws IOException, InterruptedException {
        // 文件上传
        fileUploadTools.beginUpload();
        getSession().setAttribute("uploadState", null);
        jsonMap.put("flg", true);
        return "upload";
    }

}

 

/*
 * Copyright (c) 2012-2032 Accounting Center of China Aviation(ACCA).
 * All Rights Reserved.
 */
package com.wallet.myWallet.entity;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.io.FileUtils;
import org.apache.struts2.ServletActionContext;

/**
 * 
 * @author zhouhua, 2014-7-16
 */
public class FileUploadTools {

    private String username;
    private File uploadFile[];// 上传的文件是数组类型
    private String uploadFileFileName[];// 文件名是数组类型
    private String uploadFileContentType[];

    public String beginUpload() throws IOException {
        System.out.println("用户名:" + username);
        String targetDirectory = "C:/demo_upload/";
        if (uploadFile != null && uploadFile.length > 0) {
            for (int i = 0; i < uploadFile.length; i++) {
                File target = new File(targetDirectory, uploadFileFileName[i]);
                FileUtils.copyFile(uploadFile[i], target);
            }
        }
        return "success";
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public File[] getUploadFile() {
        return uploadFile;
    }

    public void setUploadFile(File[] uploadFile) {
        this.uploadFile = uploadFile;
    }

    public String[] getUploadFileFileName() {
        return uploadFileFileName;
    }

    public void setUploadFileFileName(String[] uploadFileFileName) {
        this.uploadFileFileName = uploadFileFileName;
    }

    public String[] getUploadFileContentType() {
        return uploadFileContentType;
    }

    public void setUploadFileContentType(String[] uploadFileContentType) {
        this.uploadFileContentType = uploadFileContentType;
    }

}


用于读取文件上传进度:

/*
 * Copyright (c) 2012-2032 Accounting Center of China Aviation(ACCA).
 * All Rights Reserved.
 */
package com.wallet.myWallet.action;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.struts2.ServletActionContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

import com.wallet.core.action.BaseAction;
import com.wallet.myWallet.entity.State;

/**
 * 
 * 
 * @author zhouhua, 2014-7-16
 */
@Controller
@Scope("prototype")
public class StateAction extends BaseAction<Object> {

    /**  */
    private static final long serialVersionUID = 1L;

    public String execute() throws IOException {
        // 从session中取得名称为uploadState的State对象
        State tempState = (State) getSession().getAttribute("uploadState");
        if (tempState != null) {
            // 设置编码为utf-8
            ServletActionContext.getResponse().setCharacterEncoding("utf-8");
            // 设置响应的格式为XML
            ServletActionContext.getResponse().setContentType("text/xml");
            // 用out对象输出xml代码头
            ServletActionContext.getResponse().getWriter()
                    .print("<?xml version='1.0' encoding='" + "utf-8" + "' ?>");
            // 用out对象输出xml代码体
            ServletActionContext.getResponse().getWriter().print(tempState.getStateString());
        }
        return null;
    }
}


接下来就是jsp和js了:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%><%@ page isELIgnored="false"%><%@ taglib uri="/struts-tags" prefix="s"%><%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":"
            + request.getServerPort() + path + "/";
%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"><html><base href="<%=basePath%>"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>多文件上传,显示进度条实例</title><style type="text/css"><!--
body,td,th {
	font-size: 9pt;
}
--></style><!--参考:http://api.jqueryui.com/progressbar/--><link rel="stylesheet"
	href="./js/jqueryUI/themes/base/jquery.ui.all.css"><script src="./js/jquery-1.10.2.js"></script><script src="./js/jqueryUI/ui/jquery.ui.core.js"></script><script src="./js/jqueryUI/ui/jquery.ui.widget.js"></script><script src="./js/jqueryUI/ui/jquery.ui.progressbar.js"></script><link rel="stylesheet" href="./css/demos.css"><script src="./js/fileUpload.js"></script></head><body><br /><form action="fileUploadAction!upload.action" method="post"
		enctype="multipart/form-data" onsubmit="return submitForm()"><table width="818" border="1"><tr><td width="176"><div align="center">用户账号</div></td><td width="626"><input type="text"
					name="fileUploadTools.username" /></td></tr><tr><td><div align="center">用户附件 <br /> <a href="javascript:insertFile()">添加附件</a></div></td><td id="fileForm"><br /></td></tr></table><input type="submit" value="开始上传..." /><form><br /><div id="progressbar" style="width: 500"></div><br /><div id="progressDetail" class="demo-description"><p>进度详细信息显示于此......</p></div></body></html>


文件上传的js:

 

// 下面这三个函数是生成与刷新进度条、进度详细信息的
// 初始化进度条
$(function() {
	$("#progressbar").progressbar({
		value : 0
	});
});
// 调用查询进度信息接口
function refreshProcessBar() {
	
	$.ajax({
		url:'stateAction.action',
		data:{'timestamp': new Date().getTime()},
		type:'get',
		dataType:'xml',
		success:function(data){
			var flg=refreshProcessBarCallBack(data)	
		}
	});

}


// 查询进度信息接口回调函数
function refreshProcessBarCallBack(returnXMLParam) {
	var returnXML = returnXMLParam;
	var percent = $(returnXML).find('percent').text()
	var showText = "完成:" + percent + "%";
	showText = showText + "\n已读取"
			+ $(returnXML).find('uploadByte').text()+"MB";
	showText = showText + "\n文件总大小:"
			+ $(returnXML).find('fileSizeByte').text()+"MB";
	showText = showText + "\n上传速率:"
	+ $(returnXML).find('speed').text()+"MB/S";
	showText = showText + "\n当前上传文件为第:" + $(returnXML).find('fileIndex').text()
			+ "个";
	showText = showText + "\n开始上传时间:" + $(returnXML).find('startTime').text();
	// 刷新进度详细信息
	$('#progressDetail').empty();
	$('#progressDetail').text(showText);
	// 刷新进度条
	$("#progressbar").progressbar("option", "value", parseInt(percent));
	setTimeout("refreshProcessBar()", 1000);
	if(percent==100){
		return true;
	}else{
		return false;
	}
}
// 下面这三个函数是控制添加、删除、修改附件的(允许增加、删除附件,只允许指定后缀的文件被选择等)
var a = 0;
function file_change() {
	// 当文本域中的值改变时触发此方法
	var postfix = this.value.substring(this.value.lastIndexOf(".") + 1)
			.toUpperCase();
	// 判断扩展是否合法
	if (postfix == "JPG" || postfix == "GIF" || postfix == "PNG"
			|| postfix == "BMP" || postfix == "RAR" || postfix == "ZIP"
			|| postfix == "TXT" || postfix == "GHO" || postfix == "PDF") {
	} else {
		// 如果不合法就删除相应的File表单及br标签
		alert("您上传的文件类型不被支持,本系统只支持JPG,GIF,PNG,BMP,RAR,ZIP,TXT文件!");
		var testtest = $(this).attr('id');
		testtest = '#' + testtest;
		var sub_file = $(testtest);
		var next_a_ele = sub_file.next();// 取得a标记
		var br1_ele = $(next_a_ele).next();// 取得回车
		var br2_ele = $(br1_ele).next();// 取得回车

		$(br2_ele).remove();// 删除回车
		$(br1_ele).remove();// 删除回车
		$(next_a_ele).remove();// 删除a标签
		$(sub_file).remove();
		// 删除文本域,因为上传的文件类型出错,要删除动态创建的File表单
		return;
	}
}
function remove_file() {// 删除File表单域的方法
// 删除表单
	var testtest = $(this).val();
	testtest = '#' + testtest;
	var sub_file = $(testtest);
	var next_a_ele = sub_file.next();// 取得a标记
	var br1_ele = $(next_a_ele).next();// 取得回车
	var br2_ele = $(br1_ele).next();// 取得回车

	$(br2_ele).remove();// 删除回车
	$(br1_ele).remove();// 删除回车
	$(next_a_ele).remove();// 删除a标签
	$(sub_file).remove();// 删除File标记
}
function f() {
	// 方法名为f的主要作用是不允许在File表单域中手动输入文件名,必须单击“浏览”按钮
	return false;
}
function insertFile() {
	// 新建File表单
	var file_array = document.getElementsByTagName("input");
	var is_null = false;
	// 循环遍历判断是否有某一个File表单域的值为空
	for ( var i = 0; i < file_array.length; i++) {
		if (file_array[i].type == "file"&& file_array[i].name.substring(0, 15) == "fileUploadTools") {
			if (file_array[i].value == "") {
				alert("某一附件为空不能继续添加");
				is_null = true;
				break;
			}
		}
	}
	if (is_null) {
		return;
	}
	a++;
	// 新建file表单的基本信息
	var new_File_element = $('<input>');
	new_File_element.attr('type', 'file');
	new_File_element.attr('id', 'uploadFile' + a);
	new_File_element.attr('name', 'fileUploadTools.uploadFile');
	new_File_element.attr('size', 55);
	new_File_element.keydown(f);
	new_File_element.change(file_change);
	$('#fileForm').append(new_File_element);
	// 新建删除附件的a标签的基本信息
	var new_a_element = $('<a>');
	new_a_element.html("删除附件");
	new_a_element.attr('id', "a_" + new_File_element.name);
	new_a_element.attr('name', "a_" + new_File_element.name);
	new_a_element.val($(new_File_element).attr('id'));
	new_a_element.attr('href', "#");
	new_a_element.click(remove_file);
	$('#fileForm').append(new_a_element);
	var new_br_element = $("<br>");
	$('#fileForm').append(new_br_element);
	var new_br_element = $("<br>");
	$('#fileForm').append(new_br_element);
}
// 提交表单,提交时触发刷新进度条函数
function submitForm() {
	setTimeout("refreshProcessBar()", 1000);
	return true;
}

   Iteye的排版有问题,可能导致看的不是很清楚,如果此功能正是你需要的话,可以参看本人的CSDN博客:

http://blog.csdn.net/zhouhua0104/article/details/37922429




已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



linux strace - 追踪系统调用

$
0
0
【基本介绍】
有时候发现有个别进程占用了大量CPU或者内存,我们可以查看进程的调用情况。

【strace解释】
[root@sparkVM salt]# strace  -h
usage: strace [-dDffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]
              [-p pid] ... [-s strsize] [-u username] [-E var=val] ...
              [command [arg ...]]
   or: strace -c [-D] [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...
              [command [arg ...]]
-c -- count time, calls, and errors for each syscall and report summary
-f -- follow forks, -ff -- with output into separate files
-F -- attempt to follow vforks, -h -- print help message
-i -- print instruction pointer at time of syscall
-q -- suppress messages about attaching, detaching, etc.
-r -- print relative timestamp, -t -- absolute timestamp, -tt -- with usecs
-T -- print time spent in each syscall, -V -- print version
-v -- verbose mode: print unabbreviated argv, stat, termio[s], etc. args
-x -- print non-ascii strings in hex, -xx -- print all strings in hex
-a column -- alignment COLUMN for printing syscall results (default 40)
-e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...
   options: trace, abbrev, verbose, raw, signal, read, or write
-o file -- send trace output to FILE instead of stderr
-O overhead -- set overhead for tracing syscalls to OVERHEAD usecs
-p pid -- trace process with process id PID, may be repeated
-D -- run tracer process as a detached grandchild, not as parent
-s strsize -- limit length of print strings to STRSIZE chars (default 32)
-S sortby -- sort syscall counts by: time, calls, name, nothing (default time)
-u username -- run command as username handling setuid and/or setgid
-E var=val -- put var=val in the environment for command
-E var -- remove var from the environment for command



【例子】
strace -o whoami-strace.txt whoami

open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory) 
open("/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/mmx", 0xbffff190) = -1 ENOENT (No such file or directory) 
open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib", {st_mode=S_IFDIR|0755, st_size=2352, ...}) = 0
open("/usr/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory) 
open("/usr/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)


strace -p processId

【参考】
http://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316692.html

已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Viewing all 15843 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>