2011年7月31日 星期日

資訊展之痛徹心扉

資訊展


周末幫人買筆電,很巧碰上台北資訊展,於是就第一次去參加資訊展,成果是...損失慘重 XDD 人很多,看到不少單眼男跟 show girls!迷失了自己以往買 3C 產品的步調。啊,我現在才發現世貿是在台北 101 旁邊呢!


這次購買的目標是 Acer 筆電,原先是想買小筆電 Acer AO753 ,所以調查的 PCHome 的價格,然後就去資訊展逛逛,但是逛到一半改變了主意,往 4830TG 或 3830TG 發展,由於功課沒做好,就在第二家敗了 4830TG 了。回家後看了 PTT 的 NB-shopping 版,就十分痛心 XD 看到有人買 4830TG + 4GBx2 創見記憶體 + 750GB 空間,總價 26000 而已!驚覺自己太依賴過去的經驗,覺得新機在 PCHOME 賣三萬,那大概在資訊廣場用 27000 前後買的就差不多了,萬萬沒想到資訊展就是該用力砍價才對。


越想越傷心啊~ 錢雖然不是最重要的,但是受傷的心靈再次地提醒我:『市場調查一定要做好!千萬不要擺入過多的經驗。』我覺得這次買貴的主因是...忘了場合是資訊展,在資訊展就該用力砍價,以及要確實做好市場調查,每一間有買的一定要去問問,多花一點時間可以省到千元也是很值得的!


附上 4830TG 的美照 XD 還滿喜歡這款色調:


4810TG-01 4810TG-03 4810TG-05


Acer 筆電系列,選擇 48 開頭是因為有光碟機,若不在意可以選 38 開頭的,沒有光碟機可以輕 350g 左右吧!


2011年7月27日 星期三

[Linux] 安裝單機版 Hadoop 0.20.203 + HBase 0.90.3 @ Ubuntu 10.04.2 64Bit Server

前幾天又安裝 Hadoop 一次,查了一下,其實上一次也才九個月左右 :P 有這樣的感觸,實在是個人用到 Hadoop 的機會不高 Orz 所以每次都重裝一次,裝完沒多久又砍掉,這次安裝主要是避免新手的我,不小心玩壞同事的機器


過去的安裝紀錄:



整理一下,才發現第一次接觸 Hadoop 是 2009 年的夏天,那篇設定說穿了就是為了筆記做了啥設定,啥都不懂就跑去比賽,可還是初賽那一周才開始用 Hadoop 的說。回想起來還滿高興那時不懂 Hadoop/HBase 的,所以沒有被其中的功能給侷限住 XD 反而還是很盡情地享受資料設計,把想要的東西套用在 Hadoop 的架構上。例如做一個跑在 Hadoop 上的 Merge Sort 等,如果知道 Hadoop 的功能或架構,大概會認為有點脫褲子放屁 Orz 至於上一次安裝 Hadoop 則是架設在異質環境,用了 FreeBSD 32bit/64bit 和 Linux 64bit 環境,算第一次架了那麼多台,盡管只有五台啦,那時是學弟有跑 Hadoop 的需求,就是看看在系上工作站架看看 XD 但最後還是選擇使用 國網中心 Hadoop 公用實驗叢集,申請一下就有十多台機器可以用囉!


這次安裝 Hadoop 採用預設設定,在執行 wordcount 時會出現:


Task Id : attempt_#############_0001_r_000000_0, Status : FAILED
Error: java.lang.NullPointerException
        at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:768)
        at org.apache.hadoop.mapred.ReduceTask$ReduceCopier$GetMapEventsThread.getMapCompletionEvents(ReduceTask.java:2900)
        at org.apache.hadoop.mapred.ReduceTask$ReduceCopier$GetMapEventsThread.run(ReduceTask.java:2820)


猜測應該是機器上有多張網卡或是支援 IPv4 和 IPv6 的關係吧?總之,預設的使用情境是 Pseudo-Distributed 的,所以一堆都填 localhost ,只要把它改成一個自訂的,再到 /etc/hosts 設定一個 IP (127.0.0.1) 即可解決。


總之,又再架了一次了 Orz 說真的,這陣子總覺得雲端熱潮已經結束了,周邊也很少人談論起 Hadoop/HBase 等事,或許就像 MySQL 已經很融入 Web development 環境,大家都覺得很直觀而不用多談?還是說,其實需要 Hadoop/HBase 的環境真的不多呢?除此之外,也有感無招勝有招啊,看了很多新東西,表面上好像增廣見聞,實際上卻容易越來越局限想法。


其他資訊:


從 ubuntu-10.04.2-server-amd64.iso 光碟安裝,並使用 hadoop 當作使用者帳號:


hadoop@hadoop:~$ uname -a
Linux hadoop 2.6.32-28-server #55-Ubuntu SMP Mon Jan 10 23:57:16 UTC 2011 x86_64 GNU/Linux


設定自動登入:


hadoop@hadoop:~$ ssh-keygen -t rsa -P '' && cat $HOME/.ssh/id_rsa.pub >> $HOME/.ssh/authorized_keys
hadoop@hadoop:~$ ssh localhost
The authenticity of host 'localhost (::1)' can't be established.
RSA key fingerprint is ##:##:##:##:##:##:##:##:##:##:##:##:##:##:##:##.
Are you sure you want to continue connecting (yes/no)? yes


下載軟體 jdk-6u26-linux-x64.bin / hadoop-0.20.203.0 / hbase-0.90.3:


hadoop@pc:~$ wget http://download.oracle.com/otn-pub/java/jdk/6u26-b03/jdk-6u26-linux-x64.bin
hadoop@pc:~$ wget http://apache.cdpa.nsysu.edu.tw//hadoop/common/hadoop-0.20.203.0/hadoop-0.20.203.0rc1.tar.gz
hadoop@pc:~$ wget http://apache.stu.edu.tw//hbase/hbase-0.90.3/hbase-0.90.3.tar.gz


目錄狀態(解壓縮後,擺在家目錄,tarball 只是用來存放原始壓縮檔):


hadoop@pc:~$ ls && echo 'tarball:' && ls tarball/
hadoop-0.20.203.0  hbase-0.90.3  jdk1.6.0_26  tarball
tarball:
        hadoop-0.20.203.0rc1.tar.gz  hbase-0.90.3.tar.gz  jdk-6u26-linux-x64.bin


Hadoop 設定檔:


hadoop@pc:~$ vim hadoop-0.20.203.0/conf/hadoop-env.sh
export JAVA_HOME=$HOME/jdk1.6.0_26


hadoop@pc:~$ vim hadoop-0.20.203.0/conf/core-site.xml
<property>
  <name>fs.default.name</name>
  <value>hdfs://mycloud:9000</value>
  <description>The name of the default file system.  A URI whose
  scheme and authority determine the FileSystem implementation.  The
  uri's scheme determines the config property (fs.SCHEME.impl) naming
  the FileSystem implementation class.  The uri's authority is used to
  determine the host, port, etc. for a filesystem.</description>
</property>
<property>
  <name>dfs.replication</name>
  <value>1</value>
  <description>Default block replication.
  The actual number of replications can be specified when the file is created.
  The default is used if replication is not specified in create time.
  </description>
</property>


hadoop@pc:~$ vim hadoop-0.20.203.0/conf/mapred-site.xml
<property>
  <name>mapred.job.tracker</name>
  <value>mycloud:9001</value>
  <description>The host and port that the MapReduce job tracker runs
  at.  If "local", then jobs are run in-process as a single map
  and reduce task.
  </description>
</property>


hadoop@pc:~$ vim hadoop-0.20.203.0/conf/master
mycloud


hadoop@pc:~$ vim hadoop-0.20.203.0/conf/slaves
mycloud


hadoop@pc:~$ sudo vim /etc/hostname
mycloud


hadoop@pc:~$ sudo vim /etc/hosts
127.0.0.1 mycloud


hadoop@pc:~$ vim ~/.bashrc
PATH=$HOME/hadoop-0.20.203.0/bin:$HOME/hbase-0.90.3/bin:$HOME/jdk1.6.0_26/bin:${PATH}
hadoop@pc:~$ sudo reboot


初始化 Hadoop:


