
2010年10月29日
posted @
2010-10-29 13:51 xiaoxinchen 閱讀(1781) |
評論 (0) |
編輯 收藏

2010年9月14日
眾所周知,
Linux動態庫的默認搜索路徑是/lib和/usr/lib。動態庫被創建后,一般都復制到這兩個目錄中。當程序執行時需要某動態庫,并且該動態庫還未加載到內存中,則系統會自動到這兩個默認搜索路徑中去查找相應的動態庫文件,然后加載該文件到內存中,這樣程序就可以使用該動態庫中的函數,以及該動態庫的其它資源了。在Linux
中,動態庫的搜索路徑除了默認的搜索路徑外,還可以通過以下三種方法來指定。
方法一:在配置文件/etc/ld.so.conf中指定動態庫搜索路徑。
可以通過編輯配置文件/etc/ld.so.conf來指定動態庫的搜索路徑,該文件中每行為一個動態庫搜索路徑。每次編輯完該文件后,都必須運行命令ldconfig使修改后的配置生效。我們通過例1來說明該方法。
例1:
我們通過以下命令用源程序pos_conf.c(見程序1)來創建動態庫
libpos.so,詳細創建過程請參考文[1]。
# gcc -c pos_conf.c
# gcc -shared -fPCI -o
libpos.so pos_conf.o
#
#include <stdio.h>
void
pos()
{
printf("/root/test/conf/lib\n");
}
程序1: pos_conf.c
接著通過以下命令編譯main.c(見程序2)生成目標程序pos。
# gcc -o pos main.c -L. -lpos
#
void pos();
int main()
{
pos();
return 0;
}
程序2: main.c
然后把庫文件移動到目錄/root/test/conf/lib中。
# mkdir -p /root/test/conf/lib
# mv
libpos.so /root/test/conf/lib
#
最后編輯配置文件/etc/ld.so.conf,在該文件中追加一行"/root/test/conf/lib"。
運行程序pos試試。
# ./pos
./pos: error while loading
shared libraries: libpos.so: cannot open shared object file: No such file or
directory
#
出錯了,系統未找到動態庫libpos.so。找找原因,原來在編輯完配置文件/etc/ld.so.conf后,沒有運行命令ldconfig,所以剛才的修改還未生效。我們運行ldconfig后再試試。
# ldconfig
# ./pos /root/test/conf/lib
#
程序pos運行成功,并且打印出正確結果。
方法二:通過環境變量LD_LIBRARY_PATH指定動態庫搜索路徑(!)。
通過設定環境變量LD_LIBRARY_PATH也可以指定動態庫搜索路徑。當通過該環境變量指定多個動態庫搜索路徑時,路徑之間用冒號":"分隔。
不過LD_LIBRARY_PATH的設定作用是全局的,過多的使用可能會影響到其他應用程序的運行,所以多用在調試。(LD_LIBRARY_PATH的缺陷和使用準則,可以參考《Why
LD_LIBRARY_PATH is
bad》)。通常情況下推薦還是使用gcc的-R或-rpath選項來在編譯時就指定庫的查找路徑,并且該庫的路徑信息保存在可執行文件中,運行時它會直接到該路徑查找庫,避免了使用LD_LIBRARY_PATH環境變量查找。
下面通過例2來說明本方法。
例2:
我們通過以下命令用源程序pos_env.c(見程序3)來創建動態庫libpos.so。
# gcc -c pos_env.c
# gcc -shared -fPCI -o
libpos.so pos_env.o
#
#include <stdio.h>
void
pos()
{
printf("/root/test/env/lib\n");
}
程序3: pos_env.c
測試用的可執行文件pos可以使用例1中的得到的目標程序pos,不需要再次編譯。因為pos_conf.c中的函數pos和pos_env.c中的函數pos
函數原型一致,且動態庫名相同,這就好比修改動態庫pos后重新創建該庫一樣。這也是使用動態庫的優點之一。
然后把動態庫libpos.so移動到目錄/root/test/conf/lib中。
# mkdir -p /root/test/env/lib
# mv
libpos.so /root/test/env/lib
#
我們可以使用export來設置該環境變量,在設置該環境變量后所有的命令中,該環境變量都有效。
例如:
# export
LD_LIBRARY_PATH=/root/test/env/lib
#
但本文為了舉例方便,使用另一種設置環境變量的方法,既在命令前加環境變量設置,該環境變量只對該命令有效,當該命令執行完成后,該環境變量就無效了。如下述命令:
# LD_LIBRARY_PATH=/root/test/env/lib ./pos
/root/test/env/lib
#
程序pos運行成功,并且打印的結果是"/root/test/env/lib",正是程序pos_env.c中的函數pos的運行結果。因此程序pos搜索到的動態庫是/root/test/env/lib/libpos.so。
方法三:在編譯目標代碼時指定該程序的動態庫搜索路徑。
還可以在編譯目標代碼時指定程序的動態庫搜索路徑。這是通過gcc 的參數"-Wl,-rpath,"指定(如例3所示)。當指定多個動態庫搜索路徑時,路徑之間用冒號":"分隔。
例3:
我們通過以下命令用源程序pos.c(見程序4)來創建動態庫libpos.so。
# gcc -c pos.c
# gcc -shared -fPCI -o
libpos.so pos.o
#
#include <stdio.h>
void
pos()
{
printf("./\n");
}
程序4: pos.c
因為我們需要在編譯目標代碼時指定可執行文件的動態庫搜索路徑,所以需要用gcc命令重新編譯源程序main.c(見程序2)來生成可執行文件pos。
# gcc -o pos main.c -L. -lpos
-Wl,-rpath,./
#
再運行程序pos試試。
# ./pos ./
#
程序pos運行成功,輸出的結果正是pos.c中的函數pos的運行結果。因此程序pos搜索到的動態庫是./libpos.so。
以上介紹了三種指定動態庫搜索路徑的方法,加上默認的動態庫搜索路徑/lib和/usr/lib,共五種動態庫的搜索路徑,那么它們搜索的先后順序是什么呢?
在 介紹上述三種方法時,分別創建了動態庫./libpos.so、
/root/test/env/lib/libpos.so和/root/test/conf/lib/libpos.so。我們再用源程序
pos_lib.c(見程序5)來創建動態庫/lib/libpos.so,用源程序pos_usrlib.c(見程序6)來創建動態庫
/usr/lib/libpos.so。
#include <stdio.h>
void
pos()
{
printf("/lib\n");
}
程序5: pos_lib.c
#include <stdio.h>
void
pos()
{
printf("/usr/lib\n");
}
程序6: pos_usrlib.c
這樣我們得到五個動態庫libpos.so,這些動態庫的名字相同,且都包含相同函數原型的公用函數pos。但存儲的位置不同和公用函數pos
打印的結果不同。每個動態庫中的公用函數pos都輸出該動態庫所存放的位置。這樣我們可以通過執行例3中的可執行文件pos得到的結果不同獲知其搜索到了哪個動態庫,從而獲得第1個動態庫搜索順序,然后刪除該動態庫,再執行程序pos,獲得第2個動態庫搜索路徑,再刪除第2個被搜索到的動態庫,如此往復,將可得到Linux搜索動態庫的先后順序。程序pos執行的輸出結果和搜索到的動態庫的對應關系如表1所示:
程序pos輸出結果 |
使用的動態庫 |
對應的動態庫搜索路徑指定方式 |
./ |
./libpos.so |
編譯目標代碼時指定的動態庫搜索路徑 |
/root/test/env/lib |
/root/test/env/lib/libpos.so |
環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑 |
/root/test/conf/lib |
/root/test/conf/lib/libpos.so |
配置文件/etc/ld.so.conf中指定的動態庫搜索路徑 |
/lib |
/lib/libpos.so |
默認的動態庫搜索路徑/lib |
/usr/lib |
/usr/lib/libpos.so |
默認的動態庫搜索路徑/usr/lib |
表1: 程序pos輸出結果和動態庫的對應關系
創建各個動態庫,并放置在相應的目錄中。測試環境就準備好了。執行程序pos,并在該命令行中設置環境變量LD_LIBRARY_PATH。
# LD_LIBRARY_PATH=/root/test/env/lib ./pos
./
#
根據程序pos的輸出結果可知,最先搜索的是編譯目標代碼時指定的動態庫搜索路徑。然后我們把動態庫./libpos.so刪除了,再運行上述命令試試。
# rm libpos.so
rm: remove regular file
`libpos.so'? y
# LD_LIBRARY_PATH=/root/test/env/lib ./pos
/root/test/env/lib
#
根據程序pos的輸出結果可知,第2個動態庫搜索的路徑是環境變量LD_LIBRARY_PATH指定的。我們再把/root/test/env/lib/libpos.so刪除,運行上述命令。
# rm /root/test/env/lib/libpos.so
rm:
remove regular file `/root/test/env/lib/libpos.so'? y
#
LD_LIBRARY_PATH=/root/test/env/lib ./pos /root/test/conf/lib
#
第3個動態庫的搜索路徑是配置文件/etc/ld.so.conf指定的路徑。刪除動態庫/root/test/conf/lib/libpos.so后再運行上述命令。
# rm /root/test/conf/lib/libpos.so
rm:
remove regular file `/root/test/conf/lib/libpos.so'? y
#
LD_LIBRARY_PATH=/root/test/env/lib ./pos /lib
#
第4個動態庫的搜索路徑是默認搜索路徑/lib。我們再刪除動態庫/lib/libpos.so,運行上述命令。
# rm /lib/libpos.so
rm: remove regular
file `/lib/libpos.so'? y
# LD_LIBRARY_PATH=/root/test/env/lib ./pos
/usr/lib
#
最后的動態庫搜索路徑是默認搜索路徑/usr/lib。
綜合以上結果可知,動態庫的搜索路徑搜索的先后順序是:
1.編譯目標代碼時指定的動態庫搜索路徑;
2.環境變量LD_LIBRARY_PATH指定的動態庫搜索路徑;
3.配置文件/etc/ld.so.conf中指定的動態庫搜索路徑;
4.默認的動態庫搜索路徑/lib;
5.默認的動態庫搜索路徑/usr/lib。
在上述1、2、3指定動態庫搜索路徑時,都可指定多個動態庫搜索路徑,其搜索的先后順序是按指定路徑的先后順序搜索的。對此本文不再舉例說明,有興趣的讀者可以參照本文的方法驗證。
posted @
2010-09-14 11:03 xiaoxinchen 閱讀(226) |
評論 (0) |
編輯 收藏

