第一次提交

This commit is contained in:
卢克
2012-04-18 20:33:32 +08:00
commit af976ab35b
91 changed files with 6919 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
_site
Executable
+11
View File
@@ -0,0 +1,11 @@
---
title: 404
layout: home
---
Error 404. Nothing found :( Where did you get that link?
Please go to [homepage](/) or email me:
kejinlu@gmail.com
Executable
+2
View File
@@ -0,0 +1,2 @@
geeklu.com
Executable
View File
Executable
+6
View File
@@ -0,0 +1,6 @@
markdown: rdiscount
permalink: /:year/:month/:title/
url: http://geeklu.com
author: 卢克
pygments: true
paginate: 16
+16
View File
@@ -0,0 +1,16 @@
<section class="comment">
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'geeklu'; // required: replace example with your forum shortname
var disqus_url = '{{ site.url }}{{ page.url | remove:'index.html' }}';
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</section>
+50
View File
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="author" content="{{ site.author }}" />
<title>{{ page.title }} | {{ site.author }}</title>
<link rel="shortcut icon" href="/favicon.ico">
<link href="/feed/" rel="alternate" title="{{ site.author }}" type="application/atom+xml" />
<link rel="stylesheet" href="/media/css/style.css">
<link rel="stylesheet" href="/media/css/highlight.css">
<script type="text/javascript" src="/media/js/jquery-1.7.1.min.js"></script>
</head>
<body>
<div id="container">
<div id="main" role="main">
<header>
<h1>{{ page.title }}</h1>
</header>
<nav>
<span><a title="home page" class="" href="/">Home</a></span>
<span><a title="about" class="" href="/about/">About</a></span>
<span><a title="guestbook" class="" href="/guestbook/">Guestbook</a></span>
<span><a title="categories" class="" href="/categories/">Categories</a></span>
<span><a title="tags" class="" href="/tags/">Tags</a></span>
<span><a title="links" class="" href="/links/">Links</a></span>
<span><a title="subscribe by RSS" class="" href="/feed/">Subscribe</a></span>
</nav>
<article class="content">
{{ content }}
</article>
</div>
<footer>
<p><small>Powered by <a href="https://github.com/mojombo/jekyll">Jekyll</a> & <a href="http://pages.github.com">GitHub</a> | Copyright 2008 - {{site.time | date:"%Y"}} by <a href="http://geeklu.com">Luke</a> | <span class="label label-info">{{site.time | date:"%Y-%m-%d %H:%M:%S"}}</span></small></p>
</footer>
</div>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-12936429-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
---
layout: default
---
<section class="post">
{{ content }}
</section>
{% if page.comments %}
{% include comments.md %}
{% endif %}
+58
View File
@@ -0,0 +1,58 @@
---
layout: default
---
<section class="post">
{{ content }}
</section>
<section class="meta">
<span class="author">
<a href="http://geeklu.com">{{ site.author }}</a>
</span>
<span class="time">
/
<time datetime="{{ page.date | date:"%Y-%m-%d" }}">{{ page.date | date:"%Y-%m-%d" }}</time>
</span>
<br />
<span class="license">
Published under <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">(CC) BY-NC-SA</a>
</span>
{% if page.categories %}
<span class="categories">
in categories
{% for cat in page.categories %}
<a href="/categories/#{{ cat }}" title="{{ cat }}">{{ cat }}</a>&nbsp;
{% endfor %}
</span>
{% endif %}
{% if page.tags %}
<span class="tags">
tagged with
{% for tag in page.tags %}
<a href="/tags/#{{ tag }}" title="{{ tag }}">{{ tag }}</a>&nbsp;
{% endfor %}
</span>
{% endif %}
</section>
{% include comments.md %}
<script type="text/javascript">
$(function(){
$(document).keydown(function(e) {
var url = false;
if (e.which == 37 || e.which == 74) { // Left arrow and J
{% if page.previous %}
url = '{{ site.url }}{{ page.previous.url }}';
{% endif %}
}
else if (e.which == 39 || e.which == 75) { // Right arrow and K
{% if page.next %}
url = '{{ site.url }}{{ page.next.url }}';
{% endif %}
}
if (url) {
window.location = url;
}
});
})
</script>
+67
View File
@@ -0,0 +1,67 @@
---
layout: post
title: 蓝
categories:
- Life
tags:
- 诗
---
世界上有无数的色彩,
而我偏爱蓝色。
她让我是如此的安详,
安详得感觉到,
整个世界是如此的纯粹,
纯粹的淡淡的蓝。
小时候,
我躺在开满了
油菜花的田地里,
仰望天空,
仰望那片悠远的蓝色,
那时我的心情
就和那天空一样
的清澈明亮,
原来蓝色是一种童真。
童真
是一片淡蓝的天空
飘着朵朵白云。
长大了,
开始懂得忧愁了,
而我总是
在我的心底寻找,
童年时的那片蓝色,
但我没有找到。
原来蓝色是一种
淡淡的思念,
淡淡的忧愁,
淡淡的寂寞,
淡淡的悲伤。
蓝色简单干净,
没有
红色的耀眼,
黄色的明亮,
但她是那么的纯洁,
静静的在那儿,
像花儿般慢慢绽放。
蓝色究竟是什么?
那是一种
自然的宁静,
天空的深邃,
梦幻的遥远,
秋天的忧郁,
女子的温柔,
无穷的智慧。
还是一种
千帆过尽后,海的颜色,
百鸟飞翔后,天的颜色,
凡事经历后,心的颜色。
也许,蓝
只是
你忧郁的心情,
你泪滴的晶莹,
你动人的芳名。
+48
View File
@@ -0,0 +1,48 @@
---
layout: post
title: 大学之末
categories:
- Life
tags:
- 诗
---
岁月流逝的不仅是青春,
还有我们曾经的梦想,
小雨湿润的不单是大地,
更是我们的脸庞。
四年,
我坚强的度过每一次考试,
每一次课程设计,
始终一个人苦苦作战,
自己深谙有些事只有自己才能帮助自己,
曾经无助过,
悲伤过,
被同学笑话过,
但我知道,
惟有一个平静的心,
才能包容这个世界。
其实这个世界上,
没有敌人,
只有朋友。
四年,
我帮助过别人,
也拒绝过别人,
有些事我们必须去自己经历,
才能懂得其中的秘密,
有时候,
真的很忙,为了追寻这个残缺世界中的完美。
也许,朋友,因我的帮助而脱离困境,
也许,朋友,因为我的拒绝而更加内心坚强。
这个世界,
因为有了开始,
所以就有结束。
追求一个平静的而诗意的心,
用敏感的感官去体味生活中的点点滴滴。
我爱你们,
我认识的每一个人。
+107
View File
@@ -0,0 +1,107 @@
---
layout: post
title: 若只如初见
categories:
- Life
tags:
- 诗
---
原来这个世界上,
有如此纯粹的景象。
若我是一个画家,
我定会架起支架,
用手中的画笔,
用心中的色彩,
去涂抹
那绿而嫩黄的草坪,
那随风依依的柳条,
那粉的发白的桃花,
那轻轻晃动的湖水,
那朦胧层叠的远山,
那白苏两堤上五颜六色的小伞。
若我是一个乐家,
我定会用,
一种婉转的旋律,
去流露小孩的天真,
融入老人的慈祥,
稀释小伙子的忧伤,
描绘小姑娘的彷徨,
然后
凝结成一种希望,
渐渐的融入那柔柔的水波上,
慢慢荡漾,
消失在风景的中央。
坐在湖边的长椅上,
感觉湖中的风儿吹来的方向,
敞开无尽的幻想,
没有阳光,
没有复杂的思想,
仿佛心灵找到了港湾,
但不知为何,
逃不去的,
是那心头点点忧伤。
没有留下一张照片,
不是不想纪念,
只是那如梦般的画面,
让你留恋,
却忘却了纪念。
苏轼眼中的西子,
欧阳修心中的悠闲,
水依然是那样的水,
人却不是那时的人。
那怒放的花儿,
是为了它们自己,
还是为了需要希冀的凡尘,
人们在岁月中渐渐老去,
只是那柳绿花红,
却年复一年。
人生是美好的,
或许当你看到,
如此景象时你觉得是美好的,
忘却
那平时的忧伤,
那平日的繁忙。
一直思想,
我们是否应该一直匆忙,
用一生的力量,
去改变这世界,
或是你自己的梦想。
人是美好的,
或许至少初见时是美好的,
那温柔的眼神,
那柔软的嗓音,
那烂漫笑容,
不知是何时何地,
有过此种感觉,
人与人的关系,
犹如江南的风景,
让人不解风情。
三两成群,
一起漫步,
漫步在西湖的边上,
似乎没有方向,
没有愿望,
谈论那些话题,
那些琐碎的话题,
似乎愿意,
自己的一生都这样的走下去,
有如此的风景,
如此的游伴,
人生的旅途中,
很难有如此的风景,
如此的心灵交汇。
没有了忧伤,
似乎也没有了喜悦,
只是冥冥之中感觉,
此刻的风景,
此刻的笑容,
终会过去,
终会在某一年的某一日,
散漫的再次想起,
那如画的雨天,
那美丽的笑容。
人生若只如初见,
不管是美丽的湖边,
还是初遇时的腼腆。
+57
View File
@@ -0,0 +1,57 @@
---
layout: post
title: Flex之DataGrid
categories:
- Programming
tags:
- Flex
- Adobe
- ActionScript
---
最近一直在使用ActionScript3进行编码,也学到了不少的新的知识,虽然觉得Flex3现在还不是很成熟,但我相信,Flex4出来后应该还是有不错的前景的。
在Flex中,控件既可以使用mxml的标记来描述,也可以写在ActionScript的代码里,但是归根结底,mxml最终还是被编译成as代码的,写成mxml只是为了编程的方便而已。
今天我就来谈一谈DataGrid这个控件吧。
DataGrid 控件提供以下功能:
- 列可以具有不同宽度或同一固定宽度
- 用户可以在运行时调整其尺寸的列
- 用户可以在运行时对其重新排序的列
- 可选择自定义列标题
- 对任意列使用自定义项目渲染器以显示除文本之外的数据的功能
- 支持通过单击列对数据进行排序
一个DataGrid控件既可以使用mxml标签的形式书写,也可以使用as3的代码写出来。
如下面所示,是使用mxml标签来进行描述的,其中medals是个XML类型的对象。
<mx:DataGrid id=“dg” dataProvider=“{medals.children()}”>
<mx:columns>
<mx:DataGridColumn dataField=“@name” headerText=“国家”/>
<mx:DataGridColumn dataField=“@gold” headerText=“金牌”/>
<mx:DataGridColumn dataField=“@silver” headerText=“银牌”/>
<mx:DataGridColumn dataField=“@copper” headerText=“铜牌”/>
<mx:DataGridColumn dataField=“@total” headerText=“总计”/>
</mx:columns>
</mx:DataGrid>
如果使用as3的代码怎么写呢,首先得写在一个函数里。
{% highlight as3 %}
public function genDataGrid():void
{
var dg:DataGrid = new DataGrid();
dg.dataProvider = medals.children();
var countryColumn:DataGridColumn = new DataGridColumn();
countryColumn.dataField = “@name”;
countryColumn.headerText = “国家”;
var goldColumn:DataGridColumn = new DataGridColumn();
goldColumn.dataField=“@gold”;
goldColumn.headerText = “金牌”;
dg.columns = dg.columns.slice(0,0).concat(countryColumn).concat(goldColumn);
panel.addChild(dg);
}
{% endhighlight %}
+34
View File
@@ -0,0 +1,34 @@
---
layout: post
title: I am not a poet
categories:
- Life
tags:
- 诗
---
I just want to write a poem for you ,
But fear that I am not a poet.
I remember your lips and your eyes,
Your voice,my friend,wanders in my heart,
Your smile, my friend,left in my soul.
You smiled,and the sun shined.
All the truth come from the fear of the death,
All the tears come with the frail of the soul,
So human is poor when it’s alone,
Time is short and life is limited,
But we have a soul,
whisper to others.
But we have a wing,
fly in the mind.
In the whole life ,
We look for
the peace of the heart,
the reason why we lived,
and the love we wanted.
I wrote these words for you ,
And for me ,
To wash my dirty soul with silence.
+29
View File
@@ -0,0 +1,29 @@
---
layout: post
title: 无题
categories:
- Life
tags:
- 诗
---
好想在此写诗一回,
但我始终不敢下笔,
或许是诗不若景美,
或许是诗不及影魅,
抑或是我的诗,
比此刻风景甚悲,
忘记了天空的深邃,
忘记了风的轻微,
愿一只纸鸳的甜蜜,
放飞漫天的心醉。
好想在此放声歌唱,
歌唱那满地的青黄,
那白衣的姑娘,
那微微的碎影,
折射出太阳的方向,
也许那甜蜜的不是淡淡阳光,
而是那挂着笑容的脸庞,
和那美好的希望。
+22
View File
@@ -0,0 +1,22 @@
---
layout: post
title: 叹息
categories:
- Life
tags:
- 诗
---
不知何时才会看见淡淡的月光,
不知苦命的人们是否会有希望,
长长的街道长不过绵绵的忧伤,
昏黄的灯光指引不了尘世的彷徨,
一路奔跑,张望,
唱着无名的曲子,
走过夜市的繁忙。
叹息那些无聊的过往,
叹息那些莫名的放荡。
没有色彩的天堂,
不知是否一样的安详。
+12
View File
@@ -0,0 +1,12 @@
---
layout: post
title: Flex反射机制
categories:
- Programming
tags:
- Flex
---
这里用到一个类flash.utils.getDefinitionByName,其作用是返回由name参数指定的类的类对象引用,然后我们就可以根据这个类的类引用实例化该类。
不过有个缺陷, 使用getDefinitionByName方法获得的类必须是在发布的时候被编译到swf文中的,否则就会报错:”ReferenceError: Error #1065: 变量 <类的名字> 未定义。”
且即使你import了某个类,是没有用的,需要定义一个这个类的引用才可以解决问题。
+12
View File
@@ -0,0 +1,12 @@
---
layout: post
title: Flex反射机制
categories:
- Programming
tags:
- Flex
---
这里用到一个类flash.utils.getDefinitionByName,其作用是返回由name参数指定的类的类对象引用,然后我们就可以根据这个类的类引用实例化该类。
不过有个缺陷, 使用getDefinitionByName方法获得的类必须是在发布的时候被编译到swf文中的,否则就会报错:”ReferenceError: Error #1065: 变量 <类的名字> 未定义。”
且即使你import了某个类,是没有用的,需要定义一个这个类的引用才可以解决问题。
+42
View File
@@ -0,0 +1,42 @@
---
layout: post
title: focusOut事件
categories:
- Programming
tags:
- Flex
---
focusOut事件是指焦点离开某控件时触发的事件,这个比较简单,只是举个比较简单的例子:
{% highlight as3 %}
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="initEvent()">
<mx:Script>
<![CDATA[
public function initEvent():void
{
panelWidth.addEventListener(FocusEvent.FOCUS_OUT,focusOut);
this.addEventListener(MouseEvent.CLICK,mouseClick);
}
public function focusOut(event:FocusEvent):void
{
panel.width = parseInt(panelWidth.text,10);
}
public function mouseClick(event:MouseEvent):void
{
panelWidth.focusManager.deactivate();
}
]]>
</mx:Script>
<mx:Panel id="panel">
</mx:Panel>
<mx:Label text="宽" />
<mx:TextInput id="panelWidth"/>
</mx:Application>
{% endhighlight %}
此例子中涉及了Application 表单的 creationComplete 事件,这个一般用来对事件的注册进行初始化。
还有就是deactivate可以使控件失去焦点。(focusManager还需学习)
+76
View File
@@ -0,0 +1,76 @@
---
layout: post
title: Java自定义Annotation
categories:
- Programming
tags:
- Java
- Annotation
---
Annotation注解(也称Meta Data元数据)为我们在代码中添加信息提供一个形式化的方法,是我们在后面的某个时刻方便的使用这些数据。
JavaSE5中自带了一些Annotation,可以分为两类,
标准注解,目前Java自带的有三个@Override,@Deprecated,@Suppress Warning
元注解
元注解是用来注解其他Annotation的,用的比较多的有两个
@Target 表示该注解可以用于什么地方。其取值的类型为java.lang. annotation.ElementType枚举类型
@Retention 表示需要在什么级别保存该注解,取值为RetentionPolicy枚举类型
整个Annotation的使用过程可以分成三块,Annotation的定义,Annotation的使用,和Annotation的处理器。
### 1.首先我们可以看一下如何定义一个Annotation
{% highlight java %}
package com.luke.hello;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//用于域的声明
@Target(ElementType.FIELD)
//VM在运行期间也会保存annotation的信息,所以可以使用反射机制来读取之
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
public String echo() default("Hello,I am a field!");
}
{% endhighlight %}
### 2.我们将这个刚刚定义的Annotation拿来使用
{% highlight java %}
package com.luke.test;
import com.luke.hello.Hello;
public class Book {
@Hello(echo="Hello,I am the name of the book!")
public String name;
}
{% endhighlight %}
### 3.写一个处理器类来对Annotatin进行处理,这里要使用到Java的反射机制了呢
{% highlight java %}
package com.luke;
import java.lang.reflect.Field;
import com.luke.hello.Hello;
import com.luke.test.Book;
public class BookFileldTracker {
public static void trackBook(Class<?> cl){
for(Field f:cl.getDeclaredFields()){
Hello h = f.getAnnotation(Hello.class);
if(h!=null)
{
System.out.println(h.echo());
}
}
}
public static void main(String[] args){
trackBook(Book.class);
}
}
{% endhighlight %}
+22
View File
@@ -0,0 +1,22 @@
---
layout: post
title: 设计模式之Strategy
categories:
- Programming
tags:
- Java
---
Strategy中文名叫做策略,计谋,一听就知道是为实现某一需求而所使用的方法,相对于编程中的对象也就是对象所表现出来的行为。
Strategy模式主要用来将算法实现从类中分离出来,并封装在一个单独的类中。更简单的说,对象与其行为(behaviour)这本来紧密联系的两部分被解耦,分别放在了两个不同的类中。这使得对同一个 行为,可以方便的在任何时候切换不同的实现算法。而通过对策略的封装,为其提供统一的接口,也可以很容易的引入新的策略。
想起这种设计模式主要是因为最近在使用as3实现panel的几种常见布局,由于布局的算法和容器本身是分离的,使用了策略的设计模式。
为了方便我用GNU的java实现的Layout给大家介绍一下,具体代码可以使用”google代码搜索”来进行搜索相关的类。
AWT的LayoutManager,是 Strategy模式的一个例子。对于GUI而言,每个组件(Component)在容器中(Container)的排放是需要遵循一定的算法的。通常的方法是使用绝对坐标,就像VB,Delphi之类的工具所作的那样,记录每个组件在容器中的位置。这当然会带来一些问题,比如在窗体缩放的时候,就需要手工编码改变组件的大小和位置,以使得原来的比例得以保存。而在AWT中,引入了布局管理器(LayoutManager)的概念,使得布局的方法大大丰富,编码过程也变得简单。
一个容器,比如Applet,Panel等,仅仅记录其包含的组件,而布局管理器中封装了对容器中组件进行布局的算法,具体地说,就是指明容器中组件的位置和尺寸的大小。通过布局管理器,你只需要确定想放置的组件间的相对位置即可,这一方面简化编码,另一方面也有助于实现软件的平台无关性。
每一个容器均有一个布局管理器,当容器需要布置它的组件时,它调用布局管理器的方法布置容器内的组件。LayoutManager2继承于LayoutManager,提供更为细致的布局功能,它可以让布局管理器为组件加上约束条件已确定组件如何被布置。例如,为了确定组件被摆放在边框内的位置,BorderLayout在它的组件上加上方向指示。
特别的,通过实现LayoutManager或者LayoutManager2接口,可以很容易实现自定义的布局策略。
回到模式的话题上来,如果有几个很相似的类,其区别仅仅是在个别行为上的动作不同,这时候就可以考虑使用Strategy模式。这样,通过策略组合,将原来的多个类精简为一个带有多个策略的类。这很符合OO设计的原则:找到变化的部分,并将其封装起来!Strategy模式同样的为子类继承提供了一个好的替代方案,当使用继承机制的时候,行为的改变是静态的,你指能够改变一次–而策略是动态的,可以在任何时候,切换任何次数。更为重要的是,策略对象可以在不同的环境中被不同的对象所共享。以布局管理器为例,虽然每一个容器只有一个布局管理器,但是一个布局管理器可以为多个容器工作。
Strategy模式也有一些缺点,比如,应用程序必须知道所有的策略对象,并从中选者其一。而且在策略对象被使用的时候,它和Context对象之间通常是紧耦合的,Context对象必须为策略对象提供与具体算法相关的数据或者其它的东西,而这些数据的传递可能并不能够风装载抽象地策略类中,因为并不是所有的算法都会需要这些数据的。另外,因为策略对象通常由应用程序所创建,Context对象并不能够控制Strategy的生命期,而在概念上,这个策略应该从属于Context对象,其生命期不应该超出 Context的范围对象。
通常的,Strategy很容易和Bridge模式相混淆。确实,他们有着很相近的结构,但是,他们却是为解决不同的问题而设计的。Strategy模式注重于算法的封装,而Bridge模式注重于分离抽象和实现,为一个抽象体系提供不同的实现。
@@ -0,0 +1,98 @@
---
layout: post
title: Flex Associative arrays
categories:
- Programming
tags:
- Flex
---
关联数组有时候也称为”哈希”或”映射”,由key和value组成。
关联数组是key和value的无序集合。
在ActionScript3中引入了名为”字典”的高级关联数组。
字典是 flash.utils 包中 Dictionary 类的实例,使用的键可以为任意数据类型,
但通常为 Object 类的实例。即字典的key不局限于 String 类型的值。
关联数组的key分类有以下2种。
### 1.key为字符串
在 as3中有两种创建关联数组的方法。
##### a.使用 Object 构造函数
Object 类的实例在功能上等同于关联数组。
如:
{% highlight as %}
var luke:Object = {name:"Luke", age:22};
trace(luke["name"], luke["age"]);
// 输出:luke 22
//其实这些key和value的对在对象中是其属性和属性值
{% endhighlight %}
如在声明数组时不需要初始化,则以[] 或者 .的方式 添加值。
如:
{% highlight as %}
var luke:Object = new Object();
luke["name"] = "Luke";
luke.age = "22";
{% endhighlight %}
##### b.用 Array 构造函数,如果将关联数组声明为 Array 类型,则将无法使用对象文本初始化该数组。
{% highlight as %}
var luke:Array = new Array();
monitorInfo["type"] = "Flat Panel";
monitorInfo["resolution"] = "1600 x 1200";
trace(monitorInfo["type"], monitorInfo["resolution"]);
// 输出: Flat Panel 1600 x 1200
{% endhighlight %}
用 Array 构造函数创建关联数组没有什么优势。即使使用 Array 构造函数或 Array 数据类型,
也不能将 Array 类的 Array.length 属性或任何方法用于关联数组。
最好将 Array 构造函数用于创建索引数组。
即不赞成用array做字典以及哈希组织数据。
### 2.key为对象
用 Dictionary 类创建使用对象而非字符串作为键的关联数组。
如,考虑这样一个应用程序,它可根据 Sprite 对象与特定容器的关联确定 Sprite 对象的位置。
可以使用 Dictionary 对象,将每个 Sprite 对象映射到一个容器。
如下例子:
{% highlight as %}
import flash.display.Sprite;
import flash.utils.Dictionary;
var groupMap:Dictionary = new Dictionary();
// 作为键名的对象
var spr1:Sprite = new Sprite();
var spr2:Sprite = new Sprite();
var spr3:Sprite = new Sprite();
// 用作值的对象
var groupA:Object = new Object();
var groupB:Object = new Object();
// 在字典中创建新的键-值对。
groupMap[spr1] = groupA;
groupMap[spr2] = groupB;
groupMap[spr3] = groupB;
if (groupMap[spr1] == groupA)
{
trace("spr1 is in groupA");
}
if (groupMap[spr2] == groupB)
{
trace("spr2 is in groupB");
}
if (groupMap[spr3] == groupB)
{
trace("spr3 is in groupB");
}
{% endhighlight %}
+29
View File
@@ -0,0 +1,29 @@
---
layout: post
title: 也许只是一个梦
categories:
- Life
tags:
- 诗
---
很久没有做梦了,
或者已经忘却了做梦,
就那样一天一天,
一夜一夜的流逝,
那些曾经的过往,
只是留在心底的某个地方,
只是偶尔溢出丝丝感伤。
也许那早已过去,
也许那只是一个想象的地方,
也许只是梦还未醒,
也许只是怀念你的模样。
只是梦见了你,
只是想起来你,
只是让原本凉凉的秋风,
变得湿润,
变得迷茫。
如果说夜是伤感的精灵,
梦便是精灵的翅膀。
@@ -0,0 +1,14 @@
---
layout: post
title: Flex中Tree的dragDropHandler方法详解
categories:
- Programming
tags:
- Flex
---
首先通过isDefaultPrevented来判断事件是否调用过preventDefault来取消默认的事件,当然不是所有事件都可以取消,
hideDropFeedback(event);在拖拽的过程中有一个小黑线来指示当前放置的位置,调用这个函数,当drop后将黑线隐藏掉,
接下来重要的就是数据的处理了,如何获得数据了,event.dragSource.hasFormat(“treeItems”)是用来判断dragSource中时候有名字为treeItems的数据,取数据的话就是用dataForFormat,其类型是一个Array类型的。
如果是内部拖动的话,就要判断一下,以防止将自己拖进自己的子节点中。
判断的方法是取得目标的所有父节点,然后与被拖拽的节点进行比较,若拖动的节点为目标位置的父节点的话就直接返回
@@ -0,0 +1,16 @@
---
layout: post
title: The First Quarter In Alibaba
categories:
- Life
tags:
- 工作
---
学校里的欢声笑语似乎就在昨日的记忆中,那些校园的小路,在脑海中依然是那么清晰。但细数起来,已过一个季度,从那炎热的夏季,茂盛的梧桐,到现在凉爽的初秋,飘香的金桂,在这个无穷的宇宙中,在这个蓝色的星球上,一个季度也许只是那么一瞬间,但对于人,可能是一个生命的降临,一个生命的逝去,亦或是一个人生的转变。
至今在那本小本子上依然有着“阿里巴巴”的四个字,这个小本子记录着毕业前期找工作的一些历程,一路走来,坎坎坷坷,但总算得到一个理想的结果。
今年春天的时候,第一次来到杭州,依然记得那烂漫的桃花,平静的湖水,感觉杭州真的很美,也是第一次走进阿里巴巴,走进那充满激情的橙色。
短短的时间里,要熟悉这边的开发环境,开发流程,虽然有导师带着,但是大家都很忙,说实话当时心里不是很有底,但既然来了,就硬着头皮看代码,学着使用SVN,还记得做第一个小需求时的激动,还记得第一次发布时的紧张,但这些都过来了,对系统也了解得越来越多,我们几个志同道合的同事开始在一起吃饭,一起谈论着自己工作的事情,我们保持着应有的低调。
我在工作的时候,总是多问自己几个为什么,找到根本的东西,然后将其从我们的系统中抽象剥离出来形成独立的知识点,所以工作对我而言也是一个加强学习的过程,只是从以前的课本学习到实践中学习。渐渐的也认识到系统的弱点的地方,有时候甚至对那繁琐的流程感到厌烦,当然我深深的知道流程对于一个庞大的系统来说是多么重要,任何事物的存在都有其原因,也有其利弊,我们需要的是使其利大于弊,并达到最优,当然有好多问题是历史遗留问题,鉴于人力物力或当下的性价比来看没有做的价值,但是确实给后期维护开发带来很大的重复性劳动,就比如系统的技术性文档和业务文档,人的记忆毕竟是有限的,而系统是庞大的,如果没有一个完善的最新的文档来对系统的技术个业务进行描述,那么有一些细节之处便会成为盲区,成为bug孕育的土壤。
从心底讲,我觉得阿里巴巴很久之前没有把技术搞得很完善,往往许多项目都是赶着时间进行的,代码质量有所欠缺,当然近年开始大力发展技术,这对阿里巴巴来讲是很明智的。
不管怎么讲,阿里巴巴是个伟大的公司,人性化的公司,在这里让我感觉到了温暖和工作的激情。工作几个月以来感觉自己进步很多,又学会了一种新的语言,懂得了搜索引擎的原理,这些我都感到很有价值,当然自己还有一些地方不够好,比如有时候不够主动,做事总是担心别人忙,害怕麻烦别人。在今后的日子里我一定克服自己的不足,好好工作和学习,使自己在最短的时间里学到最多的知识,为以后成为一名优秀的架构师而努力。
+23
View File
@@ -0,0 +1,23 @@
---
layout: post
title: Thinking In Seam
categories:
- Programming
tags:
- Java
---
马越,Redhat 中国社区总架构师,作为Seam框架先行者马越,积极推动中国开源技术的发展。
今天他给我们讲解了Seam。
如今Java在企业应用领域里大行其道,于是诞生了太多太多的框架,Struts,Spring,Hibernate,GWT,JSF…当面对这么多框架的时候,我们如何选择,选择之后又如何将其在服务器中跑起来,这些都需要花费人力。
马越提及在欧美国家,JSF非常的流行,最近Seam的下载量更是非常的高,而中国则主要还是Struts,Spring,Hibernate居多。
首先JSF是SUN提出的JavaEE页面的规范, 有着大量的Widget组件,基于事件驱动.
提及EJB大家都可能感到退却,EJB3之前,EJB确实是非常的繁琐,写一个EJB需要大量的辅助类的配合,然而EJB3出来之后,使用Java5的Annotation特性使得EJB和POJO并无差异,只是增加一些Annotation而已。
然而JSF的生命周期和EJB的生命周期不同,这使得将两者结合起来非常的困难,然而Seam的出现改变了这一现象。Seam其实不能说它是一个框架,而是一个有状态的应用栈,通过它可以方便将其他的框架的整合在起来。
在Seam中一个核心的概念就是Context,在以前的Web应用中,我们可以设计到request/response Context ,Session Context,Application Context,然而这样的设计能够很好的满足如今复杂多变的企业应用吗,显然是不够的,比如我们在网页上做一个调查问卷,可能问卷的内容不止一页,这是我们就需要一种比Request Context长而比Session Context短的生命周期的Context,这样可以横跨几个request且不会放在Seesion中占用系统资源,所以Coversation Contest 应运而生。还有一个问题,一个业务流程启动后可能需要不止一个人员的参与,所以这时需要一个比Seesion Context更广的Context,于是Seam提出了Business process context.其实这些概念都是一些简单的概念。
其实Seam中是直接将Data Object暴露在页面上的。而我们现有的系统却都避免了这种情况。
+19
View File
@@ -0,0 +1,19 @@
---
layout: post
title: Flash中不同类型的坐标
categories:
- Programming
tags:
- Flex
---
在鼠标事件中经常碰到坐标这个概念,在Flash中有三种坐标的表示方式,大家也是经常容易混淆的。
下面是本人画的一个示意图:
![Flash坐标系](http://farm6.staticflickr.com/5117/6929479758_eb58a961d7_d.jpg)
* 第一种是本地坐标,本地坐标指定的像素位置随组件的左上角而变化。MouseEvent中可以由localX,localY获得
* 第二种是全局坐标,全局坐标指定的像素位置随舞台的左上角,即应用窗口的最外侧边缘而变化。,MouseEvent中可以由stageX,stageY获得
* 第三种是内容坐标,内容坐标指定的像素位置随组件内容的左上角而变化,并包括组件的所有内容区域,甚至包括当前剪切掉但必须能够通过滚动组件访问的任何区域。
DisplayObject中提供了本地坐标和全局坐标互相转换的方法,
Container中提供了内容坐标和本地坐标,全局坐标互相转换的方法。
+16
View File
@@ -0,0 +1,16 @@
---
layout: post
title: FLEX RSL
categories:
- Programming
tags:
- Flex
---
今天来公司想发布一些更新,Flex项目编译完成之后发现比原来的swf小了一大截,之前的swf文件1. 2多,而现在的只有700多k,由于以前不是我发布的,所以我就没管直接换上了,发布完成后,发现自己的机器可以上,别人上的时候报RSLerror的什么错误,仔细查看后终于发现了问题。
RSL(Runtime Shared Library)的简写,里面放的是Flex的一些基础共享库,在编译项目的时候可以在Flex Builder中设置是否将RSL打包到项目中,我检查了一下自己的设置,是FrameWork linkage, 所以不是打包在项目里,固然文件变小了。如果将RSL分离出来那么在输出的目录下将会多两个文件framework_3.1.0.2710.swz和framework_3.1.0.2710. swf(那一串数字是版本号),当你访问的时候过程是这样的,当你第一次访问应用的时候,flash player会优先将framework_3.1.0.2710. swz文件加载放到cache目录下,以后就不需再次加载了,如果这个动作失败了,就会加载framework_3.1.0.2710.swf,该文件只能下载到ie缓存.并不能达到RSL功能,只保证项目可正常运行.
出现RSL error的情况可能是以下两种情况
第一,使用低于9.0.115版本的Flash Player
第二,framework_3.1.0.2710.swz和framework_3.1.0.2710.swf未放到服务器的目录下。
我能访问的原因就是在我本地测试的时候已经将那swz放在了player的cache下了。
@@ -0,0 +1,68 @@
---
layout: post
title: iBatis 自动生成主键问题
categories:
- Programming
tags:
- Java
- iBatis
- Database
---
今天在使用iBatis的时候遇到了一些主键自动生成的问题,之前的一些项目使用的都是Oracle数据库,而这次我使用的是Mysql。
所以遇到了问题。
问题:在插入一条记录的时候,要获得插入记录的ID,在Mysql中主键是使用了Autoincrement.但是在Sqlmap中使用selectKey获得的总是不正确。
原因:经过查找原因,发现Mysql和Oracle的主键生成机制是不一样的。
在主流的数据库中,主键生成有两种方式,一种是预生成(pre-generate)主键的,如Oracle和PostgreSQL;有些是事后生成(post-generate)主键的,如MySQL和 SQL Server。但是不管是哪种方式,我们都可以用iBATIS的
节点来获取语句所产生的主键,只是使用的时候有点不一样。
解决:
下面给出例子,假设我们往数据库的Product表中插入数据。
{% highlight sql %}
<!--—Oracle SEQUENCE Example -->
<![CDATA[
SELECT STOCKIDSEQUENCE.NEXTVAL AS ID FROM DUAL
]]>
</selectKey>
<![CDATA[
insert into PRODUCT (PRD_ID,PRD_DESCRIPTION)
values (#id#,#description#)
]]>
</insert>
<!—- Mysql LAST_INSERT_ID() Example -->
<!--[CDATA[
insert into PRODUCT (PRD_DESCRIPTION)
values (#description#)
]]-->
<!--[CDATA[
SELECT LAST_INSERT_ID() as value
]]-->
<!--— Microsoft SQL Server IDENTITY Column Example -->
<!--[CDATA[
insert into PRODUCT (PRD_DESCRIPTION)
values (#description#)
]]-->
<!--[CDATA[
SELECT @@IDENTITY AS ID
]]-->
{% endhighlight %}
在上面的代码中我们将selectKey放在insert语句的不同的位置来区分是预生成主键还是事后生成主键,当然我们可以指定selectKey的type属性来指定,type=”pre” 或者 type=”post”,举例如下
{% highlight sql %}
<!--— Mysql LAST_INSERT_ID() Example -->
<!--[CDATA[
SELECT LAST_INSERT_ID() as value
]]-->
<!--[CDATA[
insert into PRODUCT (PRD_DESCRIPTION)
values (#description#)
]]>
{% endhighlight %}
@@ -0,0 +1,46 @@
---
layout: post
title: Javascript中的数值类型
categories:
- Programming
tags:
- Javascript
---
Javascript作为一种弱类型语言,所以数字在JS里没有整型,浮点型,短整型,长整型,双精度等数字类型的区分,
所有的数字在Javascript里都是用8个字节来表示的.
#### 1.Number对象
在JS语言中,有一个原始数字的包装对象Number
Number对象有一些方法和属性,
这个对象有如下一些属性
- MAX_VALUE 可表示的最大的数
- MIN_VALUE 可表示的最小的数
- NaN 非数字值
- NEGATIVE_INFINITY 负无穷大,溢出时返回该值
- POSITIVE_INFINITY 正无穷大,溢出时返回该值
#### 2.数制
在JS中 八进制数是以0打头,十六进制数以0x开头
每个数字都可以通过函数转换为对应进制
{% highlight js %}
var num=255;
document.writeln(num.toString(16)+' hex
'); // Outputs: ff
document.writeln(num.toString(8)+' octal
'); // Outputs: 377
document.writeln(num.toString(2)+' binary
'); // Outputs: 11111111
{% endhighlight %}
#### 3.字符串和数的转换
这里介绍两个函数
parseInt(string[, radix])和parseFloat(String)
如果parseInt不给进制的参数的时候默认是十进制
@@ -0,0 +1,92 @@
---
layout: post
title: Javascript原型链研究
categories:
- Programming
tags:
- Javascript
---
> 前言:如果想深入理解Javascript的面向对象编程思想,那么对于原型链的理解将十分重要,由于看了李战老师的<悟透Javascript>,产生了很多疑问,于是带着疑问在网络上查找答案,也翻出了Ecma-262来看了,总算明白了一些东西,但还是有些疑问.
### 一.类别和对象
在Javascript没有所谓的其他高级语言中的”类”的概念,所有的”数据”除了基本类型便是对象.
如果从集合论的角度在思考问题,我们可以将所有的这些数据进行分类,那么可以得到如下几类:
Undefined,Null,Boolean,Number,String还有Object
所有的基本数据类型必定属于前面五种类型(Type)之一,而复杂的数据类型只有Object一种.
我们可以举例说明一下,
{% highlight js %}
Undefine={undefined}
Null={null}
Boolean={true,false}
Number={0,1,2,0.1……}
String={'a','b','123',…}
Object={json对象,function f(){},…}
{% endhighlight %}
以上的只是简单的说明,并不严谨.
有了以上的说明我们再来看typeof这个运算符
其实typeof就是根据上面的这些类型来判断并返回结果的,
Ecma-262文档中有如下表格
![typeof](http://farm8.staticflickr.com/7058/7075638741_2a8828c3c2_d.jpg)
我们从这里可以看出在Javascript中Object类别的成员又可以分成两类,普通的对象和函数对象.
### 2.内建对象(Build-in Object)
在Javascript语言中有一些内建对象,其中有Global,Object,Function,Array,String,Boolean,Number,Math,Date,RegExp(注意这里和上面所讲的类别是两码事)还有一些其他的错误类,这里暂不一一列举.这些内建对象其实都是构造器,或者说它们都是函数.这里我们可以通过new运算符创建相应的对象.如
var s = new String(‘string’);这样得到的是一个字符串对象,typeof s 是”object”而如果没有使用new而是var s=String(‘string’);得到的是一个基本类型字符串,typeof s得到的是”string”
### 3.原型Prototype
由于Javascript中没有类,那通过什么来实现面向对象中的继承呢,对,就是原型,原型本身也是对象.
每一个对象都有一个隐式的原型,而函数对象除了隐式的原型引用外,还有一个显式的原型引用。
在一般情况下,对象的隐式原型是不可以访问的,而函数对象的显式原型可以通过 FunctionName.prototype进行访问,
但在Firefox中你可以通过对象的__proto__属性访问对象的隐式原型。
说了这么多,你可以还是对这两种原型没有理解,我们举个例子吧。
{% highlight js %}
function Person(name){
this.name=name;
}
alert(Person.prototype);
alert(Person.prototype.constructor);
alert(Person.__proto__===Function.prototype);
alert(Person.constructor);
var p = new Person('Luke');
alert(p.prototype);
alert(p.__proto__===Person.prototype);
alert(p.constructor);
{% endhighlight %}
运行上面的代码我们可以知道,Person这个函数他的显式原型是一个object对象,而这个原型对象的构造器就是Person自己。
我们又可以知道这个函数的哦隐式原型是内置对象Function的显式原型,且我们可以知道Person的构造器是Function函数。
接着我又创建了一个对象p,可以知道如果直接使用prototype是无法访问的,结果为undefined,因为p是一个普通对象,只能在Firefox中通过__proto__来访问其隐式的原型,可以知道其隐式的原型为Person的显式原型。
我这里就很武断的得出一个结论来,当然不一定正确。
一个创建的对象,如果不主动的修改其隐式的原型(当然我们无法修改),那么默认的隐式原型为其constructor的显式原型。
### 4.原型链
通过上面的例子我们已经可以隐约的知道,构造函数和通过构造函数创建的对象之间通过原型对象联系在一起了,Javascript也正是通过这种途径来进行继承的。
我们看上图,MyFunction为一个函数,它的显式原型是FP,当然FP本身也是一个对象,而且其constructor是MyFunction
o1,o2是通过MyFunction创建的两个对象,这两个对象的隐式原型都是FP.
这里有一个疑问,你也可以通过上一节的代码继续进行测试,
{% highlight js %}
function Person(name){
this.name=name;
}
var o = Person.prototype;
alert(o.constructor);
alert(o.__proto__===Person.prototype);
alert(o.__proto__===Object.prototype);
{% endhighlight %}
> 通过这段代码我们可以发现一个构造函数的显式原型的constructor是这个函数自身,而其隐式原型却不是这个函数的prototype,这是不是违反了常理呢?????????这时我注意到,这个函数的prototype并不是我们用户自己创建的,而是由Javascript引擎进行创建管理的,也许它为了维持原型链的存在,使得prototype自己的隐式原型设置成了原型的终点Object.prototype
Javascript内建对象的原型链
![prototype](http://farm8.staticflickr.com/7138/6929572382_cffb5c382a_z_d.jpg)
+26
View File
@@ -0,0 +1,26 @@
---
layout: post
title: 在这春天里,外婆走了
categories:
- Life
tags:
- 感悟
---
外婆真的走了,剩下的只是一堆骨灰和曾经的记忆。
在这明媚的春天,花草树木都开始发芽了,而我却在思考死亡。
似乎只有孩子的世界才是那么的美好,不要太多的思考问题,和小伙伴们一起玩耍,一起追逐彩色的蝴蝶,在一片金黄的油菜花的田地里捉迷藏;去看天空里变幻的云朵,把它们看成是天上奔跑的动物;躺在妈妈怀里数着天上的星星,看着那长长的银河,虽然夜空很深邃,但那时的我却感到如此的安详;冬天里在雪地里打雪仗,上学的路上看着自己踩出的脚印。
然而现在,一切都过去了,那些岁月似乎成了一个不可追逐的梦,而且是一个伤感的梦。
看着外婆瘦弱的脸庞,我的视线模糊了,外婆一共生了7个孩子,那时候生活很艰苦。我还记得小时候经常来外婆家吃饭,喜欢吃外婆做的鸡蛋和鱼。
看着自己的妈妈和姨妈们失声痛哭的时候,我突然意识到他们失去了自己的妈妈,一个曾经给予他们生命的女人,是她一手把他们带大 ,给他们奶喝,那些曾经的一点一滴是无法让他们忘记的回忆。外公也特别的伤心,在床上泣不成声,我这是第一次看见外公的哭泣,一个上了年纪的人哭成这样,让人看了心痛,但外婆走了,这是不可改变的事实。
都说人死了会去另一个世界,但我知道这只是一个谎言,眼前的一切的思考都是基于我们的大脑这个器官,当人死去,这一切的意识都随之消失,所以我觉得这太残忍,毕竟这种感情是或者的人无法割舍的。
一个人找一个相爱的人是不容易的,我们的时间是有限的,多和朋友一起玩玩,多陪陪自己的父母,多关心自己身边的人。
也许死亡并不恐怖,恐怖的是带着遗憾死去。
@@ -0,0 +1,61 @@
---
layout: post
title: 关于Javascriot的对象的constructor属性
categories:
- Programming
tags:
- Javascript
---
在Javascript中经常有一些让你匪夷所思的结果,我觉得一个主要的原因是大家最其规范了解甚少.
其实这也无可厚非, JS中有太多的不够优美的地方,且Ecma-262的文档始终没有一份完整的中文版.
今天想说的是Javascript中关于对象的constructor属性,这里需要你了解Javascript的原型链的知识.
在一个对象里访问其属性或者方法,如果对象本身没有就会去其隐式原型中访问,这样一层一层的过去直至原型链的根源,即Object对象的隐式原型.
这里我们先讲一下构造函数,一般在JS中我们定义一个构造函数,函数名都是以大写字母开始,这是一个良好的编程习惯.
每一个构造函数中存在一个显式原型和一个隐式原型,显式原型是你可以改变的,而隐式是你无法访问的.
当你使用new运算符创建一个构造函数的对象的时候,这个对象的隐式原型即为构造函数的显式原型,这样就实现了基于原型的继承.
我们自己new出来的对象自己是没有constructor属性的,那么当你访问这个属性的时候,它回去它的原型链中去遍历寻找.
这里举个简单的例子,方便大家理解.
{% highlight js %}
function Con()
{
};
var a = new Con();
alert(a.constructor);
{% endhighlight %}
这里我们可以看到,结果a的constructor属性为Con,
![js构造函数](http://farm8.staticflickr.com/7062/6929652050_26f3be4487_z_d.jpg)
你可能会想a对象就是通过Con构造函数new出来的这很正常.那我们看下面的例子.
{% highlight js %}
function Con()
{
};
Con.prototype = obj;
var a = new Con();
alert(a.constructor);
{% endhighlight %}
![更改Con的显式原型后](http://farm8.staticflickr.com/7084/6929652054_a25c77dde1_d.jpg)
从这里我们可以断定,a的constructor和其隐式原型相关,在之前的Javascript原型链研究中我抛出了一个关于原型且和constructor相关的疑问,一直没有得到澄清,最近在看Javascript : The Good Parts中似乎看到了结论.
这本书在第五章 继承 的Pseudoclassical 中如是说
{% highlight js %}
/*When a function object is created ,
the Function constructor that produces
the function object runs some code like this:*/
this.prototype ={constructor:this}
{% endhighlight %}
突然之间有种恍然大悟的感觉,原来之前我猜测的是正确的,当然这个描述我没有在ecma-262中找到依据,但是Douglas Crockford的话,我信了.
我们回过头来思考一下,每一个通过构造函数的new出来的对象的constructor(也就是说每一个构造函数的显式原型的constructor属性)默认为这个构造函数本身,这样你在思考一下,一切明了.
+33
View File
@@ -0,0 +1,33 @@
---
layout: post
title: 初试Python
categories:
- Programming
tags:
- Python
---
上周末一个同事姐姐,说他每周都要整理一封excel表格,去掉其中的空行并把某一列的内容拆分成两列,然后做统计。
其实这个需求本可以做成一个系统的,她也向上提了需求,可由于资源问题,一直没有做。
当然如果每次人肉做的话,得花几个钟头,因为数据量是有点大的。
数据的格式基本如下
2009-4-23 23:16,xxxxx ,116.10.174.655 (地区)
代表的分别是时间,用户名,IP地址地区
所要作的就是将地区单独出去,然后根据地区来统计
2009-4-23 23:16,xxxxx ,116.10.174.655 ,地区
所以就用python写了下面的几行代码
{% highlight python %}
fr=open('./data.csv')
fw=open('./ok.csv','w')
for line in fr.readlines():
if(len(line) > 10):
line = line.replace('(',',')
line = line.replace(')','')
fw.write(line)
fr.close()
fw.close()
{% endhighlight %}
文件名都是写死的,转换好了之后再保存为xls的就可以了,非常的快。
@@ -0,0 +1,201 @@
---
layout: post
title: Python脚本实例 包括xml,json,http,编码
categories:
- Programming
tags:
- Python
---
这几天在研究python相关的一些东西,其中涉及的内容包括:
xml的解析,json的解析,urllib的使用,urllib的使用,python中中文编码的问题等
项目托管在Google上,地址是http://code.google.com/p/ipmobile/source/checkout
当然一切都来源于实际工作嘛,首先引入缘由,情景是这样的:
有一个Excel表格,有注册日期,用户ID,注册IP,注册手机号这几个字段,
但现在需要获取注册IP所在的地址 以及手机号所属地区等信息,
并根据这些信息进行分类统计等工作
当然如果在没有编码人员介入的情况下,
需要一个个的到一些网络上的页面上查出地址的信息,然后填到excel里,
而这完全是重复劳动,完全可以抽象并做出系统,在没有人力进行系统开发的情况下,
可以使用简单的脚本进行替代,
本文中,本人使用python脚本进行了相关研究和实验性的工作.用Python写出的代码的可读性非常的高的
首先是一个简单的例子使用过的是有道的接口获取IP和手机号码的地址的
{% highlight python %}
#!/usr/bin/env python
#coding=utf-8
"""
getinfo.py
Created by Luke on 2009-04-29.
Copyright (c) 2009 Alibaba. All rights reserved.
"""
import urllib
from xml2dict import XML2Dict
import codecs
def gbk2utf8(xml):
return xml.decode("gbk").encode("utf8").replace('gbk', 'utf-8');
def get_ip_info(ip):
url="http://www.yodao.com/smartresult-xml/search.s?type=ip&q=%s"%ip
return gbk2utf8(urllib.urlopen(url).read())
def get_mobile_info(mobile):
url="http://www.yodao.com/smartresult-xml/search.s?type=mobile&q=%s"%mobile
return gbk2utf8(urllib.urlopen(url).read())
def main():
fr = open('x.csv')
wr = codecs.open('y.csv','w','gbk')
xml = XML2Dict()
for line in fr.readlines():
line = line.rstrip('\n')
items = line.split(',')
ip_item = items[2]
mobile_item = items[3]
ip_dict = xml.fromstring(get_ip_info(ip_item))
mobile_dict = xml.fromstring(get_mobile_info(mobile_item))
new_line=''
if ip_dict.smartresult!=None:
new_line = items[0]+','+items[1]+','+items[2]+','
+ip_dict.smartresult.product.location.encode("gbk")
else:
new_line = items[0]+','+items[1]+','+items[2]+','
if mobile_dict.smartresult!=None:
new_line = new_line+','+items[3]+','
+mobile_dict.smartresult.product.location.encode("gbk")+'\n'
else:
new_line = new_line+','+items[3]+','+'\n'
print new_line.decode("gbk")
wr.write(new_line.decode("gbk"))
fr.close();
wr.close();
if __name__ == '__main__':
main()
{% endhighlight %}
这段代码从有道那获得的结果是xml编码格式为gbk的,所以在编码上我将其先转换成utf8的,然后再通过xml2dict将其转换成Python的字典结构,这样我就可以直接读取节点的值了.
这段代码还使用了urllib模块还有读文件写文件的相关知识点,总体来说还是很简单的,主要就是字符串的拼接了,这里的文件格式都是csv的很容易就可以编程excel的格式
但是问题来了,有的IP地址在有道这里查不出来,而在ip138这个网站上可以查出来,后来试了好多,发现IP138的数据库的数据貌似全一些,但是IP138没有提供任何数据读取的API,那怎么办呢?嘿嘿,那就用最那个的办法了,在脚本里解析使用正则表达式处理html,解析出想要的结果,代码如下:
{% highlight python %}
"""
ip138poster.py
Created by Luke on 2009-05-04.
Copyright (c) 2009 Alibaba. All rights reserved.
"""
import httplib,urllib; #加载模块
import re
class Ip138Poster:
def __init__(self):
#定义一些文件头
self.headers = {"Content-Type":"application/x-www-form-urlencoded",
"Connection":"Keep-Alive","Referer":"http://www.ip138.com/ips.asp"};
#与网站构建一个连接
self.conn = httplib.HTTPConnection("www.ip138.com");
def post(self,ip_addr):
#定义需要进行发送的数据
params = urllib.urlencode({'ip':ip_addr,'action':'2'});
#开始进行数据提交 同时也可以使用get进行
self.conn.request(method="POST",url="/ips8.asp",
body=params,headers=self.headers);
#返回处理后的数据
response = self.conn.getresponse();
#判断是否提交成功
if response.status == 200:
s=response.read()
print s.decode("gbk")
lis = re.findall(r"(?<=<li>).*?(?=</li>)",s)
return lis
def get_main_data(self,ip_addr):
##下面的十六进制的值为"本站主数据:"的unicode
self.main_data_parttern = r"(?<=\xe6\x9c\xac\xe7\xab\x99\xe4\xb8\xbb\xe6\x95\xb0\xe6\x8d\xae\xef\xbc\x9a).*"
list = self.post(ip_addr)
if len(list)>0:
main_data = re.findall(self.main_data_parttern,list[0].decode("gbk").encode("utf8"))
if len(main_data)>0:
ret_value = main_data[0]
return ret_value.decode("utf8")
def __del__(self):
#关闭连接
self.conn.close()
##由于需要匹配中文,所以需要转换,然后再匹配
def UTF2Hex(s):
temp = s.encode("UTF-8").encode("hex")
line = ""
for i in range(0,len(temp)-1,2):
line += "\\x" + temp[i] + temp[i+1]
return line
def GBK2Hex(s):
temp = s.encode("GBK").encode("hex")
line = ""
for i in range(0,len(temp)-1,2):
line += "\\x" + temp[i] + temp[i+1]
return line
if __name__ == '__main__':
poster = Ip138Poster()
print poster.get_main_data("121.0.29.231")
{% endhighlight %}
这里我还提供一个json的可选方案,可惜的是目前我没有发现国内的可以使用json协议获取ip地址信息的API(SOAP的万网好像有个),所以IP的API是使用国外的,手机号码信息查询国内是有API的,可以直接使用,代码非常非常简单,如下:
{% highlight python %}
#!/usr/bin/env python
# encoding: utf-8
"""
locatingsystem.py
Created by Luke on 2009-05-04.
Copyright (c) 2009 Alibaba.com. All rights reserved.
"""
import sys
import os
import urllib
import json
class LocatingSystem:
def get_ip_json(self,ip):
url="http://iplocationtools.com/ip_query.php?ip=%s&output=json"%ip
return json.read(urllib.urlopen(url).read())
def get_mobile_json(self,mobile):
url="http://api.showji.com/locating/?m=%s&output=json"%mobile
return json.read(urllib.urlopen(url).read())
def main():
ls = LocatingSystem()
print ls.get_ip_json("134.34.54.56")
print ls.get_mobile_json("13819127490")
if __name__ == '__main__':
main()
{% endhighlight %}
@@ -0,0 +1,55 @@
---
layout: post
title: Regular Expression In Python
categories:
- Programming
tags:
- Python
---
> 本篇文章主要讲解一下Python中正则表达式的使用,这里并不会给你讲解正则表达式本身的相关知识,本文假设你已经对正则表达式有了一定的了解.
在Python中正则表达式的功能包含在re这个模块中,所以使用前你需要
import re
本文从几个方面介绍一下Python中使用正则表达式的注意点:
原始字符串(Raw String)
在Python中有一种比较特殊的字符串,在字符串的前面有r或者R,这种字符串中”\”不再有转义字符的作用了,而就是其本身.
这种原始字符串主要是为了正则表达式而设计的,也就是为了增强其可读性.
字符串中一些特殊字符需要转义,如”\”,tab键,换行,回车等,而正则表达式中也有需要转义的字符如”\”,”\b”,”\w”等等.
所以如果使用普通字符串表示正则表达式的pattern的话,会使用难以阅读.
举个简单的例子:
比如要匹配一个以\word开头的字符串,如果要写成正则表达式的话,因为\w是转义字符,故这里需要对\进行转义,也就是正则表达式为”\\word”,但是写成字符串本身的时候,”\是需要转义”的,所以最后,使用python普通字符串表示的话就应该是”\\\\word”
(正则表达式一次转义,字符串本身一次转义)
但是如果使用原始字符串的话,就不需要为字符串本身作转义了.可以直接写成r”\\word”
几个函数
re模块中有几个很长用的函数
–匹配和搜索–
re.match(pattern,string[, flags])
re.search(pattern,string[, flags])
这两个函数有点类似,不同点就是,match必须是从string的第一个字符开始就和partten匹配,而search是搜索整个string
举个例子
{% highlight python %}
print re.match(r'<a.*?/>','<a href="http://hityou.net"/>')
print re.match(r'<a.*?/>','I am a python guy ! <a href="http://hityou.net"/>')
print re.search(r'<a.*?/>','I am a python guy ! <a href="http://hityou.net"/>')
{% endhighlight %}
还有一个用于搜索的函数是re.findall(pattern,string[, flags])
其作用是搜索整个字符串便将匹配到的结果以list的形式返回
–替换函数–
这里只介绍re.sub(pattern,repl,string,[, count])
其实普通的替换功能大家都知道,我这里讲一下替换子表达式的方式
{% highlight python %}
str=' I am a python guy ! '
print re.sub(r'',r'\1',str)
{% endhighlight %}
这里所做的功能是将字符串中的html格式的链接替换成链接本身,其中r’\1′表明替换成正则表达式的第一个子表达式(正则表达式中括号中的为子表达式)
@@ -0,0 +1,59 @@
---
layout: post
title: First Look At Non-Thread-safe Servlets
categories:
- Programming
tags:
- Java
- Servlet
---
Servlet名称的由来:
Servlet由字面上可以看出是指运行在服务器端的小程序,大家应该曾经听说过Java Applet这个东东,它是指运行在客户端的Java小程序.
在这之前处理动态网页一般的时候都是使用CGI(公共网关接口)来处理,但是CGI处理请求的时候是这样的,每得到一个请求,它就会创建一个新的进程来进行处理,这样对计算机资源的消耗可能是有点大的.
而Servlet的处理过程就完全不一样了, 当Servlet每接受到一个请求, 是使用一个线程来进行处理的,而Servlet本身始终只保持一个实例.所以我们可以下一个这样的结论,Servlet是单实例多线程的运行方式,每个请求在一个独立的线程中运行,而提供服务的Servlet实例只有一个。
一般情况下Servlet的容器的工作情况如下:
![Servlet容器示意](http://farm8.staticflickr.com/7120/7075762499_60640bd472_z_d.jpg)
拿Tomcat来说吧,在配置文件中你可以配置最大线程数和最小线程数,线程池在Web容器初始化的时候,初始了最小线程数个工作线程,当一个请求过来时,需要判断是静态资源还是映射到某个servlet,如果是servlet的话就去找,如果当前的servlet没有被初始化,初始化时会调用Servlet的init方法.那么会创建一个这个Servlet的实例,下次有线程需要用到这个Servlet的时候直接使用就可以了. 使用的时候会调用Servlet的service方法.
由上面这样的工作方式,你也许会发现,Servlet对象里的对象成员是线程不安全的,比如说某个线程修改了Servlet对象中的一个属性,那么另一个线程读取的时候是读取的修改之后的值.所以我们一般情况下是不会在Servlet中定义成员变量的.
为此我做了一个小小的实验,本实验使用的构建工具为maven,如果你想要运行或部署此demo相当的简单,只需安装配置好maven后,然后在项目的根目录下 输入 mvn jetty:run 即可,mvn会帮你自动安装jetty和相关插件的.
实验的代码相当的简单,
{% highlight java %}
package com.luke;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestServlet extends HttpServlet
{
private int openCount=0;
protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException
{
doPost(httpServletRequest, httpServletResponse);
}
protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException
{
int countLocal = 0;
openCount++;
countLocal++;
PrintWriter printWriter = httpServletResponse.getWriter();
printWriter.println("openCount = " + openCount);
printWriter.println(" localCount = " + countLocal);
}
}
{% endhighlight %}
每一个新的请求都会将这个Servlet的成员变量+1,所以openCount一直在变,本例只是想说明Servlet是线程不安全的.
+59
View File
@@ -0,0 +1,59 @@
---
layout: post
title: Cx_oracle
categories:
- Programming
tags:
- Python
---
由于一些缘故,需要人肉数据库。于是我想写些脚本去Oracle数据库中查找。
我很喜欢Python的简介,决定使用Python来做,于是去网络上找Python连Oracle的方法,
找到了cx_Oracel这个模块。
注意点:你下载的版本一定要对应到你本机客户端的Oracle的版本,你需要将相应版本的oci.dll放到Python的home目录下的Lib/site_packages下
> 问题描述:需要找出一个数据中所有应用表中的所有字段中哪些字段的数据记录包含了html或特殊字符的
这个问题如果人肉做,不堪设想。
好吧,我承认我很懒,于是我写了程序。
> 思路:查出所有的表名,查出每一个表下的所有的字段,去搜索字段为varchar2的包含哪些特殊字符的,搜出的结果数>0的就做把其记录下来。
好了,啥也不说了,代码说明一切。
{% highlight python %}
# coding=gbk
import cx_Oracle
result = open('database_check.txt','w')
conn=cx_Oracle.connect ('zeus/alizeus@ZEUS.TEST')
curs=conn.cursor ()
tname_sql="select TNAME from tab where TNAME like 'ALI%'"
tab_name=curs.execute (tname_sql)
tab_name_rows=curs.fetchall()
for tn in tab_name_rows:
table_name = tn[0]
column_sql="select TABLE_NAME,COLUMN_NAME,DATA_TYPE,DATA_LENGTH from USER_TAB_COLUMNS where TABLE_NAME="+"'"+table_name+"'"
tab_columns = curs.execute(column_sql)
columns = curs.fetchall()
for col in columns:
column_name=col[1]
data_type=col[2]
data_length=col[3]
if data_type=='VARCHAR2':
check_sql = "select count(*) from "+table_name+" where "+column_name+" like '%<%' or "+column_name+" like '%''%' or "+column_name+" like '%&%'"
curs.execute(check_sql)
count = curs.fetchone()
if count[0]>0:
print check_sql
print count
result.write('表:'+table_name+' '+'字段:'+column_name+'\n')
result.write(check_sql+'\n')
result.write(str(count[0]))
result.write('\n\n')
result.close()
{% endhighlight %}
+31
View File
@@ -0,0 +1,31 @@
---
layout: post
title: 关于Form的GET和POST
categories:
- Programming
tags:
- HTML
---
关于Form的get和post提交的方法,大家应该都知道是什么概念,
get的本意是为了从服务器端获取数据,post是为了向服务器端发送数据.
而我比较疑惑的是,form的action地址后面带的参数是不是在get的时候是无效的呢?
也就说:
{% highlight html %}
<form action="/helloform/hello?age=22" method="get">
<input type="submit" />
</form>
{% endhighlight %}
当提交表单之后,服务器端是无法获取age=22这个值的.
带着这样的疑问,我通过Maven构建了一个非常简单的例子,这个例子只涉及一个Servlet类和一个jsp文件.
经过实践最后得出了结论
* form默认的提交方式是get,所以很多时候需要我们自己指定method为post
* 使用get的时候,action=后面自己写的参数是无法带到服务器端的,且get方式提交form的时候,是将form中的键值以参数的方式拼到url后面的.
* post方式提交的表单是可以获取action后面自己写的参数的.
* 如果表单中存在同名的项,我们可以通过request.getParameterValues(“XXX”)的方式获得一个名称为XXX的值的数组.
@@ -0,0 +1,159 @@
---
layout: post
title: 深度剖析Java的字符编码
categories:
- Programming
tags:
- Java
---
> Java中的String在JVM运行时都是Unicode编码的
### 字符集
在计算机的世界里,我们需要表示太多太多的字符,为了计算机能够正确的显示这些字符,我们将这些字符编码,使得字符和一系列的代号一一对应。当我们的系统按照一种编码方式去读取一个文件的时候,会自动的将里面的编码转换成相应的字符显示在屏幕上。(我们这里并不讨论如何将字符在显示器上通过点阵的方式显示的这个过程)
中文由于其字符数多,其编码方式自然比西方的字符复杂。所以在编写代码,软件使用的过程中,我们经常碰到中文乱码的相关问题。
g.cn的首页是UTF-8的编码(浏览器会首先根据接受到的html自动检测其编码),这个时候如果我们强行以GB2312的编码来解析页面的话就会显示如上的乱码。
这是为什么呢???
首先我们请求一个网址,服务器返回的内容是以指定字符集编码的字节传到浏览器端的,浏览器再按照一定的编码方式去解析这些字节。但是如果传来的正确的字节的编码方式和你解析字节的编码方式不一致,那么就会乱码。
下面我们先介绍几种日常使用中经常碰到的编码。
在我们的日常使用中,我们会碰到iso 8859-1,gb2312,gbk,gb18030,big5,unicode等字符集或者说字符编码,这些都是同一个层次的概念,有些同学可能会问,那UTF-8,UTF-16呢?其实unicode是比较特殊的,虽然通过unicode编码,每一个字符对应一个唯一编码,但是其在计算机上的实现方式却可以有好几种,Unicode的实现方式称为Unicode转换格式(Unicode Translation Format,简称为UTF),所以说UTF-8或者UTF-16只是Unicode编码的一种实现方式。下面我们单独对几个编码进行讲解下:
1. ISO 8859-1
正式编号为ISO/IEC 8859-1:1998,又称Latin-1或“西欧语言”,是国际标准化组织内ISO/IEC 8859的第一个8位字符集。它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入96个字母及符号,藉以供使用附加符号的拉丁字母语言使用。曾推出过 ISO 8859-1:1987 版。ISO-8859-1是单字节编码。
2. GBK(国标扩展)
全名为汉字内码扩展规范,英文名Chinese Internal Code Specification。K 即是“扩展”所对应的汉语拼音(KuoZhan11)中“扩”字的声母。GBK是对GB2312的扩展,这样GBK在支持简体中文的同时也支持繁体中文。现时中华人民共和国官方强制使用GB18030标准.
3. Unicode
再来说说Unicode吧,在java或者javascript中我们构造一个“中文”的unicode串的时候一般是使用”\u4E2D\u6587”来表示,占两个字节。UTF-8则是可变长字符编码,比如一般的英文字符只需要一个字节,而中文则每个字符占用三个字节。而UTF-16两个字节为一个编码单元(固定的),所以从字节的角度来看无法和ASCII实现兼容,且UTF-16存在大尾序和小尾序的两种不同的存储形式。
### 字符编码贯穿java代码编译运行的始终
下面是我们需要考虑的问题:
1. 源文件的编码
2. 编译时的指定的编码参数(Eclipse会根据你源文件的编码格式自动选用相应的编译编码参数)
3. 系统的默认编码(可以通过System.getProperty(“file.encoding”)来获取)
4. 控制台终端显示所设置的编码
5. 运行时JVM中的String都是Unicode编码
首先我们通过一个简单的java代码的编译运行来说明编码在这个过程中的使用
{% highlight java %}
package com.lukejin.stringtest;
import java.io.UnsupportedEncodingException;
public class StringTest {
public static void main(String[]args) throws UnsupportedEncodingException{
String chinese="ab中文";
System.out.println(chinese);
}
}
{% endhighlight %}
这个代码看起来非常简单。
假设源文件的编码格式是GBK,那么当你通过相关可以查看二进制格式的软件查看的时候你可以发现如下编码
![charset1](http://farm8.staticflickr.com/7050/6929714018_7d6ceeb92b_z_d.jpg)
这时我们可以知道”中文”的GBK编码为 D6 D0 CE C4(GBK编码一个中文占两个字节)
在使用Eclipse编译之后(eclipse编译时帮你自动添加了编译参数 -encoding gbk),你可以通过二进制编辑器打开编译后的class
![charset2](http://farm8.staticflickr.com/7276/7075789639_55c644e516_z_d.jpg)
这是编译之后的”中文”已经被转换成UTF-8的编码了三个字节表示一个中文字符。
E4 B8 AD E6 96 87
那么在JVM中运行的时候是怎么一种情形呢?
首先chinese是一个正确的”ab中国”的Unicode字符串,
System.out.println(chinese);
这句会将chinese按照系统默认的编码encode成字节流送到输出流里,
然后终端里会对输出的流里的字节按照终端的编码进行decode得到字符
### 和编码有关的两个方法
关于String的编码有两个比较重要的方法需要提及
`getBytes(String charset)`
`new String(byte[] bytes,String charset)`
这两个方法都是相对于String而言,即相对于Unicode字符串而言。
这里我们就来详细讲解下这两个函数的功能,
GetBytes是将字符串中的一个个字符char按照charset对应的编号进行编码,得到的是编码后的字节数组,这是一个encode的过程,
而new String(byte[] bytes,String charset) 是将byte数组按照charset去解码,将解出来的一个个字符用unicode字符存储,并返回这个unicode字符串。
下面以实例和图的方式展示上面的过程
假设String a=”中文”;//当然你可以以unicode的形式写成String a=”\u4E2D\u6587″;
`Byte[] bs = A.getBytes(“gbk”)`
![charset3](http://farm8.staticflickr.com/7231/7075790277_a89bc6c191_b_d.jpg)
`String b= new String(bs,”iso-8859-1″);//如果这里使用gbk编码进行解码的话,会自然的得到原来的a`
![charset4](http://farm6.staticflickr.com/5040/6929715570_b853f37462_b_d.jpg)
可以看出这个时候已经为乱码了,不过由于没有信息丢失,所以还是可以恢复成中文的。
恢复的过程为 String c = new String(b.getBytes(“iso-8859-1″),”gbk”);
这个过程可以参考上面的两个图进行思考。
首先b按照iso-8859-1进行编码得到”D6D0 CEC4”
”D6D0 CEC4”按照GBK进行解码得到字符串”中文”
“中文” 分别使用unicode进行表示(由于java中String都是unicode),所以b为”中文”(”\u4E2D\u6587″)
### Web编程中常见的编码问题
那为什么我们需要在java中进行所谓的转码呢?
关键原因就是我们构造字符串的时候使用了错误的字符集。
比如前台传过来的是gbk编码的字节流bytes,但是服务器端却错误的以iso-8859-1的字符集解码成字符。
这个过程相当于 new String(bytes,”iso-8859-1”);
当然很多时候这个过程不是由我们来写的,而是由servlet框架来完成,当然你可以改变这个字符集的值。
我们Servlet后台发送响应给前台,这里会有一个编码的概念,就是你输出的内容的编码,以及设置的让接受端浏览器以什么样的编码来解码。
比如我们可以在通过在response.getWriter();之前使用
response.setContentType(“text/html; charset=utf8″);
这样的语句来设置输出的编码,其实这个语句起两个作用,
第一,设置了输出内容的编码方式 比如我们有一个a这个字符串,那么将它发送到浏览器的时候肯定都是字节流,那些字节是这样的a.getBytes(你上面这个设置的编码)
第二,发送给浏览器的response的报头中的编码设置成这个编码,使得浏览器可以以正确的编码去解码字节数组。
当然Servlet 2.4版本以后,提供了setCharacterEncoding这个一个单独的方法可以单独设置编码的。这个你可以通过阅读相关web容器的源码得知。
这两个方法都必须在 `response.getWriter();`之前作用才起作用,且响应的字节流编码字符集和response的header的contentType的charset是一致的。
![charset5](http://farm8.staticflickr.com/7093/6929715704_7a88fb0dae_d.jpg)
这里举一个我碰到的问题:
后台有一个Servlet是前台的一个JQuery的ajax调用的,返回一个包含中文的字符串,由于历史原因,得到字符串是以iso-8859-1解码的得到String a.且系统的默认编码是iso-8859-1编码的,如果我们将这个a直接在控制台上print出来,却发现能够正确的输出中文,(心想:奇怪了,不应该出现中文?)其实原理是这样的,输出到控制台经过如下两个步骤,
首先1.将错误的String按照iso-8859-1进行编码成bytes,这个bytes和正确的gbk编码的bytes是一致的,这个时候我们的SecureCRT设置的编码是GBK,所以能够正确的显示中文,也就是我们以GBK的方式去解码一个iso8859-1的编码,歪打正着,中文反而能显示了。
现在回到Web上来思考,由于response写出的字节编码和浏览器解析的编码是一致的(如果不一致,我们可以模拟控制台的方式)
所以我们必须先做一个转换 String b = new String(a.getBytes(“iso-8859-1″),”gbk”) 这样,b中的字符串就是正确的字符串,
这个时候我们只要以支持中文的编码送到客户端的浏览器就可以了
response.setContentType(“text/html;charset=utf-8″);
out = response.getWriter();
out.write(str);
很多时候,一些同学烦扰乱码还和javascript相关,这个主要的原因是,其实在js的运行时的内部,String也是以Unicode进行编码的,(或者准确的说时utf-16)。
所以在进行ajax的使用的过程中,同学们容易遇到一些乱码问题的困扰。需要注意的是服务器端返回给ajax的字节只要保证是正确的编码就可以了。(比如中文的话,只要保证是相应中文的正确的gbk,utf-8等编码)
### 乱码的总结
在Java运行时的世界里,乱码产生(编译时产生的这里不管)的源头存在于两个地方,其实也就是我上面提及的两个函数(当然有时候是框架帮我们调用了其中的某个函数,所以你得到的已经是一个由网络上传过来的字节数组转换后的String了),
getBytes(String charset) 如果按照指定的charset去对一个unicode String进行编码,但是发现这个编码体系里(比如iso-8859-1)没有这个字符,那么就会编码成3F(其实就是一个问号),这样就造成了信息的丢失了,是不可以恢复的。
new String(byte[] bytes,String charset) 如果对一个字节数组按照指定的字符集去解码,但是字符集突然对其中一段编码不认识的时候,例如某一段字节数组按照UTF-8解码的时候,不认识了,到了unicode字符串这边就是”\uFFFD”,其实这个东西叫做’REPLACEMENT CHARACTER’,显示的是一个问号
具体可以查看
Unicode Character ‘REPLACEMENT CHARACTER’ (U+FFFD)
所以这个地方也肯定发生了信息的丢失,无法恢复的。如果大家想不明白的可以直接按照上面的图类似的想法。
所以我们碰到的乱码往往是下面的情况
1. 一种编码的文件以另一种编码的方式去解析读取,这样肯定出现乱码,这在我们的操作系统里打开文件的时候经常出现。
2. 以错误的编码方式对传过来的字节流进行了解码。所以得到了错误的unicode字符串。
3. 以和控制台不一致的编码对正确的unicode字符串进行编码,并送至控制台显示。会出现乱码。
@@ -0,0 +1,43 @@
---
layout: post
title: Public-key cryptography
categories:
- Web
tags:
- 安全
---
互联网的诞生之初就有信息安全问题的存在。
在信息技术高度发展的今天,信息安全更为重要,从对称加密,再到非对称加密,人们一直在为网络的安全做出斗争。
今天我这里谈的 主要是非对称加密。在传统的对称加密的情况下,密码很容易被泄露,这样信息的安全性就得不到保证。
但非对称加密的出现使得人们看到了希望。
在非对称加密中,有一对密钥,公钥和私钥。用公钥加密的信息只有用私钥才能解密,用私钥加密的信息只有用公钥才能解密。
其中公钥是公开出去的,可以给任何人。私钥是保密的只能自己知道。
所以我们可以通过非对称加密做一些对称加密无法做到的事情,
假设我们用公钥加密的过程为PubKeyEncrypt() 公钥解密为 PubKeyDecrypt()
私钥加密的过程为 PriKeyEncrypt()私钥解密的过程为PriKeyDecrypt()
#### 1.身份认证
比如A和B进行交互,A发消息给B,B要怎么样才能判断接受的消息确实来自A呢? 可以这样
PriKeyEncrypt(“hello”, A’s Private Key)然后发给B,然后B通过A的公钥来解密,如果正确的解密PubKeyDecrypt(msg,A’s Public Key)得到hello,就说明信息确实来自A
但是这个时候问题出来了,B如何得到A的公钥呢?除非A和B认识,且A当面通过U盘或者别的方式把A的公钥交给B,然后B拿回去使用(在网络化的今天这很不现实),要不然在网络上直接传输的很有可能被拦截,然后被替换掉,那怎么办呢?
这个时候涉及一个公钥的发布的问题,目前的那些CA机构(Certificate Authority)所做的事情便是这个事(我心里老在想,CA机构给别人弄弄认证和证书一年就可以弄那么多钱,但是他的私钥的保密性的重要性,不言而喻啊)。
A将自己的公钥(通常是一个CSR–Certificate Signing Request文件,CSR文件包含了你的公钥,以及你的组织和域名的信息)和相关的营业证书或者能够证明法人身份的证件传真发给CA机构,CA机构(第三方的大家都可以信任的)会对A的身份进行全面的认证,确保A就是真正的A(这个时候需要A提供能够证明其身份的证明),同时明确各自在法律上的责任。如果认证成功之后,CA机构使用自己的私钥将A提交的公钥以及相关信息的哈希值进行加密,也叫签名。PriKeyEncrypt(Hash value,CA’s Private Key) 这个时候得到的便是一个数字证书了。用户使用大家都信任的CA机构的Public Key将证书的签名解密可以得到相应的数字证书的哈希值然后比较看是否正确。
比如B得到A的证书(这个证书别人是无法伪造的,除非得到CA机构的私钥),B通过CA机构的公钥将这个证书解密,得到A的公钥,然后再来使用,这个时候就没有问题了。
#### 2.数据的保密性和完整性
也许很多人看到数据的保密性会直接想到,通过非对称的方式对传输的数据进行加密不就可以保证了数据的保密性了么?
但是有一个成本的问题,非对称加密解密的成本比对称加密大的多,
这个时候我们需要对称加密和非对称加密相结合使用了。
比如B和A进行交互,B通过A的公钥将一个密码加密后传给A并协商好下面传输的信息都通过这个密码进行加密。
由于只有A的私钥才能解开这个将要使用的密码,所以密码的传输时绝对安全的。
数据的保密性得到保障之后,我们怎么才能保障数据在传输过程中,没有被恶意删减或修改呢,这个时候我们就需要保障数据的完整性。
保障数据的完整性,我们通常的做法就是通过哈希函数对需要传输的数据进行哈希求值(哈希值也称为输入数据的数字指纹Digital Fingerprint或消息摘要Message Digest等),一般情况下我们可以使用MD5或者SHA-1,一般情况下认为,只要原文有一点变化,哈希值就会变化。
算出的哈希值附在消息上一起发送给对方B,为了确保A在发给B的过程中,为了防止哈希值和原文同时被恶意修改,我们可以通过使用A的私钥对哈希值进行一次加密,这样只有得到A的私钥才能正确的修改这个哈希值。
#### 3.不可否认性
这个比较好理解,比如A和B,A经过CA认证了,如果A通过自己的私钥加密发送消息给了B
那么A就无法否认了,因为只有使用A的私钥加密的信息才能被A的公钥进行解密。
+54
View File
@@ -0,0 +1,54 @@
---
layout: post
title: Git原理浅析
categories:
- Tools
tags:
- Git
- Linux
---
git的底层从其本质上讲是一个内容寻址文件系统,然后基于这个内容寻址文件系统实现了一套vcs(版本控制系统)的高层接口,方便我们使用.当然git也提供了底层接口,便于我们使用之做出符合自己需求的系统.
我们把文件内容交给git进行管理,总得有一个地方来存放这些内容是吧!
是的,在git中,所有的文件内容都保存在git仓库的objects目录中.
#### 初始化git库
我们初始化一个git仓库有两种方式,git init和git –bare init
这两者的区别是,前者会在当前目录下生成一个.git目录(此目录即为git库的目录),而当前目录为我们的工作目录,一般是checkout后的文件,我们编程时所读写的内容都在此目录下.
后者的bare的意思就是裸的意思,也就是直接把当前目录当作git库的目录,这个一般用在远程git库上,因为我们在远程git库上没有checkout的需求,只是用作单纯的git库
git库还有个优点就是直接拷贝到另一个地方就可以直接用了,只要你的相应的机器上安装了git即可.
#### git对象
git中一个非常重要的概念就是git对象,我们可以把git系统想象成一个强大的key-value存储,每一个对象都对应着一个40位的哈希值.通过这个哈希值我们便可以很容易的取得对象(当然我们可以为这些哈希值取一些有意义的别名,方便我们使用).我们可以把这个哈希值看作指针.而对应的对象就是指针所指向的实体.对象和对象之间还可以通过通过指针进行一些关联的操作.
git对象可分为四种类型:
- blob对象 用来存放文件数据
- tree对象 对应着目录,tree的内容为blob对象的指针或者其他tree对象的指针
- commit对象 每一次commit都会产生一个新的commit对象,其包含了一个指向tree对象的指针,指向前一次commit对象的指针,还包含了commit的时间,作者和注释等信息,就相当于为项目做了一次snapshot,通过commit对象我们可以跟踪到前一次commit对象,这样就可以实现log功能了
- tag对象 一种特殊的commit对象
git库目录
接下来分析git库目录中各个文件的作用
{% highlight bash %}
Lukes-MacBook:test.git Luke$ ls -al
total 32
drwxr-xr-x 11 Luke staff 374 Jun 4 20:21 .
drwxr-xr-x 24 Luke staff 816 Jun 4 20:21 ..
-rw-r--r-- 1 Luke staff 23 Jun 4 20:21 HEAD (当前分支的指针)
drwxr-xr-x 2 Luke staff 68 Jun 4 20:21 branches
-rw-r--r-- 1 Luke staff 85 Jun 4 20:21 config
-rw-r--r-- 1 Luke staff 73 Jun 4 20:21 description
drwxr-xr-x 12 Luke staff 408 Jun 4 20:21 hooks (可以实现在特定操作的前或者后触发一些动作)
drwxr-xr-x 3 Luke staff 102 Jun 4 20:21 info
drwxr-xr-x 64 Luke staff 2176 Jun 4 20:21 objects (blob,tree,commit,tag 对象)
-rw-r--r-- 1 Luke staff 85 Jun 4 20:21 packed-refs
drwxr-xr-x 4 Luke staff 136 Jun 4 20:21 refs (指向各个分支的指针)
{% endhighlight %}
objects保存的时候,以40位哈希值的前两位作为子目录的名称,后38位作为对象的文件名
git系统会定期对所有的objects进行打包操作,这样可以减少磁盘占用空间
git中最新版本的都是直接保存的,以前版本是通过引用最新的文件以及差异进行获取的,这是因为大都数时候我们对最新的分支代码更为关注
@@ -0,0 +1,77 @@
---
layout: post
title: SSH免密码远程登录设置
categories:
- Web
tags:
- 安全
- Linux
---
由于这几天需要写一个线上日志查询的小系统。打算使用ssh远程登录进行相关log文件的操作。
为了操作的方便,就找了一个ssh的java实现 jsch http://www.jcraft.com/jsch/
然后写了一些代码进行测试。
后来发现实际环境中,需要通过 public key 的方式进行ssh的登录验证。
所以我在测试环境上需要配置下。
这里写一下配置的过程。
假设有A机器和B机器,A机器通过非密码的方式登录B机器,
### 1.生成密钥对
这个时候我们来到A机器进行相关操作
一般情况下,我们的公钥私钥都是放在 ~/.ssh目录下的
如果没有的话,需要自己生成,
{% highlight bash %}
luke@luke-desktop:~/.ssh$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/luke/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/luke/.ssh/id_rsa.
Your public key has been saved in /home/luke/.ssh/id_rsa.pub.
The key fingerprint is:
9a:34:fd:c6:9f:3d:81:f5:be:f1:e5:df:a1:25:bb:2c luke@luke-desktop
The key's randomart image is:
+--[ RSA 2048]----+
| |
| |
| |
| . . |
| o S o . |
| . + o . . .|
| o + . *.|
| . E.o*.B|
| +=ooB|
+-----------------+
luke@luke-desktop:~/.ssh$ ls
id_rsa id_rsa.pub known_hosts
{% endhighlight %}
其中id_rsa为私钥 id_rsa.pub为公钥,
至于known_hosts文件,它是用来保存以前远程登录过的主机的公钥的
### 2.在远程机器中添加本地机器的公钥
现在我们需要做的是将A机器的公钥加入到B机器的authorized_keys文件中(可以直接拷贝粘贴,如果 authorized_keys文件不存在的话,就在B机器中新建~/.ssh/authorized_keys),然后开启开启B机器的 PubkeyAuthentication的选项
vi /etc/ssh/sshd_config
设置下面的选项:
PubkeyAuthentication yes
重启sshd service sshd restart
这个时候就可以在A机器上ssh登录B 机器了,现在不需要输入用户对应的linux密码了,但是需要输入创建私钥的时候所输入的passphrase,但是这个密码无须在网络上进行传输了。
### 3.使用ssh-add
我们可以通过ssh-add命令将私钥的密码交给ssh-agent进行管理,这样我们登录远程机器的时候,就会直接使用缓存起来的passphrase,而不会让你每次手动的输入。
### 4.其他
一开始发现,本机登录远程机器在登录的时候相当慢,后来发现和两个选项有关
{% highlight bash %}
#GSSAPIAuthentication yes
UseDNS no
{% endhighlight %}
还有一个有点悲剧的事情就是我为了重新验证设置的正确性,将本地的密钥对全部删除后,重新生成了一对,结果发现服务器端被我限死成公钥方式的登录,我一想,这下可彻底的悲剧了,登录不了阿。不过灵机一动,翻出以前的一个邮件,有一个系统急救的方法,就是通过一个跳板机器,然后vnc直接连如XEN虚拟机,然后在公钥验证的下面将密码验证方式设置为yes,重启sshd服务,然后重启从本地登录,重复上面的设置过程,恩,虚惊一场阿,不过下次修改删除操作还是先做好备份才靠谱。
+26
View File
@@ -0,0 +1,26 @@
---
layout: post
title: Joel on software
categories:
- SE
tags:
- 软件工程
---
‘Joel on software’ ,断断续续的将这本书看了差不多了,说实话,这本书的内容还是蛮好的,但还是有点后悔买纸质的书,因为感觉文章有一个个的故事,事件,问题组合而成,有一种”快餐”的感觉.
Joel ,fogcreek公司的创始人,该公司的主要产品是FogBugz这个项目管理,缺陷跟踪系统.
他也是The Stack Overflow Network的创始人之一,http://stackoverflow.com/这个网站是程序员问答的一个社交网站.
至于他的这本书,我想应该是他在自己有所成就之后对自己的经历的一个总结吧,书中很多观点很不错,很多时候很多东西大家都懂,但是大部分没有或者很难用精确的言语表达出来.
下面就谈谈书中的一些东西吧.
文中很多的地方都提及到优秀程序员,优秀的程序员你很难从人才市场找到,对于这点给出了三点建议 走出去,实习生,建立自己的社区,走出去的意思就是多参加一些技术研讨会议,因为那里往往有业界的牛人和对相应技术感兴趣的程序员,话说今年的北京的QCon大会便成为各大公司招聘的好去处.实习生嘛,可以在低成本的情况下发现未来的优秀程序员,何乐而不为.建立社区的目的就是在网络上笼络一堆气味相投的人,从中寻找合适的人.
Joel提到,员工推荐是最不靠谱的员工招聘方式之一.
程序员不在乎钱,但是不能比同行的低太多,当程序员对当前的工作不满意的时候,他们可能就会抱怨工资低了,的确,当程序员觉得自己的工作很糟糕的时候,只能退而求其次,寻找物质上的点点寄托,平衡下心理不平衡,当然这也有个度.
文中还有一个地方有谈及对于软件的源码的态度,当我们使用一个买来的软件的时候,当碰到了问题,如果没有源码的话,那是相当的纠结了,如果等官方的答复的话,可能需要等很长时间,但是如果自己改,又没有源码,所以很多公司基于开源软件的思路还是不错的,这样,才能对开发的应用很好的掌控. 很多时候很多软件并没有什么高深的内容或者算法,只要有源码,你就可以一步步的跟进去,知道发生的一切.但是如果没有源码,那它就是一个黑盒,出了问题,无计可施.
当然自己修改源码有时候也有注意的地方,比如一个大的公司,部门间共享相关项目的源码的话,可能自己的部门有特殊的厄一些需求,需要自己修改一些代码,这个时候最好能够将修改的patch提交给对方的团队,让他们来升级,要不然当他们下次升级系统的时候,你自己还需要做一次merge.也不利于代码分支的维护.
文中还讲了一些项目管理中的问题,比如对于项目的日程规划,只有一线的程序才能提出完成日期的估计值,管理层制定的日期计划最终都将注定失败.管理层对程序员施压是一个无比傻逼的错误,如果进度慢于计划,程序员本身就惶惶不可终日,你再强加压力,只会让代码的质量更加恶化,不是程序崩溃就是程序员崩溃.如果进度快于日程表,那么程序员就会欢欣鼓舞,效率很高,根本不需要上面施压.
@@ -0,0 +1,61 @@
---
layout: post
title: tail -f 不起作用了?!
categories:
- Programming
tags:
- Linux
---
#### 问题描述
在测试tail -f的时候,发现一个奇怪的现象,首先 我在一个窗口中
tail -f test.txt
然后在另一个窗口中用vim编辑这个文件,增加了几行字符,并保存,这个时候发现第一个窗口中并没有变化,没有将最新的内容显示出来。
#### 查找原因
然后我查看了以下tail的帮助,看到了下面的内容
> -F same as –follow=name –retry
我就加上-F,重复上面的实验过程,
发现这次有变化了,但是是完全的重新加载了一次,
> tail: `test.txt’ has been replaced; following end of new file
这个提示表明,tail原来跟踪的文件被替换了,所以重新加载了一次。
这个时候把目光转移到vim。
这个时候重新实验,tail -f test.txt,然后vim编辑,保存。
通过lsof|grep test.txt(列出打开test.txt文件的进程)
发现tail -f跟踪的文件在vim编辑后是被删除掉了。
保存前
{% highlight bash %}
luke@luke-desktop:~/Desktop$ lsof|grep test.txt
tail 10158 luke 3r REG 8,2 0 4984552 /home/luke/Desktop/test.txt
vim 10173 luke 4u REG 8,2 12288 4981647 /home/luke/Desktop/.test.txt.swp
{% endhighlight %}
vim保存后
{% highlight bash %}
luke@luke-desktop:~/Desktop$ lsof|grep test.txt
tail 10158 luke 3r REG 8,2 0 4984552 /home/luke/Desktop/test.txt~ (deleted)
vim 10173 luke 4u REG 8,2 12288 4981647 /home/luke/Desktop/.test.txt.swp
{% endhighlight %}
这个时候再去细读了下tail的帮助文件。
> With –follow (-f), tail defaults to following the file descriptor, which means that even if a tail’ed file is renamed, tail will continue to track its end. This default behavior is not desirable when you really want to track the actual name of the file, not the file descriptor (e.g., log rotation). Use –follow=name in that case. That causes tail to track the named file by reopening it periodically to see if it has been removed and recreated by some other program.
看了之后明白了一切。
#### 结论
当使用vim编辑保存一个文件之后,vim创建了一个新的文件,老的文件被删除,所以他们的inode不一样了。
而tail -f 默认情况下是根据文件描述符来进行文件跟踪的,而文件描述符创建的时候又是依赖于inode的,所以vim保存之后不会被tail -f发现(因为是新的inode)。
PS:我们可以通过下面的方式来进行测试
echo testest>>test.txt
@@ -0,0 +1,67 @@
---
layout: post
title: How to open a url in another application
categories:
- Programming
tags:
- objc
- Cocoa
---
我们经常希望在网页中通过特定schema的连接关联打开相关的程序进行一些操作,比如qq的聊天链接等.
在Cocoa中,这个操作属于Launch Services的一部分.
还有我们平时文档关联特定的程序进行打开这样的操作也属于Launch Services的范畴.
在系统中存在一个Launch Services Database,它包含了”什么样的文件可以通过什么样的程序打开,或者什么样的URL可以通过什么样的程序打开的” 这些信息.
下面我们还是来重点谈URL和Application关联的问题.
#### 1.URL注册
首先我们得让程序告诉操作系统程序自己所关联的URL的schema.
这个我们可以在Info.plist中添加相关信息.
{% highlight xml %}
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>MyURLHandler</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myurl</string>
</array>
</dict>
</array>
{% endhighlight %}
添加这些之后,重新编译运行程序,系统后台运行的程序会将刚刚的对应的url和程序的关联信息写进Launch Services Database,这个时候,页面中的 myurl://xxx 这样的url会通过你的程序来处理了.
注:
CFBundleURLTypes中的CF的意思是Core Foundation
Mac OS X通过如下几种方式进行注册.
1. 当用户登录后,会有一个后台运行的程序对Application目录进行扫描,发现文档绑定或者url绑定的信息,并将其复制到Launch Services Database
2. 当打开一个新的文件夹,然后Finder发现某个Application,Finder会将相关程序的关联信息写入Launch Services Database
3. 当用户打开某个没有关联的文档的时候,系统会弹出一个程序选择对话框,选择之后,会将相关关联写入Launch Services Database
#### 2.URL接受和处理
刚刚我们对URL进行注册了,点击之后,系统会发送一个事件消息给程序,所以第一我们得设置一个处理器来接受相关事件.
下面的代码一般我们写在AppDelegate中,比如可以写在applicationDidFinishLaunching函数中.当程序启动完成时运行这段代码.
{% highlight objc %}
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:self
andSelector:@selector(handleURLEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
{% endhighlight %}
然后定义handleURLEvent:withReplyEvent:这个方法
{% highlight objc %}
- (void)handleURLEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
NSLog(@"The url is %@",[event descriptorAtIndex:1] stringValue]);
}
{% endhighlight %}
@@ -0,0 +1,91 @@
---
layout: post
title: Jsch – Pure Java implementation of SSH2
categories:
- Programming
tags:
- Java
- SSH
---
## 一.SSH的介绍
SSH(Secure Shell)是一种网络协议,用于互联网上不同设备之间信息的安全传输.在安全性方面其使用了公钥的加密技术.
关于SSH还有一个故事,以前有一个叫做Tatu Ylönen的芬兰程序员开发了一个叫做SSH的网络协议和服务程序,后来Tatu Ylönen对SSH源码的协议进行了改变,以至于远远超出了公开源代码许可的限度.这引起了很多依赖ssh的人的不满,于是OpenBSD的开发人员便着手开发自己的SSH,于是OpenSSH便诞生了.
目前大都数的Linux中都运行着OpenSSH,为用户安全的提供远程登录管理和其他数据传输的功能.
## 二.JSCH的介绍
相对于C实现的openssh,今天我要说的是一个Java的SSH2实现,这样我们就可以完全通过java来调用ssh实现一些功能,比如通过jsch远程执行一些linux命令,或者通过jsch实现一个基于java的sftp客户端等等.
JSCH的官方网站http://www.jcraft.com/jsch/
Maven中的坐标http://mvnrepository.com/artifact/com.jcraft/jsch/0.1.42
目前有许多项目都是用到了jsch:
Ant(1.6 or later).
JSch has been used for Ant’s sshexec and scp tasks.
Eclipse(3.0).
Our Eclipse-CVSSSH2 plug-in has been included in Eclipse SDK 3.0. This plug-in will allow you to get ssh2 accesses to remote CVS repository by JSch.
NetBeans 5.0(and later)
Jakarta Commons VFS
Maven Wagon
Rational Application Devloper for WebSphere Software
HP Storage Essentials
JIRA
可惜的是JSCH并没有文档,所以很多时候只能看样例代码或者自己看api文档来摸索使用.
在介绍如何使用之前先看下一个必要的知识.
一个ssh的连接我们称之为一个Session,从一个建立好的Session中我们可以获取各种类型的Channel,每一种Channel的功能是不一样的.如exec通道是用来执行一个单独的命令并获得返回结果,shell远端终端方式的交互,sftp可以传输文件等,具体的通道类型可以看这里http://www.ssh.com/support/documentation/online/ssh/guardian/11/scb_ssh_channel_types.html
通常我们本机的ssh的相关文件保存在~/.ssh目录下
我们可以通过ssh-keygen -t rsa 来生成一对ras类型的公私钥.
## 三.代码片段示例
### 1.初始化Session
JSch jsch = new JSch();
//当然你也可以通过密码的方式进行用户的验证
//密码的验证方式需要在下面使用session.setPassword("pass");
jsch.addIdentity("/home/luke/.ssh/id_rsa");
Session session = jsch.getSession("user", "hostname", 22);
session.setConfig("StrictHostKeyChecking", "no");
// making a connection with timeout.
session.connect(30000);
这样一个ssh的Session便建立好了,下面我们可以通过这个Session获取一些Channel,运行一些功能
### 2.exec channel 示例
{% highlight java %}
Channel channel = session.openChannel("exec");
//command is your command to be exexuted
((ChannelExec) channel).setCommand(command);
BufferedReader fromServer = new java.io.BufferedReader(new InputStreamReader(
(channel.getInputStream())));
channel.connect();
StringBuffer sb = new StringBuffer();
Thread.sleep(1000);
while (fromServer.ready()) {
String tt = fromServer.readLine();
sb.append(tt+'\n');
}
channel.disconnect();
System.out.println(sb.toString);
{% endhighlight %}
在ssh的channel中,我们的输入输出都是建立在Java I/O的流模型之上的,
Channel向远程服务的输出为OutputStream,远程机器返回的内容是通过Channel的InputStream,
所以我们可以以流的形式向Channel的OutputStream放要传给远程机器的命令,也可以从Channel的InputStream中读取内容.(这一点在shell类型的channel中尤为重要)
### 3.sftp channel的代码片段示例
{% highlight java %}
ChannelSftp channel = (ChannelSftp)session.openChannel("sftp");
channel.connect(1000);
channel.get("远程文件", "本地目录");
channel.disconnect();
{% endhighlight %}
这样我们便实现了获取远程文件的功能,更多使用方法还请参阅官方jar包中的实例代码.
@@ -0,0 +1,102 @@
---
layout: post
title: Cometd & Jetty Continuations
categories:
- Programming
tags:
- Java
- Jetty
- Cometd
---
> 前言:随着web的不断发展,出现了诸如ajax,comet等技术,其实都是为了提高用户体验。在web并发量越来越高的今天,异步成了一个热点。servlet3.0也引入了异步处理的概念。
Comet是一种服务器端推的技术,所谓服务器端推也就是当有事件要通知给某个用户的时候,是由服务器端直接发送到用户的浏览器。
服务器端Push目前一般有两种方式,HTTP streaming和Long polling。详细的介绍可以看这里 http://en.wikipedia.org/wiki/Push_technology
有一个Comet的框架叫做Cometd,使用的方式为Long polling。它是使用了jetty continuations特性,jetty continuations使得异步的request成为可能,这里我们来讨论下为何需要jetty continuations呢?
比如我们的浏览器的一个请求发送到服务器端了,并进行长轮询,保持了连接不结束,直到一次长轮询timeout或者有事件发生,并接收到服务端推来的消息,所以在一次长轮询的过程中,大部分时间都是在等待,如果使用老式同步的方式进行编程的话,那么有多少个连接就需要多少个线程在那里,而大都数都是在等待,所以这无疑是系统资源的巨大浪费。
jetty continuations很好的解决了这一问题,当有请求过来之后,将连接的相关信息封装到一个continuation的对象中,通过调用continuation的suspend方法,然后返回,把当前线程交还到线程池,所以这个时候线程可以返回到线程池等待并处理其他新的请求。
当有事件要发给之前的某个请求的时候,再调用对应的continuation的resume方法,将原来的哪个请求重新发送到servelt进行处理,并将消息发送给客户端,然后客户端会重新进行一次长轮询。
下面我来由上而下详细讲述jetty continuation实现方式
## 一.Cometd
Cometd是一个Comet的框架,实现方式为基于jetty continuation的长轮询。
Cometd的消息都基于一个叫做Bayeux的消息协议
Bayeux的主要目的是支持使用ajax的客户端与服务器端之间灵敏,快速的信息交互。
Bayeux是一种用来在客户端和服务器端传输低延迟的异步消息(主要通过http)的一种协议。它定义的消息通过命名通道进行路由并且能够进行交互传 送:server -> client, client -> server 甚至 client -> client (当然还是需要通过server中转)。默认的,此通道已经引用了发布的路由语义,但同时也支持其它路由模块。
从服务器端向客户端异步发送的数据通常被叫做 “服务器推”(server-push)。这种使用ajax的web应用和服务器推技术的结合称作“Comet”。 Cometd是一个提供多种开发语言的Bayeux项目,由Dojo基金会提供支持。
### 1.Cometd Server
在服务器端推中,一个常见的应用场景便是,消息的生产者可以往一些消息channel中通过bayeux协议发送消息,服务器端对bayeux消息进行处理解析,发送到所有订阅了这个channel的消息消费者,消费者接收到这个消息之后便可以自己再做自己的处理。在这样的模型中于是出现了几个重要的概念:
Bayeux
这个在cometd框架中是ContinuationBayeux类,
bayeux对象在cometd运行的上下文中非常重要,可以说是一个核心对象,它管理着所有的clients和channel,并肩负着消息的处理和维护。
channel在cometd中是一个树形的结构,在bayeux对象中,保存着根channel(/),从根channel我们可以找到它的子channel
bayeux存在一个handle方法,对消息进行处理
Client
一个client对应着一个消息的生产者或者消费者,对应的类为ContinuationClient。
client中包含一个contiunation对象,以及对continuation对象进行操作的suspend和resume的方法
subscriptions保存着当前client的所有订阅的频道channel
client由一个clientId唯一标示,每一个client还对应着一个browserId。
client可以通过deliver方法来进行接受消息操作,接受消息无非就是将接收到的消息加入到自己的消息队列中,并将continuation对象进行resume,这样之前冻结的request重新发送到servlet进行处理。
Channel
channel在cometd中对应ChannelImpl类
channel中维护这一个subscribers的列表,用来保存订阅了当前channel的订阅者
channel还维护者自己的子channel
每一个channel都对应这一个channelId 也就是channel的路径字符串
ContinuationCometdServlet
Cometd Server运行时的入口为ContinuationCometdServlet
Handshake
客户端和服务器端建立连接之前需要先握手,在服务器端将客户端标识起来。一个请求过来如果cookie中没有broswer_id那么就生成一个broswer_id(相当于session_id 因为这些请求并非无状态的,需要在多次长轮询中记住请求是来自同一个客户端),并写到cookie中。握手的时候还会生成一个ContinuationClient对象,每一个ContinuationClient对象由一个client_id唯一标识。并在握手成功后将这个clientId作为消息的一部分发给客户端。由于握手的消息是属于元数据消息,所以在Bayeux中提供了所有元消息channel的Handler。这些handler最终将消息交给各种Transport去发送,Tranport对象 此类对象主要是用来处理消息的发送,比如JSONTransport或者JSONPTransport,这两个对象中都有response的引用,所以可以用来直接回复客户端。
Subscribe
订阅的主要作用是将订阅某个Channel的客户端所对应的ContinuationClient对象加入到对应的channel的subscribers列表中,并将所订阅的channel加入到ContinuationClient对象的subscriptions数组中。订阅成功之后就应该开始了long-pooling了,这个时候,服务器端会将当前的请求封装到一个Continuation中,并将Continuation对象设到ContinuationClient对象中,然后将ContinuationClient对象suspend,让出当前处理线程。
Publish
当一个客户端往一个channel中publish消息的时候,服务器端接收到这个message,有两种方式处理这个消息,一个是在相应的channel中广播这个消息,这个时候调用channel的push方法,对每一个订阅了当前channel及其子channel中的ContinuationClient进行消息分发,将消息复制到每一个ContinuationClient自己的消息队列中,并将ContinuationClient中的Continuation唤醒resume。这个时候之前暂停的request会被重新发送到servlet,并且servlet可以知道其状态,并进行消息的处理。还有一种消息的发送是直接调用某个ContinuationClient对象进行消息的发送。
### 2.Cometd Client
上面讲了服务器端的各种情况的处理,当然光有服务器端的处理是没有用的,还需要客户端的配合。
客户端既可以是后端的java程序,更多的时候是浏览器。
浏览器中采用Ajax的方式和服务器进行交互,cometd提供了基于dojo和jquery两个js框架的cometd-client
## 二.Jetty NIO & Continuation
在Cometd部分提及到当服务器端接收到来自客户端发来的消息,然后对消息进行处理。当然在web应用中,消息的接受最初都是由web容器来接受,这涉及到了socket的处理,这个时候web容器本身的io性能就非常重要了。
在传统的BIO服务器模型中,一个新的连接来了,就要创建一个线程来接受这个socket连接(一个连接一个线程),进行简单的封装后,分发给web容器的”线程池“,线程池会选择一个线程来处理连接请求并给予响应。
而在NIO的模型中,只需要一个线程便可以处理所有的连接,当有新的连接建立的时候,只需要将连接注册到selector中,直到某次检测到有请求了,便开始对请求进行处理,并将他们都交给”线程池“(所有的连接都是一个线程在处理,直到发现了请求,再将请求分发出去)。
在高效的Comet应用的最底层还需要一个高效的web容器。
### 1.Jetty Server With NIO
Jetty是一个纯java实现的非常轻量级的web容器,高度组件化,可以很方便的将各种组件进行组装,而且可以非常容易的将jetty嵌入到自己的应用中。
jetty运行时的核心类是Server类,这个类的配置一般在jetty.xml中配置,然后jetty自带的一个简单的ioc容器将server加载初始化。
下图主要描述了Jetty在NIO的模式下工作的情形,这里只说到将任务分配到ThreadPool,后面的ThreadPool的处理没有说,大家可以去看下源码。
![Jetty](http://farm8.staticflickr.com/7115/7076600691_0b07e3c8ea_z_d.jpg)
在jetty中,web容器启动是从Server开始的,一个Server可以对应多个Connector,从名字就可以知道,Connector是来处理外部连接的,Connector的实现有多种,即可以是非阻塞的(如SelectChannelConnector),也可以是阻塞的(如BlockingChannelConnector,当然jetty中这个阻塞的已经使用nio优化过,性能应该比使用java io实现的好),
我们不能直接说谁的性能好,谁的性能不好,关键还是看应用场景,因为NIO实现的非阻塞的话,doSelect的过程是阻塞的。所以当并发量小,且请求可以快速得到响应的话,用阻塞的就可以很好的满足了,但是当并发量很大,且后端资源紧张,请求需要等待很长一段时间的(比如长轮询),那么NIO的性能肯定必传统的高很多很多倍。
这里稍微讲一下NIO的概念把,在NIO的Scoket通讯模型中,一个socket连接对应一个SocketChannel,SocketChannel可以将某个事件注册到某一个Selector上,然后对Selector进行select操作,当有请求来的时候,并可以通过Selector的selectedKeys()获得所有收到事件的channel,然后便可以对channel进行操作了。这个其实和linux中的select函数类似,只不过这里是面向对象的,在linux中,我们将需要监听的sockt连接加入到一个文件描述符的集合中FD_SET中,然后select函数对这个集合进行检测,根据得到的结果来判断某个fd对应的标志位是否为1来判断是否有数据。这样也就是一个线程可以同事处理多个连接。
换话题了,我们都知道请求最终都是在Servlet中被处理的,而Servlet得到的是request,response,这些对象什么时候出来的呢?不急,上面不是说到一个EndPoint(实现了Runnable接口)EndPoint对象在被初始化的时候就对其_connection成员进行了初始化,生成一个HttpConnection对象,newConnection的方法其实在SelectChannelConnector中被覆盖了。然后这个EndPoint对象不是被分配到ThreadPool了么,ThreadPool将其加入到队列中,当有空闲线程的时候,就对这个endPoint对象进行处理了,运行EndPoint的run方法,然后会调用自己的connection对象的handle方法,最终将connection对象交给Server的handler进行处理。Server本身继承自HandlerWrapper,自己的_handler是一个HandlerCollection的实例,HandlerCollection实例的配置在jetty.xml中有配置,在处理httpconnection对象的时候所配置的handler会依次被执行。
DefaultHandler中就涉及到上下文处理,然后交给各个项目的servlet进行处理。
### 2.Jetty Continuation
Jetty Continuation在jetty7的实现中也就8个类,非常的简洁。
ContinuationSupport类是产生Continuation对象的工厂类。它会根据当前服务器容器的类型选择不同类型的Continuation对象,比如支持jetty6版本的,还有支持servlet3.0版本(对servlet3.0异步处理进行了封装),还有一个阻塞的Continuation实现FauxContinuation。
+31
View File
@@ -0,0 +1,31 @@
---
layout: post
title: Why Asynchronous Web
categories:
- Programming
tags:
- Java
---
这两天在继续了解web应用中的异步处理问题。
然后看到了淘宝文初的博客http://blog.csdn.net/cenwenchu79
,他在几篇文章中多次提及jetty的continuation和servlet3的异步处理特性。看了之后收获不少。
#### 异步处理的层次
异步处理在Web应用中可以分三个层次:
* Socket数据传输的异步化,NIO
* HTTP请求的异步化(Jetty Continuation,Servlet 3)
* 后台服务的异步化
#### 异步处理的原因
我们肯定会思考,为何需要异步化呢?
当访问的并发量很大的时候,如果Socket数据传输如果不做异步化处理,那么每一个socket连接都需要一个单独的线程来处理socket io,这样无疑会花费很多的系统资源,而且程序员需要考虑避免死锁,线程安全等问题。当java引入nio后,就有了异步的socket处理方式,将和socket关联的socketchannel注册到一个selector中,然后调用selector的select方法,当某一个socket有数据可读的时候,就会自动的反应到selector检测的结果中,这样便可以异步的进行处理。
当每一个请求的处理时间较长,http请求不做异步化的话,就会长期占用线程池中的线程(没有线程池或者线程池的最大线程数设置的过大的话,会产生内存消耗过大的问题),那么新的http请求将无法得到及时的响应,大部分的thread都进入阻塞状态。当引入jetty的continuation或者servlet3的Asynchronous ,可以将当前请求先暂停,让出处理线程,当原来的请求可以响应的时候,在答复原来的请求(continuation是将request重复提交一次)。
当后台业务处理组件资源紧张的时候,如果不做异步话就会出现资源争抢的情况,就会存在锁问题,性能降低,很多线程会处理阻塞状态。一个系统模块的服务的问题可能会导致整个系统的性能问题。
所以使用异步方式的根本原因就是:
计算机的cpu或者内存等资源都是有限的,我们要控制系统线程数量,并且使得系统的整体性能不会因为某些阻塞时间较长的线程而急剧下降。
+146
View File
@@ -0,0 +1,146 @@
---
layout: post
title: JSONP
categories:
- Programming
tags:
- Javascript
---
## 一.同源策略
### 1.什么是同源策略
同源策略阻止从一个domain源加载的文档或脚本获取或设置另一个源加载的文档的属性。这个策略可以追溯到 Netscape Navigator 2.0。
这里的同源是指访问文档的 协议、端口(如果指明了的话)和主机名都相同。
在同源策略中有一个例外,脚本可以设置 document.domain 的值为当前域的一个后缀,比如域aaa.xxx.com的后缀可以是xxx.com。如果这样做的话,短的域将作为后续同源检测的依据。例如,假设在 http://aaa.xxx.com/dir/other.html 中的一个脚本执行了下列语句:
document.domain = "xxx.com";
这条语句执行之后,页面将会成功地通过对 http://xxx.com/dir/page.html 的同源检测。
举个例子比如有http://aaa.xxx.com/a.html 和http://bbb.xxx.com/b.html两个网页,然后将b.html通过iframe的方式嵌在a.html中,那么默认情况下是无法在a.html中通过js脚本操作b.html中的元素的。
### 2.同源策略的困扰
在如今各种各样的应用中,比如企业内部的网站,有时候需要整合多个应用的页面,并做处理,这个时候便受到同源策略的影响. 特别是ajax非常流行的今天,需要异步和其他应用交互的场景也非常的多,但ajax是通过XMLHttpRequest这个js对象来进行操作的,所以必然受到同源策略的约束。
## 二.常用解决方案
### 1.反向代理
通过反向代理我们可以将对domain B的请求 ,转换成对domain A的请求,这样就可以保证以同源的方式来访问原本非同源的页面。
### 2.script引用的方法
大家也许都知道下面的这种js脚本引用的方式,而浏览器是允许src使用非同源的文件的,
{% highlight js %}
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
</script>
{% endhighlight %}
这样我们就可以想出一个办法来变相的和非同源的文档交互,我们可以将script的src属性设置为一个动态的请求的地址,并给他传递一个回掉函数,服务器端根据这个回掉函数的名字,构造出一个方法调用机算好方法调用的参数。
举例
{% highlight html %}
<html>
<body>
<div id="result"></div>
</body>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
</script>
<script type="text/javascript">
function callback(data){
$('#result').html(data);
}
</script>
<script type="text/javascript"
src="http://127.0.0.1/~Luke/test/test.js">
</script>
</html>
{% endhighlight %}
test.js的内容其实可以放在任何地方
callback('just test');
当然test.js也可以是任何动态的内容
## 三.JSONP
理解JSONP
JSONP是JSON With Padding的意思。
json是js对象的表示方法,这里就不多说。
jsonp的方式从根本上其实就是上一节中所讲的script引用的方法,只是所引用的那个文件不是静态的js文件,而是一个动态的内容。其返回的内容是回掉函数的调用,重要的是函数调用的时候,使用了根据请求的参数获取的json对象对回掉函数的参数进行填充。这就是jsonp名字的来由。
假设我在80端口的apache下部署了一个静态的页面
{% highlight html %}
<html>
<body>
<div id="result"></div>
</body>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
</script>
<script type="text/javascript">
function show(data){
$('#result').html(data.name);
}
</script>
<script type="text/javascript"
src="http://127.0.0.1:8080/?name=Luke&callback=show">
</script>
</html>
{% endhighlight %}
然后8080端口部署了一个web.py的代码
{% highlight py %}
import web
urls = ("/.*", "hello")
app = web.application(urls, globals())
class hello:
def GET(self):
p = web.input()
return '%s({name:"%s"})'%(p.callback,p.name)
if __name__ == "__main__":
app.run()
{% endhighlight %}
这样之后可以通过访问http://localhost/~Luke/test/test.html看到结果
jQuery中的jsonp支持
jquey现在也提供了jsonp的调用方式,
{% highlight js %}
$(document).ready(function(){
$.ajax({
url:'http://127.0.0.1:8080/?name=Luke&callback=?',
dataType:"jsonp",
jsonp:"jsonpcallback",
success:function(data){
$('#result').html(data.name);
}
});
});
{% endhighlight %}
注意这里并不是ajax调用。
或者
{% highlight js %}
$.getJSON("http://127.0.0.1:8080/?name=Luke&callback=?",
function(data){
$('#result').html(data.name);
});
{% endhighlight %}
其实现原理就是在运行的时候,会将我们提供的匿名回掉函数动态生成一个有名字的callback函数,并创建一个src为上述地址的script元素,末尾的问号替换成动态生成的callback函数名,并在head中动态插入script的代码。
但是由于这种方式是GET方法,所以能够传递的参数的量是有限的。
+56
View File
@@ -0,0 +1,56 @@
---
layout: post
title: Fail Fast
categories:
- Programming
tags:
- Java
---
Fail Fast从字面上看是“快速失败”的意思,或者叫做“速错”,其含义就是让可能发生的错误尽早的被发现。
无论是人类,还是代码这个道理都是适用的。我们在做事的时候,犯错误是很常见的,但是我们应该尽量使得错误发生之初就被发现,并找出原因进行纠正。一开始不重视或者变相的处理了,这会使得错误延后,反而使得损失增大。
还有个事例就是创业,我们就要抱着“如果要失败那就快点”的心态,加快自己前进的步伐。
哈哈,说远了,今天主要说的是程序设计中的Fail Fast。
在我们软件开发的过程中,当碰到问题了,我们第一步就是重现问题,然后进行debug,找出问题的原因,但往往有些bug隐藏的比较深,经过n次特定的操作的在特定的环境下才会重现,或者出错后,报错的stack traces的没有指出错误的真正起点。
所以我们设计一个Fail Fast的系统显得很重要了。
很多人设计的系统,为了增强系统的健壮性,自动的将系统中出现的问题处理掉,并继续运行,但是在未来的某个时间引发奇怪的错误。
而fail fast的系统的做法却是相反的,当有问题发生,立即出错,并将出错信息显示出来。这样bug就很容易被发现,而不会进入到最终的产品中。
比如下面的代码片段,假设maxConnections从一个配置文件中读取最大连接数这个数值,
当发现读取到的值为null的时候,我们一般有两种做法,直接返回null或者返回一个默认值。
(在JDK的HashMap的实现中也遵循fail fast原则,当获取遍历游标iterator之后,如果别的线程对map进行修改等操作会抛出异常的,因为这样会使得算法执行的环境发送变化,导致遍历过程中可能发生错误。)
{% highlight java %}
public int maxConnections() {
string property =getProperty("maxConnections");
if (property == null) {
return 10;
} else {
return property.toInt();
}
}
{% endhighlight %}
但是这在fail fast的原则下则应该如下
{% highlight java %}
public int maxConnections() {
string property =getProperty("maxConnections");
if (property == null) {
throw new
NullReferenceException (
"maxConnections property not found in"
+ this.configFilePath);
} else {
return property.toInt();
}
}
{% endhighlight %}
假设上面的代码是某个web应用的一部分,当某次升级的时候,不小心将maxConnections写错了,或者不小心删除了,那么如果采取默认值的情况的话,可能程序不会报错,但是运行之后可能会发现因为连接数变小了而使得应用很慢,且这个错误不便于查找,但是如果直接抛异常的话,错误就显而易见了。
在很多时候,在系统中我们需要建立自己的错误异常处理体系,比如编写一系列的assert检测方法,并定义自己的异常类型,在正确的地方抛出异常。
fail fast的方式可能使你感觉到软件的健壮性不是很好,的确它让我们更容易的找到错误,那么当我们最终将软件部署成产品的时候,还需要这样的写法么,很多时候我们在发布的时候都回将assert关闭,为了避免因为错误使得程序crash掉,但是真的有必要这么做么,其实没有必要,有时候线上的错误很少会发生,如果发生了错误,而不直接显示,那么我们开发人员找错误的时候,首先就需要重现问题,而这个可能很难重现出来。当然我们可以使得异常的方式处理的友好些。比如跳出一些错误异常窗口,然后询问是否将异常发生时的状态和错误信息发送给开发人员。
这样将有助于软件bug的修复。
+71
View File
@@ -0,0 +1,71 @@
---
layout: post
title: 谈谈 Hash Table
categories:
- Programming
tags:
- Java
---
## 一.数据结构
在我们编程的世界里数据的基本组织可以说有三种形式。
* 结构体(或对象)
* 数组
* 链表
其他任何的数据组织形式都可以看作是这三种数据组织形式的组合变体。
结构体(或对象)可以是基本数据类型或者其他结构体(或对象)的组合。结构体或对象一般用来描述一个复杂数据实体。
数组一般是一组同类型的变量的集合,在内存中表现为一片连续的空间,因为空间是连续的,且每一个数据单元占的内存空间的大小是相等的,所以可以根据地址的偏移对数据元素实现快速访问,但是当需要插入或者删除一个元素的时候,则需要对目标元素的之后的所有元素进行移动了。
链表的单个节点一般为结构体或者对象,因为链表的单个节点除了需要保存数据之外还需要维护它的相邻节点的关系,如果想获得链表中的某个节点的值,需要从链表的头结点开始遍历,直到找到需要的东西,而插入或者删除某个节点的话,需要找到相应的节点,修改其以及其相邻节点的相关指针的引用即可。
像其他的数据结构,比如 队列,栈,树,都可以通过数组或者链表来组织,并实现相应的操作功能。
## 二.Hash Table
这个世界上没有十全十美的东西,所以我们要学会取舍。任何技术的实现都没有最好的只要最合适的,也就说实现的最佳方案是和应用场景息息相关的。
很多时候,我们想对数据进行快速的存取(比如缓存的实现),并用一个key来标记自己存取的数据。我们可以把它叫做key-value的结构。
说到“快速”我们很快想到数组,因为数组可以在O(1)的时间复杂内完成指定位置元素的读写操作。
所以在理想状态,如果一个数组足够长,且存在一个函数可以将每一个key映射到唯一的一个数组下标,那么我们就可以很完美的解决问题。但往往资源都是有限的,我们没有那么大的空间,也不能设计一个无比负责的映射算法保证每一个key对应到一个唯一的数组下标。所以我们会选择一些折中的方案。
hash table便是为解决这类问题而存在的。
### 1.哈希函数
Hash或者你可以翻译成散列或者杂凑,hash操作其本质上就是将一个数据映射成另一个数据,通常情况下原数据的长度比hash后的数据容量大。
这种映射的关系我们叫做哈希函数。
一般情况下 哈希函数的输入可能的总数要远远多于哈希值所能表示的总数,所以就有可能两个不同的输入对应同一个哈希值,通常把具有不同关键码而具有相同哈希值的记录称作“同义词”。
在信息安全领域中也经常使用到哈希函数,不过需要使用的是单向哈希函数,就是无法通过哈希的结果反推出输入,所以经常应用于密码的加密,传输内容的完整性检查,在安全领域常用的哈希算法有 MD5,SHA1等。
在哈希表的应用中,哈希函数常用余数法进行,也就是通过求模的方式算出哈希值。
### 2.哈希表
哈希表是一种数据结构,实现key-value的快速存取。之前说过数组可以实现快速存取,所以哈希表肯定会使用到数组。在这里,我们把每一个数组的单元叫做一个bucket(桶)。
构造哈希函数
这里哈希函数的作用就是将key映射到一个存储地址。所以构造一个哈希表我们得先构造哈希函数。
如果一个key哈希后对应地址中已经存放了值了,这种情况我们叫做哈希冲突(Hash collisions)。
如果存在一个哈希函数,使得每一个输入都能对应到唯一的一个存储单元中(没有冲突),那么这样的哈希函数我们可以叫它完美哈希函数(Perfect Hash Function,简称PHF)。
但为了哈希函数简单,运行速度快,往往不会使用完美哈希函数。所以冲突肯定会存在的,为了减少冲突,我们希望哈希函数的结果均匀的分布在地址单元的空间中。这样可以有效的减少冲突。
装填因子Load factor a=哈希表的实际元素数目(n)/ 哈希表的容量(m) a越大,哈希表冲突的概率越大,但是a越接近0,那么哈希表的空间就越浪费。
一般情况下建议Load factor的值为0-0.7,Java实现的HashMap默认的Load factor的值为0.75,当装载因子大于这个值的时候,HashMap会对数组进行扩张至原来两倍大。
冲突解决
既然冲突不可避免,那么我们就必须对冲突进行解决(总不能把之前的内容覆盖掉把),
解决冲突的方式主要分两类
开放定址法(Open addressing)这种方法就是在计算一个key的哈希的时候,发现目标地址已经有值了,即发生冲突了,这个时候通过相应的函数在此地址后面的地址去找,直到没有冲突为止。这个方法常用的有线性探测,二次探测,再哈希。
这种解决方法有个不好的地方就是,当发生冲突之后,会在之后的地址空间中找一个放进去,这样就有可能后来出现一个key哈希出来的结果也正好是它放进去的这个地址空间,这样就会出现非同义词的两个key发生冲突。
链接法(Separate chaining)链接法是通过数组和链表组合而成的。当发生冲突的时候只要将其加到对应的链表中即可。
与开放定址法相比,链接法有如下几个优点:
①链接法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
②由于链接法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而链接法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
④在用链接法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
当然链接法也有其缺点,拉链法的缺点是:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。
+140
View File
@@ -0,0 +1,140 @@
---
layout: post
title: Java HashMap 核心源码解读
categories:
- Programming
tags:
- Java
---
本篇对HashMap实现的源码进行简单的分析。
所使用的HashMap源码的版本信息如下:
{% highlight java %}
/*
* @(#)HashMap.java 1.73 07/03/13
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
{% endhighlight %}
### 一.概述
在Java中每一个对象都有一个哈希码,这个值可以通过hashCode()方法获得。hashCode()的值和对象的equals方法息息相关,是两个对象的值是否相等的依据,所以当我们覆盖一个类的equals方法的时候也必须覆盖hashCode方法。
例如String的hashCode方法为:
{% highlight java %}
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
{% endhighlight %}
可以看得出,一个字符串的哈希值为s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1],是一个整数。也就是说所有的字符串可以通过hashCode()将其映射到整数的区间中,由于在java中整数的个数是有限的(四个字节有正负,第一位为符号位-2^31 ~ 2^31 -1),当s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]足够大的时候可能会溢出,导致其变成负值。从上面的情况我们可以看出两个不同的字符串可能会被映射到同一个整数,发生冲突。因此java的开发人员选择了31这个乘数因子,尽量使得各个字符串映射的结果在整个java的整数域内均匀分布。
谈完java对象的哈希码,我们来看看今天的主角HashMap,HashMap可以看作是Java实现的哈希表。HashMap中存放的是key-value对,对应的类型为java.util.HashMap.Entry,所以在HashMap中数据都存放在一个Entry引用类型的数组table中。这里key是一个对象,为了把对象映射到table中的一个位置,我们可以通过求余法来,所以我们可以使用 [key的hashCode % table的长度]来计算位置(当然在实际操作的时候由于需要考虑table上的key的均匀分布可能需要对key的hashCode做一些处理)。
### 二.源码解析
相关属性
首先肯定是需要一个数组table,作为数据结构的骨干。
{% highlight java %}
transient Entry[] table;
{% endhighlight %}
这边定义了一个Entry数组的引用。
继续介绍几个概念把
capacity容量 是指数组table的长度
loadFactor 装载因子,是实际存放量/capacity容量 的一个比值,在代码中这个属性是描述了装载因子的最大值,默认大小为0.75
threshold(阈值)代表hashmap存放内容数量的一个临界点,当存放量大于这个值的时候,就需要将table进行夸张,也就是新建一个两倍大的数组,并将老的元素转移过去。threshold = (int)(capacity * loadFactor);
put方法详解
{% highlight java %}
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
{% endhighlight %}
在HashMap中我们的key可以为null,所以第一步就处理了key为null的情况。
当key为非null的时候,你也许会认为:恩,直接和table长度相除取模吧,但是这里没有,而是又好像做了一次哈希,这是为什么呢?这个还得先看indexFor(hash, table.length)方法,这个方法是决定存放位置的
{% highlight java %}
static int indexFor(int h, int length) {
return h & (length-1);
}
{% endhighlight %}
明眼的都可以发现,因为在HashMap中table的长度为2^n (我们把运算都换成二进制进行考虑),所以h & (length-1)就等价于h%length,这也就是说,如果对原本的hashCode不做变换的话,其除去低length-1位后的部分不会对key在table中的位置产生任何影响,这样只要保持低length-1位不变,不管高位如何都会冲突,所以就想办法使得高位对其结果也产生影响,于是就对hashCode又做了一次哈希
{% highlight java %}
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
{% endhighlight %}
当找到key所对应的位置的时候,对对应位置的Entry的链表进行遍历,如果以及存在key的话,就更新对应的value,并返回老的value。如果是新的key的话,就将其增加进去。modCount是用来记录hashmap结构变化的次数的,这个在hashmap的fail-fast机制中需要使用(当某一个线程获取了map的游标之后,另一个线程对map做了结构修改的操作,那么原先准备遍历的线程会抛出异常)。addEntry的方法如下
{% highlight java %}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
{% endhighlight %}
get方法
{% highlight java %}
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
{% endhighlight %}
get方法其实就是将key以put时相同的方法算出在table的所在位置,然后对所在位置的链表进行遍历,找到hash值和key都相等的Entry并将value返回。
+67
View File
@@ -0,0 +1,67 @@
---
layout: post
title: Tomcat升级
categories:
- Programming
tags:
- Java
---
之前tomcat使用的版本是5.5.26 ,由于存在DDOS攻击的漏洞,所以需要进行升级,
本来打算升级到5.5.30的,无奈发现部署了新版本的tomcat,F5就直接认为其不可用,
经过在所升级的机器上使用links之类的工具直接访问localhost,发现web应用是正常的。
这个时候把目光转移到F5上,应该和F5的健康检查有一定的关系。
第二天,找到相应的网络工程师。一台开起tomcat5.5.30 另一台不变,果真发现5.5.30的机器被F5认为不可用。
通过求同寻异的方法,最后定位到一个通过openssl s_client -host host -port port(应用限制使用https方式访问)然后GET某个标志应用状态的页面来检查的策略上。
通过linux下测试果然发现5.5.30上GET的结果为:
GET /xxx/xxx/status
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
Date: Mon, 26 Jul 2010 19:39:47 GMT
Connection: close
找到原因了,怎么办呢?为了快速解决问题,所以尝试了下6版本的安全版本6.0.28 发现GET /xxx/xxx/status的结果是OK的,于是就直接升级到这个版本。
今天再回过头来看之前5.5.30的问题的原因。
发现当指定为 HTTP/1.0的协议是可以的
{% highlight html %}
GET /xxx/xxx/status HTTP/1.0
{% endhighlight %}
两次回车
{% highlight html %}
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 2
Date: Mon, 26 Jul 2010 19:46:46 GMT
Connection: close
OKclosed
{% endhighlight %}
如果指定HTTP/1.1 的话
按一次回车不会显示结果 ,如果这个时候输入host:123.343.343.43之类的host信息再回车的话,可以获取结果,但是发现连接没有关闭还可以继续发送GET请求。
{% highlight html %}
GET /xxx/xxx/status HTTP/1.1
host:127.0.0.1
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 2
Date: Mon, 26 Jul 2010 19:48:55 GMT
OK
{% endhighlight %}
由于没有精力去调试tomcat的源码,而且找官方文档也没发现什么信息。唉。
还有tomcat在各个版本以及各个平台下的ssl的配置可能会存在各种问题
比如通过https访问的时候显示ssl_error_rx_record_too_long的错误或者显示连接被断开。
所以配置Connector 的时候,不行的话尝试添加protocol=”org.apache.coyote.http11.Http11Protocol”和SSLEnabled=”true”等参数。
+174
View File
@@ -0,0 +1,174 @@
---
layout: post
title: ConcurrentHashMap原理分析
categories:
- Programming
tags:
- Java
---
## 一.Java并发基础
当一个对象或变量可以被多个线程共享的时候,就有可能使得程序的逻辑出现问题。
在一个对象中有一个变量i=0,有两个线程A,B都想对i加1,这个时候便有问题显现出来,关键就是对i加1的这个过程不是原子操作。要想对i进行递增,第一步就是获取i的值,当A获取i的值为0,在A将新的值写入A之前,B也获取了A的值0,然后A写入,i变成1,然后B也写入i,i这个时候依然是1.
当然java的内存模型没有上面这么简单,在Java Memory Model中,Memory分为两类,main memory和working memory,main memory为所有线程共享,working memory中存放的是线程所需要的变量的拷贝(线程要对main memory中的内容进行操作的话,首先需要拷贝到自己的working memory,一般为了速度,working memory一般是在cpu的cache中的)。volatile的变量在被操作的时候不会产生working memory的拷贝,而是直接操作main memory,当然volatile虽然解决了变量的可见性问题,但没有解决变量操作的原子性的问题,这个还需要synchronized或者CAS相关操作配合进行。
多线程中几个重要的概念:
#### 可见性
也就说假设一个对象中有一个变量i,那么i是保存在main memory中的,当某一个线程要操作i的时候,首先需要从main memory中将i 加载到这个线程的working memory中,这个时候working memory中就有了一个i的拷贝,这个时候此线程对i的修改都在其working memory中,直到其将i从working memory写回到main memory中,新的i的值才能被其他线程所读取。从某个意义上说,可见性保证了各个线程的working memory的数据的一致性。
可见性遵循下面一些规则:
* 当一个线程运行结束的时候,所有写的变量都会被flush回main memory中。
* 当一个线程第一次读取某个变量的时候,会从main memory中读取最新的。
* volatile的变量会被立刻写到main memory中的,在jsr133中,对volatile的语义进行增强,后面会提到
* 当一个线程释放锁后,所有的变量的变化都会flush到main memory中,然后一个使用了这个相同的同步锁的进程,将会重新加载所有的使用到的变量,这样就保证了可见性。
#### 原子性
还拿上面的例子来说,原子性就是当某一个线程修改i的值的时候,从取出i到将新的i的值写给i之间不能有其他线程对i进行任何操作。也就是说保证某个线程对i的操作是原子性的,这样就可以避免数据脏读。
通过锁机制或者CAS(Compare And Set 需要硬件CPU的支持)操作可以保证操作的原子性。
#### 有序性
假设在main memory中存在两个变量i和j,初始值都为0,在某个线程A的代码中依次对i和j进行自增操作(i,j的操作不相互依赖),
{% highlight java %}
i++;
j++;
{% endhighlight %}
由于,所以i,j修改操作的顺序可能会被重新排序。那么修改后的ij写到main memory中的时候,顺序可能就不是按照i,j的顺序了,这就是所谓的reordering,在单线程的情况下,当线程A运行结束的后i,j的值都加1了,在线程自己看来就好像是线程按照代码的顺序进行了运行(这些操作都是基于as-if-serial语义的),即使在实际运行过程中,i,j的自增可能被重新排序了,当然计算机也不能帮你乱排序,存在上下逻辑关联的运行顺序肯定还是不会变的。但是在多线程环境下,问题就不一样了,比如另一个线程B的代码如下
{% highlight java %}
if(j==1) {
System.out.println(i);
}
{% endhighlight %}
按照我们的思维方式,当j为1的时候那么i肯定也是1,因为代码中i在j之前就自增了,但实际的情况有可能当j为1的时候i还是为0。这就是reordering产生的不好的后果,所以我们在某些时候为了避免这样的问题需要一些必要的策略,以保证多个线程一起工作的时候也存在一定的次序。JMM提供了happens-before 的排序策略。这样我们可以得到多线程环境下的as-if-serial语义。
这里不对happens-before进行详细解释了,详细的请看这里http://www.ibm.com/developerworks/cn/java/j-jtp03304/,这里主要讲一下volatile在新的java内存模型下的变化,在jsr133之前,下面的代码可能会出现问题
{% highlight java %}
Map configOptions;
char[] configText;
volatile boolean initialized = false;
// In Thread A
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfigOptions(configText, configOptions);
initialized = true;
// In Thread B
while (!initialized)
sleep();
// use configOptions
{% endhighlight %}
jsr133之前,虽然对 volatile 变量的读和写不能与对其他 volatile 变量的读和写一起重新排序,但是它们仍然可以与对 nonvolatile 变量的读写一起重新排序,所以上面的Thread A的操作,就可能initialized变成true的时候,而configOptions还没有被初始化,所以initialized先于configOptions被线程B看到,就产生问题了。
JSR 133 Expert Group 决定让 volatile 读写不能与其他内存操作一起重新排序,新的内存模型下,如果当线程 A 写入 volatile 变量 V 而线程 B 读取 V 时,那么在写入 V 时,A 可见的所有变量值现在都可以保证对 B 是可见的。
结果就是作用更大的 volatile 语义,代价是访问 volatile 字段时会对性能产生更大的影响。这一点在ConcurrentHashMap中的统计某个segment元素个数的count变量中使用到了。
## 二.线程安全的HashMap
什么时候我们需要使用线程安全的hashmap呢,比如一个hashmap在运行的时候只有读操作,那么很明显不会有问题,但是当涉及到同时有改变也有读的时候,就要考虑线程安全问题了,在不考虑性能问题的时候,我们的解决方案有Hashtable或者Collections.synchronizedMap(hashMap),这两种方式基本都是对整个hash表结构做锁定操作的,这样在锁表的期间,别的线程就需要等待了,无疑性能不高。
## 三.ConcurrentHashMap实现原理
数据结构
ConcurrentHashMap的目标是实现支持高并发、高吞吐量的线程安全的HashMap。当然不能直接对整个hashtable加锁,所以在ConcurrentHashMap中,数据的组织结构和HashMap有所区别。
一个ConcurrentHashMap由多个segment组成,每一个segment都包含了一个HashEntry数组的hashtable,
每一个segment包含了对自己的hashtable的操作,比如get,put,replace等操作,这些操作发生的时候,对自己的hashtable进行锁定。由于每一个segment写操作只锁定自己的hashtable,所以可能存在多个线程同时写的情况,性能无疑好于只有一个hashtable锁定的情况。
源码分析
在ConcurrentHashMap的remove,put操作还是比较简单的,都是将remove或者put操作交给key所对应的segment去做的,所以当几个操作不在同一个segment的时候就可以并发的进行。
{% highlight java %}
public V remove(Object key) {
int hash = hash(key.hashCode());
return segmentFor(hash).remove(key, hash, null);
}
{% endhighlight %}
而segment中的remove操作除了加锁之外和HashMap中的remove操作基本无异。
{% highlight java %}
/**
* Remove; match on key only if value null, else match both.
*/
V remove(Object key, int hash, Object value) {
lock();
try {
int c = count - 1;
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K,V> first = tab[index];
HashEntry<K,V> e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;
V oldValue = null;
if (e != null) {
V v = e.value;
if (value == null || value.equals(v)) {
oldValue = v;
// All entries following removed node can stay
// in list, but all preceding ones need to be
// cloned.
++modCount;
HashEntry<K,V> newFirst = e.next;
for (HashEntry<K,V> p = first; p != e; p = p.next)
newFirst = new HashEntry<K,V>(p.key, p.hash,
newFirst, p.value);
tab[index] = newFirst;
count = c; // write-volatile
}
}
return oldValue;
} finally {
unlock();
}
}
{% endhighlight %}
上面的代码中关于volatile类型的变量count值得一提,这里充分利用了Java 5中对volatile语义的增强,count = c的操作必须在modCount,table等操作的后面,这样才能保证这些变量操作的可见性。
Segment类继承于ReentrantLock,主要是为了使用ReentrantLock的锁,ReentrantLock的实现比
synchronized在多个线程争用下的总体开销小。
put操作和remove操作类似。
接下来我们来看下get操作。
{% highlight java %}
public V get(Object key) {
int hash = hash(key.hashCode());
return segmentFor(hash).get(key, hash);
}
{% endhighlight %}
也是使用了对应的segment的get
{% highlight java %}
V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K,V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}
{% endhighlight %}
上面的代码中,一开始就对volatile变量count进行了读取比较,这个还是java5对volatile语义增强的作用,这样就可以获取变量的可见性。所以count != 0之后,我们可以认为对应的hashtable是最新的,当然由于读取的时候没有加锁,在get的过程中,可能会有更新。当发现根据key去找元素的时候,但发现找得的key对应的value为null,这个时候可能会有其他线程正在对这个元素进行写操作,所以需要在使用锁的情况下在读取一下value,以确保最终的值。
其他相关涉及读取的操作也都类似。
+64
View File
@@ -0,0 +1,64 @@
---
layout: post
title: Linux培训笔记
categories:
- OS
tags:
- Linux
---
上周末,参加了一个关于Linux的培训,感觉还不错,以前一些比较模糊的概念渐渐的变得清晰了。
### 一.Shell相关
shell命令在进行运行前都先进行一些必要的替换
echo $PATH 变量替换
echo * 通配符,比如这里的*,代表当前目录中的所有文件
反引号` 替换反引号中的命令为其输出 echo `ls`
alias替换
shell外部命令调用,fork子进程,和父进程一模一样,在子进程中exec,并将子进程替换,并只继承原来的环境变量,shell进行wait等待子进程结束。
没有进行export的变量,shell脚本是看不到的
unix 中,任何命令尽量做成外部命令。
kill 外部命令内部化,shell一看是kill,就不去fork子进程,就直接帮你做了。
ELF(Executable and Linkable Format)即可执行连接文件格式,脚本文件u+s是没有用的。
{% highlight bash %}
#!脚本文件 #!是给内核看的,但是解释器不需要看#!,所以在脚本中#为注解
{% endhighlight %}
### 二.内存管理相关
用过windows的同学很容易会把虚拟内存理解错了,windows中所谓的虚拟内存其实应该叫做交换内存。
什么情况下我们才应该认为计算机系统内存不够了呢?
内存频繁换入换出说明内存不够
内核会将空闲的部分内存用来做cache和buffer
cache多 buffer少
overcommit(过提交) 容易造成OOM killer,可以关闭过提交
多进程原理,中断处理,保存现场,进到内核,恢复现场的时候内核里可能有很多现场,内核就选一个恢复。信号的实现就是基于中断。
### 三.文件系统相关
一些标识
-普通文件
d 目录
l 链接文件
c 字符文件
b 块文件
p 管道文件
s socket文件
u+s 执行命令的时候,赋予命令宿主的权限
bitmap,用来记录文件系统的容量使用情况
块位图,索引节点位图。
硬连接 两个文件项指向同一个inode
符号链接 一个符号链接就是一个文本文件,存了一个字符串 把符号连接的内容替换其,形成一个新的路径。符号连接中写绝对路径。
@@ -0,0 +1,35 @@
---
layout: post
title: HenPlus
categories:
- Programming
tags:
- Java
---
今天给大家介绍一个简单易用的基于JDBC的SQL-shell:HenPlus
由于在线上环境没有root权限,所以我们无法完成程序的安装,安装HenPlus,首先需要安装一个java-readlinehttp://java-readline.sourceforge.net/,在编译安装的时候会提示你没有权限,但是目标文件都已经编译好了,一个libJavaReadline.so ,一个libreadline-java.jar。一会儿我们会使用到这两个文件。
然后我们下载安装henplus,安装的时候指定目录,你就安装到你的home目录下的某个子目录里就ok了。安装应该没有问题,安装好了之后,你会发现有两个子目录。bin和share,bin是启动脚本,share是jar包。
这个时候你运行bin下的脚本会报错,需要对henplus进行修改
{% highlight bash %}
# location of the readline lib.
# Modify this, if you installation stores this at a different
# position.
LD_LIBRARY_PATH=$THISDIR/../lib:../share/jni:$LD_LIBRARY_PATH
CLASSPATH=$CLASSPATH:../share/libreadline-java.jar:../share/oracle-jdbc-10.1.0.2.0.jar
{% endhighlight %}
这个地方指定到刚刚编译好的两个文件,保存。当然为了方便,我也直接将oracle的jdbc的驱动指定了。
然后在次运行./henplus
进入到命令行后,
连接数据库
{% highlight bash %}
connect jdbc:oracle:thin:username/password@192.16.134.32:1521:xxx name
{% endhighlight %}
name是你自己取的名字,下次你再用的时候,进来后可以直接connect name,这些设置信息会默认保存在你自己home目录下.henplus中
之后你便可以运行sql什么的拉,记得sql后面加分号然后回车进行运行。
@@ -0,0 +1,294 @@
---
layout: post
title: NPAPI插件编程起步
categories:
- Programming
tags:
- NPAPI
- Mac
- C
---
最近需要写一个Mac平台上的简单的跨浏览器的插件,需要在js中调用本地方法,而npapi满足此要求。
NPAPI就是Netscape Plugin Application Programming Interface的缩写了,虽然Netscape已经去了,但是这个却被沿用下来,在各大浏览器中都得以实现。还是纪念下曾经的浏览器的鼻祖啊。
在网络上搜索了很长时间,一直没有找到合适的满足自己需求的代码例子。且这方面的文档也少的可怜。
还是先提一下,有两个系列的文章还是不错,虽然或许可能也不完全正确,但是帮助理解npapi的编程模型是非常有帮助的:
http://colonelpanic.net/category/plugindev/npapi-plugindev/
http://rintarou.dyndns.org/category/browser/plugin
下面是我寻找解决方法的过程
一开始找到mozilla的一篇官方文档:Writing a plugin for Mac OS X,这里面提到了了官方的代码库中有demo,于是就跑到公司checkout下mozilla的分支。并看了下里面的一个mac的例子.
不过这个例子没有使用npruntime的东西,加上一开始的时候对npapi不熟悉,就继续搜索.
在google, stackoverflow,github, google codes 中不停的搜索,结果甚少,最后找到一个npruntime的例子,Call Native API from Google Chrome Extension on Mac OS X只可惜编译后只能在mac的chrome下运行。
后来又找到了Firebreath这个跨平台的浏览器NPAPI插件开发框架,可以通过python和cmake的配合,生成适合不同操作系统的浏览器插件的工程,于是就测试了,果真生成xcode项目后,编译,然后测试了下js调用插件的方法,还真能在各个浏览器(测试了三个比较常用的safari,chrome,firefox)下运行。但是无奈由于包含一些高级的程序代码其最小编译后的大小都2m+对于一个功能单一的插件来讲,无疑是让人无法接受的,所以继续找demo.
后来找到了npsimple-win32 这个windows下的vs的项目,我将其移植到mac上,编译运行后,唯独在safari下无法运行,悲剧的一塌糊涂,对于一个npapi不同的浏览器在实现的具体细节上竟然有细微的差别,真是太变态鸟。
最后无奈之下,将Weikit开源项目中的例子弄出来,然后和npruntime的相关代码进行整合,发现在safari,chrome可以运行在firefox中没法运行,好吧,至少现在两个例子的并集是完整的,我就将两个代码进行查看,最终找出了问题的所在,在safari下需要启动CoreGraphics,而在firefox下scriptable的NPObject的hasProperty和getProperty必须设置,可能在firefox下调用某函数,先去scriptable NPObject中找有没有这个名字的属性,然后在找方法吧。
最后终于写出了一个简单的例子
{% highlight c %}
#import <WebKit/npapi.h>
#import <WebKit/npfunctions.h>
#import <WebKit/npruntime.h>
// Browser function table,可以通过它来得到浏览器提供的功能
static NPNetscapeFuncs* browser;
static const char *plugin_method_name_open = "open";
////////////////////////////////////
/*******各种接口的声明*********/
//在NPAPI编程的接口中你会发现有NP_打头的,有NPP_打头的,有NPN_打头的
//NP是npapi的插件库提供给浏览器的最上层的接口
//NPP即NP Plugin是插件本身提供给浏览器调用的接口,主要被用来填充NPPluginFuncs的结构体
//NPN即NP Netscape ,是浏览器提供给插件使用的接口,这些接口一般都在NPNetscapeFuncs结构体中
//Mach-o entry points,浏览器和创建交流的最上层的接口
NPError NP_Initialize(NPNetscapeFuncs *browserFuncs);
NPError NP_GetEntryPoints(NPPluginFuncs *pluginFuncs);
void NP_Shutdown(void);
//NPP Functions
NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved);
NPError NPP_Destroy(NPP instance, NPSavedData** save);
NPError NPP_SetWindow(NPP instance, NPWindow* window);
NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype);
NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason);
int32 NPP_WriteReady(NPP instance, NPStream* stream);
int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer);
void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname);
void NPP_Print(NPP instance, NPPrint* platformPrint);
int16_t NPP_HandleEvent(NPP instance, void* event);
void NPP_URLNotify(NPP instance, const char* URL, NPReason reason, void* notifyData);
NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value);
NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value);
//Functions for scriptablePluginClass
bool plugin_has_method(NPObject *obj, NPIdentifier methodName);
bool plugin_invoke(NPObject *obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result);
bool hasProperty(NPObject *obj, NPIdentifier propertyName);
bool getProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result);
////////////////////////////////////
static struct NPClass scriptablePluginClass = {
NP_CLASS_STRUCT_VERSION,
NULL,
NULL,
NULL,
plugin_has_method,
plugin_invoke,
NULL,
hasProperty,
getProperty,
NULL,
NULL,
};
//接口的实现
NPError NP_Initialize(NPNetscapeFuncs* browserFuncs)
{
browser = browserFuncs;
return NPERR_NO_ERROR;
}
NPError NP_GetEntryPoints(NPPluginFuncs* pluginFuncs)
{
pluginFuncs->version = 11;
pluginFuncs->size = sizeof(pluginFuncs);
pluginFuncs->newp = NPP_New;
pluginFuncs->destroy = NPP_Destroy;
pluginFuncs->setwindow = NPP_SetWindow;
pluginFuncs->newstream = NPP_NewStream;
pluginFuncs->destroystream = NPP_DestroyStream;
pluginFuncs->asfile = NPP_StreamAsFile;
pluginFuncs->writeready = NPP_WriteReady;
pluginFuncs->write = (NPP_WriteProcPtr)NPP_Write;
pluginFuncs->print = NPP_Print;
pluginFuncs->event = NPP_HandleEvent;
pluginFuncs->urlnotify = NPP_URLNotify;
pluginFuncs->getvalue = NPP_GetValue;
pluginFuncs->setvalue = NPP_SetValue;
return NPERR_NO_ERROR;
}
void NP_Shutdown(void)
{
}
bool plugin_has_method(NPObject *obj, NPIdentifier methodName) {
// This function will be called when we invoke method on this plugin elements.
NPUTF8 *name = browser->utf8fromidentifier(methodName);
bool result = strcmp(name, plugin_method_name_open) == 0;
browser->memfree(name);
return result;
}
bool plugin_invoke(NPObject *obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result) {
// Make sure the method called is "open".
NPUTF8 *name = browser->utf8fromidentifier(methodName);
if(strcmp(name, plugin_method_name_open) == 0) {
browser->memfree(name);
BOOLEAN_TO_NPVARIANT(false, *result);
// Meke sure the arugment has at least one String parameter.
if(argCount > 0 && NPVARIANT_IS_STRING(args[0])) {
// Build CFURL object from the arugment.
NPString str = NPVARIANT_TO_STRING(args[0]);
CFURLRef url = CFURLCreateWithBytes(NULL, (const UInt8 *)str.UTF8Characters, str.UTF8Length, kCFStringEncodingUTF8, NULL);
if(url) {
// Open URL with the default application by Launch Service.
OSStatus res = LSOpenCFURLRef(url, NULL);
CFRelease(url);
BOOLEAN_TO_NPVARIANT(res == noErr, *result);
}
}
return true;
}
browser->memfree(name);
return false;
}
bool hasProperty(NPObject *obj, NPIdentifier propertyName) {
return false;
}
bool getProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result) {
return false;
}
//NPP Functions Implements
NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved)
{
// Create per-instance storage
//obj = (PluginObject *)malloc(sizeof(PluginObject));
//bzero(obj, sizeof(PluginObject));
//obj->npp = instance;
//instance->pdata = obj;
if(!instance->pdata) {
instance->pdata = browser->createobject(instance, &scriptablePluginClass);
}
// Ask the browser if it supports the CoreGraphics drawing model
NPBool supportsCoreGraphics;
if (browser->getvalue(instance, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) != NPERR_NO_ERROR)
supportsCoreGraphics = FALSE;
if (!supportsCoreGraphics)
return NPERR_INCOMPATIBLE_VERSION_ERROR;
// If the browser supports the CoreGraphics drawing model, enable it.
browser->setvalue(instance, NPPVpluginDrawingModel, (void *)NPDrawingModelCoreGraphics);
return NPERR_NO_ERROR;
}
NPError NPP_Destroy(NPP instance, NPSavedData** save)
{
// If we created a plugin instance, we'll destroy and clean it up.
NPObject *pluginInstance=instance->pdata;
if(!pluginInstance) {
browser->releaseobject(pluginInstance);
pluginInstance = NULL;
}
return NPERR_NO_ERROR;
}
NPError NPP_SetWindow(NPP instance, NPWindow* window)
{
return NPERR_NO_ERROR;
}
NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype)
{
*stype = NP_ASFILEONLY;
return NPERR_NO_ERROR;
}
NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason)
{
return NPERR_NO_ERROR;
}
int32 NPP_WriteReady(NPP instance, NPStream* stream)
{
return 0;
}
int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer)
{
return 0;
}
void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname)
{
}
void NPP_Print(NPP instance, NPPrint* platformPrint)
{
}
int16_t NPP_HandleEvent(NPP instance, void* event)
{
return 0;
}
void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
{
}
NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value)
{
NPObject *pluginInstance=NULL;
switch(variable) {
case NPPVpluginScriptableNPObject:
// If we didn't create any plugin instance, we create it.
pluginInstance=instance->pdata;
if (pluginInstance) {
browser->retainobject(pluginInstance);
}
*(NPObject **)value = pluginInstance;
break;
default:
return NPERR_GENERIC_ERROR;
}
return NPERR_NO_ERROR;
}
NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value)
{
return NPERR_GENERIC_ERROR;
}
{% endhighlight %}
测试代码
{% highlight html %}
<html>
<head>
<script>
function run() {
var plugin = document.getElementById("pluginId");
plugin.open("http://www.geeklu.com");
}
</script>
</head>
<body >
<embed width="0" height="0" type="test/x-open-with-default-plugin" id="pluginId">
<button onclick="run()">run</button>
</body>
</html>
{% endhighlight %}
+15
View File
@@ -0,0 +1,15 @@
---
layout: post
title: 插件和扩展的区别
categories:
- Programming
tags:
- Sofeware
---
尽管我们每天都和浏览器的插件或者扩展打交道,但很多人还是无法正确区分插件和扩展的差异。
插件(Plugins)顾名思义,就是将别的东西“插”进来,这里就拿浏览器举例子吧,在浏览器中,插件的功能就是将第三方的库提供的功能通过embed,object标签在页面中应用起来,比如Flash插件,Silverlight插件,Quicktime插件。这些插件就像驱动程序一样,使得你的浏览器可以调用本地方法,系统的接口,实现浏览器无法独立实现的功能,或者这么讲,插件使得别的程序才能处理的内容在浏览器的页面中得以展现和处理。
那扩展(Extensions or Add-ones)呢?扩展通常是通过程序自身所开放的api来实现的用于扩展程序自身功能的东西,在浏览器中,比如Firefox的Firebug,浏览器的Twitter的插件,这些插件很多都是增加一些浏览器上的窗口或者按钮,来扩展浏览器的功能。这里有一点需要说明一下,扩展本身可以包含一个或者多个插件,但是插件不包含扩展。
参考了 [http://colonelpanic.net/2010/08/browser-plugins-vs-extensions-the-difference/](http://colonelpanic.net/2010/08/browser-plugins-vs-extensions-the-difference/)
@@ -0,0 +1,28 @@
---
layout: post
title: 科学的本质
categories:
- Book
tags:
- 科学
---
最近在读张五常的《经济解释》这本书,前几章大篇的理论知识,大大的讨论了“科学”这个东西,因为本书后面需要对各种经济现象进行解释,而科学则是进行解释的依据,所以本书的前几章是在做铺设,打基础。
那么我也结合了自己的实际情况说说“科学”这个概念到底是什么。我们一谈到科学就会想到物理,化学这些自然科学,但实际上科学是一个很广义的概念,如社会科学,心理科学等。
科学本质上是人类对现象的抽象,(当然这一切有一个大的前提,就是这个世界是一个因果联系的世界,任何事情的发生必定有其原因,如果没有这个前提,那么很多东西都没法说了)。当然宇宙中的现象是无穷无尽的,所以为了便于对已发生现象进行解释(求因的过程),或者对未发生的现象进行预测(推测结果),人类就对这个宇宙进行抽象,建立各种科学体系。
那么如何对科学进行验证呢?验证的过程肯定是多次的,所以需要对现象进行重现,首先用科学理论推测结果,然后重现现象,如果事实发生的结果和科学理论推测的结果一致,那就算被证实了。
科学创新就是建立新的抽象,建立新的模型,当然这也分两类,第一是站在巨人的肩上对已存在的抽象模型进行扩展,第二种是推翻现有抽象,建立新的抽象。
“科学不是求对,也不是求错:科学所求的是”可能被事实推翻”!可能被事实推翻而没有被推翻,就算是被证实了。”–《经济解释》
所以从某种意义上说,”经验”也是科学。
很多时候我们一直在质疑,什么才是正确的呢?或者这样会不会是错误的呢?或许这并不重要,重要的是基于理论所创造出来的东西对你和使用者是不是有价值的,如果有那就算这个理论是科学的了。
这个世界上没有完全正确与错误,所以我们做事的时候不要太追求完美。
+21
View File
@@ -0,0 +1,21 @@
---
layout: post
title: Derby Database
categories:
- Programming
tags:
- Java
- Database
---
最近由于要给一个历史系统迁移环境,基本上属于黑盒操作,没有文档,源码倒是有。
这个系统的有一个部分使用了derby数据库,嗯,之前听说过derby数据库是一个纯java实现的比较轻量级的数据库,只需要几个jar包就ok了,且属于apache的开源项目了。
我遇到的第一事情是数据库文件在那里了,我得连进去看看表啊,表的数据什么的吧。于是需要一个数据库连接工具。
搜了下发现jdk自带了一个derby连接工具叫做ij,至于为何叫ij不得而已,猜测i的意思是交互interactive的意思吧。
ij工具的位置在$JAVA_HOME/db/bin下,使用起来也很简单。
derby有两重运行的方式,一种是单独运行,一种是随程序一起运行和程序一起共享一个jvm。
ij的使用非常简单。
{% highlight bash %}
connect "jdbc:derby:/path/to/your/db;create=true;user=xxxx;password=xxx"
{% endhighlight %}
运行了之后你可以通过help;查看能够使用的命令。
@@ -0,0 +1,117 @@
---
layout: post
title: 从URL启动程序:也谈谈旺旺的页面启动
categories:
- Programming
tags:
- Software
---
## 一.引子
很多时候为了方便,我们都需要从页面上启动一些本地的程序,比如QQ,MSN,Skype等。
我们先拿QQ来举例,QQ提供了一个QQ在线状态的服务,[QQ在线状态服务](http://wp.qq.com)
我们可以将自己的“QQ在线状态” 放置在自己的个人博客或者空间中,方便他人知道你是否在线,
如果想和你聊天的话,只需要点下图标便会调出QQ进行聊天了。
## 二.原理剖析
下面将循序渐进的讲解IM在线状态以及链接到程序的实现原理
### 1.如何从浏览器中调出程序
我们都知道,一般情况下,浏览器中是无法直接和本机的其他的程序进行交互的,在IE中,我们可以通过ActiveX对象的方式进行。但是这个方式只适用于IE浏览器,另一种比较通用的方式便是URL协议的方式,我们将某种URL的协议注册给某个程序来进行处理,比如将tencent://这样的协议注册给QQ程序来进行处理,当浏览器需要访问这样的协议的时候就转给QQ程序进行处理。这种URL协议的方式是可以跨平台的,比如在Windows上你需要添加注册表项;而在Mac系统中上,你需要在对应的Application的属性列表文件 info.plist 中增加URL处理协议项CFBundleURLTypes,当你将程序丢入Applications目录,或者自动被Mac系统扫描到的时候,会将URL以及相应的App注册到对应的数据库中。
一些细节可以查看这里 [Register protocol](http://kb.mozillazine.org/Register_protocol)
### 2.等等,我还不知道系统中有没有安装相应程序
如果我们不考虑应用的健壮性,那么我们可以直接在页面上这么搞
{% highlight html %}
<a href="tencent://message/?Menu=yes&uin=435882010&Site=&Service=201&sigT=...">
<img src="你想显示的图标的地址"/>
</a>
{% endhighlight %}
这样当你点这个图标的时候,就会呼出QQ程序,但是如果我根本没有安装程序呢?那么什么都不会出现。
所以我们需要在启动url协议之前,检测处理相应url协议的程序是否安装了,如果没有安装就跳到软件的下载页面或者软件的web版。
那么现在的问题就是如何检测本机是否已经安装了对应程序,和1中类似,在IE中我们可以通过ActiveX的方式来检测,但是在其他浏览器中我们无法进行,我们肯定需要浏览器和本地api进行交互,好在我们有NPAPI这个跨平台跨浏览器的标准接口,所以我们可以开发一个浏览器的插件,使用js和npapi插件进行交互,npapi插件再调用本地api来检查程序是否安装(比如在windows中查看注册表,或者mac中查看系统是否有程序能够响应相应的URL)。然后js中根据判断的返回值来进行下一步操作。
PS:腾讯的QQ目前好像只支持IE上的URL启动,而没有通过NPAPI插件的方式解决跨浏览器的问题。
### 3.判断是否在线,以便显示在线状态
这个其实蛮好办的,需要一个服务器端的一个接口,可以传过去一个uid,然后返回一个图片地址,或者在线状态都可以,
然后根据服务器端返回的结果进行不同的显示即可。比如qq在线状态的图标是这样的
{% highlight html %}
<img border="0" src="http://wpa.qq.com/pa?
p=2:435882010:41&r=0.2603091907221824"
alt="点击这里给我发消息" title="点击这里给我发消息">
{% endhighlight %}
### 4.总结
当图标外层是链接的时候,一般链接的target属性的值为_blank,请求的地址为服务器的一个url,服务器根据url传递的uid和其他的一些参数,返回一段新的html,新的htm中包含了自动运行的js,执行完了之后,就会将新的页面自动关闭,所以你看到的就是一闪而过一个新的页面,然后相应的程序便得到了响应了,返回的js通过一定的函数方法来检测是否安装,然后判断,以及将window.location赋值为相应的url,以便启动程序来处理请求。
如果附在图片上的为事件的话,那么就是将相关js已经包含在本页中了,点击之,便交给js函数来处理了。
## 三.旺旺URL启动的分裂与纠结
大家使用旺旺大概都是在购物的时候才会使用到,当然内部员工例外。
不过这样足够了,旺旺的定位其实就是电子商务,只是她令人不爽的地方太多了。
首先旺旺版本多,淘宝版的旺旺,中文站的旺旺,英文站的旺旺ATM(Alibaba TradeManager)
虽然最新版本的旺旺整合了淘宝旺旺,阿里旺旺中文站,但是帐户体系还是分开的,
很明显这些由于公司在一开始策略上的失误,即使为了不同网站的用户进行个性化定制IM的功能,但是账户体系也不应该分开,而应该统一起来。
不同的旺旺帐号,通过uid的前缀进行区分,比如淘宝的帐号前缀就是cntaobao
好了还是来说说URL启动的事情吧。
旺旺主要用于电子商务活动中的即使通讯,所以我们在浏览某个产品的网页的时候,可能很快就会感兴趣,所以就有从页面中点击一下直接和店主进行沟通的欲望,所以从URL启动旺旺其实是一个很必要的需求。
通过上一节我们都知道了如何从页面的URL启动一个程序,旺旺也是通过同样的方式来进行的。
最新版的旺旺的url协议是aliim:的方式,不过在老版本里是wangwang:这样的协议,更有甚者还有和yahoo相关的协议比如yahooWW:这样的协议也出现在一些js中。
### 1.淘宝页面中的URL启动
旺旺在这方便有一个专门的服务叫做“旺旺点灯” [旺旺点灯](http://promotion.koubei.com/s/www/wangwang/index.html)
这里有一些demo
[wangwang_demo](http://assets.taobaocdn.com/tbra/dpl/modules/wangwang_demo.html)
目前在淘宝中发现三类启动的方式
最新版本的webww的方式,具体的js脚本可以看这里,正式使用的时候是这个的压缩版 http://a.tbcdn.cn/p/header/webww.js,这个js中是使用了淘宝的js框架kissy进行了重写的。你可以搜索aliim进行相关代码的查看,这个的实现原理是,首先在html页面种,写一些特定id的元素比如
{% highlight html %}
<span class="ww-light ww-large" data-nick="truen%E6%97%97%E8%88%B0%E5%BA%97"
data-tnick="truen%E6%97%97%E8%89%A6%E5%BA%97" data-encode="true" data-display="inline">
<a href="http://www.taobao.com/webww/?ver=1&touid=cntaobaotruen%E6%97%97%E8%88%B0%E5%BA%97&
siteid=cntaobao&status=2&portalId=&
gid=b2be10ade26ceb489a62d1b95eba0619&itemsId=" target="_blank"
class="ww-inline ww-online" title="点此可以直接和卖家交流选好的宝贝,或相互交流网购体验,还支持语音视频噢。"><span>旺旺在线</span>
</a>
</span>
{% endhighlight %}
最外层的span将在相关js加载的时候动态的进行渲染,显示成旺旺是否在线的图标以及附加上相关的单击事件,这样你单击图标的时候,便会由js来进行处理。这个webww的js从代码中看还支持firefox和chrome的npapi插件。当然如果发现本机没有安装旺旺的情况下,这里会将你调转到web版旺旺的页面。
淘宝的页面中还发现力另外两类的调用方式,一般出现在店铺的伙计的链接上,
> http://amos1.taobao.com/msg.ww?v=2&uid=andostore安都专卖:小梅
或者
> http://amos.im.alisoft.com/msg.aw?v=2&uid=591mmm旗舰店:19号&site=cntaobao&s=2&charset=utf-8
这两类都是链接的方式,点击之后返回一个新的html,通过新的html中的自动运行的js来处理请求。方式和QQ在线状态的方式类似。只是我通过IE上的插件或者firebug调试发现,这个没有支持npapi插件,只由ie下的支持。
### 2.中文站旺旺URL启动
中文站页面上的旺旺在线状态基本都是使用直接js事件的方式进行的,
目前我发现三个不同的地方使用不同的js进行了旺旺的调用,可以在js文件中搜索aliim进行相关代码的查看
企业在线 [企业在线](http://img.s.aliimg.com/pm/js/itbu/pm/jsserver/launcher-v2.js)
联系方式 [联系方式](http://style.china.alibaba.com/js/app/winport/pages/detail/winport-merge.js)
搜索结果页面 [搜索结果页面](http://style.china.alibaba.com/js/app/search/v2.0/screens/selloffer.img-v3.js)
也基本只支持IE下的。
### 3.国际站ATM的URL启动
这个有些奇怪,通过查看国际站中的相关js,没有发现aliim:协议的踪影,
后来发现,它并没有使用aliim协议,而是直接通过IE下ActiveX以及插件的方式直接和ATM进行通讯。
而且发现,国际站中的旺旺的id在页面中都是加密过后的结果。
### 4.总结
从上面罗列的这些情况可以看出,旺旺的url启动方式分支较多,且js没有进行统一。
而且各个系统中各自管各自的,在上层没有一个人来推动旺旺的统一。
也许将来这个状况会有所改善,只是走了这么久了,很多人不愿意发生改变,且改变起来“代价”觉得很高。
@@ -0,0 +1,35 @@
---
layout: post
title: Check Memory With Cron Job
categories:
- Programming
tags:
- Shell
---
更新:crontab 默认的环境变量比较少,所以你需要设置自己的运行程序所需要的环境变量
手头维护着一个蛮旧的系统,是SUN之前开发,时间一长就偶尔会内存溢出,占内存达百分之八十多,苦于又没有源码,所以今天写了个小脚本用来检测,发现情况不对狠狠的杀掉重启。
使用crontab进行配置,每隔2分钟进行一次检查
{% highlight bash %}
crontab -e
*/2 * * * * sh /home/admin/checkmem.sh
{% endhighlight %}
checkmem的脚本为
{% highlight bash %}
#!/bin/bash
export PATH=$PATH:java的bin:xxxx
export JAVA_HOME=/usr/xxx/java
javaMem=`top -b -n 1|grep java|awk '{print $10}'`
DATE=`date +"%Y-%m-%d %H:%M:%S"`
echo "$DATE $javaMem" >> /home/admin/checkmem.log
if [ `echo "$javaMem>55.0"|bc` -eq 1 ]; then
echo "restart tomcat" >> /home/admin/checkmem.log
killall -9 java
nohup sh /home/admin/apache-tomcat-6.0.29/bin/startup.sh & >/dev/null
fi
{% endhighlight %}
脚本每次检查都会将结果记录到日志文件中,方便查看。
@@ -0,0 +1,134 @@
---
layout: post
title: 浮点数解惑
categories:
- Programming
tags:
- IEEE
- Number
---
> 注:文章中涉及的运算的结果,通过[http://babbage.cs.qc.edu/IEEE-754/Decimal.html](http://babbage.cs.qc.edu/IEEE-754/Decimal.html)进行验证
在我们编程的时候经常会涉及到浮点数。
而且你会奇怪的发现 0.1+0.2==0.3的逻辑值为false,你知道这是为什么么?
使用python直接计算得到的结果是0.30000000000000004
这其实涉及到浮点数在计算机中的表示方法,舍入的方式,以及如何进行数学运算等问题。
## 一.十进制转二进制
首先看下十进制的浮点数如何通过二进制的方式来表示.
比如3.6如果使用二进制的方式来进行表示应该如何做呢?
我们可以将整数和小数分开进行。
对于整数部分我们可以使用“除二取余,倒序排列”法来进行。
3除以2 上1余1
1除以2 上0余1
所以3使用二进制表示就是11
小数部分“乘二取整,顺序排列”法来进行
0.6乘以2 得1.2 1
0.2乘以2 得0.4 0
0.4乘以2 得0.8 0
0.8乘以2 得1.6 1
0.6乘以2 得1.2 1 从这里开始1001循环
所以3.6使用二进制进行表示的话就是11.100110011001….(1001循环)
## 二.IEEE754标准
### 1.IEEE 754 存储格式
在计算机里表示一个数字可没有像我们在纸上写上一个阿拉伯数字那么简单。
目前的计算机能够认识的就是0101,所以我们的数得转换成二进制表示,但同时数有正负,所以一般有一个符号位的概念。
那么浮点数呢?我们知道浮点数可以使用科学计数法来进行表示(在科学记数法中,一个数被写成一个1与10之间的实数(尾数)与一个10的幂(指数)的积,为了得到统一的表达方式,该尾数并不包括10)。
类似,二进制的浮点数也可以写成类似十进制的科学计数的形式,只不过底数不是10,而是2了。
比如 0.00101可以表示成1.01X2^(-3)
现在我们来正式谈谈IEEE 754标准了,
一个按照IEEE 754标准表示的浮点数一般由一位符号位,m位指数(注意,实际存储的是真实指数值加上2^m-1 之后的值,因为为了方便指数的对其比较,不加的话,指数有正有负比较起来不方便),n位尾数组成(由于尾数在存储的时候其整数位上都规定为1,所以为了节省空间,这个整数位上的1就省去了),且高位在左低位在右.
常见的C或者Java语言,都实现了IEEE 754的浮点数标准。
比如C或者Java中的float和double分别对应单精度浮点数和双精度浮点数,这两类表示的方式相同,不同的只是指数和尾数的位数不一样而已。
单精度浮点数由32位二进制位组成(四个字节),其中1位符号位,8位指数位,23位尾数位。
双精度浮点数由64位二进制位组成(八个字节),其中1位符号位,11位指数位,52位尾数位。
### 2.舍入规则
IEEE 754-1985 中提及了四种舍入规则
- 舍入到最接近,在一样接近的情况下偶数优先(Ties To Even)(这是默认的舍入方式):会将结果舍入为最接近且可以表示的值,但是当存在两个数一样接近的时候,则取其中的偶数(在二进制中式以0结尾的)
- 向+∞方向舍入:会将结果朝正无限大的方向舍入。
- 向-∞方向舍入: 会将结果朝负无限大的方向舍入。
- 向0方向舍入: 会将结果朝0的方向舍入。
对于第一种舍入规则,有必要详细的说明下。
在十进制中比如0.45 按照第一个规则保留小数点后面一位小数进行舍入的话,就应该为0.4 因为0.45和0.4和0.5的距离一样,但是4为偶数,所以取0.4.
(对于这部分你可以在大脑中想象出一根数轴来)
对于二进制数来说也一样,比如有这样的一个二进制表示的数 0.1011
如果想保留小数点后面一位,发现后一位之后为011,很显然0.1011距离0.1近,距离1.0远,所以后面的011果断舍去得到0.1
但是如果需要保留小数点后面两位,发现两位之后是11,很显然0.1011距离0.11更近,所以果断进位,得到0.11
如果保留小数点后3位,这个时候问题来了,因为0.1011和0.101,0.110的距离相等,但是由于0.110末位为偶,所以得到0.110
## 三.问题解析
现在我们来看看开始说的0.1+0.2为什么不等于0.3的问题
在Python中浮点数是64位双精度浮点数,相当于C语言中的double
0.1在计算机中如何表示的呢?
0.1X2=0.2 0
0.2X2=0.4 0
0.4X2=0.8 0
0.8X2=1.6 1
0.6X2=1.2 1
0.2X2=0.4 0 开始0011循环
所以0.1的二进制为
0.00011001100110011001100110011….
现在将其表示为IEEE 754 浮点数的形式
0.1是正数,符号位为0
0.00011001100110011…=1.100110011001…X2^(-4)
所以其指数是-4,加上1023就是1019,1019的二进制值是01111111011
剩下的就是尾数部门了,去除首尾的整数部分,末尾待舍去的是1001…所以需要向前进一位
0011111110111001100110011001100110011001100110011001100110011010
同理可以得到0.2的IEEE 754的格式为(其中指数为-3,填入的时候是-3+1023=1020)
0011111111001001100110011001100110011001100110011001100110011010
两个数相加,这个时候发现指数不一致,所以需要对齐,一般情况下是向右移,因为最右边的即使溢出了,损失的精度远远小于左边溢出。
所以这个时候指数的真正的值都为-3,然后0.1右移后的尾数与0.2的尾数进行相加(别忘记首位的1)
这个时候尾数相加
{% highlight html %}
0.1100110011001100110011001100110011001100110011001101
+1.1001100110011001100110011001100110011001100110011010
--------------------------------------------------------
10.0110011001100110011001100110011001100110011001100111
规格化之后(注意末尾有进位)
为1.0011001100110011001100110011001100110011001100110100X2^(-2)
{% endhighlight %}
0011111111010011001100110011001100110011001100110011001100110100
其化成十六进制的表示为3FD3333333333334
通过在线的求值工具http://babbage.cs.qc.edu/IEEE-754/64bit.html可以求得,其值为0.30000000000000004
而0.3在计算机中的格式为
0011111111010011001100110011001100110011001100110011001100110011
化成十进制为
0.29999999999999999
所以说造成0.1+0.2!=0.3的根本原因在于舍入上。0.1和0.2在舍入的时候都是进位,而0.3没有进位,所以0.3<0.1+0.2
## 四.其他
如果当我们数值计算的精度超出了IEEE 754所能表达的范围的时候,我们应该怎么办呢?为了满足这个需求,一般在高级语言中都有软件的方式实现,比如Java中的BigDecimal。
在经济、金融和与人相关的程序中,建议使用十进制浮点数。但是,由于没有硬件支持,用软件实现的十进制浮点计算比硬件实现的二进制浮点计算要慢。不过IBM公司貌似有硬件上的支持https://www.ibm.com/developerworks/wikis/display/WikiPtype/Decimal+Floating+Point
参考资料:
[http://en.wikipedia.org/wiki/IEEE_754-1985](http://en.wikipedia.org/wiki/IEEE_754-1985)
[http://babbage.cs.qc.edu/IEEE-754/References.xhtml](http://babbage.cs.qc.edu/IEEE-754/References.xhtml)
[http://babbage.cs.qc.edu/IEEE-754/](http://babbage.cs.qc.edu/IEEE-754/)
[http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html](http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html)
+41
View File
@@ -0,0 +1,41 @@
---
layout: post
title: NSImage
categories:
- Programming
tags:
- objc
- Cocoa
---
### 1.图片的加载
NSImage 这个应该是经常用到的一个类,最常用的莫过于 [NSImage imageNamed:@"name"] 来加载一个资源中的图片。
可能这并没有你想象的那么简单。
{% highlight objc %}
NSImage *myImage = [NSImage imageNamed:@"imageName.png"] ;
{% endhighlight %}
加载一个图片的时候,先到到缓存中搜寻,看cache中是否存在,如果不存在则从资源中加载,用完了之后,其实这个图片会被缓存起来,且在缓存中一个图片对应一个唯一标识,默认为不带后缀的图片名(具体方式),
当然你可以主动的为某一个图片注册一个自己的标识,比如
{% highlight objc %}
NSImage *myImage = [NSImage imageNamed:@"imageName.png"] ;
[myImage setName:@"myImage"];
{% endhighlight %}
这样之后你就可以通过[NSImage imageNamed:@"myImage"]来获取这个缓存中的图片了,如果你硬是不想缓存某个图片,那么你可以在用完了图片之后进行如下操作
{% highlight objc %}
[myImage setName:nil];
{% endhighlight %}
这样下次你加载图片的时候就会重新从资源中加载了。
### 2.坐标系的问题
在cocoa中默认情况下的View采用的坐标系是类似于数学坐标系的,左下角为坐标原点(在cocoa touch中,左上角为坐标原点)。
但是为了方便,cocoa中有些控件默认情况下是Flipped的(如NSTableView NSButton 链接),即坐标原点在左上角,这当然是为了控件实现的方便。
如果你需要在一个Flipped View中画一个NSImage,那你得注意了,最好别用NSImage的setFlipped方法(Deprecated in Mac OS X v10.6),因为前面讲过了,图片是在缓存中的,可能你下次需要在一个非Flipped View中画这个图,那你之前把图片设置成flipped会造成影响,所以NSImage在draw的时候优先选择drawInRect:fromRect:operation:fraction:respectFlipped:hints:这个方法
@@ -0,0 +1,58 @@
---
layout: post
title: Jenkins CI Used For Mac App Dev
categories:
- SE
tags:
- Jenkins
- Hudson
- CI
---
### 介绍
在开发一个软件的过程中,测试或者项目经理经常需要得到软件的最新版本。
如果每次都是开发手动编译给他们,有点浪费时间,也不科学。
所以这个任务可以交给计算机来作,每当你有代码提交的时候或者每当系统定期检测到有代码更新的时候,系统自动进行编译打包,并可以通过浏览器来下载最新的软件版本。
你也许会说,这个不是持续集成的一部分么?哈哈,的确是。
这里使用的CI(持续集成)软件是 Jenkins CI,以前叫做Hudson ci后来迫于oracle的相关政策,年初的时候改名了。[https://github.com/jenkinsci](https://github.com/jenkinsci) 这里是其代码。
其实CI囊括的东西远远不止自动编译了,还可以有代码提交后的自动化测试,生成各种测试报告,自动打包部署等等。
### 安装配置及使用
这里要说的是开发一个mac软件,想要使用Jenkins进行定期编译打包应该怎么做。
首先你得有一台mac server或者一台普通的mac机器做服务器。
下载Jenkins [http://jenkins-ci.org/](http://jenkins-ci.org/) 可以直接下载mac系统的安装包。
安装好了之后,会在Applications目录下多一个Jenkins的目录,里面就一个war包,jenkins.war
这个时候你可以启动Jenkins服务了
进去/Applications/Jenkins目录,运行 java -jar jenkins.war –httpPort=9080 –ajp13Port=9009 (当然你可以通过nohup的方式使得其在后台运行)你也可以不指定http和ajp的端口,使用默认的值,默认的http端口是8080
启动好了之后就可以通过浏览器 访问[http://localhost:9080/](http://localhost:9080/)
系统的一些设置都在 Manage Jenkins 选项中,你可以在插件管理的界面中选择安装一些插件。
比如帐号权限管理的插件,Git支持的插件,这里就先不要权限管理了。直接安装一个git插件。
然后进入主界面,新建一个Job,
勾选第一个Build a free-style software project,下一步中填写项目描述,源码管理处选择Git,然后填写Git库地址以及分支
Build Triggers 可以选择定期执行
Build这边,由于是Mac项目,不像Java项目可以有Ant或者Maven这些三方的构建工具。
命令行编译XCode的项目只能通过XCode自带的命令行工具来,所以只能选shell脚本的方式,这边的输入框中可以写脚本,也可以将具体的脚本写在项目中随项目一起提交到git库中,这里只负责一些环境变量的设置以及脚本的调用。
{% highlight bash %}
REVISION="${GIT_COMMIT:0:7}"
FOLDER="#${BUILD_NUMBER}.Rev.${REVISION}"
export ARCHIVE_DIR="/Users/Luke/Test/${FOLDER}"
mkdir -p "$ARCHIVE_DIR"
Scripts/build.sh CONFIGURATION TARGET APPNAME
{% endhighlight %}
xcodebuild的具体用法可以参考官方的文档
获取脚本传递过来的参数并算好项目的路径以及编译之后app的路径
{% highlight bash %}
xcodebuild -target "${TARGET}" -configuration "${CONFIGURATION}"
{% endhighlight %}
编译成功之后,进行zip打包并移到ARCHIVE_DIR中
Post-build Actions 就是build结束后的后继操作,比如可以将编译结果邮件发送给相应的人员。
+19
View File
@@ -0,0 +1,19 @@
---
layout: post
title: SQLite Autoincrement
categories:
- Programming
tags:
- SQLite
---
在SQLite3的表中每一行记录都有一个ROWID的隐藏字段,
你在sql中可以通过ROWID,_ROWID_, OID这三个名称来访问这个隐藏字段,他们都是等价的。
但是你可以定义一个显式的字段作为这个隐藏字段的别名。
比如你创建表的时候定义一个名为uid的字段,字段类型为 INTEGER PRIMARY KEY ,那么这个字段就为这个隐藏字段的别名了。
你插入记录的时候可以显式的为uid提供一个值,但是也可以不提供,或者插入一个NULL,这个时候SQLite3的引擎会自动算出这个字段的值了。
默认的ROWID的select算法是将表中已经存在的最大的ROWID的值加1,如果到表中最大的ROWID已经达了64位整数的最大值,那么系统随机的取值,然后看对应的id是否在用,如果在用则放弃,继续找,经过一定次数的尝试,如果还没有找到,则报错。
但是如果你将字段的类型定义为 INTEGER PRIMARY KEY AUTOINCREMENT 那么这个时候ROWID的select算法就和默认的不一样了。这时候,ROWID也是自增的,但是自增的ROWID的得保证当前数据库的当前表中之前一直没用过,即使你插入过100行记录,ROWID分别位1到100,这个时候你将这100条记录都删除掉,这个时候你再插入的时候,ROWID是从101开始的,而在默认的情况的下,还是从1开始的。
参考地址 : [http://www.sqlite.org/autoinc.html](http://www.sqlite.org/autoinc.html)
+42
View File
@@ -0,0 +1,42 @@
---
layout: post
title: 关于Field Editor
categories:
- Programming
tags:
- Mac
- Cocoa
---
### 1.介绍
在Cocoa 中,每一个窗口中的所有NSControl对象(包括按钮,输入框NSTextField…),都公用一个称之为Field Editor的对象(其实就是一个NSTextView的单例)。
同一时间只可能有一个NSControl对象处于active状态。所以一个窗口会存在一个公用的Field Editor来处理界面中的所有NSControl对象的输入以及键盘响应。
当一个NSControl对象Active的时候,一个Field Editor会被插入到responder chain中并作为first responder,所以这个时候你获取first responder并不是你的那个Text Field 而是这个Field Editor对象。
这个时候view的层次是这样的,Field Editor的superview是一个_NSKeyboardFocusClipView对象,_NSKeyboardFocusClipView对象的superview才是Text Field。
且Field Editor的delegate被设置成text field。
当你用鼠标点击一个输入框,使其从一般状态进去编辑状态的时候,
First Responder先是这个输入框,然后瞬间交给FieldEditor。所以如果你想测试这个,你可以写一个NSTextField的子类,并在becomeFirstResponder和resignFirstResponder中打log,这个时候你会发现 这两个方法会被连续的调用,之后Field Editor成为了FirstResponder。然后你将焦点再次转移到别的控件,这个时候Field Editor的resignFirstResponder会被调用,然后再在另一个NSControl中进行着上面的过程。
### 2.Field Editor的delegate方法
其实就是NSTextViewDelegate中定义的那些方法
比如你想改变编辑时按 方向键的行为,你就需要subclass nstextfield并实现下面的方法,
{% highlight objc %}
-(BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector
{% endhighlight %}
### 3.自定义的Field Editor
有时候,我们可能需要自定义输入框编辑状态下的外观或者行为,这个时候可能需要使用我们自己定义的Field Editor了,
这里有两个方法,一个是subclass一个nswindow,然后覆盖下面的方法
{% highlight objc %}
- (NSText *)fieldEditor:(BOOL)createFlag forObject:(id)anObject
{% endhighlight %}
返回自定义的Field Editor,
或者在NSWindowControl中实现一个nswindow的delegate方法
{% highlight objc %}
- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)anObject
{% endhighlight %}
这里你可以返回你自定义的Field Editor,如果某些情况下还是需要使用系统默认的Field Editor,那么返回nil就可以了。
@@ -0,0 +1,54 @@
---
layout: post
title: Mac 多屏显示 编程
categories:
- Programming
tags:
- objc
- Cocoa
---
### 一.多屏介绍
mac可以支持多屏显示,有一个屏幕是作为主屏存在的,也就是那个显示Menu Bar以及Dock的屏幕。别的屏幕可以作为扩展屏,或者作为镜像屏幕(其实就是两个屏幕的内容一样)。
我们可以将Menubar 拖到任意一个屏幕(改变主屏),我们可以调整屏幕的左右或者上下位置,我们可以调整屏幕的错位大小(就是上下或者左右拖动屏幕,最大程度的时候,两个屏幕角对角,以保证俩个屏幕有相交区域,鼠标从一个屏幕进入另一个屏幕,需要从从相交的区域进去)。
### 二.NSScreen类
在Cocoa中,我们可以通过NSScreen类获取所有screen的信息。
可以通过 `+ (NSArray *)screens` 这个类方法获取所有的screen对象,其中第一个元素是放置Menu Bar的屏幕。“The screen containing the menu bar is always the first object (index 0) in the array returned by the screens method.”
+ (NSScreen *)mainScreen 这个类方法所获得的screen并不一定是包含Menu bar的screen,而是key window(当前响应用户键盘事件的窗口)所在的screen。
我们可以通过- (NSRect)frame方法获得screen的frame,包括menu bar以及dock的区域。
- (NSRect)visibleFrame获得的区域不包含menu bar以及dock占用的区域。
### 三.计算区域并截图
多个屏幕的情况下,其实系统将所有的screen放置在一个包括所有屏幕的矩形内。
我们可以通过如下方法计算出整个矩形的frame
{% highlight objc %}
NSRect fullFrame = NSZeroRect;
for (NSScreen *screen in [NSScreen screens]) {
fullFrame = NSUnionRect(fullFrame, [screen frame]);
}
{% endhighlight %}
![1](http://farm8.staticflickr.com/7138/7076984355_3f3f1b6908_z_d.jpg)
图中坐标显的是对称的,如果你将其中一个screen上下拖动,就会变得不对称,各个screen的坐标也会随着变化。
在Cocoa中可以通过一个非常简单的方式对屏幕进行截图,方法如下,cropRect为整个screen中的frame。
{% highlight objc %}
CGImageRef cgImageRef = CGWindowListCreateImage(cropRect,
kCGWindowListOptionOnScreenOnly,
kCGNullWindowID,
kCGWindowImageDefault);
{% endhighlight %}
之前我们用screen的frame获得的区域都是以包含menu bar的屏幕的左下角为坐标原点的。
而在CG的相关方法中,则以左上角为坐标原点。
如果只是在单屏幕的情况下,cropRect的计算就非常简单。但是在多屏的情况下,且各个屏幕的分辨率可能不一样,所以就有点复杂了,比如像刚才的情况,想截图副屏左上角的四分之一的区域,这个时候就需要将包含menubar的screen的左上角作为原点在推算出目标区域坐标。
![2](http://farm8.staticflickr.com/7236/6930917036_d3670bf75f_z_d.jpg)
+32
View File
@@ -0,0 +1,32 @@
---
layout: post
title: NSCell copyWithZone
categories:
- Programming
tags:
- objc
- Cocoa
---
有时候,我们的TableView需要显示的内容是自定义样式的。
这个时候你需要创建一个自定义的Cell来渲染你自己想要的内容。
所以你继承了NSCell,并拥有一个数据对象,用来作为渲染的数据来源。
由于NSTableView需要使用到NSCell的copyWithZone方法在适当的时候复制NSCell,而NSCell默认的copyWithZone方法的实现方式是使用NSCopyObject创建了原始cell对象的一份浅拷贝,所以在复制的时候只会简单的复制指针的值,而不会去深拷贝或者去retain,那么这样程序运行的时候就会出错了。原因就是Cell所引用的某个对象被错误的完全释放了(复制的时候没有retain,但是在释放cell的时候,将这个对象release了一把)。所以在实现copyWithZone的时候可以采用如下方法。
{% highlight objc %}
- (id)copyWithZone:(NSZone *)zone
{
id cellCopy = [super copyWithZone:zone];
/* Assume that other initialization takes place here. */
cellCopy->titleCell = nil;
[cellCopy setTitleCell:[self titleCell]];
return cellCopy;
}
{% endhighlight %}
参考文档:
1.[内存管理编程指南:实现对象复制](http://www.apple.com.cn/developer/iphone/library/documentation/UserExperience/Conceptual/MemoryMgmt/Articles/mmImplementCopy.html)
2.[NSTableView copies cells – bug or feature?](http://www.cocoabuilder.com/archive/cocoa/57340-nstableview-copies-cells-bug-or-feature.html)
+17
View File
@@ -0,0 +1,17 @@
---
layout: post
title: SWATCH 计时器的使用
categories:
- Life
tags:
- Swatch
---
一般Swatch手表的计时都是采用所谓的三针计时方式:一个是秒针,一个是分针,另外一个是1/10秒针,当开始计时时,秒针开始计时,走到一圈时即是一分,分针就计为1分,当终止计时时,1/10秒针计算的是1/10秒的时间。所以三针计时方式可以精确到1/10秒。
今天刚拿到上图中的这块表,完全搞不定如何计时。最大的误解就在于那个最长的秒针。尼玛这个最长的秒针不是手表本身的秒针,而是计时的秒针的啊,这完全违反一般的认识啊!!!! 而下方的小圆盘中的不停走动的秒针才是表的正真的秒针啊!!!
左上方是用于计分钟的,一格是一分,右上方是记1/10秒的,一格是1/10秒。而中间的这个最大的秒针是计时用的秒针。
表的右侧有三个功能按钮,在计时归零的状态下,按一下最上方的按钮,最大的秒针开始走动,代表开始计时,再按一下暂停计时,这个时候,1/10秒的地方就会显示对应的10分之几秒,然后再按上方的按钮则继续计时,再按暂停计时。在暂停的时候按下下方的按钮,则进行计时归零。如果归零有偏位,则可以通过拔开中间的按钮一档,然后按上方或者下方的按钮进行秒针和1/10秒的归零位置调整。
说实话,swatch计时功能使用最大的障碍就是使用最大的秒针来作为计时用的秒针(而一般人的认识中这个大指针是表的秒针)。
@@ -0,0 +1,57 @@
---
layout: post
title: iPhone中Png图片格式的研究
categories:
- OS
tags:
- iPhone
- png
- iOS
---
有时候我们看到一个App,想看看他的一些界面是如何实现的,这个时候需要查看一下它的图片资源,不过iOS的png图片编译后一般的图片阅读器都是没法查看的,本文将告诉的原因和转换出原图的方法(得安装XCode)。
> ipa 解压,将png相关文件夹拷贝出来,在命令行下使用/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/pngcrush -revert-iphone-optimizations xxx.png yyy.png
我们都知道一个编译好的iPhone app 其中的png图片一般普通的图片阅读器是无法直接读取的,这是因为XCode在编译的过程中,将图片进行了优化,实际上它已经不是一个png图片了。
这边有一些apple iPhone png自己格式的一些说明
[http://iphonedevwiki.net/index.php/CgBI_file_format](http://iphonedevwiki.net/index.php/CgBI_file_format)
在Png数据中,我们最关心的莫过于png的数据块,其中包含了png每一个像素的信息,当然了为了减少存储空间,这些像素信息都是压缩保存的。而且是使用zlib进行压缩的,压缩后 包含zlib header 信息,还有由于解压验证的crc信息。
而iPhone的CgBI格式的png则将原始的png图片作如下变化:
* 增加一个新的关键块 CgBI Chunk 四个字节
* zlib的header和CRC信息全部从IDAT中移除
* 红蓝交换,每一个像素(RGBA)中的R和B进行调换变成BGRA ,解压后每一个像素有四个字节组成,也就是将每一个像素的 第一个字节和第三个字节调换
* 透明像素处理 Premultiplied Alpha,这个的意思是为了图像加载变得更快,预先将Alpha的信息乘到像素的颜色信息中去,这样后期计算的时候就可以减少CPU或者GPU计算了
把一个正常的PNG图片优化成iPhone 的png图片格式可以使用XCode自带的工具 `/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/pngcrush -iphone`
还有一个第三方的开源工具也可以
[https://github.com/DHowett/pincrush](https://github.com/DHowett/pincrush)
如果你想把一个经过优化后的图片还原成普通图片阅读器可以查看的png图片,就是对上面的过程进行反向处理。
现在可以找到的第三方的转换的一般有如下几个
ipin.py(Python版本) [http://www.axelbrz.com.ar/?mod=iphone-png-images-normalizer](http://www.axelbrz.com.ar/?mod=iphone-png-images-normalizer)
iPhonePNG(C版本) [http://www.newsfirerss.com/blog/?p=176](http://www.newsfirerss.com/blog/?p=176)
经过本人测试,上面的这些转换工具都没有对图片alpha相关信息的做任何处理,也可能是别的原因,有一些图片转换后的结果和原始图片还是有出入的。
编译后如果使用第三方的python或者C版本的代码来转换,转换后的图片都是这样的,感觉边角的像素有点问题,不过大部分情况下 ,图片都是ok的
我尝试通过修改第三方的代码,想将Premultiplied Alpha 还原过去,但是还是存在各种问题,最终没有结果。
只能最终采用XCode自带的工具进行转换 `/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/pngcrush -revert-iphone-optimizations 1.png 2.png`
这个pngcrush是apple改自开源的pngcrush 只可惜苹果修改后的版本却没有开源出来。
为了避免每次都需要在命令行中进行操作,你可以通过automator新建一个shell的service
{% highlight bash %}
for path in "$@"
do
mv "$path" "$path".tmp
/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/pngcrush -revert-iphone-optimizations "$path".tmp "$path"
rm "$path".tmp
done
{% endhighlight %}
当然你可以修改脚本,并可以作用于文件和文件夹,对目标进行判断,文件夹则递归文件夹中的png文件进行逐个处理。
+225
View File
@@ -0,0 +1,225 @@
---
layout: post
title: Blocks编程
categories:
- Programming
tags:
- iOS
- Objc
---
* 介绍
* 声明创建和调用
* Block和变量
* Block实际应用
---
### 1.介绍
Block是一个C Level的语法以及运行时的一个特性,非常像标准C中的函数(函数指针),但是其运行需要编译器和运行时支持,目前LLVM+Clang可以很好的支持Block(苹果修改过的GCC也可以)。Block和函数不同的是其语义 **闭包** 特性,以及可以有匿名block的存在。
你可以在LLVM的官方网站查看[Block语言规范][link-block-spec].
[link-block-spec]:http://clang.llvm.org/docs/BlockLanguageSpec.txt
你可以通过`^` 运算符来声明一个block变量,或用来表明block定义的开始,而block的代码块则是包含在一对花括号`{}`内的.
{% highlight objc %}
int multiplier = 2;
int (^myBlock)(int) = ^(int num){
return num * multiplier;
};
printf("%d",myBlock(4));
{% endhighlight %}
上面代码中的`myBlock`就是Block的变量名,由myBlock变量的声明可以看出,它返回值为int类型,且存在一个int型的参数。
等于号后面就是Block的定义并将其赋值给myBlock .
Block的调用就和C函数的使用类似.
###2. 声明创建和调用
**声明Block变量**
Block变量保存着指向Block的指针,声明一个Block变量就和声明一个函数指针变量类似,只是将*改成了^.
其他的就和C的类型系统保持一致了。
{% highlight objc %}
void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
{% endhighlight %}
另 Block还支持可变参数variadic (...) ,没有参数的话,变量列表的地方必须写上void关键字.
**声明Block类型**
你可以通过typedef声明Block的类型,这样多个地方需要使用同种类型的Block的时候会比较方便,
{% highlight objc %}
typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;
{% endhighlight %}
**Block创建**
声明了一个Block变量之后,可以为这个变量赋值
{% highlight objc %}
blockReturningVoidWithVoidArgument = ^{
printf("%s","Block Returing Void With Void Argument.");
}
{% endhighlight %}
当然你可以在声明变量的同时赋值
{% highlight objc %}
void (^blockReturningVoidWithVoidArgument)(void) = ^{
printf("%s","Block Returing Void With Void Argument.");
}
{% endhighlight %}
**Block调用**
Block的调用和函数的调用是非常相似的,如上面定义的blockReturningVoidWithVoidArgument,调用的时候则直接
`blockReturningVoidWithVoidArgument();`便可.
**匿名Block**
当一个Block作为函数参数的时候,一般实参都是以匿名Block的方式传过去的。
{% highlight objc %}
void callVoidVoid(void (^closure)(void)) {
closure();
}
int main(int argc, char *argv[]) {
__block int i = 10;
callVoidVoid(^{ ++i; });
if (i != 11) {
printf("*** %s didn't update i\n", argv[0]);
return 1;
}
printf("%s: success\n", argv[0]);
return 0;
}
{% endhighlight %}
当然你也可以直接调用匿名Block,如
^{ ++i; }();
### 3.Block和变量
一个Block的内部是可以引用自身作用域外的变量的,包括static变量,extern变量或自由变量(定义一个变量的时候,如果不加存储修饰符,默认情况下就是自由变量auto,auto变量保存在stack中的.除了auto之外还存在register,static等存储修饰符) ,对于自由变量,在Block中是只读的。在引入block的同时,还引入了一种特殊的变量存储修饰符`__block`,通过它的变量叫做block变量,block变量在block内部可以进行写操作的。这些变量中,自由变量是最特殊的,在Block声明的时候,自由变量在Block内部只读且其值被固定住(自由变量被拷贝了一份,且限定为const),即使在block调用前改变了这个自由变量的值,block调用的时候,看到的却还是block声明的时候的那个值。
**代码示例 3.1**
{% highlight objc %}
__block int blockValue = 0;
int autoValue = 0;
void(^printValue)(void) = ^{
printf("blockValue = %d\n",blockValue);
printf("autoValue = %d\n",autoValue);
};
blockValue ++;
autoValue ++;
printValue();
{% endhighlight %}
3.1中的代码,输出的值为
{% highlight objc %}
blockValue = 1
autoValue = 0
{% endhighlight %}
可以看到自由变量尽管自增了,但是在调用printValue这个Block的时候,看到的还是其定义的时候看到的那个autoValue的值,autoValue的值在Block的内部示无法修改的,要不然编译器会报错:
Semantic Issue: Variable is not assignable (missing __block type specifier)
Block定义时内存是分配在stack上的,当其作用域结束,就会被自动释放,所以你不需要去担心它的内存情况,我们可以对一个Block进行`Block_copy()`操作,`Block_copy()`之后,Block会被拷贝到heap中的内存中,且其所有的引用到的自由变量也将会被拷贝,当然你得记得通过`Block_release()`释放heap的内存空间哦。
在objc中Block是和对象一样被看作一等公民的(其实这是objc的Block扩展的功劳),你可以像使用对象那样对Block进行retain(retain只对heap中的Block起作用),copy以及release操作.
在Block内部如果引用到对象或者对象的成员变量,那么当Block被拷贝`Block_copy()`之后,这个对象的引用计数会增加。
**代码示例 3.2**
{% highlight objc %}
NSObject *testObject = [[NSObject alloc] init];
NSLog(@"%lu",[testObject retainCount]); //1
NSLog(@"%lu",[self retainCount]); //1
void(^testBlock)(void) = ^{
NSLog(@"The Test String : %@", testObject);
NSLog(@"The Window Object : %@", _window);
};
NSLog(@"%lu",[testObject retainCount]); //1
NSLog(@"%lu",[self retainCount]); //1
void(^testBlock2)(void) = Block_copy(testBlock); //testBlock会被拷贝到heap中,所以用完了要自己调用Block_release进行释放
NSLog(@"%lu",[testObject retainCount]); //2
NSLog(@"%lu",[self retainCount]); //2
testBlock2();
Block_release(testBlock2);
NSLog(@"%lu",[testObject retainCount]); //1
NSLog(@"%lu",[self retainCount]); //1
[testObject release];
{% endhighlight %}
Block的闭包特性使得Block可以脱离其定义的作用域进行运行,所以你可以在一个函数中返回一个Block,在别的线程或者当前线程的RunLoop中进行运行,而不用担心那些引用到的外部变量是否被释放掉了。
###4.Block实际应用
那么我们一般什么时候会用到Block呢?
Blocks通常是一小段自包含的代码片段.所以它经常被用于多线程运行的代码单元(如GCD),或用于处理聚合类元素单元,或者作为某个函数调用完成后的回调函数.
Block用作回调函数比传统的回调函数有以下的优越性:
* 在函数调用的时候,将Block作为一个参数传给函数
* 允许访问本地变量,这样可以避免通过结构体将本地变量封装后传递给回调函数
**应用1: Animations & Completion Handler**
{% highlight objc %}
[UIView animateWithDuration:2
animations:^{
self.view.backgroundColor = [UIColor redColor];
}
completion:^(BOOL finished){
if (finished){
self.view.backgroundColor = [UIColor blueColor];
}
}];
{% endhighlight %}
**应用2: Enumeration**
对数据集合类中的每一个元素进行遍历,每次传入一个对象,进行处理
{% highlight objc %}
NSArray *cards = [NSArray arrayWithObjects:@"Jack", @"Queen", @"King", @"Ace", nil];
[cards enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
NSLog(@"%@ card at index %d", object, index);
}];
{% endhighlight %}
**应用3: Notification Handler**
{% highlight objc %}
[[NSNotificationCenter defaultCenter]
addObserverForName:@"TestNotification"
object:nil
queue:aNSOperationQueue
usingBlock:^(NSNotification *notification){
NSLog(@"Notification: %@",notification);
}];
{% endhighlight %}
**应用4: GCD**
{% highlight objc %}
dispatch_queue_t imageDownloadQueue = dispatch_queue_create("Image Download Queue", NULL);
dispatch_async(imageDownloadQueue, ^{
NSURL *imageURL = [NSURL URLWithString:@"http://xxx.xx.com/a.png"];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
[imageView setImage:image];
});
});
{% endhighlight %}
+476
View File
@@ -0,0 +1,476 @@
---
layout: post
title: iOS持久化
categories:
- Programming
tags:
- iOS
- Objc
---
* 文件系统
* 归档和序列化
* 数据库
---
##1.文件系统
不管是Mac OS X 还是iOS的文件系统都是建立在UNIX文件系统基础之上的。
###1.1 沙盒模型
在iOS中,一个App的读写权限只局限于自己的沙盒目录中。
>**沙盒模型到底有哪些好处呢?**
>安全:别的App无法修改你的程序或数据
>保护隐私:别的App无法读取你的程序和数据
>方便删除:因为一个App所有产生的内容都在自己的沙盒中,所以删除App只需要将沙盒删除就可以彻底删除程序了
iOS App沙盒中的目录
* App Bundle ,如xxx.app 其实是一个目录,里面有app本身的二进制数据以及资源文件
* Documents, 存放程序产生的文档数据
* Library , 下面默认包含下面两个目录 Caches Preferences
* tmp, 临时文件目录
如果我们想在程序中获取上面某个目录的路径,应该如何实现呢?
下面就讲讲路径的获取,
通过`NSPathUtilities.h `中的`NSSearchPathForDirectoriesInDomains`函数,我们便可以获取我们想要的路径。
此函数具体声明如下:
>NSArray *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);
>**directory** 目录类型 比如Documents目录 就是NSDocumentDirectory
>**domainMask** 在iOS的程序中这个取NSUserDomainMask
>**expandTilde** YES,表示将~展开成完整路径
注意函数返回的类型为数组,在iOS中一般这个数组中只包含一个元素,所以直接取lastObject即可。
###1.2 NSFileManager
NSFileManager提供一个类方法获得一个单例。
{% highlight objc %}
/* Returns the default singleton instance.*/
+ (NSFileManager *)defaultManager;
{% endhighlight %}
下面罗列了NSFileManager的常用方法
* 新建目录
{% highlight objc %}
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error;
{% endhighlight %}
createIntermediates这个参数一般为YES,表示如果目录路径中间的某个目录不存在则创建之,如果是NO的话,则要保证所创建目录的父目录都必须已经存在
* 获取目录下的所有文件
{% highlight objc %}
- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error;
{% endhighlight %}
如果目录为空,则返回空数组
* 其他的一些方法
{% highlight objc %}
- (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- (BOOL)linkItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error;
{% endhighlight %}
更多的可以查看文档 [NSFileManager Class Reference](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/Reference/Reference.html)。
在实际项目中,我们一般会写一个工具类来负责项目中所有的路径操作。
##2. 归档(Archives) 和 序列化(Serializations)
我们经常听到“序列化”,“反序列化”这样的字眼,其实“序列化”的意思就是将对象转换成字节流以便保存或传输,“反序列化”便是一个相反的过程,从字节流转到对象。
在这节中涉及到一种文件类型plist,plist就是Property List 的缩写,即所谓的属性列表,属性列表有两种数据格式,一种是XML的,方便阅读和编辑;另一种是二进制的,节省存储空间,以及提高效率。
在Objective-C中这个对象和字节流的互转分成两类:
* **归档** 普通自定义对象和字节流之间的转换
* **序列化** 某些特定类型(NSDictionary, NSArray, NSString, NSDate, NSNumber,NSData)的数据和字节流之间(通常将其保存为plist文件)的转换
不过本质上讲上述两种都是对象图([Object Graph](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Archiving/Articles/objectgraphs.html#//apple_ref/doc/uid/20001293-CJBDFIBI))和字节流之间的转换.
Apple关于序列化和归档的编程指南: [Archives and Serializations Programming Guide](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Archiving/Archiving.html) 。
###2.1 归档
如果我们需要将自定义的一个对象保存到文件,应该如何做呢?
这里引入两个东西:一个是NSCoding协议 ;另一个是NSKeyedArchiver,NSKeyedArchiver其实继承于NSCoder,可以以键值对的方式将对象的属性进行序列化和反序列化。
具体的过程可以这样描述 **通过NSKeyedArchiver 可以将实现了NSCoding协议的对象 和 字节流 相互转换** 。
像一些框架中的数据类型如NSDictionary,NSArray,NSString... 都已经实现了NSCoding协议,所以可以直接对他们进行归档操作。
这里来一个比较完整的例子,一个Address类,一个User类,User类下有个Address类型的属性。
**Address类**
{% highlight objc %}
@interface Address : NSObject<NSCoding>{
NSString *country;
NSString *city;
}
@property(nonatomic,copy) NSString *country;
@property(nonatomic,copy) NSString *city;
@end
//////////////////////////////////////////////////////
#import "Address.h"
@implementation Address
@synthesize country;
@synthesize city;
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:country forKey:@"country"];
[aCoder encodeObject:city forKey:@"city"];
}
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
[self setCountry:[aDecoder decodeObjectForKey:@"country"]];
[self setCity:[aDecoder decodeObjectForKey:@"city"]];
}
return self;
}
@end
{% endhighlight %}
**User类**
{% highlight objc %}
#import <Foundation/Foundation.h>
#import "Address.h"
@interface User : NSObject<NSCoding>{
NSString *_name;
NSString *_password;
Address *_address;
}
@property(nonatomic,copy) NSString *name;
@property(nonatomic,copy) NSString *password;
@property(nonatomic,retain) Address *address;
@end
/////////////////////////////////////////////////////////
#import "User.h"
@implementation User
@synthesize name = _name;
@synthesize password = _password;
@synthesize address = _address;
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeObject:_password forKey:@"password"];
[aCoder encodeObject:_address forKey:@"address"];
}
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
[self setName:[aDecoder decodeObjectForKey:@"name"]];
[self setPassword:[aDecoder decodeObjectForKey:@"password"]];
[self setAddress:[aDecoder decodeObjectForKey:@"address"]];
}
return self;
}
@end
{% endhighlight %}
**使用示例**
{% highlight objc %}
Address *myAddress = [[[Address alloc] init] autorelease];
myAddress.country = @"中国";
myAddress.city = @"杭州";
User *user = [[[User alloc] init] autorelease];
user.name = @"卢克";
user.password = @"lukejin";
user.address = myAddress;
[NSKeyedArchiver archiveRootObject:user toFile:@"/Users/Luke/Desktop/user"];
id object = [NSKeyedUnarchiver unarchiveObjectWithFile:@"/Users/Luke/Desktop/user"];
NSLog(@"Object Class : %@",[object class]);
{% endhighlight %}
通过查看文件内容可以发现,保存的是plist的二进制数据格式。
转成XML可以看到如下内容:
{% highlight xml %}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/ PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>$archiver</key>
<string>NSKeyedArchiver</string>
<key>$objects</key>
<array>
<string>$null</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>8</integer>
</dict>
<key>address</key>
<dict>
<key>CF$UID</key>
<integer>4</integer>
</dict>
<key>name</key>
<dict>
<key>CF$UID</key>
<integer>2</integer>
</dict>
<key>password</key>
<dict>
<key>CF$UID</key>
<integer>3</integer>
</dict>
</dict>
<string>卢克</string>
<string>lukejin</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>7</integer>
</dict>
<key>city</key>
<dict>
<key>CF$UID</key>
<integer>6</integer>
</dict>
<key>country</key>
<dict>
<key>CF$UID</key>
<integer>5</integer>
</dict>
</dict>
<string>中国</string>
<string>杭州</string>
<dict>
<key>$classes</key>
<array>
<string>Address</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>Address</string>
</dict>
<dict>
<key>$classes</key>
<array>
<string>User</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>User</string>
</dict>
</array>
<key>$top</key>
<dict>
<key>root</key>
<dict>
<key>CF$UID</key>
<integer>1</integer>
</dict>
</dict>
<key>$version</key>
<integer>100000</integer>
</dict>
</plist>
{% endhighlight %}
###2.2 序列化
在实际的项目中,我们一般是将NSDictionary或NSArray的对象保存到文件或者从文件读取成对象。
当然这种只是适用于数据量不是很大的应用场景。
NSDictionary和NSArray 都有一个写入文件的方法
{% highlight objc %}
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
{% endhighlight %}
NSDictionary和NSArray会直接写成plist文件。
####2.2.1 序列化的方式
序列化可以通过两种途径来进行
#####使用数据对象自带的方法
写文件
{% highlight objc %}
NSMutableDictionary *dataDictionary = [[[NSMutableDictionary alloc] init] autorelease];
[dataDictionary setValue:[NSNumber numberWithInt:222] forKey:@"intNumber"];
[dataDictionary setValue:[NSArray arrayWithObjects:@"1",@"2", nil] forKey:@"testArray"];
[dataDictionary writeToFile:@"/Users/Luke/Desktop/test.plist" atomically:YES];
{% endhighlight %}
写完的文件内容如下:
{% highlight xml %}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>intNumber</key>
<integer>222</integer>
<key>testArray</key>
<array>
<string>1</string>
<string>2</string>
</array>
</dict>
</plist>
{% endhighlight %}
从文件读取
{% highlight objc %}
NSDictionary *dictionaryFromFile = [NSDictionary dictionaryWithContentsOfFile:@"/Users/Luke/Desktop/test.plist"];
{% endhighlight %}
##### 使用NSPropertyListSerialization类
通过NSPropertyListSerialization类可以将数据对象直接转成NSData或者直接写到文件或者流中去.
{% highlight objc %}
NSMutableDictionary *dataDictionary = [[[NSMutableDictionary alloc] init] autorelease];
[dataDictionary setValue:[NSNumber numberWithInt:222] forKey:@"intNumber"];
[dataDictionary setValue:[NSArray arrayWithObjects:@"1",@"2", nil] forKey:@"testArray"];
NSString *error;
NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:dataDictionary
format:NSPropertyListXMLFormat_v1_0
errorDescription:&error];
if(xmlData) {
NSLog(@"No error creating XML data.");
[xmlData writeToFile:@"/Users/Luke/Desktop/test2.plist" atomically:YES];
}
else {
if (error) {
NSLog(@"error:%@", error);
[error release];
}
}
{% endhighlight %}
读取
{% highlight objc %}
NSDictionary *dictionaryFromFile = (NSDictionary *)[NSPropertyListSerialization
propertyListWithData:[NSData dataWithContentsOfFile:@"/Users/Luke/Desktop/test2.plist"]
options:0
format:NULL
error:&error];
{% endhighlight %}
####2.2.2 User Defaults
User Defaults 顾名思义就是一个用户为系统以及程序设置的默认值。每个用户都有自己的一套数据,用户和用户之间没法共享的。
我们都知道每一个程序都会保存一些设置数据,比如记住上次窗口的位置和大小,记住是否弹出某些提示信息等。苹果提供了一个统一的解决方案,就是每一个app都有一个plist文件专门用以保存偏好设置数据。
plist文件名默认是程序Bundle identifier,扩展名为plist.
除了程序自己的设置外,系统还有一些全局的或者其它的一些设置,也属于User Defaults的范畴,User Defaults的持久化数据都保存在 `~/Library/Preferences` 目录中.
这里有一点简要的说一下,User Defaults 中存放的key value分放在多个Domain中,取的时候按一定的次序取查找,次序如下:
* **The Argument Domain** 程序启动的时候以参数的方式传入的
* **The Application Domain** 通过NSUserDefaults往里面写数据的时候默认就是写到这个Domain的,通过Bundle identifier来标识
* **The Global Domain** 用户的全局的设置(系统的偏好设置)会放在这个Domain下,比如用户的语言设置,滚动条的设置等,里面的设置会对所有的程序起作用。
* **The Languages Domains**
* **The Registration Domain** 这个domain里面的key value是提供默认值的,一般会在程序启动的设置进行设置,他们都不会被持久化到文件的。当某个key对应的值在上面的那些domain中都不存在的时候,就到这里找。
Mac系统还为user defaults提供了很好的命令行工具,`defaults` 你可以通过下面的方式查看具体使用方式
man defaults
可以通过`defaults domains`查看当前用户的所有的domain,通过 `defaults read NSGlobalDomain` 读取 **The Global Domain** 中的所有值。
**NSUserDefaults** 类来读写Preferences设置,而无需考虑文件位置等细节问题。
**NSUserDefaults** 用起来和 **NSDictionary** 很相似,多了一个Domain的概念在里面。
**NSUserDefaults** 一样提供了一个获取单例的方法.
+ (NSUserDefaults *)standardUserDefaults
NSUserDefaults提供了一系列的接口来根据key获取对应的value,搜索的次序按照上面提及到的次序在各个Domain中进行查找。还提供了一系列的 Setting Default Values的方法,这些设置的值都是在 **The Application Domain** 下的.当然也提供了修改其他Domain下的值的方法,只是需要整体的设置。
##3.数据库
Mac上自带安装了SQLite3 ,如果你之前接触过关系型数据库,你可以通过命令行来对SQLite进行初步的认识
{% highlight bash %}
$ sqlite3 test.db
SQLite version 3.7.5
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>create table if not exists names(id integer primary key asc, name text);
sqlite> insert into names(name) values('Luke');
sqlite> select * from names;
1|Luke
sqlite>
{% endhighlight %}
那如果在代码中使用SQLite呢?
* 添加sqlite的动态链接库 libsqlite3.0.dylib
* 引入头文件 #import "sqlite3.h"
这样之后你便可以通过C的接口来操作数据库了
{% highlight objc %}
sqlite3 *database;//sqlite3的类型其实只是一个结构体struct
NSArray *documentsPaths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory
, NSUserDomainMask
, YES);
NSString *databaseFilePath=[[documentsPaths objectAtIndex:0] stringByAppendingPathComponent:@"luke.db"];
//打开数据库
if (sqlite3_open([databaseFilePath UTF8String], &database)==SQLITE_OK) {
NSLog(@"open sqlite db ok.");
char *errorMsg;
const char *createSql="create table if not exists names (id integer primary key asc,name text)";
//创建表
if (sqlite3_exec(database, createSql, NULL, NULL, &errorMsg)==SQLITE_OK) {
NSLog(@"create ok.");
}else {
NSLog(@"error: %s",errorMsg);
sqlite3_free(errorMsg);
}
//插入数据
const char *insertSql="insert into names (name) values(\"Luke\")";
if (sqlite3_exec(database, insertSql, NULL, NULL, &errorMsg) == SQLITE_OK) {
NSLog(@"insert ok.");
}else {
NSLog(@"error: %s",errorMsg);
sqlite3_free(errorMsg);
}
const char *selectSql="select id,name from names";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database, selectSql, -1, &statement, nil) == SQLITE_OK) {
NSLog(@"select ok.");
}
while (sqlite3_step(statement)==SQLITE_ROW) {
int _id=sqlite3_column_int(statement, 0);
char *name=(char *)sqlite3_column_text(statement, 1);
NSString *nameString = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
NSLog(@"row>>id %i, name %@",_id,nameString);
}
sqlite3_finalize(statement);
}
sqlite3_close(database);
{% endhighlight %}
你会发现这完全是C语言编程,和Objective-C的代码混在一起格格不入,也很不方便,所以便有人开发了开源的sqlite c接口的wrapper
+ FMDB [https://github.com/ccgus/fmdb](https://github.com/ccgus/fmdb)
+ EGODatabase [https://github.com/enormego/egodatabase](https://github.com/ccgus/fmdb) (部分代码来自FMDB,thread safe)
具体的使用方法,各自的文档都写的比较清楚。
FMDB不支持多线程同时使用同一个数据库连接进行操作,否则会有线程安全问题,有可能导致数据库文件损坏。EGODatabase则引入了多线程的支持,部分代码借鉴了FMDB,两者在使用上非常的相似。另EGODatabase提供了异步数据库操作的支持,将数据库操作封装成数据库请求(其继承于NSOperation),数据库请求创建好了,丢到一个OperationQueue中被异步的进行执行,当请求数据完成之后 ,相应的delegate方法会被调用,然后你可以在主线程更新显示了.
+892
View File
@@ -0,0 +1,892 @@
---
layout: post
title: Mac&iOS Socket
categories:
- Programming
tags:
- Socket
- C
---
###大纲
* 一.Socket简介
* 二.BSD Socket编程准备
* 1.地址
* 2.端口
* 3.网络字节序
* 4.半相关与全相关
* 5.网络编程模型
* 三.socket接口编程示例
* 四.使用select
* 五.使用kqueue
* 六.使用流
>注:文档中设计涉及的代码也都在本人github目录下,分别为socketServer和socketClient.对应着各个分支。
>![分支](http://farm6.staticflickr.com/5271/7078374971_632b0b2f71_d.jpg)
---
###一.Socket简介
在UNIX系统中,万物皆文件(Everything is a file)。所有的IO操作都可以看作对文件的IO操作,都遵循着这样的操作模式:打开 -> 读/写 -> 关闭,打开操作(如open函数)获取“文件”使用权,返回文件描述符,后继的操作都通过这个文件描述符来进行。很多系统调用都依赖于文件描述符,它是一个无符号整数,每一个用户进程都对应着一个文件描述符表,通过文件描述符就可以找到对应文件的信息。
在类UNIX平台上,对于控制台的标准输入输出以及标准错误输出都有对应的文件描述符,分别为0,1,2。它们定义在 `unistd.h`中
{% highlight c %}
#define STDIN_FILENO 0 /* standard input file descriptor */
#define STDOUT_FILENO 1 /* standard output file descriptor */
#define STDERR_FILENO 2 /* standard error file descriptor */
{% endhighlight %}
在Mac系统中,可以通过Activity Monitor来查看某个进程打开的文件和端口。
![已打开文件](http://farm6.staticflickr.com/5446/7078375235_2d4e64cb1c_z_d.jpg)
UNIX内核加入TCP/IP协议的时候,便在系统中引入了一种新的IO操作,只不过由于网络连接的不可靠性,所以网络IO比本地设备的IO复杂很多。这一系列的接口叫做BSD Socket API,当初由伯克利大学研发,最终成为网络开发接口的标准。
网络通信从本质上讲也是进程间通信,只是这两个进程一般在网络中不同计算机上。当然Socket API其实也提供了专门用于本地IPC的使用方式:UNIX Domain Socket,这个这里就不细说了。本文所讲的Socket如无例外,均是说的Internet Socket。
在本地的进程中,每一个进程都可以通过PID来标识,对于网络上的一个计算机中的进程如何标识呢?网络中的计算机可以通过一个IP地址进行标识,一个计算机中的某个进程则可以通过一个无符号整数(端口号)来标识,所以一个网络中的进程可以通过`IP地址+端口号`的方式进行标识。
###二.BSD Socket编程准备
####1.地址
在程序中,我们如何保存一个地址呢?在 `<sys/socket.h>`中的sockaddr便是描述socket地址的结构体类型.
{% highlight c %}
/*
* [XSI] Structure used by kernel to store most addresses.
*/
struct sockaddr {
__uint8_t sa_len; /* total length */
sa_family_t sa_family; /* [XSI] address family */
char sa_data[14]; /* [XSI] addr value (actually larger) */
};
{% endhighlight %}
为了方便设置用语网络通信的socket地址,引入了sockaddr_in结构体(对于UNIX Domain Socket则对应sockaddr_un)
{% highlight c %}
/*
* Socket address, internet style.
*/
struct sockaddr_in {
__uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;//得是网络字节序
struct in_addr sin_addr;//in_addr存在的原因则是历史原因,其实质是代表一个IP地址的32位整数
char sin_zero[8];//bzero之,纯粹是为了兼容sockaddr
};
{% endhighlight %}
在实际编程的时候,经常需要将sockaddr_in强制转换成sockaddr类型。
####2.端口
说到端口我们经常会联想到硬件,在网络编程中的端口其实是一个标识而已,或者说是系统的资源而已。系统提供了端口分配和管理的机制。
####3.网络字节序
谈网络字节序(Endianness)之前我们先说说什么是字节序。字节序又叫端序,就是指计算机中存放 **多字节数据**的字节的顺序。典型的就是数据存放在内存中或者网络传输时的字节的顺序。常用的字节序有大端序(big-endian),小端序(litle-endian,另还有不常见的混合序middle-endian)。不同的CPU可能会使用不同的字节序,如X86,PDP-11等处理器为小端序,Motorola 6800,PowerPC 970等使用的是大端序。小端序是指低字节位存放在内存地址的低端,高端序是指高位字节存放在内存的低端。
举个例子来说明什么是大端序和小端序:
比如一个4字节的整数 16进制形式为 0x12345678,最左边是高位。
大端序
<table>
<tr><td>低位</td><td> </td><td> </td><td>高位</td></tr>
<tr><td>12</td><td>34</td><td>56</td><td>78</td></tr>
</table>
小端序
<table>
<tr><td>低位</td><td> </td><td> </td><td>高位</td></tr>
<tr><td>78</td><td>56</td><td>34</td><td>12</td></tr>
</table>
TCP/IP 各层协议将字节序使用的是大端序,我们把TCP/IP协议中使用的字节序称之为网络字节序。
编程的时候可以使用定义在`sys/_endian.h`中的相关的接口进行本地字节序和网络字节序的互转。
{% highlight c %}
#define ntohs(x) __DARWIN_OSSwapInt16(x) // 16位整数 网络字节序转主机字节序
#define htons(x) __DARWIN_OSSwapInt16(x) // 16位整数 主机字节序转网络字节序
#define ntohl(x) __DARWIN_OSSwapInt32(x) //32位整数 网络字节序转主机字节序
#define htonl(x) __DARWIN_OSSwapInt32(x) //32位整数 主机字节序转网络字节序
{% endhighlight %}
>以上声明中 n代表netwrok, h代表host ,s代表short,l代表long
如果数据是单字节的话,则其没有字节序的说法了。
####4.半相关与全相关
半相关(half-association)是指一个三元组 `(协议,本地IP地址,本地端口)`,通过这个三元组就可以唯一标识一个网络中的进程,一般用于listening socket。但是实际进行通信的过程,至少需要两个进程,且它们所使用的协议必须一致,所以一个完成的网络通信至少需要一个五元组表示`(协议,本地地址,本地端口,远端地址,远端端口)`,这样的五元组叫做全相关。
####5.网络编程模型
网络存在的本质其实就是网络中个体之间的在某个领域的信息存在不对等性,所以一般情况下总有一些个体为另一些个体提供服务。提供服务器的我们把它叫做服务器,接受服务的叫做客户端。所以在网络编程中,也存在服务器端和客户端之分。
<table>
<tr>
<td>服务器端</td><td>客户端</td>
<tr>
<td>创建Socket</td><td>-</td>
</tr>
<tr>
<td>将Socket和本地的地址端口绑定</td><td>-</td>
</tr>
<tr>
<td>开始进行侦听</td><td>创建一个Socket和服务器的地址并通过它们向服务器发送连接请求</td>
</tr>
<tr>
<td>握手成功,接受请求,得到一个新的Socket,通过它可以和客户端进行通信</td><td>连接成功,客户端的Socket会绑定到系统分配的一个端口上,并可以通过它和服务器端进行通信</td>
</tr>
</table>
###三.BSD Socket编程详解
下面的例子是一个简单的一对一聊天的程序,分服务器和客户端,且发送消息和接受消息次序固定。
####Server端代码
{% highlight c %}
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
int main (int argc, const char * argv[])
{
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;//Address families AF_INET互联网地址簇
server_addr.sin_port = htons(11332);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
//创建socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有连接
if (server_socket == -1) {
perror("socket error");
return 1;
}
//绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信
int bind_result = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
perror("bind error");
return 1;
}
//listen侦听 第一个参数是套接字,第二个参数为等待接受的连接的队列的大小,在connect请求过来的时候,完成三次握手后先将连接放到这个队列中,直到被accept处理。如果这个队列满了,且有新的连接的时候,对方可能会收到出错信息。
if (listen(server_socket, 5) == -1) {
perror("listen error");
return 1;
}
struct sockaddr_in client_address;
socklen_t address_len;
int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &address_len);
//返回的client_socket为一个全相关的socket,其中包含client的地址和端口信息,通过client_socket可以和客户端进行通信。
if (client_socket == -1) {
perror("accept error");
return -1;
}
char recv_msg[1024];
char reply_msg[1024];
while (1) {
bzero(recv_msg, 1024);
bzero(reply_msg, 1024);
printf("reply:");
scanf("%s",reply_msg);
send(client_socket, reply_msg, 1024, 0);
long byte_num = recv(client_socket,recv_msg,1024,0);
recv_msg[byte_num] = '\0';
printf("client said:%s\n",recv_msg);
}
return 0;
}
{% endhighlight %}
####Client端代码
{% highlight c %}
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
int main (int argc, const char * argv[])
{
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(11332);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket error");
return 1;
}
char recv_msg[1024];
char reply_msg[1024];
if (connect(server_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))==0) {
//connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。
while (1) {
bzero(recv_msg, 1024);
bzero(reply_msg, 1024);
long byte_num = recv(server_socket,recv_msg,1024,0);
recv_msg[byte_num] = '\0';
printf("server said:%s\n",recv_msg);
printf("reply:");
scanf("%s",reply_msg);
if (send(server_socket, reply_msg, 1024, 0) == -1) {
perror("send error");
}
}
}
// insert code here...
printf("Hello, World!\n");
return 0;
}
{% endhighlight %}
上面的服务器端和客户端连接成功之后打开的端口的情况是怎么样的呢?
* 服务器端 ,存在一个用于listen的半相关的socket,一个用于和客户端进行通信的全相关的socket
![服务器端进程打开文件](http://farm8.staticflickr.com/7279/7078375445_082672001e_z_d.jpg)
* 客户端 存在一个用于和服务器端进行通信的全相关的socket
![客户端进程打开文件](http://farm8.staticflickr.com/7242/6932301638_10cf5ab8b8_z_d.jpg)
由于accept只运行了一次,所以服务器端一次只能和一个客户端进行通信,且使用的send和recv方法都是阻塞的,所以上面这个例子存在一个问题就是服务器端客户端连接成功之后,发送,接受,发送,接受的次序就被固定了。比如服务器端发送消息之后就等客户端发送消息了,没有接受到客户端的消息之前服务器端是没有办法发送消息的。使用select这个这个系统调用可以解决上面的问题。
###四.使用select
select这个系统调用,是一种多路复用IO方案,可以同时对多个文件描述符进行监控,从而知道哪些文件描述符可读,可写或者出错,不过select方法是阻塞的,可以设定超时时间。
select使用的步骤如下:
* 1.创建一个fd_set变量(fd_set实为包含了一个整数数组的结构体),用来存放所有的待检查的文件描述符
* 2.清空fd_set变量,并将需要检查的所有文件描述符加入fd_set
* 3.调用select。若返回-1,则说明出错;返回0,则说明超时,返回正数,则为发生状态变化的文件描述符的个数
* 4.若select返回大于0,则依次查看哪些文件描述符变的可读,并对它们进行处理
* 5.返回步骤2,开始新一轮的检测
若上面的聊天程序使用select进行改写,则是下面这样的
####服务器端
{% highlight c %}
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#define BACKLOG 5 //完成三次握手但没有accept的队列的长度
#define CONCURRENT_MAX 8 //应用层同时可以处理的连接
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
int main (int argc, const char * argv[])
{
char input_msg[BUFFER_SIZE];
char recv_msg[BUFFER_SIZE];
//本地地址
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
//创建socket
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1) {
perror("socket error");
return 1;
}
//绑定socket
int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
perror("bind error");
return 1;
}
//listen
if (listen(server_sock_fd, BACKLOG) == -1) {
perror("listen error");
return 1;
}
//fd_set
fd_set server_fd_set;
int max_fd = -1;
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
while (1) {
FD_ZERO(&server_fd_set);
//标准输入
FD_SET(STDIN_FILENO, &server_fd_set);
if (max_fd < STDIN_FILENO) {
max_fd = STDIN_FILENO;
}
//服务器端socket
FD_SET(server_sock_fd, &server_fd_set);
if (max_fd < server_sock_fd) {
max_fd = server_sock_fd;
}
//客户端连接
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
FD_SET(client_fds[i], &server_fd_set);
if (max_fd < client_fds[i]) {
max_fd = client_fds[i];
}
}
}
int ret = select(max_fd+1, &server_fd_set, NULL, NULL, &tv);
if (ret < 0) {
perror("select 出错\n");
continue;
}else if(ret == 0){
printf("select 超时\n");
continue;
}else{
//ret为未状态发生变化的文件描述符的个数
if (FD_ISSET(STDIN_FILENO, &server_fd_set)) {
//标准输入
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
//输入 ".quit" 则退出服务器
if (strcmp(input_msg, QUIT_CMD) == 0) {
exit(0);
}
for (int i=0; i<CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
send(client_fds[i], input_msg, BUFFER_SIZE, 0);
}
}
}
if (FD_ISSET(server_sock_fd, &server_fd_set)) {
//有新的连接请求
struct sockaddr_in client_address;
socklen_t address_len;
int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
if (client_socket_fd > 0) {
int index = -1;
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == 0) {
index = i;
client_fds[i] = client_socket_fd;
break;
}
}
if (index >= 0) {
printf("新客户端(%d)加入成功 %s:%d \n",index,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}else{
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");
send(client_socket_fd, input_msg, BUFFER_SIZE, 0);
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}
}
}
for (int i = 0; i <CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
if (FD_ISSET(client_fds[i], &server_fd_set)) {
//处理某个客户端过来的消息
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(client_fds[i],recv_msg,BUFFER_SIZE,0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE) {
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("客户端(%d):%s\n",i,recv_msg);
}else if(byte_num < 0){
printf("从客户端(%d)接受消息出错.\n",i);
}else{
FD_CLR(client_fds[i], &server_fd_set);
client_fds[i] = 0;
printf("客户端(%d)退出了\n",i);
}
}
}
}
}
}
return 0;
}
{% endhighlight %}
####客户端
{% highlight c %}
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define BUFFER_SIZE 1024
int main (int argc, const char * argv[])
{
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(11332);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1) {
perror("socket error");
return 1;
}
char recv_msg[BUFFER_SIZE];
char input_msg[BUFFER_SIZE];
if (connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))==0) {
fd_set client_fd_set;
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
while (1) {
FD_ZERO(&client_fd_set);
FD_SET(STDIN_FILENO, &client_fd_set);
FD_SET(server_sock_fd, &client_fd_set);
int ret = select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);
if (ret < 0 ) {
printf("select 出错!\n");
continue;
}else if(ret ==0){
printf("select 超时!\n");
continue;
}else{
if (FD_ISSET(STDIN_FILENO, &client_fd_set)) {
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
if (send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1) {
perror("发送消息出错!\n");
}
}
if (FD_ISSET(server_sock_fd, &client_fd_set)) {
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv(server_sock_fd,recv_msg,BUFFER_SIZE,0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE) {
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("服务器:%s\n",recv_msg);
}else if(byte_num < 0){
printf("接受消息出错!\n");
}else{
printf("服务器端退出!\n");
exit(0);
}
}
}
}
}
return 0;
}
{% endhighlight %}
当然select也有其局限性。当fd_set中的文件描述符较少,或者大都数文件描述符都比较活跃的时候,select的效率还是不错的。Mac系统中已经定义了fd_set 最大可以容纳的文件描述符的个数为1024
{% highlight c %}
//sys/_structs.h
#define __DARWIN_FD_SETSIZE 1024
/////////////////////////////////////////////
//Kernel.framework sys/select.h
#define FD_SETSIZE __DARWIN_FD_SETSIZE
{% endhighlight %}
每一次select 调用的时候,都涉及到user space和kernel space的内存拷贝,且会对fd_set中的所有文件描述符进行遍历,如果所有的文件描述符均不满足,且没有超时,则当前进程便开始睡眠,直到超时或者有文件描述符状态发生变化。当文件描述符数量较大的时候,将耗费大量的CPU时间。所以后来有新的方案出现了,如windows2000引入的IOCP,Linux Kernel 2.6中成熟的epoll,FreeBSD4.x引入的kqueue。
###五.使用kqueue
Mac是基于BSD的内核,所使用的是kqueue(kernel event notification mechanism,详细内容可以Mac中 `man 2 kqueue`),kqueue比select先进的地方就在于使用事件触发的机制,且其调用无需每次对所有的文件描述符进行遍历,返回的时候只返回需要处理的事件,而不像select中需要自己去一个个通过FD_ISSET检查。
kqueue默认的触发方式是level 水平触发,可以通过设置event的flag为`EV_CLEAR` 使得这个事件变为边沿触发,可能epoll的触发方式无法细化到单个event,需要查证。
kqueue中涉及两个系统调用,kqueue()和kevent()
* kqueue() 创建kernel级别的事件队列,并返回队列的文件描述符
* kevent() 往事件队列中加入订阅事件,或者返回相关的事件数组
kqueue使用的流程一般如下:
* 创建kqueue
* 创建struct kevent变量(注意这里的kevent是结构体类型名),可以通过EV_SET这个宏提供的快捷方式进行创建
* 通过kevent系统调用将创建好的kevent结构体变量加入到kqueue队列中,完成对指定文件描述符的事件的订阅
* 通过kevent系统调用获取满足条件的事件队列,并对每一个事件进行处理
{% highlight c %}
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/event.h>
#include <sys/types.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#define BACKLOG 5 //完成三次握手但没有accept的队列的长度
#define CONCURRENT_MAX 8 //应用层同时可以处理的连接
#define SERVER_PORT 11332
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit"
int client_fds[CONCURRENT_MAX];
struct kevent events[10];//CONCURRENT_MAX + 2
int main (int argc, const char * argv[])
{
char input_msg[BUFFER_SIZE];
char recv_msg[BUFFER_SIZE];
//本地地址
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero),8);
//创建socket
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock_fd == -1) {
perror("socket error");
return 1;
}
//绑定socket
int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
perror("bind error");
return 1;
}
//listen
if (listen(server_sock_fd, BACKLOG) == -1) {
perror("listen error");
return 1;
}
struct timespec timeout = {10,0};
//kqueue
int kq = kqueue();
if (kq == -1) {
perror("创建kqueue出错!\n");
exit(1);
}
struct kevent event_change;
EV_SET(&event_change, STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
EV_SET(&event_change, server_sock_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
while (1) {
int ret = kevent(kq, NULL, 0, events, 10, &timeout);
if (ret < 0) {
printf("kevent 出错!\n");
continue;
}else if(ret == 0){
printf("kenvent 超时!\n");
continue;
}else{
//ret > 0 返回事件放在events中
for (int i = 0; i < ret; i++) {
struct kevent current_event = events[i];
//kevent中的ident就是文件描述符
if (current_event.ident == STDIN_FILENO) {
//标准输入
bzero(input_msg, BUFFER_SIZE);
fgets(input_msg, BUFFER_SIZE, stdin);
//输入 ".quit" 则退出服务器
if (strcmp(input_msg, QUIT_CMD) == 0) {
exit(0);
}
for (int i=0; i<CONCURRENT_MAX; i++) {
if (client_fds[i]!=0) {
send(client_fds[i], input_msg, BUFFER_SIZE, 0);
}
}
}else if(current_event.ident == server_sock_fd){
//有新的连接请求
struct sockaddr_in client_address;
socklen_t address_len;
int client_socket_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
if (client_socket_fd > 0) {
int index = -1;
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == 0) {
index = i;
client_fds[i] = client_socket_fd;
break;
}
}
if (index >= 0) {
EV_SET(&event_change, client_socket_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
printf("新客户端(fd = %d)加入成功 %s:%d \n",client_socket_fd,inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}else{
bzero(input_msg, BUFFER_SIZE);
strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");
send(client_socket_fd, input_msg, BUFFER_SIZE, 0);
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d \n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));
}
}
}else{
//处理某个客户端过来的消息
bzero(recv_msg, BUFFER_SIZE);
long byte_num = recv((int)current_event.ident,recv_msg,BUFFER_SIZE,0);
if (byte_num > 0) {
if (byte_num > BUFFER_SIZE) {
byte_num = BUFFER_SIZE;
}
recv_msg[byte_num] = '\0';
printf("客户端(fd = %d):%s\n",(int)current_event.ident,recv_msg);
}else if(byte_num < 0){
printf("从客户端(fd = %d)接受消息出错.\n",(int)current_event.ident);
}else{
EV_SET(&event_change, current_event.ident, EVFILT_READ, EV_DELETE, 0, 0, NULL);
kevent(kq, &event_change, 1, NULL, 0, NULL);
close((int)current_event.ident);
for (int i = 0; i < CONCURRENT_MAX; i++) {
if (client_fds[i] == (int)current_event.ident) {
client_fds[i] = 0;
break;
}
}
printf("客户端(fd = %d)退出了\n",(int)current_event.ident);
}
}
}
}
}
return 0;
}
{% endhighlight %}
其实kqueue的应用场景非常的广阔,可以监控文件系统中文件的变化(对文件变化的事件可以粒度非常的细,具体可以查看kqueue的手册),监控系统进程的生命周期。GCD的事件处理便是建立在kqueue之上的。
###六.使用Streams
使用Objective-C的一大优点便是面向对象编程,使得逻辑抽象得更加优美,更加符合人类思维。
一开始说过,无论是对于文件的操作或者对于网络的操作,本质上都是IO操作,无非写数据和读数据,可以对这种输入输出进行抽象,抽象成输入流和输出流, **从输入流中读取数据,往输出流中写数据**。
Cocoa中的NSInputStream和NSOutputStream便是输入流和输出流的抽象,它们的实现分别基于CoreFoundation中的CFReadStream和CFWriteStream。
输入输出流对runloop有很好的支持。
NSInputStream和CFReadStream以及NSOutputStream和CFWriteStream之间可以通过 "toll-free bridging"实现无缝的类型转换。
CoreFoundation中的CFStream提供了输入输出流和CFSocket绑定的函数。
这样便可以通过输入输出流和远端进行通信了。
首先通过XCode创建一个Foundation(C的也行,但是你得将`main.c` 改成`main.m`)的命令行项目.
创建一个ChatServer的类,包含一个run的方法。在Cocoa的程序中有一点是和C语言不同的,你无需自己去写一个死循环充当runloop,框架本身就对runloop进行了支持,需要做的就是将事件源加入到当前线程的runloop中,然后启动runloop。
所以在run方法中,创建好用于侦听连接请求的socket,socket有对应的处理连接accept的回调函数,以及把它封装成runloop的输入源,加入到当前runloop。
我们还得从标准输入获取需要发送消息,所以使用了CFFileDescriptor,它是文件描述符的objc的封装,加入了runloop的支持,通过它可以将标准输入以输入源的方法加入到当前runloop,当标准输入缓冲区有数据可读的时候,设置好的回调函数便会被调用。
最后启动runloop。
**ChatServer中的run方法**
{% highlight objc %}
- (BOOL)run:(NSError **)error{
BOOL successful = YES;
CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,
IPPROTO_TCP,
kCFSocketAcceptCallBack,
(CFSocketCallBack)&SocketConnectionAcceptedCallBack,
&socketCtxt);
if (NULL == _socket) {
if (nil != error) {
*error = [[NSError alloc]
initWithDomain:ServerErrorDomain
code:kServerNoSocketsAvailable
userInfo:nil];
}
successful = NO;
}
if(YES == successful) {
// enable address reuse
int yes = 1;
setsockopt(CFSocketGetNative(_socket),
SOL_SOCKET, SO_REUSEADDR,
(void *)&yes, sizeof(yes));
uint8_t packetSize = 128;
setsockopt(CFSocketGetNative(_socket),
SOL_SOCKET, SO_SNDBUF,
(void *)&packetSize, sizeof(packetSize));
setsockopt(CFSocketGetNative(_socket),
SOL_SOCKET, SO_RCVBUF,
(void *)&packetSize, sizeof(packetSize));
struct sockaddr_in addr4;
memset(&addr4, 0, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(CHAT_SERVER_PORT);
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)];
if (kCFSocketSuccess != CFSocketSetAddress(_socket, (CFDataRef)address4)) {
if (error) *error = [[NSError alloc]
initWithDomain:ServerErrorDomain
code:kServerCouldNotBindToIPv4Address
userInfo:nil];
if (_socket) CFRelease(_socket);
_socket = NULL;
successful = NO;
} else {
// now that the binding was successful, we get the port number
NSData *addr = [(NSData *)CFSocketCopyAddress(_socket) autorelease];
memcpy(&addr4, [addr bytes], [addr length]);
self.port = ntohs(addr4.sin_port);
// 将socket 输入源加入到当前的runloop
CFRunLoopRef cfrl = CFRunLoopGetCurrent();
CFRunLoopSourceRef source4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
CFRunLoopAddSource(cfrl, source4, kCFRunLoopDefaultMode);
CFRelease(source4);
//标准输入,当在命令行中输入时,回调函数便会被调用
CFFileDescriptorContext context = {0,self,NULL,NULL,NULL};
CFFileDescriptorRef stdinFDRef = CFFileDescriptorCreate(kCFAllocatorDefault, STDIN_FILENO, true, FileDescriptorCallBack, &context);
CFFileDescriptorEnableCallBacks(stdinFDRef,kCFFileDescriptorReadCallBack);
CFRunLoopSourceRef stdinSource = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, stdinFDRef, 0);
CFRunLoopAddSource(cfrl, stdinSource, kCFRunLoopDefaultMode);
CFRelease(stdinSource);
CFRelease(stdinFDRef);
CFRunLoopRun();
}
}
return successful;
}
{% endhighlight %}
当有客户端连接请求过来时, SocketConnectionAcceptedCallBack这个回调函数会被调用,根据新的全相关的socket,生成输入输出流,并设置输入输出流的delegate方法,将其添加到当前的runloop,这样流中有数据过来的时候,delegate方法会被调用。
**SocketConnectionAcceptedCallBack函数**
{% highlight objc %}
static void SocketConnectionAcceptedCallBack(CFSocketRef socket,
CFSocketCallBackType type,
CFDataRef address,
const void *data, void *info) {
ChatServer *theChatServer = (ChatServer *)info;
if (kCFSocketAcceptCallBack == type) {
// 摘自kCFSocketAcceptCallBack的文档,New connections will be automatically accepted and the callback is called with the data argument being a pointer to a CFSocketNativeHandle of the child socket. This callback is usable only with listening sockets.
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
// create the read and write streams for the connection to the other process
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle,
&readStream, &writeStream);
if(NULL != readStream && NULL != writeStream) {
CFReadStreamSetProperty(readStream,
kCFStreamPropertyShouldCloseNativeSocket,
kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream,
kCFStreamPropertyShouldCloseNativeSocket,
kCFBooleanTrue);
NSInputStream *inputStream = (NSInputStream *)readStream;//toll-free bridging
NSOutputStream *outputStream = (NSOutputStream *)writeStream;//toll-free bridging
inputStream.delegate = theChatServer;
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
outputStream.delegate = theChatServer;
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
Client *aClient = [[Client alloc] init];
aClient.inputStream = inputStream;
aClient.outputStream = outputStream;
aClient.sock_fd = nativeSocketHandle;
[theChatServer.clients setValue:aClient
forKey:[NSString stringWithFormat:@"%d",inputStream]];
NSLog(@"有新客户端(sock_fd=%d)加入",nativeSocketHandle);
} else {
close(nativeSocketHandle);
}
if (readStream) CFRelease(readStream);
if (writeStream) CFRelease(writeStream);
}
}
{% endhighlight %}
当客户端有数据传过来时,相应的NSInputStream的delegate方法被调用
{% highlight objc %}
- (void) stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventOpenCompleted: {
break;
}
case NSStreamEventHasBytesAvailable: {
Client *client = [self.clients objectForKey:[NSString stringWithFormat:@"%d",stream]];
NSMutableData *data = [NSMutableData data];
uint8_t *buf = calloc(128, sizeof(uint8_t));
NSUInteger len = 0;
while([(NSInputStream*)stream hasBytesAvailable]) {
len = [(NSInputStream*)stream read:buf maxLength:128];
if(len > 0) {
[data appendBytes:buf length:len];
}
}
free(buf);
if ([data length] == 0) {
//客户端退出
NSLog(@"客户端(sock_fd=%d)退出",client.sock_fd);
[self.clients removeObjectForKey:[NSString stringWithFormat:@"%d",stream]];
close(client.sock_fd);
}else{
NSLog(@"收到客户端(sock_fd=%d)消息:%@",client.sock_fd,[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
}
break;
}
case NSStreamEventHasSpaceAvailable: {
break;
}
case NSStreamEventEndEncountered: {
break;
}
case NSStreamEventErrorOccurred: {
break;
}
default:
break;
}
}
{% endhighlight %}
当在debug窗口中输入内容并回车时,标准输入缓冲区中便有数据了,这个时候回调函数FileDescriptorCallBack将被调用,处理标准输入。
{% highlight objc %}
static void FileDescriptorCallBack(CFFileDescriptorRef f,
CFOptionFlags callBackTypes,
void *info){
int fd = CFFileDescriptorGetNativeDescriptor(f);
ChatServer *theChatServer = (ChatServer *)info;
if (fd == STDIN_FILENO) {
NSData *inputData = [[NSFileHandle fileHandleWithStandardInput] availableData];
NSString *inputString = [[[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"准备发送消息:%@",inputString);
for (Client *client in [theChatServer.clients allValues]) {
[client.outputStream write:[inputData bytes] maxLength:[inputData length]];
}
//处理完数据之后必须重新Enable 回调函数
CFFileDescriptorEnableCallBacks(f,kCFFileDescriptorReadCallBack);
}
}
{% endhighlight %}
+121
View File
@@ -0,0 +1,121 @@
---
layout: post
title: Bonjour
categories:
- Programming
tags:
- iOS
- Objc
---
###一.Bonjour介绍
一般在进行Socket编程或者网络访问的时候,首先需要确认对方网络服务已经开启,且需要知道对方的域名或地址以及端口,然后才可以进行进一步操作。在互联网上好点,网络服务方一般常年开启,且一般IP地址是固定的,另由于DNS服务的存在,只要记住对方的域名便可以。但是在局域网,设备不一定连在上面,即使连上了,服务也不一定开了,每当设备连接到局域网的时候,IP地址一般都是动态分配的,所以情况变的复杂。Bonjour的存在便是苹果为了解决局域网设备间连接麻烦的问题。
直白的说Bonjour就是是一种协议,使得局域网中的计算机可以方便的发布服务,发现服务和连接服务,达到零配置([Zeroconf][link-zeroconf])的目的。
[link-zeroconf]:http://zeroconf.org
Zeroconf Working Group指出要实现零配置网络服务的3个要求:
>+ IP地址
>+ **名字** 到 **IP地址** 的转换(即使没有DNS服务器的情况下)
>+ 发现网络中的服务
对于第一个要求相关系统和设备可以直接支持的,如动态IP地址分配。
第二个要求则可以通过多播(UDP协议向局域网内一组机器发送数据)的方式发送 类似DNS查询的请求,开启着的网络服务收到之后便作出回应,告知自己的名字。
第三个要求则通过DNS-SD来实现
Bonjour一般的工作模式便是:在同一个局域网中,一方开启服务,通过Bonjour接口将这个服务发布,服务搜索方在服务列表中便可以看到对应的设备的名字,选择设备便可以进行连接了。整个过程无需事先知道服务发布方的IP地址和端口号。
我们常用的软件如iTunes的共享,keynote的remote控制或者支持Bonjour协议的打印机都可以看到Bonjour的影子。
###二.Bonjour的实现及使用
从上面的描述可以看出,Bonjour的用途便是在局域网内发布服务和搜索服务。
下面从实现层面讲解Bonjour。
* Multicast DNS Responder(mDNSResponder,开源项目)
* Low-Level Socket Based API(dns_sd.h, The DNS Service Discovery API)
* CoreFoundation(CFNetService/CFNetServiceBroswer)
* Foundation(NSNetService/NSNetServiceBroswer)
一般情况下我们使用Foundation这一层接口就可以了,也是最方便的。
当然服务方在发布服务之前你得先启好网络服务,比如listening socket创建好,且开始侦听某个端口.
**1.发布服务**
{% highlight objc %}
netService = [[[NSNetService alloc] initWithDomain:@""
type:@"_test._tcp"
name:@""
port:port] autorelease];
if(netService != nil) {
[netService scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
netService.delegate = self;
[netService publish];
}
{% endhighlight %}
**2.浏览服务**
+ 创建Service Broswer, 需要指定service type和domain,得和发布服务时候的type对应。还得设置delegate,然后实现其delegate方法,以便发现了服务之后进行处理以及对发现的服务进行获取IP地址和端口的结果进行处理。
{% highlight objc %}
testServiceBrowser = [[NSNetServiceBrowser alloc] init];
testServiceBrowser.delegate = self;
[testServiceBrowser searchForServicesOfType:@"_test._tcp" inDomain:@""];
{% endhighlight %}
+ 实现Service Broswer 的delegate方法,处理服务增加或减少的事件
{% highlight objc %}
//pragma mark NetServiceBroswer Delegate
- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser
didFindService:(NSNetService*)service
moreComing:(BOOL)moreComing {
[netServiceArray addObject:service];
if (!moreComing) {
[serviceTableView reloadData];
}
}
- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser
didRemoveService:(NSNetService*)service
moreComing:(BOOL)moreComing {
[netServiceArray removeObject:service];
if (!moreComing) {
[serviceTableView reloadData];
}
}
{% endhighlight %}
+ 连接服务
上面发现的Net Service是不带IP地址和端口信息的。
从服务列表中选择一个已经发现的服务,进行Resolve,便可以获取服务的详细信息了。
{% highlight objc %}
- (IBAction)connect:(id)sender{
NSUInteger selectedRow = [serviceTableView selectedRow];
NSNetService *selectedServiece = [netServiceArray objectAtIndex:selectedRow];
selectedServiece.delegate = self;
[selectedServiece resolveWithTimeout:5.0];
}
{% endhighlight %}
Resolve成功
{% highlight objc %}
//NSNetService Delegate
- (void)netServiceDidResolveAddress:(NSNetService *)sender{
NSLog(@"service ip:%@ port:%d",sender.address,sender.port);
if ([sender getInputStream:&inputStream outputStream:&outputStream]) {
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[outputStream open];
//发送数据
NSData *helloData = [@"Hello" dataUsingEncoding:NSUTF8StringEncoding];
[outputStream write:[helloData bytes] maxLength:[helloData length]];
}
}
{% endhighlight %}
上面的代码充分利用了输入输出流进行通信。如果你自己的使用socket的连接也是可以的,因为这个时候已经可以获取了对方的IP地址和端口了。
+182
View File
@@ -0,0 +1,182 @@
---
layout: post
title: Mac&iOS之多线程
categories:
- Programming
tags:
- iOS
- Objc
---
* 一.线程概述
* 二.创建线程
* 1.使用NSThread
* 2.使用NSObject
* 3.POSIX Thread
* 三.多线程进阶
* 1.NSOperation & NSOperationQueue
* 2.GCD
* 四.进程间通信
* 1.和主线程的通信
* 2.任意线程间的通信
* 五.RunLoop
---
###一.线程概述
有些程序是一条直线,起点到终点;有些程序是一个圆,不断循环,直到将它切断。直线的如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花一现那样;圆如操作系统,一直运行直到你关机。
一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。Mac和iOS中的程序启动,创建好一个进程的同时,一个线程便开始运行,这个线程叫主线程。主线程在程序中的地位和其他线程不同,它是其他线程最终的父线程,且所有界面的显示操作即AppKit或UIKit的操作必须在主线程进行。
系统中的每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则共用进程的内存空间。每创建一个新的线程,都需要一些内存(如每个线程有自己的Stack空间)和消耗一定的CPU时间。另外当多个线程对同一个资源出现争夺的时候需要注意线程安全问题。
###二.创建线程
创建一个新的线程就是给进程增加了一个执行流,执行流总得有要执行的代码吧,所以新建一个线程需要提供一个函数或者方法作为线程的入口。
####1.使用NSThread
NSThread提供了创建线程的途径,还可以提供了检测当前线程是否是主线程的方法。
使用NSThread创建一个新的线程有两种方式:
* 1.创建一个NSThread的对象,调用其start方法。对于这种方式的NSThread对象的创建,可以使用一个目标对象的方法初始化一个NSThread对象,或者创建一个继承NSThread类的子类,实现其main方法,然后在直接创建这个子类的对象。
* 2.使用 `detachNewThreadSelector:toTarget:withObject:`这个类方法创建一个线程,这个比较直接了,直接使用目标对象的方法作为线程启动入口。
####2.使用NSObject
其实NSObject直接就加入了多线程的支持,允许对象的某个方法在后台运行。如:
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
####3.POSIX Thread
由于Mac和iOS都是基于Darwin系统,Darwin系统的XUN内核,是基于Mach和BSD的,继承了BSD的POSIX接口,所以可以直接使用POSIX线程的相关接口来使用线程。
创建线程的接口为 `pthread_create`,当然在创建之前可以通过相关函数设置好线程的属性。以下为POSIX线程使用简单的例子。
{% highlight c %}
//
// main.c
// pthread
//
// Created by Lu Kejin on 1/27/12.
// Copyright (c) 2012 Taobao.com. All rights reserved.
//
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *pthreadRoutine(void *);
int main (int argc, const char * argv[])
{
pthread_attr_t attr;
pthread_t pthreadID;
int returnVal;
returnVal = pthread_attr_init(&attr);
returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int threadError = pthread_create(&pthreadID, &attr, &pthreadRoutine, NULL);
returnVal = pthread_attr_destroy(&attr);
if (threadError != 0)
{
// Report an error.
}
sleep(10);
return 0;
}
void *pthreadRoutine(void *data){
int count = 0;
while (1) {
printf("count = %d\n",count++);
sleep(1);
}
return NULL;
}
{% endhighlight %}
###三.多线程进阶
####NSOperation&NSOperationQueue
很多时候我们使用多线程,需要控制线程的并发数,毕竟线程也是消耗系统资源的,当程序中同时运行的线程过多时,系统必然变慢。
所以很多时候我们会控制同时运行线程的数目。
NSOperation可以封装我们的操作,然后将创建好的NSOperation对象放到NSOperationQueue中,OperationQueue便开始启动新的线程去执行队列中的操作,OperationQueue的并发度是可以通过如下方式进行设置:
- (void)setMaxConcurrentOperationCount:(NSInteger)count
####GCD
GCD是Grand Central Dispatch的缩写,是一系列的BSD层面的接口,在Mac 10.6 和iOS4.0以后才引入的,且现在NSOperation和NSOperationQueue的多线程的实现就是基于GCD的。目前这个特性也被移植到FreeBSD上了,可以查看libdispatch这个开源项目。
比如一个在UIImageView中显示一个比较大的图片
{% highlight objc %}
dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(imageDownloadQueue, ^{
NSURL *imageURL = [NSURL URLWithString:@"http://test.com/test.png"];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
[imageView setImage:image];//UIKit必须在主线程执行
});
});
{% endhighlight %}
当然,GCD除了处理多线程外还有很多非常好的功能,其建立在强大的kqueue之上,效率也能够得到保障。
###四.线程间通信
线程间通信和进程间通信从本质上讲是相似的。线程间通信就是在进程内的两个执行流之间进行数据的传递,就像两条并行的河流之间挖出了一道单向流动长沟,使得一条河流中的水可以流入另一条河流,物质得到了传递。
**1.performSelect On The Thread**
框架为我们提供了强制在某个线程中执行方法的途径,如果两个非主线程的线程需要相互间通信,可以先将自己的当前线程对象注册到某个全局的对象中去,这样相互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了,由于主线程比较特殊,所以框架直接提供了在出线程执行的方法。
{% highlight objc %}
@interface NSObject (NSThreadPerformAdditions)
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// equivalent to the first method with kCFRunLoopCommonModes
...
@end
{% endhighlight %}
**2.Mach Port**
在苹果的Thread Programming Guide的Run Pool一节的Configuring a Port-Based Input Source
这一段中就有使用Mach Port进行线程间通信的例子。
其实质就是父线程创建一个NSMachPort对象,在创建子线程的时候以参数的方式将其传递给子线程,这样子线程中就可以向这个传过来的NSMachPort对象发送消息,如果想让父线程也可以向子线程发消息的话,那么子线程可以先向父线程发个特殊的消息,传过来的是自己创建的另一个NSMachPort对象,这样父线程便持有了子线程创建的port对象了,可以向这个子线程的port对象发送消息了。
当然各自的port对象需要设置delegate以及schdule到自己所在线程的RunLoop中,这样来了消息之后,处理port消息的delegate方法会被调用,你就可以自己处理消息了。
###五.RunLoop
RunLoop从字面上看是运行循环的意思,这一点也不错,它确实就是一个循环的概念,或者准确的说是线程中的循环。
本文一开始就提到有些程序是一个圈,这个圈本质上就是这里的所谓的RunLoop,就是一个循环,只是这个循环里加入很多特性。
首先循环体的开始需要检测是否有需要处理的事件,如果有则去处理,如果没有则进入睡眠以节省CPU时间。
所以重点便是这个需要处理的事件,在RunLoop中,需要处理的事件分两类,一种是输入源,一种是定时器,定时器好理解就是那些需要定时执行的操作,输入源分三类:performSelector源,基于端口(Mach port)的源,以及自定义的源。编程的时候可以添加自己的源。RunLoop还有一个观察者Observer的概念,可以往RunLoop中加入自己的观察者以便监控着RunLoop的运行过程,CFRunLoop.h中定义了所有观察者的类型:
{% highlight objc %}
enum CFRunLoopActivity {
kCFRunLoopEntry = (1 << 0),
kCFRunLoopBeforeTimers = (1 << 1),
kCFRunLoopBeforeSources = (1 << 2),
kCFRunLoopBeforeWaiting = (1 << 5),
kCFRunLoopAfterWaiting = (1 << 6),
kCFRunLoopExit = (1 << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
typedef enum CFRunLoopActivity CFRunLoopActivity;
{% endhighlight %}
如果你使用过select系统调用写过程序你便可以快速的理解runloop事件源的概念,本质上讲事件源的机制和select一样是一种多路复用IO的实现,在一个线程中我们需要做的事情并不单一,如需要处理定时钟事件,需要处理用户的触控事件,需要接受网络远端发过来的数据,将这些需要做的事情统统注册到事件源中,每一次循环的开始便去检查这些事件源是否有需要处理的数据,有的话则去处理。
拿具体的应用举个例子,NSURLConnection网络数据请求,默认是异步的方式,其实现原理就是创建之后将其作为事件源加入到当前的RunLoop,而等待网络响应以及网络数据接受的过程则在一个新创建的独立的线程中完成,当这个线程处理到某个阶段的时候比如得到对方的响应或者接受完了网络数据之后便通知之前的线程去执行其相关的delegate方法。所以在Cocoa中经常看到`scheduleInRunLoop:forMode:`这样的方法,这个便是将其加入到事件源中,当检测到某个事件发生的时候,相关的delegate方法便被调用。对于CoreFoundation这一层而言,通常的模式是创建输入源,然后将输入源通过`CFRunLoopAddSource`函数加入到RunLoop中,相关事件发生后,相关的回调函数会被调用。如CFSocket的使用。
另外RunLoop中还有一个运行模式的概念,每一个运行循环必然运行在某个模式下,而模式的存在是为了过滤事件源和观察者的,只有那些和当前RunLoop运行模式一致的事件源和观察者才会被激活。
每一个线程都有其对应的RunLoop,但是默认非主线程的RunLoop是没有运行的,需要为RunLoop添加至少一个事件源,然后去run它。一般情况下我们是没有必要去启用线程的RunLoop的,除非你在一个单独的线程中需要长久的检测某个事件。
@@ -0,0 +1,19 @@
---
layout: post
title: 使用Jekyll和Github写博客
categories:
- Life
tags:
- blog
- jekyll
- github
---
周末将博客搬到Github上了,使用了[Jekyll](https://github.com/mojombo/jekyll)以及[Github Page](http://pages.github.com)功能,Github Page直接支持Jekyll。
Jekyll是一个博客生成引擎,可以将markdown写成的博客生成静态的HTML博客。
当然Jekyll提供了一些的辅助工具,支持模板,可以辅助你生成分类和标签页。
[Github Page](http://pages.github.com)是Github的一个非常好的服务,免费,可以绑定域名。
这样彻底摆脱了对Wordpress,摆脱了对PHP环境的依赖,博客使用纯文本的形式也方便备份。
Executable
+7
View File
@@ -0,0 +1,7 @@
---
title: About me
layout: page
comments: yes
---
我是卢克.
+34
View File
@@ -0,0 +1,34 @@
---
title: Categories
layout: page
---
<div id='tag_cloud'>
{% for cat in site.categories %}
<a href="#{{ cat[0] }}" title="{{ cat[0] }}" rel="{{ cat[1].size }}">{{ cat[0] }} ({{ cat[1].size }})</a>
{% endfor %}
</div>
<ul class="listing">
{% for cat in site.categories %}
<li class="listing-seperator" id="{{ cat[0] }}">{{ cat[0] }}</li>
{% for post in cat[1] %}
<li class="listing-item">
<time datetime="{{ post.date | date:"%Y-%m-%d" }}">{{ post.date | date:"%Y-%m-%d" }}</time>
<a href="{{ site.url }}{{ post.url }}" title="{{ post.title }}">{{ post.title }}</a>
</li>
{% endfor %}
{% endfor %}
</ul>
<script src="/media/js/jquery.tagcloud.js" type="text/javascript" charset="utf-8"></script>
<script language="javascript">
$.fn.tagcloud.defaults = {
size: {start: 1, end: 1, unit: 'em'},
color: {start: '#f8e0e6', end: '#ff3333'}
};
$(function () {
$('#tag_cloud a').tagcloud();
});
</script>
+25
View File
@@ -0,0 +1,25 @@
---
---
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title>{{ site.author }}</title>
<link href="{{ site.url }}/feed/" rel="self" />
<link href="http://geeklu.com" />
<lastBuildDate>{{ site.time | date_to_xmlschema }}</lastBuildDate>
<webMaster>[email protected]</webMaster>
{% for post in site.posts limit:10 %}
<item>
<title>{{ post.title | xml_escape }}</title>
<link href="{{ site.url }}{{ post.url }}"/>
<pubDate>{{ post.date | date_to_xmlschema }}</pubDate>
<author>{{ site.author }}</author>
<guid>{{ site.url }}{{ post.id }}</guid>
<content:encoded><![CDATA[{{ post.content }}]]></content:encoded>
</item>
{% endfor %}
</channel>
</rss>
+7
View File
@@ -0,0 +1,7 @@
---
layout: page
title: 留言
comments: yes
---
使用评论留言哦!
Executable
+53
View File
@@ -0,0 +1,53 @@
---
title: Luke's Homepage
layout: page
---
<ul class="listing">
{% for post in paginator.posts %}
{% capture y %}{{post.date | date:"%Y"}}{% endcapture %}
{% if year != y %}
{% assign year = y %}
<li class="listing-seperator">{{ y }}</li>
{% endif %}
<li class="listing-item">
<time datetime="{{ post.date | date:"%Y-%m-%d" }}">{{ post.date | date:"%Y-%m-%d" }}</time>
<a href="{{ post.url }}" title="{{ post.title }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
<div id="post-pagination" class="paginator">
{% if paginator.previous_page %}
{% if paginator.previous_page == 1 %}
<a href="/"><前页</a>
{% else %}
<a href="/page{{paginator.previous_page}}"><前页</a>
{% endif %}
{% else %}
<span class="previous disabled"><前页</span>
{% endif %}
{% if paginator.page == 1 %}
<span class="current-page">1</span>
{% else %}
<a href="/">1</a>
{% endif %}
{% for count in (2..paginator.total_pages) %}
{% if count == paginator.page %}
<span class="current-page">{{count}}</span>
{% else %}
<a href="/page{{count}}">{{count}}</a>
{% endif %}
{% endfor %}
{% if paginator.next_page %}
<a class="next" href="/page{{paginator.next_page}}">后页></a>
{% else %}
<span class="next disabled" >后页></span>
{% endif %}
(共{{ paginator.total_posts }}篇)
</div>
Executable
+8
View File
@@ -0,0 +1,8 @@
---
title: links
layout: page
comments: yes
---
暂无
+62
View File
@@ -0,0 +1,62 @@
.highlight .hll { background-color: #ffffcc }
.highlight { background: #f8f8f8; }
.highlight .c { color: #408080; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #008000; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #BC7A00 } /* Comment.Preproc */
.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */
.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #808080 } /* Generic.Output */
.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0040D0 } /* Generic.Traceback */
.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008000 } /* Keyword.Pseudo */
.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #B00040 } /* Keyword.Type */
.highlight .m { color: #666666 } /* Literal.Number */
.highlight .s { color: #BA2121 } /* Literal.String */
.highlight .na { color: #7D9029 } /* Name.Attribute */
.highlight .nb { color: #008000 } /* Name.Builtin */
.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
.highlight .no { color: #880000 } /* Name.Constant */
.highlight .nd { color: #AA22FF } /* Name.Decorator */
.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0000FF } /* Name.Function */
.highlight .nl { color: #A0A000 } /* Name.Label */
.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #19177C } /* Name.Variable */
.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mf { color: #666666 } /* Literal.Number.Float */
.highlight .mh { color: #666666 } /* Literal.Number.Hex */
.highlight .mi { color: #666666 } /* Literal.Number.Integer */
.highlight .mo { color: #666666 } /* Literal.Number.Oct */
.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
.highlight .sc { color: #BA2121 } /* Literal.String.Char */
.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
.highlight .sx { color: #008000 } /* Literal.String.Other */
.highlight .sr { color: #BB6688 } /* Literal.String.Regex */
.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
.highlight .ss { color: #19177C } /* Literal.String.Symbol */
.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #19177C } /* Name.Variable.Class */
.highlight .vg { color: #19177C } /* Name.Variable.Global */
.highlight .vi { color: #19177C } /* Name.Variable.Instance */
.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
+493
View File
@@ -0,0 +1,493 @@
@import url(http://fonts.googleapis.com/css?family=Galdeano);
@media screen {
.container {
width:auto;
padding:20px;
}
h1 {
font-size:20px;
line-height:30px;
}
.sitename {
font-size:24px;
font-weight:700;
}
}
html {
font-size:100%;
-webkit-text-size-adjust:100%;
-ms-text-size-adjust:100%;
}
a:focus {
outline:5px auto 0;
outline-offset:-2px;
}
a:hover,a:active {
outline:0;
}
img {
max-width:100%;
height:auto;
border:0;
-ms-interpolation-mode:bicubic;
-webkit-border-radius:4px;
-moz-border-radius:4px;
border-radius:4px;
}
p img {
clear:both;
display:block;
margin-left:auto;
margin-right:auto;
border:4px solid #FFF;
-webkit-box-shadow:0 1px 3px #ccc;
-moz-box-shadow:0 1px 3px #ccc;
}
body {
font-family:Galdeano,"Hiragino Sans GB","Microsoft YaHei",Trebuchet,"Trebuchet MS",Tahoma,"Lucida Grande","Lucida Sans Unicode",Verdana,sans-serif;
font-size:15px;
line-height:30px;
color:#555;
background-color:#fff;
margin:0;
}
a {
color:#b9221f;
text-decoration:none;
}
a:hover {
color:#771614;
text-decoration:none;
}
p a {
border-bottom:1px dashed #bbb;
}
.container {
margin-left:auto;
margin-right:auto;
zoom:1px;
width:700px;
}
p {
font-size:15px;
line-height:30px;
color:#555;
margin:0 0 15px;
}
p small {
font-size:13px;
color:#999;
}
.lead {
margin-bottom:30px;
font-size:20px;
font-weight:200;
line-height:45px;
}
h1,h2,h3,h4,h5,h6 {
font-weight:400;
color:#555;
text-rendering:optimizelegibility;
margin:0;
}
h1 small,h2 small,h3 small,h4 small,h5 small,h6 small {
font-weight:400;
color:#999;
}
h1 {
font-size:30px;
line-height:60px;
}
h2 {
font-size:24px;
line-height:60px;
}
h3 {
line-height:45px;
font-size:18px;
}
h6 {
font-size:11px;
color:#999;
text-transform:uppercase;
}
ul,ol {
margin:0 0 15px 25px;
padding:0;
}
ul ul,ul ol,ol ol,ol ul {
margin-bottom:0;
}
ul {
list-style:disc;
}
ol {
list-style:decimal;
}
ul.unstyled {
margin-left:0;
list-style:none;
}
dl {
margin-bottom:30px;
}
dd {
margin-left:15px;
}
hr {
border:0;
border-top:1px solid #e5e5e5;
border-bottom:1px solid #fff;
margin:30px 0;
}
em {
font-style:italic;
}
.muted {
color:#999;
}
abbr {
font-size:90%;
text-transform:uppercase;
border-bottom:1px dotted #ddd;
cursor:help;
}
blockquote {
border-left:5px solid #eee;
margin:0 0 30px;
padding:0 0 0 15px;
}
blockquote p {
margin-bottom:0;
font-size:13px;
font-weight:300;
line-height:25px;
}
blockquote small {
display:block;
line-height:30px;
color:#999;
}
blockquote small:before {
content:'\2014 \00A0';
}
blockquote.pull-right {
float:right;
padding-left:0;
padding-right:15px;
border-left:0;
border-right:5px solid #eee;
}
blockquote.pull-right p,blockquote.pull-right small {
text-align:right;
}
q:before,q:after,blockquote:before,blockquote:after {
content:"";
}
address {
display:block;
margin-bottom:30px;
line-height:30px;
font-style:normal;
}
small {
font-size:100%;
}
cite {
font-style:normal;
}
code,pre {
font-family:Monaco,Menlo,"Courier New",monospace;
font-size:12px;
color:#333;
-webkit-border-radius:3px;
-moz-border-radius:3px;
border-radius:3px;
padding:0 3px 2px;
}
code {
color:#d14;
background-color:#f7f7f9;
border:1px solid #e1e1e8;
padding:3px 4px;
}
pre {
display:block;
font-size:12px;
line-height:20px;
background-color:#f5f5f5;
border:1px solid rgba(0,0,0,0.15);
-webkit-border-radius:4px;
-moz-border-radius:4px;
border-radius:4px;
white-space:pre-wrap;
word-break:break-all;
margin:0 0 15px;
padding:14.5px;
}
pre.prettyprint {
margin-bottom:30px;
background-color:#f7f7f9;
border:1px solid #e1e1e8;
padding:8px;
}
pre code {
background-color:transparent;
padding:0;
border: none;
}
.lit {
color:#195f91;
}
.fun {
color:#dc322f;
}
.str,.atv {
color:#D14;
}
.kwd,.linenums .tag {
color:#1e347b;
}
.typ,.atn,.dec,.var {
color:teal;
}
.pln {
color:#48484c;
}
.prettyprint.linenums {
-webkit-box-shadow:inset 40px 0 0 #fbfbfc,inset 41px 0 0 #ececf0;
-moz-box-shadow:inset 40px 0 0 #fbfbfc,inset 41px 0 0 #ececf0;
box-shadow:inset 40px 0 0 #fbfbfc,inset 41px 0 0 #ececf0;
}
.fade {
-webkit-transition:opacity .15s linear;
-moz-transition:opacity .15s linear;
-ms-transition:opacity .15s linear;
-o-transition:opacity .15s linear;
transition:opacity .15s linear;
opacity:0;
}
.fade.in {
opacity:1px;
}
.collapse {
-webkit-transition:height .35s ease;
-moz-transition:height .35s ease;
-ms-transition:height .35s ease;
-o-transition:height .35s ease;
transition:height .35s ease;
position:relative;
overflow:hidden;
height:0;
}
.collapse.in {
height:auto;
}
.nav {
margin-left:0;
margin-bottom:20px;
list-style:none;
}
.nav>li>a {
display:block;
background-color:#eee;
}
.nav>li>a:hover {
text-decoration:none;
color:#fff;
background-color:#b9221f;
}
.pills {
zoom:1px;
}
.pills>li>a {
margin-right:2px;
line-height:14px;
margin-top:2px;
margin-bottom:2px;
-webkit-border-radius:5px;
-moz-border-radius:5px;
border-radius:5px;
padding:8px 12px;
}
.pager {
margin-left:0;
margin-bottom:30px;
list-style:none;
text-align:center;
zoom:1px;
}
.pager li {
display:inline;
}
.pager a {
display:inline-block;
background-color:#f8f8f6;
border:1px solid #ddd;
-webkit-border-radius:15px;
-moz-border-radius:15px;
border-radius:15px;
padding:5px 14px;
}
.pager a:hover {
text-decoration:none;
background-color:#f5f5f5;
}
.label {
font-size:11.25px;
font-weight:700;
color:#fff;
text-transform:uppercase;
background-color:#999;
-webkit-border-radius:3px;
-moz-border-radius:3px;
border-radius:3px;
padding:1px 3px 2px;
}
.label-important {
background-color:#b94a48;
}
.label-warning {
background-color:#f89406;
}
.label-success {
background-color:#468847;
}
.label-info {
background-color:#3a87ad;
}
.hide {
display:none;
}
.invisible {
visibility:hidden;
}
.sitename {
font-size:40px;
font-weight:700;
}
footer,header,nav,.show {
display:block;
}
a.label:hover,.pills .active>a,.pills .active>a:hover {
color:#fff;
background-color:#b9221f;
}
h1 small,h2 small {
font-size:18px;
}
h3 small,h4 {
font-size:14px;
}
h4,h5,h6,li,dt,dd {
line-height:30px;
}
h4 small,h5 {
font-size:12px;
}
dt,strong {
font-weight:700;
}
.com,.pun,.opn,.clo {
color:#93a1a1;
}
.pills:before,.pills:after,.pager:before,.pager:after {
display:table;
content:"";
}
.pills:after,.pager:after {
clear:both;
}
.pills>li,.pager .previous a,.pull-left {
float:left;
}
.pager .next a,.pull-right {
float:right;
}
+139
View File
@@ -0,0 +1,139 @@
/*------------------------------------
* sext
*
* @author linghua.zhang@me.com
* @link http://lhzhang.com/
* @update 2011-12-20
*
* |/ | (~ (~
* |\.|._)._)
* --------------------------------- */
@import url(http://fonts.googleapis.com/css?family=Galdeano);
@import url(http://fonts.googleapis.com/css?family=Electrolize);
@import url(http://fonts.googleapis.com/css?family=Cuprum);
body{font-size:14px;
margin-left:auto;
margin-right:auto;
font-family:"Galdeano","Hiragino Sans GB","Microsoft YaHei",Trebuchet,"Trebuchet MS",Tahoma,"Lucida Grande","Lucida Sans Unicode",Verdana,sans-serif;color:#000000;background-color:#ffffff;}
*{padding:0;margin: 0;border:none;outline:none;}
a{color:#dd0000;text-decoration:none;}
a:hover{color:#333333;text-decoration:underline;}
hr{margin:.7em 0;border-top:1px dashed #d0d0d0;border-bottom:1px dashed #f9f9f9;}
p{padding:.5em 0;}
li{padding:.2em 0;}
ol,ul{list-style-position:inside;}
ol ul, ul ol, ul ul, ol ol {margin-left:1em;}
pre code{margin:1em 0;font-size:13px;line-height:1.6;display:block;overflow:auto;}
blockquote{display:block;text-align:justify;border-left:4px solid #eeeeee;}
blockquote{margin:1em 0em;padding-left:1.5em;}
blockquote p{margin:0;padding:.5em 0;}
#container{width:600px;margin:2em auto;display:block;padding:0 2em;}
.content{font-size:16px;line-height:24px;}
.content h1{padding:.5em 0;}
.content img{max-width:600px;display:block;margin:4px auto;-webkit-box-shadow:#999 1px 1px 4px;-moz-box-shadow:#999 1px 1px 4px;box-shadow:#999 1px 1px 4px;}
#tag_cloud{margin-bottom: 1em;}
#tag_cloud a:hover{color:#333333 !important;text-decoration:none;}
.listing-item,.listing-seperator{list-style-type:none;}
.listing-item a{
padding:.2em 1em;
}
.listing-item time{color:#999999;}
.listing-seperator{font-weight:bold;}
.meta{text-align:right;font-size:12px;}
header{padding:.5em 0;border-bottom: 1px solid #cccccc;}
nav{font-family:"Electrolize",sans-serif;font-size:1.1em;text-transform:uppercase;float:left;margin-left:-12em;width:9em;text-align:right;}
nav a{display:block;padding:.7em 1em;color:#000000;}
nav a:hover{background-color:#ff0000;text-decoration:none;color:#f9f9f9;-webkit-transition:color .2s linear;}
.post{padding:.5em 0em;}
h1{font-family:"Cuprum","Hiragino Sans GB","Microsoft YaHei",sans-serif;font-size:1.6em;color:#000;}
h2{font-family:"Cuprum","Hiragino Sans GB","Microsoft YaHei",sans-serif;font-size:1.4em;color:#111;}
h3{font-family:"Cuprum","Hiragino Sans GB","Microsoft YaHei",sans-serif;font-size:1.2em;color:#222;}
h4{font-family:"Cuprum","Hiragino Sans GB","Microsoft YaHei",sans-serif;font-size:1em;color:#333;}
pre,code{font-family:Menlo,"Andale Mono",Consolas,"Courier New",Monaco,monospace;font-size:13px;}
code{background-color:#F7F7F7;}
embed,object,iframe{display:block;margin:4px auto;}
.gist{font-size: 12px;line-height:1.6;}
footer{
display:block;
border-top: 1px dashed #CCC;
}
footer small {
font-size: 13px;
color: #999;
}
footer a {
margin: 1px;
border: 1px solid rgba(1,1,1,0);
border-bottom: 1px dashed #BBB;
color: #B9221F;
text-decoration: none;
}
footer a:hover {
color: rgba(250,250,250,1.0) !important;
-webkit-border-radius: 4px;
border: 1px solid rgba(0,0,0,0.45);
text-shadow: rgba(0,0,0,0.6) 0px -1px 0px;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(110,110,110,1.0)), to(rgba(100,100,100,1.0)));
-webkit-box-shadow: rgba(0,0,0,1.0) 0px 0px 2px,rgba(255,255,255,0.15) 0px 1px 1px inset;
text-decoration: none;
}
.label {
font-size:11.25px;
font-weight:700;
color:#fff;
text-transform:uppercase;
background-color:#999;
-webkit-border-radius:3px;
-moz-border-radius:3px;
border-radius:3px;
padding:1px 3px 2px;
}
.label-important {
background-color:#b94a48;
}
.label-warning {
background-color:#f89406;
}
.label-success {
background-color:#468847;
}
.label-info {
background-color:#3a87ad;
}
.paginator {
font: 14px Arial,Helvetica,sans-serif;
color: #AAA;
margin: 20px 0;
line-height: 150%;
display: block;
}
.paginator a,.paginator span{
padding: 0 4px;
margin: 2px;
text-decoration: none;
}
.paginator .current-page {
color: white;
background: #ff0000;
}
.paginator a:hover{
color: white;
background: #83BF73;
}
+1
View File
File diff suppressed because one or more lines are too long
Vendored Executable
+4
View File
File diff suppressed because one or more lines are too long
+80
View File
@@ -0,0 +1,80 @@
(function($) {
$.fn.tagcloud = function(options) {
var opts = $.extend({}, $.fn.tagcloud.defaults, options);
tagWeights = this.map(function(){
return $(this).attr("rel");
});
tagWeights = jQuery.makeArray(tagWeights).sort(compareWeights);
lowest = tagWeights[0];
highest = tagWeights.pop();
range = highest - lowest;
if(range === 0) {range = 1;}
// Sizes
if (opts.size) {
fontIncr = (opts.size.end - opts.size.start)/range;
}
// Colors
if (opts.color) {
colorIncr = colorIncrement (opts.color, range);
}
return this.each(function() {
weighting = $(this).attr("rel") - lowest;
if (opts.size) {
$(this).css({"font-size": opts.size.start + (weighting * fontIncr) + opts.size.unit});
}
if (opts.color) {
$(this).css({"color": tagColor(opts.color, colorIncr, weighting)});
}
});
};
$.fn.tagcloud.defaults = {
size: {start: 14, end: 18, unit: "pt"}
};
// Converts hex to an RGB array
function toRGB (code) {
if (code.length == 4) {
code = jQuery.map(/\w+/.exec(code), function(el) {return el + el; }).join("");
}
hex = /(\w{2})(\w{2})(\w{2})/.exec(code);
return [parseInt(hex[1], 16), parseInt(hex[2], 16), parseInt(hex[3], 16)];
}
// Converts an RGB array to hex
function toHex (ary) {
return "#" + jQuery.map(ary, function(i) {
hex = i.toString(16);
hex = (hex.length == 1) ? "0" + hex : hex;
return hex;
}).join("");
}
function colorIncrement (color, range) {
return jQuery.map(toRGB(color.end), function(n, i) {
return (n - toRGB(color.start)[i])/range;
});
}
function tagColor (color, increment, weighting) {
rgb = jQuery.map(toRGB(color.start), function(n, i) {
ref = Math.round(n + (increment[i] * weighting));
if (ref > 255) {
ref = 255;
} else {
if (ref < 0) {
ref = 0;
}
}
return ref;
});
return toHex(rgb);
}
function compareWeights(a, b)
{
return a - b;
}
})(jQuery);
Executable
+34
View File
@@ -0,0 +1,34 @@
---
title: tags
layout: page
---
<div id='tag_cloud'>
{% for tag in site.tags %}
<a href="#{{ tag[0] }}" title="{{ tag[0] }}" rel="{{ tag[1].size }}">{{ tag[0] }}</a>
{% endfor %}
</div>
<ul class="listing">
{% for tag in site.tags %}
<li class="listing-seperator" id="{{ tag[0] }}">{{ tag[0] }}</li>
{% for post in tag[1] %}
<li class="listing-item">
<time datetime="{{ post.date | date:"%Y-%m-%d" }}">{{ post.date | date:"%Y-%m-%d" }}</time>
<a href="{{ site.url }}{{ post.url }}" title="{{ post.title }}">{{ post.title }}</a>
</li>
{% endfor %}
{% endfor %}
</ul>
<script src="/media/js/jquery.tagcloud.js" type="text/javascript" charset="utf-8"></script>
<script language="javascript">
$.fn.tagcloud.defaults = {
size: {start: 1, end: 1, unit: 'em'},
color: {start: '#f8e0e6', end: '#ff3333'}
};
$(function () {
$('#tag_cloud a').tagcloud();
});
</script>