hadoop@mycloud:~$ hadoop namenode -format
INFO namenode.NameNode: STARTUP_MSG: 
/************************************************************
STARTUP_MSG: Starting NameNode
STARTUP_MSG:   host = mycloud/127.0.0.1
STARTUP_MSG:   args = [-format]
STARTUP_MSG:   version = 0.20.203.0
STARTUP_MSG:   build = http://svn.apache.org/repos/asf/hadoop/common/branches/branch-0.20-security-203 -r 1099333; compiled by 'oom' on Wed May  4 07:57:50 PDT 2011
************************************************************/
INFO util.GSet: VM type       = 64-bit
INFO util.GSet: 2% max memory = 19.33375 MB
INFO util.GSet: capacity      = 2^21 = 2097152 entries
INFO util.GSet: recommended=2097152, actual=2097152
INFO namenode.FSNamesystem: fsOwner=hadoop
INFO namenode.FSNamesystem: supergroup=supergroup
INFO namenode.FSNamesystem: isPermissionEnabled=true
INFO namenode.FSNamesystem: dfs.block.invalidate.limit=100
INFO namenode.FSNamesystem: isAccessTokenEnabled=false accessKeyUpdateInterval=0 min(s), accessTokenLifetime=0 min(s)
INFO namenode.NameNode: Caching file names occuring more than 10 times 
INFO common.Storage: Image file of size 112 saved in 0 seconds.
INFO common.Storage: Storage directory /tmp/hadoop-hadoop/dfs/name has been successfully formatted.
INFO namenode.NameNode: SHUTDOWN_MSG: 
/************************************************************
SHUTDOWN_MSG: Shutting down NameNode at mycloud/127.0.0.1
************************************************************/


啟動 Hadoop 及相關檢查方式: 


hadoop@mycloud:~$ start-all.sh 
starting namenode, logging to /home/hadoop/hadoop-0.20.203.0/bin/../logs/hadoop-hadoop-namenode-mycloud.out
mycloud: starting datanode, logging to /home/hadoop/hadoop-0.20.203.0/bin/../logs/hadoop-hadoop-datanode-mycloud.out
mycloud: starting secondarynamenode, logging to /home/hadoop/hadoop-0.20.203.0/bin/../logs/hadoop-hadoop-secondarynamenode-mycloud.out
starting jobtracker, logging to /home/hadoop/hadoop-0.20.203.0/bin/../logs/hadoop-hadoop-jobtracker-mycloud.out
mycloud: starting tasktracker, logging to /home/hadoop/hadoop-0.20.203.0/bin/../logs/hadoop-hadoop-tasktracker-mycloud.out


hadoop@mycloud:~$ jps
984 NameNode
1526 TaskTracker
1626 Jps
1368 JobTracker
1139 DataNode
1303 SecondaryNameNode


hadoop@mycloud:~$ hadoop dfsadmin -report
Configured Capacity: 81232019456 (75.65 GB)
Present Capacity: 75465768975 (70.28 GB)
DFS Remaining: 75465744384 (70.28 GB)
DFS Used: 24591 (24.01 KB)
DFS Used%: 0%
Under replicated blocks: 1
Blocks with corrupt replicas: 0
Missing blocks: 0

------------------------------------------------
Datanodes available: 1 (1 total, 0 dead)
Name: 127.0.0.1:50010
Decommission Status : Normal
Configured Capacity: 81232019456 (75.65 GB)
DFS Used: 24591 (24.01 KB)
Non DFS Used: 5766250481 (5.37 GB)
DFS Remaining: 75465744384(70.28 GB)
DFS Used%: 0%
DFS Remaining%: 92.9%
Last contact: ##########


hadoop@mycloud:~$ netstat -plten | grep java


使用 wordcount 測試:


hadoop@mycloud:~$ hadoop fs -mkdir in
hadoop@mycloud:~$ hadoop fs -put ~/hadoop-0.20.203.0/README.txt in
hadoop@mycloud:~$ hadoop jar ~/hadoop-0.20.203.0/hadoop-examples-0.20.203.0.jar wordcount in out
INFO input.FileInputFormat: Total input paths to process : 1
INFO mapred.JobClient: Running job: job_####_0001
INFO mapred.JobClient:  map 0% reduce 0%
INFO mapred.JobClient:  map 100% reduce 0%
INFO mapred.JobClient:  map 100% reduce 100%
INFO mapred.JobClient: Job complete: job_####_0001
INFO mapred.JobClient: Counters: 25
INFO mapred.JobClient:   Job Counters 
INFO mapred.JobClient:     Launched reduce tasks=1
INFO mapred.JobClient:     SLOTS_MILLIS_MAPS=14829
INFO mapred.JobClient:     Total time spent by all reduces waiting after reserving slots (ms)=0
INFO mapred.JobClient:     Total time spent by all maps waiting after reserving slots (ms)=0
INFO mapred.JobClient:     Launched map tasks=1
INFO mapred.JobClient:     Data-local map tasks=1
INFO mapred.JobClient:     SLOTS_MILLIS_REDUCES=10740
INFO mapred.JobClient:   File Output Format Counters 
INFO mapred.JobClient:     Bytes Written=1306
INFO mapred.JobClient:   FileSystemCounters
INFO mapred.JobClient:     FILE_BYTES_READ=1836
INFO mapred.JobClient:     HDFS_BYTES_READ=1476
INFO mapred.JobClient:     FILE_BYTES_WRITTEN=45881
INFO mapred.JobClient:     HDFS_BYTES_WRITTEN=1306
INFO mapred.JobClient:   File Input Format Counters 
INFO mapred.JobClient:     Bytes Read=1366
INFO mapred.JobClient:   Map-Reduce Framework
INFO mapred.JobClient:     Reduce input groups=131
INFO mapred.JobClient:     Map output materialized bytes=1836
INFO mapred.JobClient:     Combine output records=131
INFO mapred.JobClient:     Map input records=31
INFO mapred.JobClient:     Reduce shuffle bytes=1836
INFO mapred.JobClient:     Reduce output records=131
INFO mapred.JobClient:     Spilled Records=262
INFO mapred.JobClient:     Map output bytes=2055
INFO mapred.JobClient:     Combine input records=179
INFO mapred.JobClient:     Map output records=179
INFO mapred.JobClient:     SPLIT_RAW_BYTES=110
INFO mapred.JobClient:     Reduce input records=131


hadoop@mycloud:~$ hadoop fs -lsr 
drwxr-xr-x   - hadoop supergroup          0 /user/hadoop/in
-rw-r--r--   3 hadoop supergroup       1366 /user/hadoop/in/README.txt
drwxr-xr-x   - hadoop supergroup          0 /user/hadoop/out
-rw-r--r--   3 hadoop supergroup          0 /user/hadoop/out/_SUCCESS
drwxr-xr-x   - hadoop supergroup          0 /user/hadoop/out/_logs
drwxr-xr-x   - hadoop supergroup          0 /user/hadoop/out/_logs/history
-rw-r--r--   3 hadoop supergroup      10522 /user/hadoop/out/_logs/history/job_####_0001_####_hadoop_word+count
-rw-r--r--   3 hadoop supergroup      19894 /user/hadoop/out/_logs/history/job_####_0001_conf.xml
-rw-r--r--   3 hadoop supergroup       1306 /user/hadoop/out/part-r-00000


 hadoop@mycloud:~$ hadoop fs -cat out/p*