2010年8月8日
序論
我曾發表過文件輸入輸出的文章,現在覺得有必要再寫一點。文件 I/O 在C++中比烤蛋糕簡單多了。 在這篇文章里,我會詳細解釋ASCII和二進制文件的輸入輸出的每個細節,值得注意的是,所有這些都是用C++完成的。
一、ASCII 輸出
為了使用下面的方法,
你必須包含頭文件<fstream.h>(譯者注:在標準C++中,已經使用<fstream>取
代<fstream.h>,所有的C++標準頭文件都是無后綴的。)。這是 <iostream.h>的一個擴展集,
提供有緩沖的文件輸入輸出操作. 事實上, <iostream.h> 已經被<fstream.h>包含了,
所以你不必包含所有這兩個文件, 如果你想顯式包含他們,那隨便你。我們從文件操作類的設計開始, 我會講解如何進行ASCII I/O操作。
如果你猜是"fstream," 恭喜你答對了! 但這篇文章介紹的方法,我們分別使用"ifstream"?和 "ofstream" 來作輸入輸出。
如果你用過標準控制臺流"cin"?和 "cout," 那現在的事情對你來說很簡單。 我們現在開始講輸出部分,首先聲明一個類對象。
ofstream fout;
這就可以了,不過你要打開一個文件的話, 必須像這樣調用ofstream::open()。
fout.open("output.txt");
你也可以把文件名作為構造參數來打開一個文件.
ofstream fout("output.txt");
這是我們使用的方法, 因為這樣創建和打開一個文件看起來更簡單. 順便說一句, 如果你要打開的文件不存在,它會為你創建一個,
所以不用擔心文件創建的問題. 現在就輸出到文件,看起來和"cout"的操作很像。 對不了解控制臺輸出"cout"的人, 這里有個例子。
int num = 150;char name[] = "John Doe";fout << "Here is a number: " << num << "\n";fout << "Now here is a string: " << name << "\n";
現在保存文件,你必須關閉文件,或者回寫文件緩沖. 文件關閉之后就不能再操作了,
所以只有在你不再操作這個文件的時候才調用它,它會自動保存文件。 回寫緩沖區會在保持文件打開的情況下保存文件, 所以只要有必要就使用它。
回寫看起來像另一次輸出, 然后調用方法關閉。像這樣:
fout << flush; fout.close();
現在你用文本編輯器打開文件,內容看起來是這樣:
Here is a number: 150 Now here is a string: John Doe
很簡單吧! 現在繼續文件輸入, 需要一點技巧, 所以先確認你已經明白了流操作,對 "<<" 和">>" 比較熟悉了, 因為你接下來還要用到他們。繼續…
二、ASCII 輸入
輸入和"cin" 流很像. 和剛剛討論的輸出流很像, 但你要考慮幾件事情。在我們開始復雜的內容之前, 先看一個文本:
12 GameDev 15.45 L This is really awesome!
為了打開這個文件,你必須創建一個in-stream對象,?像這樣。
ifstream fin("input.txt");
現在讀入前四行. 你還記得怎么用"<<" 操作符往流里插入變量和符號吧?好,?在 "<<" (插入)?操作符之后,是">>" (提取) 操作符. 使用方法是一樣的. 看這個代碼片段.
int number; float real; char letter, word[8]; fin >> number; fin >> word; fin >> real; fin >> letter;
也可以把這四行讀取文件的代碼寫為更簡單的一行。
fin >> number >> word >> real >> letter;
它是如何運作的呢? 文件的每個空白之后, ">>" 操作符會停止讀取內容, 直到遇到另一個>>操作符.
因為我們讀取的每一行都被換行符分割開(是空白字符), ">>"
操作符只把這一行的內容讀入變量。這就是這個代碼也能正常工作的原因。但是,可別忘了文件的最后一行。
This is really awesome!
如果你想把整行讀入一個char數組, 我們沒辦法用">>"?操作符,因為每個單詞之間的空格(空白字符)會中止文件的讀取。為了驗證:
char sentence[101]; fin >> sentence;
我們想包含整個句子, "This is really awesome!" 但是因為空白, 現在它只包含了"This". 很明顯, 肯定有讀取整行的方法, 它就是getline()。這就是我們要做的。
fin.getline(sentence, 100);
這是函數參數. 第一個參數顯然是用來接受的char數組. 第二個參數是在遇到換行符之前,數組允許接受的最大元素數量. 現在我們得到了想要的結果:“This is really awesome!”。
你應該已經知道如何讀取和寫入ASCII文件了。但我們還不能罷休,因為二進制文件還在等著我們。
三、二進制 輸入輸出
二進制文件會復雜一點, 但還是很簡單的。
首先你要注意我們不再使用插入和提取操作符(譯者注:<< 和 >> 操作符).
你可以這么做,但它不會用二進制方式讀寫。你必須使用read() 和write() 方法讀取和寫入二進制文件. 創建一個二進制文件, 看下一行。
ofstream fout("file.dat", ios::binary);
這會以二進制方式打開文件, 而不是默認的ASCII模式。首先從寫入文件開始。函數write() 有兩個參數。 第一個是指向對象的char類型的指針, 第二個是對象的大小(譯者注:字節數)。 為了說明,看例子。
int number = 30; fout.write((char *)(&number), sizeof(number));
第一個參數寫做"(char *)(&number)". 這是把一個整型變量轉為char
*指針。如果你不理解,可以立刻翻閱C++的書籍,如果有必要的話。第二個參數寫作"sizeof(number)". sizeof()
返回對象大小的字節數. 就是這樣!
二進制文件最好的地方是可以在一行把一個結構寫入文件。 如果說,你的結構有12個不同的成員。 用ASCII?文件,你不得不每次一條的寫入所有成員。 但二進制文件替你做好了。 看這個。
struct OBJECT { int number; char letter; } obj; obj.number = 15;obj.letter = ‘M’; fout.write((char *)(&obj), sizeof(obj));
這樣就寫入了整個結構! 接下來是輸入. 輸入也很簡單,因為read()?函數的參數和 write()是完全一樣的, 使用方法也相同。
ifstream fin("file.dat", ios::binary); fin.read((char *)(&obj), sizeof(obj));
我不多解釋用法, 因為它和write()是完全相同的。二進制文件比ASCII文件簡單, 但有個缺點是無法用文本編輯器編輯。 接著, 我解釋一下ifstream 和ofstream 對象的其他一些方法作為結束.
四、更多方法
我已經解釋了ASCII文件和二進制文件, 這里是一些沒有提及的底層方法。
檢查文件
你已經學會了open() 和close() 方法, 不過這里還有其它你可能用到的方法。
方法good() 返回一個布爾值,表示文件打開是否正確。
類似的,bad() 返回一個布爾值表示文件打開是否錯誤。 如果出錯,就不要繼續進一步的操作了。
最后一個檢查的方法是fail(), 和bad()有點相似, 但沒那么嚴重。
讀文件
方法get() 每次返回一個字符。
方法ignore(int,char) 跳過一定數量的某個字符, 但你必須傳給它兩個參數。第一個是需要跳過的字符數。 第二個是一個字符, 當遇到的時候就會停止。 例子,
fin.ignore(100, ‘\n’);
會跳過100個字符,或者不足100的時候,跳過所有之前的字符,包括 ‘\n’。
方法peek() 返回文件中的下一個字符, 但并不實際讀取它。所以如果你用peek() 查看下一個字符, 用get() 在peek()之后讀取,會得到同一個字符, 然后移動文件計數器。
方法putback(char) 輸入字符, 一次一個, 到流中。我沒有見到過它的使用,但這個函數確實存在。
寫文件
只有一個你可能會關注的方法.?那就是 put(char), 它每次向輸出流中寫入一個字符。
打開文件
當我們用這樣的語法打開二進制文件:
ofstream fout("file.dat", ios::binary);
"ios::binary"是你提供的打開選項的額外標志. 默認的, 文件以ASCII方式打開, 不存在則創建, 存在就覆蓋. 這里有些額外的標志用來改變選項。
ios::app |
添加到文件尾 |
ios::ate |
把文件標志放在末尾而非起始。 |
ios::trunc |
默認. 截斷并覆寫文件。 |
ios::nocreate |
文件不存在也不創建。 |
ios::noreplace |
文件存在則失敗。 |
文件狀態
我用過的唯一一個狀態函數是eof(), 它返回是否標志已經到了文件末尾。 我主要用在循環中。 例如, 這個代碼斷統計小寫‘e’ 在文件中出現的次數。
ifstream fin("file.txt"); char ch; int counter; while (!fin.eof()) { ch = fin.get(); if (ch == ‘e’) counter++; }fin.close();
我從未用過這里沒有提到的其他方法。 還有很多方法,但是他們很少被使用。參考C++書籍或者文件流的幫助文檔來了解其他的方法。
posted @
2010-08-08 17:37 xiaoxinchen 閱讀(190) |
評論 (0) |
編輯 收藏