BIS),  1
(ECCN)  1
(TSU)   1
(see    1
5D002.C.1,      1
...
...


設定 HBase (http://hbase.apache.org/book/quickstart.html):


hadoop@mycloud:~$ mkdir /home/hadoop/hStoreDir
hadoop@mycloud:~$ vim hbase-0.90.3/conf/hbase-env.sh
export JAVA_HOME=$HOME/jdk1.6.0_26
hadoop@mycloud:~$ vim hbase-0.90.3/conf/hbase-site.xml
<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>file:///home/hadoop/hStoreDir/hbase</value>
  </property>
</configuration>


hadoop@mycloud:~$ start-hbase.sh 
starting master, logging to /home/hadoop/hbase-0.90.3/bin/../logs/hbase-hadoop-master-mycloud.out


hadoop@mycloud:~$ hbase shell
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 0.90.3, r1100350, ### ###  # ##:##:## PDT 2011

hbase(main):001:0> 


2011年7月25日 星期一

[PHP] 快速縮網址程式碼 (TinyURL API)

想說試用一下縮網址的功能,就找了 tinyurl.com 來測試。


一開始寫了冗長的 curl 程式碼:


<?php  
        $ch = curl_init();
        curl_setopt( $ch , CURLOPT_URL , 'http://tinyurl.com/create.php' );
        curl_setopt( $ch , CURLOPT_RETURNTRANSFER , true );
        curl_setopt( $ch , CURLOPT_POST , true );
        curl_setopt( $ch , CURLOPT_POSTFIELDS , array(
                'url' => 'http://www.google.com' ,
                'submit' => 'Make TinyURL!'
        ) );
        curl_setopt( $ch , CURLOPT_HTTPHEADER , array('Expect:') );
        echo curl_exec( $ch );
        curl_close( $ch );
?>


正準備用 preg 來撈資料時,看到一段訊息:


/////////////////////////////////////////////////////////////////////////////////
///// DON'T SCREEN-SCRAPE, PLEASE CONTACT US TO FIND OUT HOW TO USE OUR API /////
/////////////////////////////////////////////////////////////////////////////////


我才開使用 tinyurl.com + api 關鍵字問 Google 得知更簡單的用法:


<?php
        $url = 'http://www.google.com';
        echo file_get_contents( 'http://tinyurl.com/api-create.php?url='.urlencode($url) );
?>


有 API 不用,這樣對嗎 :p


2011年7月24日 星期日

Android 平民機是否快來臨?


來源:http://www.android.com/media/


今天跟許青蛙吃飯碰面,好像兩年沒碰面了?聊到了 Android 手機。經由周邊人士的使用心得,在智慧手機或行動平台方面,iOS (iPhone/iPad) 的使用者體驗還是遠高於 Android 系統,也就是說有錢人還是比較肯花錢敗一支 iPhone 手機,雖然 CP 值沒有 Android 高,但是文化潮流驅使大家比較想擁有 iOS 設備,甚至有的一開始用 Android 手機的,在接觸 iOS 設備後,就被拉走了。這是 OS 的設計發展、時程關係,整體上 iOS 還是比 Android 成熟許多,並且在封閉系統中,可以將硬體效能使用得更一致更好,軟硬體開發更有效率,一來以往,就會發現 Android 的缺陷,例如開發工具沒有制式美化的 UI 架構等,以至於產出的 app 都是很簡單的畫面,那種感覺就像早期寫 HTML 時,沒有美化的 TABLE 列表,而 iOS 就像有官方制式 CSS 套版,隨便做都比較好看。


目前的近況還是把 iOS 跟 Android 定位在智慧手機、比較有錢的人才會買的,雖然市佔率一直上升,但還沒到八、九成民眾都使用的階段。然而 Android 手機的價格一直往下掉了,現在就可以看到空機 4000 元左右(去外頭通訊行,可以用更低的價買到),那代表在不遠的將來(?)就會存在綁最低方案的 0 元的 Android 手機,就像中華電信月租費 183 元綁 18 or 24 個月!


這樣推估的方式是基於月租費 183元 (綁約18或24個月) 的 0 元手機,大概可以在一些賣場上看到空機賣 2000~2500 元左右,所以 Android 假設跌到這個價錢,就即將進入高市佔率的時代了! 如果綁上網方案(mCool/mPro),那可能性又增加了。


時代變遷:


單色手機 -> 彩色手機/和弦鈴聲 -> 手機拍照/大螢幕 -> 智慧手機/上網功能 (Android 手機?)


雖然 Android 進步要追到 iOS 還有段距離,但可以看到他有不斷地增加架構、運行效能等,並搭配硬體的升級,在不久的將來,應該更可以達到以前四、五年前 Nokia/Motorola 的時代,人人都可以擁有的大眾機。近期的問題大概是 Android 專利問題 Orz 希望能夠更快、更妥善地解決。


2011年7月19日 星期二

小筆電的效能不該只看數字

跟一位在 Acer 工作的同學閒聊,跟他說前幾天我接觸了一台 ASUS EEEPC 的小筆電,只是一台要價快兩萬,使用後覺得效能還不錯,問看看他的意見,結果就被推薦一款 Acer 小筆電,要價不到一萬五,並且說 Acer 這台小筆電是採用 Intel® Celeron® 處理器,效能應該比我指的那台還好,因為 ASUS 那台是 Intel® Atom™ 處理器。只是我很不解的是前者 Intel® Atom™ 處理器 D525 (1.8GHz) 且 2 cores, 4 threads 的,總覺得比後者 Intel® Celeron® 處理器 U3600 (1.2GHz) 之 2 cores, 2 threads 來的好吧?


後來在網路上也找到評論,大部分都說 U3600 比 D252 好,於是終於找到一個網站還滿豐富的 cpu benchmarks 資料:


PassMark - CPU Benchmarks - List of Benchmarked CPUs


可以查到 U3600 的分數比 D252 來的高 XD 算是稍微有點數據來證明,只是嚴格的話應該還是要去考量測試的方式及環境等等。


經過這次的討論,讓我想起計算機組織的教授曾提過的事,當你買一台車的時候,不太會用多少馬力來評估,那為何要用 CPU 的時脈來評估效能呢?並且查看了 spec 後,覺得 CPU 的規劃的確都很不一樣:


Intel® Atom™ processor D525 (1M Cache, 1.80 GHz)


Max TDP:13 W


Intel® Celeron® Processor U3600 (2M Cache, 1.20 GHz)


Max TDP:18 W


Atom 應該是著重在省電吧?還有超省電的:


Intel® Atom™ Processor N270 (512K Cache, 1.60 GHz, 533 MHz FSB)


Max TDP:2.5 W


至於要買哪一台比較好,大概還是要試用過才知道 :P 現在對 ASUS 那台算用過,所以稍微有好感,雖然真的貴很多 XD 至於兩者 CPU 差了 5W 的部分,不曉得是不是可以用 W=IV 公式來計算可以多撐多久?只是省電又不只 CPU 的問題 XD 看看別人的實測資料吧:


Nvidia ION 的 Asus 1215N 筆電評測


一台高階小筆電--宏碁AspireOne AO753小筆電開箱  (可以找到一些大陸的測試,但在此就不列出了)


2011年7月17日 星期日

Android 開發筆記 - 設定 Windows 之 Android NDK (Native Development Kit) 開發環境

之前的學習環境都是 Ubuntu 桌機為主,無聊在 ASUS Eee PC 1215N - Intel(R) Atom(TM) CPU D525 / 1.80GHz / 4GB RAM / Windows 7 32-bit 上,安裝 Eclipse 和 ADT 後,發現單獨啟動 Android 2.3.3 模擬器竟然只要 5 分鐘左右耶,這真是神奇了!(這個速度對這類裝備已經算很驚奇了),隨後我便開始著手看看在 Windows 使用 Android NDK 的環境了。


關於 NDK 的使用,採用 Cgywin 環境,如此一來就可以透過 ndk-build 進行編譯的動作了。


安裝項目:



  • Java Platform (JDK) - jdk-6u26-windows-i586.exe (76.8MB)

  • Eclipse Classic - eclipse-SDK-3.7-win32.zip (174MB)

  • Android SDK - installer_r12-windows.exe (34.8MB)

  • Android NDK - android-ndk-r6-windows.zip (64.5MB)

  • Cgywin - 安裝完大小約 225MB


    • 來源選 ftp://ftp.ntu.edu.tw (不行再改用 http://ftp.ntu.edu.tw)

    • 安裝項目:


      • 必要項目 Devel 分類:


        • gcc: C compiler upgrade helper

        • gcc-core: C compiler

        • gcc-g++: C++ compiler

        • gcc-mingw-core: Mingw32 support headers and libraries for GCC

        • gcc-mingw-g++: Mingw32 support headers and libraries for GCC C

        • make: The GNU version of the 'make' utility

        • mingw-runtime: MinGW Runtime



      • 非必要:

        • Net > openssh: The OpenSSH server and client programs

        • Editors > vim: ViIMproved - enhanced vi editor

        • Utils > screen: Run separate screens on a single terminal





    • 小問題:

      • 路徑上有空白的問題

      • 以 Eclipse 的 workspace 來說,預設位置是 C:\Users\username\workspace ,但在 cgywin 上也可以從 c:\Documents and Settings\username\workspace 進入,但從後者來說,路徑上有空白將導致 ndk-build 過程會出錯,請避開使用,若使用者帳號有中文或空白的話,那也盡量把 workspace 移去其他地方吧






上述軟體在 Windows 上的安裝路徑:


C:\Program Files\Java
C:\cygwin
C:\eclipse
C:\Android\android-sdk
C:\Android\android-ndk-r6


隨後在 Eclipse 建立一個 Project,此例為 MyNDK:


Project name: MyNDK
Build Target: Android 2.3.3/Android Open Source Project/2.3.3/10
Application name: MyNDK
Package name: com.example.ndk
Create Activity: MyNDK
Min SDK Version: 3


其他細節請參考 Android 開發教學筆記 - 使用 Android NDK (Native Development Kit) ,除此之外要留意該篇文章的軟體版本或路徑是不一樣的,僅供參考。


透過 Cygwin 測試 ndk-build:


user@pc ~
$ cd /cygdrive/c/Users/username/workspace/MyNDK/jni

user@pc /cygdrive/c/Users/username/workspace/MyNDK/jni
$ ls
Android.mk  my-jni.c

user@pc /cygdrive/c/Users/username/workspace/MyNDK/jni
$ cat Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := my-jni
LOCAL_SRC_FILES := my-jni.c
include $(BUILD_SHARED_LIBRARY)

user@pc /cygdrive/c/Users/username/workspace/MyNDK/jni
$ cat my-jni.c
#include <string.h>
#include <jni.h>
jstring Java_com_example_ndk_MyNDK_stringFromJNI( JNIEnv* env, jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from My JNI !");
}

user@pc /cygdrive/c/Users/username/workspace/MyNDK/jni
$ cd ..

user@pc /cygdrive/c/Users/username/workspace/MyNDK
$ /cygdrive/c/Android/android-ndk-r6/ndk-build
Compile thumb  : my-jni <= my-jni.c
SharedLibrary  : libmy-jni.so
Install        : libmy-jni.so => libs/armeabi/libmy-jni.so


接著可以設定 Eclipse 讓它可以自動編譯 JNI 的部分,在 Project > Properties > New > Program :


Location: C:\cygwin\bin\bash.exe
Working Directory: ${workspace_loc:${project_path}} (不設定也行,在此沒用到)
Arguments: --login -c "cd /cygdrive/c/Users/username/workspace/${project_path} && /cygdrive/c/Android/android-ndk-r6/ndk-build"


ndk-build-eclipse-cygwin


別忘了設定 Build options,例如 Run the builder 裡勾選 After a clean / During a manual builds 等,就可以透過 clean 來啟動 ndk build 動作。


如此一來,就可以透過 cgywin 裡頭,透過 vim 編輯器撰寫 jni 的程式部分,也可以在裡面呼叫 ndk-build 來測試,最後再切換到 Eclipse 以便測試整合囉。關於這次的 Windows Android NDK 的使用,原先以為多安裝個 cygwin 很麻煩,但仔細想一下我在 Ubuntu 上的使用,一樣也是透過開啟一個 terminal 去編輯 jni 的部分,那轉換在 Windows 上的動作,其實也沒有比較多。


相關文章:



2011年7月14日 星期四

Android 開發筆記 - 使用 NDK / JNI 實作從底層呼叫上層 (C call Java)

一般使用 JNI 的情境,不外呼從 Java 呼叫 C ,接著在 C (底層)運算完後,把數值透過 return 的方式傳回給 Java (上層)端,這在官方 NDK 教學或是之前的筆記都可以看到簡單的範例:Android 開發教學筆記 - 使用 Android NDK (Native Development Kit)。然而,如果要從 Native C 去呼叫 Java function 的話,就不是那麼直觀,例如在 Unix 系統上的程式開發,則是需取得一個門牌號碼(process id),接著才跟他溝通,或是直接透過執行新的程式,去指定運行某個 function 的這種架構等。在此使用的環境為 Ubuntu 10.04 desktop 64-bit,提供簡易 C call Java 的範例。


一般 C call Java 的話,則需要建立一個 Java 運行的 JVM 通道,接著才開始找尋物件、函數並開始運作:


@ c_to_java.c


#include <stdio.h>
#include <stdlib.h>
#include <jni.h>

int main()
{
JNIEnv *env;
JavaVM *jvm;

JavaVMInitArgs vm_args;
JavaVMOption options[1];

// launch Jvm
options[0].optionString = "-Djava.class.path=."; // add user classes
vm_args.version = JNI_VERSION_1_6; //JDK version.
vm_args.options = options;
vm_args.nOptions = 1;

if( JNI_CreateJavaVM(&jvm, (void*) &env, &vm_args) < 0 )
{
fprintf( stderr , "Launch JVM Error\n" );
exit(1);
}

// find the obj & method
jclass my_class;
jmethodID my_main;

if( !( my_class = (*env)->FindClass( env, "MyJavaClass") ) )
{
fprintf( stderr , "'Class' Not Found\n" );
exit(1);
}

if( !( my_main = (*env)->GetStaticMethodID( env, my_class , "main" ,  "([Ljava/lang/String;)V" ) ) )
fprintf( stderr , "'main' Not Found\n" );
else// Call main function
(*env)->CallStaticVoidMethod( env, my_class, my_main, NULL);

// finish
(*jvm)->DestroyJavaVM(jvm);

return 0;
}


@ MyJavaClass.java


class MyJavaClass
{
public static void main( String []arg)
{
System.out.println("Hello World");
}
}


@ Makefile


CC=gcc
INCLUDE=-I/usr/lib/jvm/java-6-sun/include/ -I/usr/lib/jvm/java-6-sun/include/linux/
LIB=/usr/lib/jvm/java-6-sun/jre/lib/amd64/server/libjvm.so

all:
        javac MyJavaClass.java
        $(CC) $(INCLUDE) $(LIB) c_to_java.c     
clean:
        rm -f ./a.out  ./*.class


@  /etc/ld.so.conf.d/jvm.conf 


/usr/lib/jvm/java-6-sun/jre/lib
/usr/lib/jvm/java-6-sun/jre/lib/amd64
/usr/lib/jvm/java-6-sun/jre/lib/amd64/server


執行結果:


$ make
$ ./a.out 
Hello World


由於我是在 Ubuntu 10.04 desktop 64-bit 環境,安裝 Sun Java6,所以 gcc 編譯要透過 -I 指定 header file 和 libjvm.so 檔案外,在執行 a.out 的時候也會出現找不到 share library 的情,這時候要去設定 /etc/ld.so.conf,在此是透過建立 /etc/ld.so.conf.d/jvm.conf 並再用 sudo ldconfig -v 進行 loading ,之後在執行 a.out 就不會出錯了


接著是 Android 端之 JNI 從底層 C 呼叫上層 Java 端的部分,稍稍不一樣,整個程式運作流程:



  1. 啟動 Android app 時,進入程式內的 OnCreate 時,將本身 app 的資訊丟進 C 底層,並使用全域變數紀錄。

  2. 由於 C 帶有 Java 端相關資訊,因此 C 可以呼叫 Java 端的相關函數,完成了底層往上層呼叫的動作。


建構於Android 開發教學筆記 - 使用 Android NDK (Native Development Kit) 的範例:


請留意 NDK 版本,此例是用 android-ndk-r5c ,用新版的話,相關路徑要記得更新


目錄結構:


~/android-ndk-r5c
~/workspace/MyNDK
~/workspace/MyNDK/jni
~/workspace/MyNDK/jni/my-jni.c
~/workspace/MyNDK/jni/Android.mk
~/workspace/MyNDK/jni/Makefile  (非必要)


@ MyNDK.java


package com.example.ndk;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import java.lang.ref.WeakReference;

public class MyNDK extends Activity {
    /** Called when the activity is first created. */
static int count;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        TextView tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv);
        count = 0;
       
        initMyJNI(new WeakReference<MyNDK>(this));
    }

    private static void callback( int pid )
    {
        Log.i("MyNDK","Pid:"+pid+",Count:"+count);
    count++;
    }


    private native void initMyJNI( Object weak_this );

    public native String  stringFromJNI();

    static {
        System.loadLibrary("my-jni");
    }

}


@ my_jni.c


#include <stdio.h>
#include <string.h>
#include <jni.h>
#include <pthread.h>
#include <android/log.h>

#define LOG_TAG"MyJNI"
#define LOGV(...)__android_log_print( ANDROID_LOG_VERBOSE,LOG_TAG, __VA_ARGS__ )
#define LOGD(...)__android_log_print( ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__ )
#define LOGI(...)__android_log_print( ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__ )
#define LOGW(...)__android_log_print( ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__ )
#define LOGE(...)__android_log_print( ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__ )

// --- Header Begin ---
#defineJAVA_CALLBACK_FUNCTION_NAME"callback"
#defineJAVA_CALLBACK_FUNCTION_ARGS_TYPE"(I)V"

JNIEXPORT void JNICALL Java_com_example_ndk_MyNDK_initMyJNI( JNIEnv* env, jobject thiz, jobject weak_this );
void * threadJobRun();

static const char *classPathName = "com/example/ndk/MyNDK";
static JNINativeMethod methods[] = {
{
"initMyJNI" , 
"(Ljava/lang/Object;)V" , 
(void *)Java_com_example_ndk_MyNDK_initMyJNI

};

static JavaVM *jvm;// global for JVM init Info
static jobject mObject;// get the object
static jclass mClass;// get the class
static jmethodID mMethodID;// get the method

static pthread_t thread;

// --- End Of Header ---


static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;

if( !( clazz = (*env)->FindClass( env, className ) ) )
{
LOGE( "Unable to get the Class at registerNativeMethods" );
return JNI_FALSE;
}

if( ( (*env)->RegisterNatives( env, clazz, gMethods, numMethods ) ) < 0 )
{
LOGE( "Unable to register the methods at registerNativeMethods" );
return JNI_FALSE;
}
return JNI_TRUE;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
LOGI("IN JNI_OnLoad");
JNIEnv *env;
if( ( (*vm)->GetEnv( vm, (void **)&env, JNI_VERSION_1_6 ) ) != JNI_OK )
{
LOGE( "Unable to get the env at JNI_OnLoad" );
return -1;
}
if (!registerNativeMethods( env, classPathName, methods, sizeof(methods) / sizeof(methods[0])))
{
LOGE( "Unable to register native methods at JNI_OnLoad" );
return -1;
}
jvm = vm;
return JNI_VERSION_1_6;
}