2010年3月21日
什么是Socket
Socket接口是TCP/IP網絡的API,Socket接口定義了許多函數或例程,程序員可以用它們來開發TCP/IP網絡上的應用程序。要學Internet上的TCP/IP網絡編程,必須理解Socket接口。
Socket接口設計者最先是將接口放在Unix操作系統里面的。如果了解Unix系統的輸入和輸出的話,就很容易了解Socket了。網絡的 Socket數據傳輸是一種特殊的I/O,Socket也是一種文件描述符。Socket也具有一個類似于打開文件的函數調用Socket(),該函數返 回一個整型的Socket描述符,隨后的連接建立、數據傳輸等操作都是通過該Socket實現的。常用的Socket類型有兩種:流式Socket (SOCK_STREAM)和數據報式Socket(SOCK_DGRAM)。流式是一種面向連接的Socket,針對于面向連接的TCP服務應用;數據 報式Socket是一種無連接的Socket,對應于無連接的UDP服務應用。
Socket建立
為了建立Socket,程序可以調用Socket函數,該函數返回一個類似于文件描述符的句柄。socket函數原型為:
int socket(int domain, int type, int protocol);
domain指明所使用的協議族,通常為PF_INET,表示互聯網協議族(TCP/IP協議族);type參數指定socket的類型: SOCK_STREAM 或SOCK_DGRAM,Socket接口還定義了原始Socket(SOCK_RAW),允許程序使用低層協議;protocol通常賦值"0"。 Socket()調用返回一個整型socket描述符,你可以在后面的調用使用它。
Socket描述符是一個指向內部數據結構的指針,它指向描述符表入口。調用Socket函數時,socket執行體將建立一個Socket,實際上"建立一個Socket"意味著為一個Socket數據結構分配存儲空間。Socket執行體為你管理描述符表。
兩個網絡程序之間的一個網絡連接包括五種信息:通信協議、本地協議地址、本地主機端口、遠端主機地址和遠端協議端口。Socket數據結構中包含這五種信息。
Socket配置
通過socket調用返回一個socket描述符后,在使用socket進行網絡傳輸以前,必須配置該socket。面向連接的socket客戶端通過 調用Connect函數在socket數據結構中保存本地和遠端信息。無連接socket的客戶端和服務端以及面向連接socket的服務端通過調用 bind函數來配置本地信息。
Bind函數將socket與本機上的一個端口相關聯,隨后你就可以在該端口監聽服務請求。Bind函數原型為:
int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
Sockfd是調用socket函數返回的socket描述符,my_addr是一個指向包含有本機IP地址及端口號等信息的sockaddr類型的指針;addrlen常被設置為sizeof(struct sockaddr)。
struct sockaddr結構類型是用來保存socket信息的:
struct sockaddr {
unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字節的協議地址 */
};
sa_family一般為AF_INET,代表Internet(TCP/IP)地址族;sa_data則包含該socket的IP地址和端口號。
另外還有一種結構類型:
struct sockaddr_in {
short int sin_family; /* 地址族 */
unsigned short int sin_port; /* 端口號 */
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /* 填充0 以保持與struct sockaddr同樣大小 */
};
這個結構更方便使用。sin_zero用來將sockaddr_in結構填充到與struct sockaddr同樣的長度,可以用bzero()或memset()函數將其置為零。指向sockaddr_in 的指針和指向sockaddr的指針可以相互轉換,這意味著如果一個函數所需參數類型是sockaddr時,你可以在函數調用的時候將一個指向 sockaddr_in的指針轉換為指向sockaddr的指針;或者相反。
使用bind函數時,可以用下面的賦值實現自動獲得本機IP地址和隨機獲取一個沒有被占用的端口號:
my_addr.sin_port = 0; /* 系統隨機選擇一個未被使用的端口號 */
my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本機IP地址 */
通過將my_addr.sin_port置為0,函數會自動為你選擇一個未占用的端口來使用。同樣,通過將my_addr.sin_addr.s_addr置為INADDR_ANY,系統會自動填入本機IP地址。
注意在使用bind函數是需要將sin_port和sin_addr轉換成為網絡字節優先順序;而sin_addr則不需要轉換。
計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位字節優先順序在網絡上傳輸,所以對于在內部是以低位字節優先方式存儲數據的機器,在Internet上傳輸數據時就需要進行轉換,否則就會出現數據不一致。
下面是幾個字節順序轉換函數:
·htonl():把32位值從主機字節序轉換成網絡字節序
·htons():把16位值從主機字節序轉換成網絡字節序
·ntohl():把32位值從網絡字節序轉換成主機字節序
·ntohs():把16位值從網絡字節序轉換成主機字節序
Bind()函數在成功被調用時返回0;出現錯誤時返回"-1"并將errno置為相應的錯誤號。需要注意的是,在調用bind函數時一般不要將端口號置為小于1024的值,因為1到1024是保留端口號,你可以選擇大于1024中的任何一個沒有被占用的端口號。
連接建立
面向連接的客戶程序使用Connect函數來配置socket并與遠端服務器建立一個TCP連接,其函數原型為:
int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
Sockfd 是socket函數返回的socket描述符;serv_addr是包含遠端主機IP地址和端口號的指針;addrlen是遠端地質結構的長度。 Connect函數在出現錯誤時返回-1,并且設置errno為相應的錯誤碼。進行客戶端程序設計無須調用bind(),因為這種情況下只需知道目的機器 的IP地址,而客戶通過哪個端口與服務器建立連接并不需要關心,socket執行體為你的程序自動選擇一個未被占用的端口,并通知你的程序數據什么時候到 打斷口。
Connect函數啟動和遠端主機的直接連接。只有面向連接的客戶程序使用socket時才需要將此socket與遠端主機相連。無連接協議從不建立直接連接。面向連接的服務器也從不啟動一個連接,它只是被動的在協議端口監聽客戶的請求。
Listen函數使socket處于被動的監聽模式,并為該socket建立一個輸入數據隊列,將到達的服務請求保存在此隊列中,直到程序處理它們。
int listen(int sockfd, int backlog);
Sockfd 是Socket系統調用返回的socket 描述符;backlog指定在請求隊列中允許的最大請求數,進入的連接請求將在隊列中等待accept()它們(參考下文)。Backlog對隊列中等待 服務的請求的數目進行了限制,大多數系統缺省值為20。如果一個服務請求到來時,輸入隊列已滿,該socket將拒絕連接請求,客戶將收到一個出錯信息。
當出現錯誤時listen函數返回-1,并置相應的errno錯誤碼。
accept()函數讓服務器接收客戶的連接請求。在建立好輸入隊列后,服務器就調用accept函數,然后睡眠并等待客戶的連接請求。
int accept(int sockfd, void *addr, int *addrlen);
sockfd是被監聽的socket描述符,addr通常是一個指向sockaddr_in變量的指針,該變量用來存放提出連接請求服務的主機的信息(某 臺主機從某個端口發出該請求);addrten通常為一個指向值為sizeof(struct sockaddr_in)的整型指針變量。出現錯誤時accept函數返回-1并置相應的errno值。
首先,當accept函數監視的 socket收到連接請求時,socket執行體將建立一個新的socket,執行體將這個新socket和請求連接進程的地址聯系起來,收到服務請求的 初始socket仍可以繼續在以前的 socket上監聽,同時可以在新的socket描述符上進行數據傳輸操作。
數據傳輸
Send()和recv()這兩個函數用于面向連接的socket上進行數據傳輸。
Send()函數原型為:
int send(int sockfd, const void *msg, int len, int flags);
Sockfd是你想用來傳輸數據的socket描述符;msg是一個指向要發送數據的指針;Len是以字節為單位的數據的長度;flags一般情況下置為0(關于該參數的用法可參照man手冊)。
Send()函數返回實際上發送出的字節數,可能會少于你希望發送的數據。在程序中應該將send()的返回值與欲發送的字節數進行比較。當send()返回值與len不匹配時,應該對這種情況進行處理。
char *msg = "Hello!";
int len, bytes_sent;
……
len = strlen(msg);
bytes_sent = send(sockfd, msg,len,0);
……
recv()函數原型為:
int recv(int sockfd,void *buf,int len,unsigned int flags);
Sockfd是接受數據的socket描述符;buf 是存放接收數據的緩沖區;len是緩沖的長度。Flags也被置為0。Recv()返回實際上接收的字節數,當出現錯誤時,返回-1并置相應的errno值。
Sendto()和recvfrom()用于在無連接的數據報socket方式下進行數據傳輸。由于本地socket并沒有與遠端機器建立連接,所以在發送數據時應指明目的地址。
sendto()函數原型為:
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
該函數比send()函數多了兩個參數,to表示目地機的IP地址和端口號信息,而tolen常常被賦值為sizeof (struct sockaddr)。Sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1。
Recvfrom()函數原型為:
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是一個struct sockaddr類型的變量,該變量保存源機的IP地址及端口號。fromlen常置為sizeof (struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。Recvfrom()函數返回接收到的字節數或 當出現錯誤時返回-1,并置相應的errno。
如果你對數據報socket調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,并且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動為之加上目地和源地址信息。
結束傳輸
當所有的數據操作結束以后,你可以調用close()函數來釋放該socket,從而停止在該socket上的任何數據操作:
close(sockfd);
你也可以調用shutdown()函數來關閉該socket。該函數允許你只停止在某個方向上的數據傳輸,而一個方向上的數據傳輸繼續進行。如你可以關閉某socket的寫操作而允許繼續在該socket上接受數據,直至讀入所有數據。
int shutdown(int sockfd,int how);
Sockfd是需要關閉的socket的描述符。參數 how允許為shutdown操作選擇以下幾種方式:
·0-------不允許繼續接收數據
·1-------不允許繼續發送數據
·2-------不允許繼續發送和接收數據,
·均為允許則調用close ()
shutdown在操作成功時返回0,在出現錯誤時返回-1并置相應errno。
Socket編程實例
代碼實例中的服務器通過socket連接向客戶端發送字符串"Hello, you are connected!"。只要在服務器上運行該服務器軟件,在客戶端運行客戶軟件,客戶端就會收到該字符串。
該服務器軟件代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define SERVPORT 3333 /*服務器監聽端口號 */
#define BACKLOG 10 /* 最大同時連接請求數 */
main()
{
int sockfd,client_fd; /*sock_fd:監聽socket;client_fd:數據傳輸socket */
struct sockaddr_in my_addr; /* 本機地址信息 */
struct sockaddr_in remote_addr; /* 客戶端地址信息 */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket創建出錯!"); exit(1);
}
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(SERVPORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
perror("bind出錯!");
exit(1);
}
if (listen(sockfd, BACKLOG) == -1) {
perror("listen出錯!");
exit(1);
}
while(1) {
sin_size = sizeof(struct sockaddr_in);
if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
perror("accept出錯");
continue;
}
printf("received a connection from %s\n", inet_ntoa(remote_addr.sin_addr));
if (!fork()) { /* 子進程代碼段 */
if (send(client_fd, "Hello, you are connected!\n", 26, 0) == -1)
perror("send出錯!");
close(client_fd);
exit(0);
}
close(client_fd);
}
}
}
服務器的工作流程是這樣的:首先調用socket函數創建一個Socket,然后調用bind函數將其與本機地址以及一個本地端口號綁定,然后調用 listen在相應的socket上監聽,當accpet接收到一個連接服務請求時,將生成一個新的socket。服務器顯示該客戶機的IP地址,并通過 新的socket向客戶端發送字符串"Hello,you are connected!"。最后關閉該socket。
代碼實例中的fork()函數生成一個子進程來處理數據傳輸部分,fork()語句對于子進程返回的值為0。所以包含fork函數的if語句是子進程代碼部分,它與if語句后面的父進程代碼部分是并發執行的。
客戶端程序代碼如下:
#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define SERVPORT 3333
#define MAXDATASIZE 100 /*每次最大數據傳輸量 */
main(int argc, char *argv[]){
int sockfd, recvbytes;
char buf[MAXDATASIZE];
struct hostent *host;
struct sockaddr_in serv_addr;
if (argc < 2) {
fprintf(stderr,"Please enter the server's hostname!\n");
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL) {
herror("gethostbyname出錯!");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket創建出錯!");
exit(1);
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERVPORT);
serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
if (connect(sockfd, (struct sockaddr *)&serv_addr, \
sizeof(struct sockaddr)) == -1) {
perror("connect出錯!");
exit(1);
}
if ((recvbytes=recv(sockfd, buf, MAXDATASIZE, 0)) ==-1) {
perror("recv出錯!");
exit(1);
}
buf[recvbytes] = '\0';
printf("Received: %s",buf);
close(sockfd);
}
客戶端程序首先通過服務器域名獲得服務器的IP地址,然后創建一個socket,調用connect函數與服務器建立連接,連接成功之后接收從服務器發送過來的數據,最后關閉socket。
函數gethostbyname()是完成域名轉換的。由于IP地址難以記憶和讀寫,所以為了方便,人們常常用域名來表示主機,這就需要進行域名和IP地址的轉換。函數原型為:
struct hostent *gethostbyname(const char *name);
函數返回為hosten的結構類型,它的定義如下:
struct hostent {
char *h_name; /* 主機的官方域名 */
char **h_aliases; /* 一個以NULL結尾的主機別名數組 */
int h_addrtype; /* 返回的地址類型,在Internet環境下為AF-INET */
int h_length; /* 地址的字節長度 */
char **h_addr_list; /* 一個以0結尾的數組,包含該主機的所有地址*/
};
#define h_addr h_addr_list[0] /*在h-addr-list中的第一個地址*/
當 gethostname()調用成功時,返回指向struct hosten的指針,當調用失敗時返回-1。當調用gethostbyname時,你不能使用perror()函數來輸出錯誤信息,而應該使用herror()函數來輸出。
無連接的客戶/服務器程序的在原理上和連接的客戶/服務器是一樣的,兩者的區別在于無連接的客戶/服務器中的客戶一般不需要建立連接,而且在發送接收數據時,需要指定遠端機的地址。
阻塞和非阻塞
阻塞函數在完成其指定的任務以前不允許程序調用另一個函數。例如,程序執行一個讀數據的函數調用時,在此函數完成讀操作以前將不會執行下一程序語句。當 服務器運行到accept語句時,而沒有客戶連接服務請求到來,服務器就會停止在accept語句上等待連接服務請求的到來。這種情況稱為阻塞 (blocking)。而非阻塞操作則可以立即完成。比如,如果你希望服務器僅僅注意檢查是否有客戶在等待連接,有就接受連接,否則就繼續做其他事情,則 可以通過將Socket設置為非阻塞方式來實現。非阻塞socket在沒有客戶在等待時就使accept調用立即返回。
#include <unistd.h>
#include <fcntl.h>
……
sockfd = socket(AF_INET,SOCK_STREAM,0);
fcntl(sockfd,F_SETFL,O_NONBLOCK);
……
通過設置socket為非阻塞方式,可以實現"輪詢"若干Socket。當企圖從一個沒有數據等待處理的非阻塞Socket讀入數據時,函數將立即返 回,返回值為-1,并置errno值為EWOULDBLOCK。但是這種"輪詢"會使CPU處于忙等待方式,從而降低性能,浪費系統資源。而調用 select()會有效地解決這個問題,它允許你把進程本身掛起來,而同時使系統內核監聽所要求的一組文件描述符的任何活動,只要確認在任何被監控的文件 描述符上出現活動,select()調用將返回指示該文件描述符已準備好的信息,從而實現了為進程選出隨機的變化,而不必由進程本身對輸入進行測試而浪費 CPU開銷。Select函數原型為:
int select(int numfds,fd_set *readfds,fd_set *writefds,
fd_set *exceptfds,struct timeval *timeout);
其中readfds、writefds、exceptfds分別是被select()監視的讀、寫和異常處理的文件描述符集合。如果你希望確定是否可以 從標準輸入和某個socket描述符讀取數據,你只需要將標準輸入的文件描述符0和相應的sockdtfd加入到readfds集合中;numfds的值 是需要檢查的號碼最高的文件描述符加1,這個例子中numfds的值應為sockfd+1;當select返回時,readfds將被修改,指示某個文件 描述符已經準備被讀取,你可以通過FD_ISSSET()來測試。為了實現fd_set中對應的文件描述符的設置、復位和測試,它提供了一組宏:
FD_ZERO(fd_set *set)----清除一個文件描述符集;
FD_SET(int fd,fd_set *set)----將一個文件描述符加入文件描述符集中;
FD_CLR(int fd,fd_set *set)----將一個文件描述符從文件描述符集中清除;
FD_ISSET(int fd,fd_set *set)----試判斷是否文件描述符被置位。
Timeout參數是一個指向struct timeval類型的指針,它可以使select()在等待timeout長時間后沒有文件描述符準備好即返回。struct timeval數據結構為:
struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};
POP3客戶端實例
下面的代碼實例基于POP3的客戶協議,與郵件服務器連接并取回指定用戶帳號的郵件。與郵件服務器交互的命令存儲在字符串數組POPMessage中,程序通過一個do-while循環依次發送這些命令。
#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define POP3SERVPORT 110
#define MAXDATASIZE 4096
main(int argc, char *argv[]){
int sockfd;
struct hostent *host;
struct sockaddr_in serv_addr;
char *POPMessage[]={
"USER userid\r\n",
"PASS password\r\n",
"STAT\r\n",
"LIST\r\n",
"RETR 1\r\n",
"DELE 1\r\n",
"QUIT\r\n",
NULL
};
int iLength;
int iMsg=0;
int iEnd=0;
char buf[MAXDATASIZE];
if((host=gethostbyname("your.server"))==NULL) {
perror("gethostbyname error");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket error");
exit(1);
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(POP3SERVPORT);
serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
if (connect(sockfd, (struct sockaddr *)&serv_addr,sizeof(struct sockaddr))==-1){
perror("connect error");
exit(1);
}
do {
send(sockfd,POPMessage[iMsg],strlen(POPMessage[iMsg]),0);
printf("have sent: %s",POPMessage[iMsg]);
iLength=recv(sockfd,buf+iEnd,sizeof(buf)-iEnd,0);
iEnd+=iLength;
buf[iEnd]='\0';
printf("received: %s,%d\n",buf,iMsg);
iMsg++;
} while (POPMessage[iMsg]);
close(sockfd);
}
來自:Li
posted @
2010-03-21 21:01 xiaoxinchen 閱讀(226) |
評論 (0) |
編輯 收藏

2010年3月13日
總體思路是先打成jar再把jar打成exe。主要看1.3和2.3里的內容就可以了。
1.將項目打成jar:
1.1
要將項目打包成jar文件,方法很多,可以用Eclipse自帶的打包工具Ant打包,也可以用Eclipse的Export生成jar。經過嘗試后,我
不推薦用Ant打包,因為要自己編寫xml腳本語言,還要增加一些外部的jar,所以我打了好幾次都沒打成。
1.2
在這里介紹兩種方法生成jar,第一種是用Eclpise的Export功能。在要打包的項目上擊右鍵,選擇Export,在窗口中選擇Java里的
JAR file。Next后的窗口中已經自動選好了要打包的項目,用戶可以點擊加號查看項目里被打包的內容。在下面的JAR
file里設置你打包生成jar文件的輸出目錄,下一步在出現的窗口中選擇Use existing manifest from
workspace,在下面的Main
class后面直接點Browse,它會自動列出你項目中有主函數main的類。選擇主類后點Finish即可生成jar文件。在此說明一下,這種打包方
法不能把項目中的外部的jar包打進來,因該是也要編寫一些腳本語言,沒往深研究。所以生成后的jar有些是不能執行的。
1.3 第二種方法是利用Eclipse的一個第三方插件fatjar生成jar文件,也是本人覺得最簡單最方便的一種生成方式。先從網上下載些
插件,解壓后是一個plugins的文件夾,里面只有一個文件夾,我的是“net.sf.fjep.fatjar_0.0.24”將它copy到
Eclipser plugins文件夾下,此插件就安裝成功了,重啟Eclipse在項目上右擊就會看到多出一個“Build Fat
Jar”在前面有個綠色的“+”號,這時你就可以用此插件打包你的項目了。進去后第一個界面Jar-Name里增入要生成的jar文件名,我的是
“CAMP_fat.jar”。在Main-Class后點Browse像Export一樣它也會列出你項目中的主類,選擇后其它默認即可,Next后會
列出你要打包的所有內容,這個插件的優勢就是可以將你項目中的外部jar也打進來,有三個先項,其中Export
ANT是生成build.xml腳本文件,方便用戶以后修改腳本,其它兩個按鈕沒用。在這里什么都不點,直接點Finish就可以生成jar文件。
2.將jar打成.exe文件:
2.1
雖然此時的jar文件已經可以執行了。生成.exe的文件我也是用兩種方法實現的,用到的打包工具是j2ewiz和exe4j,它們的不同會在我下面的介
紹中體現出來。
2.2 首先是j2ewiz,這個軟件是綠色的,不用安裝,解壓后可以直接運行,但這個軟件生成的
.exe文件不是跨平臺的。運行此程序首先就是輸入要打包的jar文件,我們瀏覽JAR選擇我們之前用fatjar生成的“CAMP_fat.jar”項
目文件(詳見1.3),下面那個選項是提示用戶最低要求的JRE版本,一般選1.3。下一步,因為我們的寢室管理系統是圖形界面,所以在這里選
“Windows窗口程序”下一步它也是自動生成要執行的主類,你只要選擇就可以。下面的選框可以選擇你啟動程序顯示的圖片。下一步后這個窗可按個人喜好
選擇。下一步,如果你的程序還有什么依賴的外部jar文件,可以從這里加上,但因為之前的fatjar以經將我們項目所用的那三個連數據庫的外部類打進
CAMP_fat.jar包里了,所以這里不用再添加。如果你之前是用Export打的jar
包,那么這里就需要再把那個三個數據庫的包加進來了(詳見1.2)。下一步是添入要生成的.exe文件名,再選一個程序圖標就可以了,下一步后生
成.exe文件,點完成。雙擊生成的.exe文件就能看到運行效果了,這種exe文件還沒有脫離JDK環境,還不能跨平臺使用,只能用于小組成員測試使
用。
2.3
下面進入最關鍵的,如何打包跨平臺的.exe文件。用到的軟件是exe4j,我用的是V4.0版的,此軟件需要破解。安裝后運行左窗窗口標有十步,其實打
包過程也非常簡單。第一步完全略過,直接點Next第二步我們選擇“JAR in EXE mode”
就是選擇我們已經有制作好的jar文件。第3步上面是項目名稱,可隨便填寫,下面一個寫出你想要將打包后的exe文件輸出的目錄我的是“桌
面\project\”。第4步,由于我的演示程序是圖形的,所以選第一個,如果你的程序是控制臺的,則選擇第二個,Executable
name寫你將要生成的.exe文件的名字,Icon
File可以選擇生成文件的圖標。第5步,先別管上面的,先在下面單擊綠色的“+”號,在彈出的窗口中點Archive,然后找到起初已經做好的
CAMP_fat.jar(詳見1.3)文件,"OK"后返回,在下面的Class Path里就出現jar文件路徑后,再在上面Main
Class欄內點擊找到main所在的類。第6步,你系統的JRE版本,一般是填個1.3,下面填1.6在這里單擊advanced
options,選擇search
sequence。選這個就是因為我們要把JDK環境也打包進來,好讓程序能跨平臺使用。首先要從你系統的JDK下的JRE目錄copy到你.exe文件
的輸出目錄下“桌面\project\JRE”,然后回到exe4j中在彈出窗口刪除列表中的所有項。我的是三項,一個注冊表的,一個JAVA環境變量
的,一個JDK環境變量的,都不要。然后單擊綠“+”,選擇directory并選擇JRE的根目錄,我的是“桌面\project\JRE”就是
copy后的目錄,選完后exe4j彈出窗口中的Directory里會顯示“.\JRE”。點OK關閉該窗口,返回exe4j的主窗口,你就可以看到剛
加的路徑。再從主窗口左側窗口中單擊advanced options,并選擇preferred VM,在彈出的窗口中選擇client
hostspot VM,單擊next按鈕繼續。7、8步是一些個性設置默認即可。第9步編譯完后第10步你點那個“Click Here to
Start the Application”按鈕就可以看到程序運行效果了,然后再點”Seave
as”保存一個exe4j生成的一個文件,隨便存哪里都行,和我們的.exe程序無關。全部制作過程就完工了。
posted @
2010-03-13 18:06 xiaoxinchen 閱讀(3914) |
評論 (0) |
編輯 收藏

2010年3月11日
首先要弄清楚,在Linux系統中,內核為每一個新創建的文件分配一個Inode(索引結點),每個文件都有一個惟一的inode號。文件屬性保存在索引結點里,在訪問文件時,索引結點被復制到內存在,從而實現文件的快速訪問。
鏈接是一種在共享文件和訪問它的用戶的若干目錄項之間建立聯系的一種方法。Linux中包括兩種鏈接:硬鏈接(Hard Link)和軟鏈接(Soft Link),軟鏈接又稱為符號鏈接(Symbolic link)。
一、軟鏈接(符號鏈接)
軟鏈接克服了硬鏈接的不足,沒有任何文件系統的限制,任何用戶可以創建指向目錄的符號鏈接。因而現在更為廣泛使用,它具有更大的靈活性,甚至可以跨越不同機器、不同網絡對文件進行鏈接。
建立軟鏈接,只要在ln后面加上選項 –s。
二、硬鏈接
硬鏈接說白了是一個指針,指向文件索引節點,系統并不為它重新分配inode。可以用:ln命令來建立硬鏈接。語法
ln [options] existingfile newfile
ln[options] existingfile-list directory
用法:
第一種:為”existingfile”創建硬鏈接,文件名為”newfile”。第二種:在”directory”目錄中,
為”existingfile-list”中包含的所有文件創建一個同名的硬鏈接。常用可選[options] –f
無論”newfile”存在與否,都創建鏈接。-n 如果”newfile”已存在,就不創建鏈接。
posted @
2010-03-11 16:07 xiaoxinchen 閱讀(224) |
評論 (0) |
編輯 收藏
(1)Jre 是java runtime environment,
是java程序的運行環境。既然是運行,當然要包含jvm,也就是大家熟悉的虛擬機啦,
還有所有java類庫的class文件,都在lib目錄下打包成了jar。大家可以自己驗證。至于在windows上的虛擬機是哪個文件呢?
學過MFC的都知道什么是dll文件吧,那么大家看看jre/bin/client里面是不是有一個jvm.dll呢?那就是虛擬機。
(2)Jdk 是java development kit,是java的開發工具包,里面包含了各種類庫和工具。當然也包括了另外一個Jre.
那么為什么要包括另外一個Jre呢?而且jdk/jre/bin同時有client和server兩個文件夾下都包含一個jvm.dll。
說明是有兩個虛擬機的。這一點不知道大家是否注意到了呢?
相信大家都知道jdk的bin下有各種java程序需要用到的命令,與jre的bin目錄最明顯的區別就是jdk下才有javac,這一點很好理解,因為
jre只是一個運行環境而已。與開發無關,正因為如此,具備開發功能的jdk自己的jre下才會同時有client性質的jvm和server性質的jvm, 而僅僅作為運行環境的jre下只需要client性質的jvm.dll就夠了。
(3)記得在環境變量path中設置jdk/bin路徑麼?這應該是大家學習Java的第一步吧,
老師會告訴大家不設置的話javac和java是用不了的。確實jdk/bin目錄下包含了所有的命令。可是有沒有人想過我們用的java命令并不是
jdk/bin目錄下的而是jre/bin目錄下的呢?不信可以做一個實驗,大家可以把jdk/bin目錄下的java.exe剪切到別的地方再運行
java程序,發現了什么?一切OK!
那么有人會問了?我明明沒有設置jre/bin目錄到環境變量中啊?
試想一下如果java為了提供給大多數人使用,他們是不需要jdk做開發的,只需要jre能讓java程序跑起來就可以了,那么每個客戶還需要手
動去設置環境變量多麻煩啊?所以安裝jre的時候安裝程序自動幫你把jre的java.exe添加到了系統變量中,驗證的方法很簡單,大家看到了系統環境
變量的
path最前面有“%SystemRoot%\system32;%SystemRoot%;”這樣的配置,那么再去Windows/system32下
面去看看吧,發現了什么?有一個java.exe。
如果強行能夠把jdk/bin挪到system32變量前面,當然也可以迫使使用jdk/jre里面的java,不過除非有必要,我不建議大家這么做。使用單獨的jre跑java程序也算是客戶環境下的一種測試。
posted @
2010-03-11 12:27 xiaoxinchen 閱讀(278) |
評論 (0) |
編輯 收藏

2010年3月3日
關于2009年12月全國大學英語四、六級考試成績發布時間的通知:
2009年12月全國大學英語四、六級考試成績將于
2010年3月3日上午9點發布。
成績查詢方式
網上免費查分:
網址: cet.99sushe.com
運營商: 99宿舍網
客服電話: 010-58699163轉867
收費短信查分(2010年3月3日上午9點開始):
中國移動、聯通、電信手機用戶:
發送A 加 15位準考證號到 1066335577
如A123456789012345到
1066335577查詢成績(1元/條,不含通信費)
特別注意:
河北省的中國移動手機用戶:發送 8 加
15位準考證號到 10661660
如8123456789012345到 10661660
查詢成績(1元/條,不含通信費)
運營商: 空中網
客服電話: 010-68083018
注:2009年12月網考成績發布方式和日期另行通知。
全國大學英語四、六級考試委員會辦公室
2010年2月24日
posted @
2010-03-03 00:50 xiaoxinchen 閱讀(237) |
評論 (0) |
編輯 收藏

2009年12月29日
在許多
平臺中,Browser
控件皆被做為一個必需的控件給出,并提供了DOM接口,用于
訪問Browser的內容,相對來說SWT中的Browser控件就比較薄弱,沒有提供DOM的可控制接口,那么,如何和控件所加載的
頁面進行交互呢?比如需要在集成web
應用的
環境中實現模仿登陸、
自動填表等
功能。
SWT中對Browser有不同的實現,目前實現的有IE和Mozilla。在Browser的構造
函數中根據不同的平臺和不同的style
設置類決定使用哪個類的實現。
org.eclipse.swt.browser.Mozilla org.eclipse.swt.browser.IE 是已經實現的,而其他的
org.eclipse.swt.browser.Safari org.eclipse.swt.browser.Voyager
來源:www.va1314.com/bc
則沒有實現。
public Browser (Composite parent, int style) {
super (checkParent (parent), checkStyle (style));
String platform = SWT.getPlatform ();
Display display = parent.getDisplay ();
if ("gtk".equals (platform)) display.setData (NO_INPUT_METHOD, null); //$NON-NLS-1$
String className = null;
if ((style & SWT.MOZILLA) != 0) {
className = "org.eclipse.swt.browser.Mozilla"; //$NON-NLS-1$
} else {
if ("win32".equals (platform) || "wpf".equals (platform)) { //$NON-NLS-1$ $NON-NLS-2$
className = "org.eclipse.swt.browser.IE"; //$NON-NLS-1$
} else if ("motif".equals (platform)) { //$NON-NLS-1$
className = "org.eclipse.swt.browser.Mozilla"; //$NON-NLS-1$
} else if ("gtk".equals (platform)) { //$NON-NLS-1$
className = "org.eclipse.swt.browser.Mozilla"; //$NON-NLS-1$
} else if ("carbon".equals (platform)) { //$NON-NLS-1$
className = "org.eclipse.swt.browser.Safari"; //$NON-NLS-1$
} else if ("photon".equals (platform)) { //$NON-NLS-1$
className = "org.eclipse.swt.browser.Voyager"; //$NON-NLS-1$
} else {
dispose ();
SWT.error (SWT.ERROR_NO_HANDLES);
}
}
try {
Class clazz = Class.forName (className);
webBrowser = (
WebBrowser)clazz.newInstance ();
} catch (ClassNotFoundException e) {
} catch (Illegal
AccessException e) {
} catch (InstantiationException e) {
}
if (webBrowser == null) {
dispose ();
SWT.error (SWT.ERROR_NO_HANDLES);
}
webBrowser.setBrowser (this);
webBrowser.create (parent, style);
}
public Browser (Composite parent, int style) {
super (checkParent (parent), checkStyle (style));
String platform = SWT.getPlatform ();
Display display = parent.getDisplay ();
if ("gtk".equals (platform)) display.setData (NO_INPUT_METHOD, null); //$NON-NLS-1$
String className = null;
if ((style & SWT.MOZILLA) != 0) {
className = "org.eclipse.swt.browser.Mozilla"; //$NON-NLS-1$
} else {
if ("win32".equals (platform) || "wpf".equals (platform)) { //$NON-NLS-1$ $NON-NLS-2$
className = "org.eclipse.swt.browser.IE"; //$NON-NLS-1$
} else if ("motif".equals (platform)) { //$NON-NLS-1$
className = "org.eclipse.swt.browser.Mozilla"; //$NON-NLS-1$
} else if ("gtk".equals (platform)) { //$NON-NLS-1$
className = "org.eclipse.swt.browser.Mozilla"; //$NON-NLS-1$
} else if ("carbon".equals (platform)) { //$NON-NLS-1$
className = "org.eclipse.swt.browser.Safari"; //$NON-NLS-1$
} else if ("photon".equals (platform)) { //$NON-NLS-1$
className = "org.eclipse.swt.browser.Voyager"; //$NON-NLS-1$
} else {
dispose ();
SWT.error (SWT.ERROR_NO_HANDLES);
}
}
try {
Class clazz = Class.forName (className);
webBrowser = (WebBrowser)clazz.newInstance ();
} catch (ClassNotFoundException e) {
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
}
if (webBrowser == null) {
dispose ();
SWT.error (SWT.ERROR_NO_HANDLES);
}
webBrowser.setBrowser (this);
webBrowser.create (parent, style);
}
其中對IE的實現主要是采用調用IE的Activex控件,間接加載IE,對Mozilla由于
代碼過多,本人沒有具體研究,其本身開源,有興趣能夠參看。
那么回歸主題,如何實現與Browser控件的交互呢? 其實仔細看Browser控件的API,能夠發覺一個execute()方法,這個方法適用于在web文檔加載完畢時能夠
運行javascript
code的。這樣的話,交互就變得簡單了,因為javascript是提供dom的支持的,既然能夠調用javascript,那么就能夠調用web頁面
中的每個節點了。控制的問題處理了,可是另外的問題來了。 如何從javascript的code里邊前往
數據呢?
比如我需要將一個<input type=text id=textid />的值前往到java
code中。其實采用的方法是很投機的,因為execute()方法前往的結果是true or
false,那么對它做文章是沒有用的,我們看其他的api,能夠發覺:addStatusTextListener()方法。
這個方法能夠監聽web頁面對于statusbar文本改變的值,并反映在java
code里面,那么我們只需通過javascript把前往的值寫到window.status,那么就能夠在javacode里取到了。
具體代碼請參考下面,對于Browser的承繼重寫,通過getValue能夠取得指定id的html 控件的值,通過setValue能夠設置值。
view plaincopy to clipboardprint?
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.StatusTextEvent;
import org.eclipse.swt.browser.StatusTextListener;
import org.eclipse.swt.widgets.Composite;
public class CoolBrowser extends Browser implements StatusTextListener {
private final String DATA = "Browser_Data";
public CoolBrowser(Composite parent, int style) {
super(parent, style);
addStatusTextListener(this);
}
@Override
protected void checkSubclass() {
}
/**
* Get the value of one input control in the web
* @param id
* @return
*/
public String getValue(String id) {
if (execute("var obj = document.getElementById('" + id + "');"
+ "if( obj != null ) window.status=obj.value;")) {
return (String) getData(DATA);
}
return null;
}
/**
* Set the value of the input control
* @param id
* @param value
*/
public void setValue( String id, Object value ){
if (execute("var obj = document.getElementById('" + id + "');"
+ "if( obj != null ) obj.value='" + value + "';")) {
}
}
@Override
public void changed(StatusTextEvent event) {
setData(DATA, event.text);
}
}
posted @
2009-12-29 16:28 xiaoxinchen 閱讀(5284) |
評論 (1) |
編輯 收藏