JNIEXPORT void JNICALL Java_com_example_ndk_MyNDK_initMyJNI( JNIEnv* env, jobject thiz, jobject weak_this )
{
jclass clazz;

mClass = NULL;
mObject = NULL;
mMethodID = NULL;

if( !( clazz = (*env)->GetObjectClass( env, thiz ) ) )
{
LOGE( "Unable to get the object class" );
return;
}
if( !( mClass = (*env)->NewGlobalRef( env, clazz ) ) )
{
LOGE( "Unable to get the class ref" );
return;
}
if( !( mObject = (*env)->NewGlobalRef( env, weak_this ) ) )
{
LOGE( "Unable to get the object ref" );
return;
}

if( !( mMethodID = (*env)->GetStaticMethodID( env, mClass, JAVA_CALLBACK_FUNCTION_NAME, JAVA_CALLBACK_FUNCTION_ARGS_TYPE ) ) )
{
LOGE( "Unable to get the method ref" );
return;
}

LOGI("IN initMyJNI");

if( mClass && mMethodID )// first call
(*env)->CallStaticVoidMethod( env, mClass , mMethodID , (int)getpid() );

// thread
pthread_create( &thread, NULL, threadJobRun, NULL);
}
void * threadJobRun()
{
JNIEnv *env;
int isAttached;

isAttached = 0;
env = NULL;
LOGI("IN threadJobRun");
if( jvm )
{
LOGI("IN threadJobRun with JVM");

if( ( (*jvm)->GetEnv( jvm, (void**) &env, JNI_VERSION_1_6 ) ) < 0 )
{
LOGI( "Unable to get env at threadJobRun" );
if( ( (*jvm)->AttachCurrentThread( jvm, &env, NULL) ) < 0 )
{
LOGE( "Unalbe to attach current thread at threadJobRun" );
return NULL;
}
isAttached = 1;
}

while( 1 )
{
sleep(1);

// --- jobs begin
if( mClass && mMethodID )
{
LOGI( "call mClass & mMehtodID" );
(*env)->CallStaticVoidMethod( env, mClass , mMethodID , (int)getpid() );
}
// --- end of jobs
}

if( isAttached )
{
(*jvm)->DetachCurrentThread( jvm );
}
}
return NULL;
}

jstring Java_com_example_ndk_MyNDK_stringFromJNI( JNIEnv* env, jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from My JNI !");
}


@ Android.mk


LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := my-jni
LOCAL_SRC_FILES := my-jni.c

LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog

include $(BUILD_SHARED_LIBRARY)


@ Makefile (非必要)


all:
cd ../ && sh ~/android-ndk-r5c/ndk-build


其中 Makefile 是我自己工作上使用的,我習慣用 vim + makefile 開發,所以會需要這個檔案,所以此檔非必要,其他檔案如同 Android 開發教學筆記 - 使用 Android NDK (Native Development Kit) 敘述。比較重要的是 my-jni.c 裡頭,有一個 JNI_OnLoad 和 initMyJNI 函數,在 app 一開始執行後,會先透過 JNI_OnLoad 將相關的資訊紀錄在 global variables 中,如 JVM 以及相關動作等,接著在 Java 呼叫 initMyJNI 時,才正式將 Java Object 紀錄起來,例如 Class 、Method 等,另外,在 initMyJNI 時,已經算是做到 C call Java 之 callback 的動作了(first call),只是我的目標是在 C 層跑一個 thread ,等到有事件時去更新 Java 層,所以有多一個 thread 的運行,在 threadJobRun 中則是 thread 的動作囉。


程式運作過程中,我有透過 Logcat 印出點訊息,可以看得清楚:


DDMS_MyJNI


其中 tag 為 MyJNI 是指 C 那層的動作,可以看到流程是 IN JNI_OnLoad、IN initMyJNI,接著就是第一次 c call java,在 Java 層印出 Pid:853, Count:0 的訊息,接著才進入 Thread ,然後每秒印出一次 Pid:853, Count:number 等資訊。


其他比較重要的是 C 裡頭也能可以呼叫 logcat ,記得在 Android.mk 中要加入 LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog 才不會編譯失敗(擺放的位置也很重要,擺在上頭會失敗);而 c call java 的部分,在 Java 那邊定義的函數必須是 static 的,此例為 private static void callback( int pid ),不然也會出錯(因為 C 裡頭是用 GetStaticMethodID ),我有去找一下其他 open source (Kwaak3 / KwaakJNI.java),寫法也是這樣。


最後一提,上述 C 程式存在很多地方沒寫好,例如資源的釋放等,在此僅供用來了解流程用途。


參考資料:



2011年7月13日 星期三

iOS 開發教學 - 關於 Crash Report 和 dSYM 的使用

前幾天玩遊戲時,看到開發者請大家把 Crash Log / Crash Report / Bug Report 寄回給他,如此一來就可以偵錯,滿好奇的便也隨意在網路上找找該怎樣使用,在此便稍作筆記,並且寫一個簡單會 crash 的程式(很少人會這樣做吧 XD)。

所謂的 Crash Report 是在實體機器上,執行程式時發生 crash 時,此時 iOS 會幫忙將程式出錯的相關資訊存起來,這時候用 Xcode 的 Organizer - Devices 時,可以去點選設備,並且可以查看有哪些 report 在上頭,這時候就可以稍作觀看,但是上頭記錄的資訊是有稍微類似做過保護的,只會看到類似(這是網路上收集的資訊,但我嘗試做出的 log 好像已經轉好了?):

Thread 0 Crashed:
0   libSystem.B.dylib              0x35de3ad0 0x######## + ##
1   libSystem.B.dylib              0x35de3abe 0x######## + ##
2   libSystem.B.dylib              0x35de3ab2 0x######## + ##
3   libSystem.B.dylib              0x35dfad5e 0x######## + ##
4   libSystem.B.dylib              0x35de967e 0x######## + ##
5   libSystem.B.dylib              0x35de974c 0x######## + ##
6   libSystem.B.dylib              0x35d5c8e0 0x######## + ##
7   libSystem.B.dylib              0x35d5c798 0x######## + ##
8   CoreFoundation                 0x3750022c 0x######## + ##
9   CoreFoundation                 0x374fc206 0x######## + ##
10  CoreFoundation                 0x37504564 0x######## + ##
11  CoreFoundation                 0x37504406 0x######## + ##
12  Foundation                     0x351530f0 0x######## + ##
13  CrashTesting                   0x00002712 0x######## + ##
14  UIKit                          0x35924fd0 0x######## + ##
15  UIKit                          0x3591ea70 0x######## + ##
16  UIKit                          0x358d82dc 0x######## + ##
17  UIKit                          0x358d7b14 0x######## + ##
18  UIKit                          0x358d73ac 0x######## + ##
19  GraphicsServices               0x33e77c80 0x######## + ##
20  CoreFoundation                 0x3752f5c4 0x######## + ##
21  CoreFoundation                 0x3752f582 0x######## + ##
22  CoreFoundation                 0x3752182e 0x######## + ##
23  CoreFoundation                 0x37521504 0x######## + ##
24  CoreFoundation                 0x37521412 0x######## + ##
25  UIKit                          0x3591d54c 0x######## + ##
26  UIKit                          0x3591a550 0x######## + ##
27  CrashTesting                   0x0000266e 0x######## + ##
28  CrashTesting                   0x00002620 0x######## + ##

這時候需要使用原先編譯 app 時,在同一目錄中會產生對應的 dSYM 檔案,搭配 dwarfdump 使用才可以查詢到真正的片段程式(例如想看 line 13 的意思):

$ dwarfdump --lookup 0x00002712 -arch armv6 CrashTesting.app.dSYM/
----------------------------------------------------------------------
 File: CrashTesting.app.dSYM/Contents/Resources/DWARF/CrashTesting (armv6)
----------------------------------------------------------------------
Looking up address: 0x0000000000002712 in .debug_info... found!

0x00000132: Compile Unit: length = 0x000049b0  version = 0x0002  abbr_offset = 0x00000000  addr_size = 0x04  (next CU at 0x00004ae6)

0x0000013d: TAG_compile_unit [1] *
             AT_producer( "4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2373.6)" )
             AT_language( DW_LANG_ObjC )
             AT_name( "CrashTestingAppDelegate.m" )
             AT_entry_pc( 0x000026b4 )
             AT_stmt_list( 0x0000006b )
             AT_comp_dir( "/Volumes/Data/iPhoneDev/trunk/CrashTesting/CrashTesting" )
             AT_APPLE_major_runtime_vers( 0x02 )

0x00000166:     TAG_subprogram [16] *
                 AT_sibling( {0x000001d2} )
                 AT_name( "-[CrashTestingAppDelegate application:didFinishLaunchingWithOptions:]" )
                 AT_decl_file( "/Volumes/Data/iPhoneDev/trunk/CrashTesting/CrashTesting/CrashTestingAppDelegate.m" )
                 AT_decl_line( 17 )
                 AT_prototyped( 0x01 )
                 AT_type( {0x0000015b} ( BOOL ) )
                 AT_APPLE_isa( 0x01 )
                 AT_low_pc( 0x000026b4 )
                 AT_high_pc( 0x00002780 )
                 AT_frame_base( r7 )

0x000001b9:         TAG_lexical_block [5] *
                     AT_low_pc( 0x000026c4 )
                     AT_high_pc( 0x00002780 )
Line table file: 'CrashTestingAppDelegate.m' line 20, column 0 with start address 0x000000000000270e

Looking up address: 0x0000000000002712 in .debug_frame... found!

0x00000020: FDE
        length: 0x0000000c
   CIE_pointer: 0x00000000
    start_addr: 0x000026b4 -[CrashTestingAppDelegate application:didFinishLaunchingWithOptions:]
    range_size: 0x000000cc (end_addr = 0x00002780)
  Instructions: 0x000026b4: CFA=4294967295+4294967295

因此就可以查看到 line 13 是指 CrashTestingAppDelegate.m' line 20,所以就可以去那邊追查看看。

以上的例子僅需建立一個 Project (此例名為 CrashTesting),然後在 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 裡頭寫一段會 crash 的程式碼(此例為 double free):


    NSString *justCrash = [[NSString alloc] initWithFormat:@"CrashTesting"];
    [justCrash release];
    [justCrash release];

如此一來,在實機執行完並當機後,可以接上 Xcode 去 Organizer - Devices 查看:

xcode-crash-log

而 dSYM 檔案,則是在 Xcode 編譯的地方:

xcode-dSYM

點選後,就可以看到並且可以複製到習慣的工作目錄囉

xcode-app-dSYM

參考資料:


iPhone/iPad 遊戲 - 大家來搶錢 HD

大家來搶錢 HD-0 大家來搶錢 HD-1


前陣子趁著限時免費時,下載了這款遊戲(現在要價 1.99 美金)。後來過了一陣子後,同事一直在玩也推薦我玩,就不小心也陷入想要把地圖全破的慾念中。這款就像是兒時 PC 上的大富翁遊戲,可以選地圖、餐與人物,遊戲過程中藉由買土地、建房子、收過路費等模式進行,除此之外,有簡單的卡片系統,可以買特殊的卡片,如停留卡、購地卡、請神卡、均富卡等等,當然還有四個神仙供你參拜以及錢滾錢的股票系統。


大家來搶錢 HD-2


目前共提供 10 個地圖,玩起來還不錯,就跟 PC 上的大富翁很像,只是沒那麼複雜。這幾天開始試試股票系統,今天不小心就從 4 塊錢買進,遊戲進行中又使用了幾張紅卡後,目前市值已經有 99 元,使得 UI 顯示已經爆表看不到股票現值了 XD 然後定期會有股票現金股利吧?不知不覺手頭上的現金也蹦出一堆。我想,我應該可以正式從這款遊戲畢業了 XDD


大家來搶錢 HD-3


這款應該是香港或大陸人開發的遊戲吧?我覺得品質還不錯,隨著地圖有對應的風格背景、遊戲聲效等,最近好像有更新軟體?或許修正了一些問題或增加難度了吧。雖然我對遊戲開發還沒有熱情,但透過這款遊戲的刺激,哪天也來研究一下遊戲吧!總覺得這款是透過某種機制 porting 到 iPhone 上頭,如果也是走 OpenGL ES 的話,或許 Android 也能無痛 porting 呢。


最後一提,這款有支援 iPad 喔,算挺不賴的!


2011年7月11日 星期一

iPhone 開發教學 - A Simple Socket Server Example


來源:Introduction to CFNetwork Programming Guide


前陣子用了一些 iPhone app,其中有些 app 有提供小型的 web service,因此讓我想練習 iOS socket programming,反正暑假也到了,我就抽點時間東看西看。


花了不少時間,才擠出一點小範例。在 iOS 上寫 socket server 的方式,跟一般工作站寫的不一樣,以前在工作站寫 server 時,因為一開始執行並不會有 client 馬上連線,所以要將 server 程式用 loop 等著,以免程式終止。在 iOS 上寫 socket server 則有點像 Web 上寫 AJAX 的模式,改成 EVENT-DRIVEN 架構,變成要註冊 client 連線事件、client 傳輸資料、client 終止連線等相關事件。這些大概是跟 UI 操作架構有關,因此在 iOS 上寫 socket server 時,不需再用 loop 架構等在那邊(若用 loop 等在那邊也容易出錯)。


建立 iOS socket server 流程如下:



  1. 初始化 socket server 資訊,如使用的 IP 和 Port number

  2. 設定 socket accept 事件處理

  3. 設定 socket connect 事件處理

  4. 設定 socket close 事件處理


雖然 iOS 支援 BSD Socket,只是還是用 BSD Socket 練習,那就會沒學到新東西,所以此處採用稍微上層的 Core Foundation/CFNetwork 架構,物件上採用 CFSocket,此例是在 iPhone Simulator 上運行一支 socket server ,然後透過 cmd line 的 telnet 進行連線測試。僅須把程式碼都寫在 YourProjectAppDelegate.m 之裡頭即可(如:SocketServer)。


AddCFNetworkLib
記得要新增 CFNetwork framework


程式碼:


#import <CFNetwork/CFNetwork.h>
#import <arpa/inet.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <netdb.h>

// Global variables 
CFSocketRef server;
int serverPort = 5566;
CFMutableDictionaryRef incomingRequests;
NSFileHandle *listeningHandle;


- (void)clientHandleClose:(NSFileHandle *)incomingFileHandle close:(BOOL)closeFileHandle
{
NSLog(@"socket close");
if (closeFileHandle)
{
[incomingFileHandle closeFile];
}

[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleDataAvailableNotification object:incomingFileHandle];
CFDictionaryRemoveValue(incomingRequests, incomingFileHandle);

}

- (void)clientDataReceiveNotification:(NSNotification *)notification
{
NSLog(@"receive data");
NSFileHandle *incomingFileHandle = [notification object];
NSData *data = [incomingFileHandle availableData];

if ([data length] == 0)
{
[self clientHandleClose:incomingFileHandle close:NO];
return;
}
NSString *formatedData = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(@": %@", formatedData);
[formatedData release];
    
[self clientHandleClose:incomingFileHandle close:YES];
}

- (void)clientAcceptNotification:(NSNotification *)notification
{
NSLog(@"server accept");
    
NSDictionary *userInfo = [notification userInfo];
NSFileHandle *incomingFileHandle = [userInfo objectForKey:NSFileHandleNotificationFileHandleItem];
    
if(incomingFileHandle)
{
CFDictionaryAddValue( incomingRequests, incomingFileHandle, [(id)CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE) autorelease]);

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clientDataReceiveNotification:) name:NSFileHandleDataAvailableNotification object:incomingFileHandle];
[incomingFileHandle waitForDataInBackgroundAndNotify];
}

    
[listeningHandle acceptConnectionInBackgroundAndNotify];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
int socketSetupContinue = 1;
struct sockaddr_in addr;
    
if( !(server = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,IPPROTO_TCP, 0, NULL, NULL) ) )
{
NSLog(@"CFSocketCreate failed");
socketSetupContinue = 0;
}
    
if( socketSetupContinue )
{
int yes = 1;
if( setsockopt(CFSocketGetNative(server), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(int)) )
{
NSLog(@"setsockopt failed");
CFRelease(server);
socketSetupContinue = 0;
}
}
    
if( socketSetupContinue )
{
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_port = htons(serverPort);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
        
//CFDataRef *address = CFDataCreate(NULL, (const UInt8 *)&addr, sizeof(addr));
//[(id)address autorelease];
//if (CFSocketSetAddress(server, address) != kCFSocketSuccess) 
NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
if (CFSocketSetAddress(server, (CFDataRef)address) != kCFSocketSuccess) 
{
NSLog(@"CFSocketSetAddress failed");
CFRelease(server);
socketSetupContinue =0;
}
}
    
if( socketSetupContinue )
{
incomingRequests = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
listeningHandle = [[NSFileHandle alloc] initWithFileDescriptor:CFSocketGetNative(server) closeOnDealloc:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clientAcceptNotification:) name:NSFileHandleConnectionAcceptedNotification object:nil];
[listeningHandle acceptConnectionInBackgroundAndNotify];

        
NSLog(@"Socket listening on %s:%d", addr2ascii(AF_INET, &(addr.sin_addr.s_addr),sizeof(addr.sin_addr.s_addr),NULL), serverPort);
}
    
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}


模擬器一開啟後,就會聽在 5566 port 上頭,接著可以用 telnet localhost 進行測試:


$ telnet localhost 5566
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.


一打完就可以看到 iPhone Simulator 印出 server accept 字樣


 Socket listening on 0.0.0.0:5566
 server accept


接著隨意打一些字串,就可以看到 Simulator 印出接收到的字串以及把連線中斷的訊息,當然 client 也就中斷了。


上述粗體字是全域變數,可以加在 @interface 裡頭或是之接擺在 @implementation 後面也行。藍色部分是 Event 的設定,由於 socket handle 在系統底層也算是個 file handle,所以事件的偵測就跟偵測 file 變化類似,例如檔案開檔、新增資料和關檔等等。


參考資料:



2011年7月10日 星期日

第九位成員的誕生

工作之後,幾乎一年換一台電腦?真是噴錢噴真兇,只是說起來大概是 n 年前的主機已經不敷使用吧?仔細回想一下,家中第一台電腦是 586 等級,16MB 吧?硬碟 1.6 GB吧?有點忘了,但價錢都比第三台主機之後的貴;第二台是 P-200 ,我用他玩了太空戰士七等遊戲,還有 BBS 啦;第三台就隔得比較久,就是我升大二的 AMD XP 1800+ (大一下我還在用 P-200那台寫 C 語言交作業),也是第一次組裝電腦,感謝我的好友 Kudo 啦,而第一台跟第二台也就退役了;隔年第四台是 AMD XP 2200+ 組給求學的家人;第五台是買二手零件組了台 AMD 800 ,給家人上網;第六台算是努力工作的收穫, AMD SP 2600;第七台就是碩一時期,宿舍常跳電,讓我的主機掛了,這時起就沒換機殼了,那時還用第五台撐了會,最後則是兩台退役;工作後沒多久 AMD 2200+ 也掛了,第八台產生,這次就組得很好 XD 現在想起來還真的有些浪費,使用上並沒有那麼大的需求;第九台則是這次的新成員,也代表第六台的退役。往後大概會開始用筆電了。


這次的清單(不包含機殼):


處理器 CPU		AMD AM3 AthlonII X3-450三核/3.2G/95W/45nm/L2 Cache 512KB*3,AMD AM3 腳位
主機板 MB 華碩 M4A78LT-M LE/760G/DVI+RGB/最佳DDR3入門合板,AMD AM3/合板內建顯示
記憶體 RAM 兩隻,金士頓 DDR3 4G-1333單支,桌上型 DDR3-1333/1600單支
內接式硬碟 HDD WD 500G容量 SATA3 5000AAKX/16M/藍標/SATA3/三年保,最新一代 SATA3規格
燒錄器 CD/DVD/BD LITE-ON IHAS524/24XDVD燒錄/LabelTag功能可在光碟資料面寫上圖片及文字,終極DVD燒錄器 SATA介面
電源供應器 全漢 藍寶石二代 350W(PEAK 400)日系電容三年保/90天換新,全漢三年免費/POWER界霸主

設備再加上宅急便的運費、電話聯絡跟跨行轉帳費,不到 8900 元。若是想拿來當一台新主機,那大概要再加個 700~1000,多買個機殼吧。


第一次連燒錄器都買 SATA 介面,這是有風險的,例如作業系統中沒有支援 SATA 驅動程式(Windows XP?),可能導致無法讀取光碟機吧(不曉得透過 BIOS 將 SATA 弄成 IDE 模可否解決)?另外則是要擔心 Power 的 SATA 線夠不夠多,這次買的 Power 只有提供一條 SATA 電源線,上頭有兩個接頭,所以在安裝硬體上,會碰到光碟機跟硬碟的距離問題,必須確認兩台設備都可以接到電源線,這些問題又跟機殼有關。慶幸的,一切在舊機殼上都還順利完工了。


往後還是買這種中低階的零件就好了,CPU不用兩千五,而記憶體一條 4G-1333 才 8xx 元,也感謝主機板只有兩個記憶體插槽,不然我應該會噴更多錢吧 XD 唯一比較差的應該是硬碟只買 500 GB ,計算 CP 值並不划算,但用來組一台主機的基本零件,倒是還算夠用。這次組裝就比較留意,例如 CPU 跟主機板搭配時,是否有支援到記憶體的最大使用效率(DDR3-1333),還有 CPU 所需的瓦數主機板有沒支援(有的 AMD X3 或 X4 的 CPU 要 125W,這款主機板只有支援到 95W)。


就這樣,有了一台 3.2G 三核的 CPU 並可使用 8GB 記憶體,應該非常非常夠用了 :) 


F_AUDIO 接法 (前面板音效/前置音效)

F_AUDIO


回老家前,在某 3C 店家線上購物,剛剛好在我回到家之前送到,周末就著手組裝電腦及其相關的調教,於是碰到前面板音效的問題。新買的主機板是 ASUS M4A78LT-M LE ,此主機板支援 HD Audio 也支援 AC' 97 ,處理這個任務第一步就是要翻說明書啦,各家主機板可能都不一樣。


F_AUDIO manual
ASUS M4A78LT-M LE 說明書


F_AUDIO_NOTE
N 年前的 BBS 筆記


只是我家的機殼真的太舊了,其前置音效、USB的線都是拆開的一個 pin 腳一條,實在累人,所以翻了以前自己寫的 F_AUDIO 筆記,終於搞定了,結果測試的時候又發現前面麥克風沒聲音?最後才發現 BIOS 預設是 HD Audio 的,必須去 BIOS 裡設定


F_AUDIO_BIOS


我想,這類舊型機殼大概還是要淘汰掉才對,新的機殼都幫你把這些線包成一個大接頭,加上防呆針腳,基本上都不會再接錯,除此之外,想到舊主機板的前置 USB 還滿危險的,如果接錯的話,隨身碟一插就可能燒壞了,因此面對未知且老舊的主機,安全一點還是先接主機板上的 USB 孔吧。另外,用 "F_AUDIO 接法" 問 Google 可以看到別人寫的超詳細筆記,有需要可以參考看看:《教學》AC97和HD規範簡介和前置音效接法


2011年7月9日 星期六

Windows 7 之 KMS 啟用失敗 0xC004C003 (設定為大量授權版)

幫學弟安裝校園版軟體,校園版採用 KMS 大量授權,只是學弟一開始安裝錯作業系統,安裝成非大量授權版本(安裝過程可以看到詢問序號的畫面),雖然可以不輸入序號繼續安裝,但產品內的設定已不適用 KMS 認證了。


再重新準備大量授權版本途中,查了一些網路文章,發現 Windows 7 KMS Client Setup Keys 清單:


Windows 7 Professional		FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4
Windows 7 Professional N MRPKT-YTG23-K7D7T-X2JMM-QY7MG
Windows 7 Professional E W82YF-2Q76Y-63HXB-FGJG9-GF7QX
Windows 7 Enterprise 33PXH-7Y6KF-2VJC9-XBBR8-HVTHH
Windows 7 Enterprise N YDRBP-3D83W-TY26F-D46B2-XCKRJ
Windows 7 Enterprise E C29WB-22CC8-VJ326-GHFJW-H9DH4

並且發現 slmgr 指令可以設定 Product key ,所以就可以更新產品金鑰轉成大量授權版,進而完成大量授權之 KMS 認證機制囉!


此例恰好學弟想從 Windows 7 32-bit 升成 64-bit ,所以有前者的資料可以查閱,在 Windows 7 32-bit 之大量授權版本,其 sources/product.ini 中可以瞧見最後一行的設定:


[BuildInfo]
staged=PROFESSIONAL


而非大量授權的:


[BuildInfo]
staged=HOMEBASIC,HOMEPREMIUM,PROFESSIONAL,ULTIMATE


透過這一丁點資訊,就採用 Client Windows 7 Professional Key 了:


slmgr -ipk FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4


隨即進行 slmgr -ato 驗證,果真就成功啦!


2011年7月7日 星期四

Android 開發筆記 - 使用 Android NDK (Native Development Kit)

之把某個 android app 產品進行反組譯的動作,只是單純地想要驗證該 app 的實作流程,但在 Java 層只看到一些 UI 介面的操作,算是驗證了半個想法,但總覺得缺少什麼,所幸在原本的 apk 檔案內發現了 xxx.so 檔案,透過很基本的 strings 指令可以查看一些敏感字眼,就這樣我找到了鐵證。心中有股莫名踏實感,雖然 xxx.so 檔只能瞧見一些關鍵字,但因為撰寫程式的把 function name 取得很容易猜測功能,所以加加減減就成了一種驗證。


前陣子上過幾個小時的 android 課程,恰好稍微提到 NDK 的部份,此部份主要是設計給遊戲類的使用,例如之前寫 OpenGL ES 時,必須透過 Android Java API 來呼叫,現在則可以直接用 NDK 寫 C 語言達成,除了解決繁瑣的 C to Java 語法,更可以避開 JVM 來提昇效能。除此之外,我覺得 NDK 也是可以用拿來保護關鍵程式碼囉。此筆記就當作簡單的測試 NDK 的用法,在此不提及安裝 Android 開發環境,有興趣可以查看Android & Eclipse 開發環境- 第一次安裝筆記。在此使用 Ubuntu 10.04 64-bit 環境。


首先,從 Android NDK | Android Developers 下載 Android NDK (我在 Ubuntu 所以下載 android-ndk-r5c-linux-x86.tar.bz2) 並挑選一個地方解壓縮(此例為 ~/android-ndk-r5c),除此之外要把 Android SDK 更新至最新版,如果已經安裝好 Android 開發環境,更新方式透過 Eclipse -> [Window]-> [Android SDK and AVD Manager] -> [Installed packages] -> [Update All...]


接著要進行 NDK 環境設置,可查看 android-ndk-r5c/docs/INSTALL.html,可以得知 Android NDK 至少需使用 Android 1.5 系統,而編譯環境至少是 GNU Make 3.8.1,除此之外還需要 Nawk 或 GNU Awk 工具,而常見的 awk 則不適用。


在 Ubuntu 10.04 64-bit desktop 環境上:


$ awk -W version
GNU Awk 3.1.6
Copyright (C) 1989, 1991-2007 Free Software Foundation.
~$ make -version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc


透過 Eclipse 建立一個 android 2.3.3 project,例如 MyNDK:


Project name: MyNDK
Build Target: Android 2.3.3/Android Open Source Project/2.3.3/10
Application name: MyNDK
Package name: com.example.ndk
Create Activity: MyNDK
Min SDK Version: 3


接著,在 Eclipse 左邊 Project Explorer 建立 jni 和 libs 兩個目錄,位置擺在剛剛的 Project 裡(點一下 MyNDK,接著按右鍵選 New -> Folder 輸入 jni,之後在建立 libs 目錄):


MyNDK/jni
MyNDK/libs


在 MyNDK/jni 裡頭新增兩個檔案,分別是 JNI 程式碼和 Android.mk,結構如下:


MyNDK/jni
MyNDK/jni/Android.mk
MyNDK/jni/my-jni.c


其中 Android.mk 和 *.c 可以從 android-ndk-r5c/samples/hello-jni/jni 裡頭尋得範例,簡單筆記:


MyNDK/jni/Android.mk (參考 android-ndk-r5c/samples/hello-jni/jni/Android.mk)


LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := my-jni
LOCAL_SRC_FILES := my-jni.c
include $(BUILD_SHARED_LIBRARY)


MyNDK/jni/my-jni.c (參考 android-ndk-r5c/samples/hello-jni/jni/hello-jni.c)


#include <string.h>
#include <jni.h>
jstring Java_com_example_ndk_MyNDK_stringFromJNI( JNIEnv* env, jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from My JNI !");
}


其中要留意 function name,裡頭包括 packages name 和 Activity name 等資訊,此例是 com_example_ndk 和 MyNDK。


接著要透過 android ndk 去編譯 my-jni.c,除了在 Command line 可以切換到 MyNDK 目錄,用 android-ndk-r5c/ndk-build 進行編譯外,還可以透過 Eclipse 設定好,讓你每次自動編譯好:


Properties for MyNDK


在 Eclipse 左邊 Project Explorer 點選 MyNDK,選右鍵 -> Properties -> Builders -> New -> Program


在 Main 分頁:


Name: NDK_Builder
Location(ndb-build位置): /home/user/android-ndk-r5c/ndk-build
Working Directory:(project位置): ${workspace_loc:${project_path}}


Builders


在 Refresh 分頁:


將 Refresh resources upon completion 打勾,並且在 Specific resources 選擇 MyNDK/libs 目錄


在 Build Options 分頁:


勾選 Allocate Console(necessary for input)、Launch in backgroud、After a "Clean"、During manual builds、During auto builds 和 Specify working set of relevant resources


在 Specific resources 選擇 MyNDK/jni


如此一來,只要編譯或清除時,自動會編譯 jni 的部份,可以使用 Build all(快速鍵Ctrl+B) 試試,可以看到 Console 的輸出:


Compile thumb  : my-jni <= my-jni.c
SharedLibrary  : libmy-jni.so
Install        : libmy-jni.so => libs/armeabi/libmy-jni.so


接著,才正式要寫 Java 程式部份,切換到 MyNDK.java:


package com.example.ndk;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MyNDK extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        TextView  tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv);

    }
    public native String  stringFromJNI();
    static {
        System.loadLibrary("my-jni");
    }

}


其中特別的是要宣告 jni 使用到的函數,這跟 my-jni.c 裡頭的宣告有關,以及 System.loadLibrary 的部份,跟 jni/Android.mk 設定的編譯結果也有關係,如果都不相符,執行時會出現 exception。


最後跑起來的成果:


MyJNI


整個 Project 結構:


Project Explorer


相關文章:



2011年7月6日 星期三

iPhone 開發教學 - 測試 Xcode 4 和 iPhone 3G iOS 4.2.1 取得網路資料

好久沒碰 Xcode 了,上一次寫這種筆記應該是一年前了吧?昨天把公司的環境打理了一下,下載最新的 Xcode 4.0.2 and iOS SDK 4.3,接著把 iPhone 3G iOS 4.2.1 的機器測一下網路連線,結果會噴訊息:


warning: Unable to read symbols for /Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.2.1 (8C148)/Symbols/usr/lib/info/dns.so (file not found).


因為 Xcode 沒有支援 iOS 4.2.1 的環境,接上手機後會從手機上下載相關環境資訊,但偏偏就是沒有 info/dns.so 這個檔案,查了一些文章,解法也只是從 4.2 裡頭複製一份,如此一來就不會出現訊息。


xcode 4.0.2


實際測試時,在 iOS 4.3 模擬器可以正常,但在 iPhone 3G iOS 4.2.1 出錯,一直以為是 DNS 反查失敗,其程式大概如下:


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSData *d = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"TheWebURL"]];
    NSLog(@"%@", d );


    // Override point for customization after application launch.
    // Add the navigation controller's view to the window and display.
    self.window.rootViewController = self.navigationController;
    [self.window makeKeyAndVisible];
    return YES;
}


其中 "TheWebURL" 是比較複雜的網址,如果只是 "http://www.google.com" 這種不會有問題,但如果是 "http://www.google.com.tw/q=?ooxx" 帶有複雜參數且非常長的,看到的是 (null) 的現象,就陷入跑去找到正確的 dns.so 的深淵了。


後來,不知何時跑去印出 NSLog( "NSURL:%@" , [NSURL URLWithString:@"TheWebURL"] ),才發現在機器上也是噴 (null) ,這下可好,就跟 dns.so 沒啥關係了吧?!


急轉而下,改成找 NSURL 回傳是 null 的解法,不一會兒就解掉了:


NSString *url = [[NSString stringWithFormat:@"TheWebURL"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog( "NSURL:%@" , [NSURL URLWithString:url ] );


可以解釋的理由是網址內有特殊符號,在使用上如果丟進較新的瀏覽器時,瀏覽器有的會自動幫你處理,因此,在 iOS 4.3 環境中,NSURL 會自動處理,但是舊版 4.2.1 就沒有了。


至於 dns.so 的問題?看來只有真的碰到時,才能來解了 Orz 又藏了一顆未爆彈。