久久99中文字幕久久,99久久精品国产一区二区蜜芽,久久亚洲国产精品成人AV秋霞http://www.shnenglu.com/iuranus/category/4279.html<br><font color="#ADFF2F">Something Different,Something New</font>zh-cnTue, 07 Aug 2012 08:08:22 GMTTue, 07 Aug 2012 08:08:22 GMT60(轉(zhuǎn))MongoDB與內(nèi)存http://www.shnenglu.com/iuranus/archive/2012/08/06/186446.html攀升攀升Mon, 06 Aug 2012 05:36:00 GMThttp://www.shnenglu.com/iuranus/archive/2012/08/06/186446.htmlhttp://www.shnenglu.com/iuranus/comments/186446.htmlhttp://www.shnenglu.com/iuranus/archive/2012/08/06/186446.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/186446.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/186446.html閱讀全文

攀升 2012-08-06 13:36 發(fā)表評(píng)論
]]>
./configure -build,-host,-target設(shè)置http://www.shnenglu.com/iuranus/archive/2011/07/22/151615.html攀升攀升Fri, 22 Jul 2011 03:10:00 GMThttp://www.shnenglu.com/iuranus/archive/2011/07/22/151615.htmlhttp://www.shnenglu.com/iuranus/comments/151615.htmlhttp://www.shnenglu.com/iuranus/archive/2011/07/22/151615.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/151615.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/151615.htmlbuild:執(zhí)行代碼編譯的主機(jī),正常的話就是你的主機(jī)系統(tǒng)。這個(gè)參數(shù)一般由config.guess來猜就可以。當(dāng)然自己指定也可以。
host:編譯出來的二進(jìn)制程序所執(zhí)行的主機(jī),因?yàn)榻^大多數(shù)是如果本機(jī)編譯,本機(jī)執(zhí)行。所以這個(gè)值就等于build。只有交叉編譯的時(shí)候(也就是本機(jī)編譯,其他系統(tǒng)機(jī)器執(zhí)行)才會(huì)build和host不同。用host指定運(yùn)行主機(jī)。
target:這個(gè)選項(xiàng)只有在建立交叉編譯環(huán)境的時(shí)候用到,正常編譯和交叉編譯都不會(huì)用到。他用build主機(jī)上的編譯器,編譯一個(gè)新的編譯器(
binutils, gcc,gdb等),這個(gè)新的編譯器將來編譯出來的其他程序?qū)⑦\(yùn)行在target指定的系統(tǒng)上。
讓我們以編譯binutils為例:
1. `./configure --build=mipsel-linux --host=mipsel-linux --target=mipsel-linux' 
說明我們利用
mipsel-linux的編譯器對(duì)binutils進(jìn)行編譯,編譯出來的binutils運(yùn)行在mipsel-linux,這個(gè)binutils用來編譯能夠在mipsel-linux運(yùn)行的代碼。“當(dāng)然沒有人會(huì)用這個(gè)選項(xiàng)來編譯binutils”
2. `./configure --build=i386-linux --host=mipsel-linux
--target=mipsel-linux' will cross-build native mipsel-linux binutils oni386-linux.

說明我們利用i386-linux的編譯器對(duì)binutils進(jìn)行編譯,編譯出來的binutils運(yùn)行在mipsel-linux,這個(gè)binutils用來編譯能夠在mipsel-linux運(yùn)行的代碼。“這個(gè)選項(xiàng)可以用來為其他的機(jī)器編譯它的編譯器”。

3. `./configure --build=i386-linux --host=i386-linux
--target=mipsel-linux' will build mipsel-linux cross-binutils on i386-linux.
說明我們利用i386-linux的編譯器對(duì)binutils進(jìn)行編譯,編譯出來的binutils運(yùn)行在i386-linux,這個(gè)binutils用來編譯能夠在mipsel-linux運(yùn)行的代碼。“這個(gè)選項(xiàng)用來在i386主機(jī)上建立一個(gè)mipsel-linux的交叉編譯環(huán)境”。

4. `./configure --build=mipsel-linux --host=i386-linux
--target=mipsel-linux' will cross-build mipsel-linux cross-binutils for
i386-linux on mipsel-linux.
說明我們利用mipsel-linux的編譯器對(duì)binutils進(jìn)行編譯,編譯出來的binutils運(yùn)行在i386-linux,這個(gè)binutils用來編譯能夠在mipsel-linux運(yùn)行的代碼。“這個(gè)選項(xiàng)可以用來在i386主機(jī)上建立一個(gè)mipsel-linux的交叉編譯環(huán)境,但是交叉編譯環(huán)境在mipsel-linux 編譯出來,安裝到i386-linux主機(jī)上,估計(jì)沒有多少人會(huì)這么用吧

總的來說,只有host !=build的時(shí)候編譯才是交叉編譯。否則就是正常編譯。



攀升 2011-07-22 11:10 發(fā)表評(píng)論
]]>
UDP"打洞"原理http://www.shnenglu.com/iuranus/archive/2011/06/08/148303.html攀升攀升Wed, 08 Jun 2011 15:09:00 GMThttp://www.shnenglu.com/iuranus/archive/2011/06/08/148303.htmlhttp://www.shnenglu.com/iuranus/comments/148303.htmlhttp://www.shnenglu.com/iuranus/archive/2011/06/08/148303.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/148303.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/148303.html

1.       NAT分類

根據(jù)Stun協(xié)議(RFC3489),NAT大致分為下面四類

http://blog.csdn.net/ronmy/category/819006.aspx


1)      Full Cone

這種NAT內(nèi)部的機(jī)器A連接過外網(wǎng)機(jī)器C后,NAT會(huì)打開一個(gè)端口.然后外網(wǎng)的任何發(fā)到這個(gè)打開的端口的UDP數(shù)據(jù)報(bào)都可以到達(dá)A.不管是不是C發(fā)過來的.

例如 A:192.168.8.100 NAT:202.100.100.100 C:292.88.88.88

A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)

任何發(fā)送到 NAT(202.100.100.100:8000)的數(shù)據(jù)都可以到達(dá)A(192.168.8.100:5000)

2)      Restricted Cone

這種NAT內(nèi)部的機(jī)器A連接過外網(wǎng)的機(jī)器C后,NAT打開一個(gè)端口.然后C可以用任何端口和A通信.其他的外網(wǎng)機(jī)器不行.

例如 A:192.168.8.100 NAT:202.100.100.100 C:292.88.88.88

A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)

任何從C發(fā)送到 NAT(202.100.100.100:8000)的數(shù)據(jù)都可以到達(dá)A(192.168.8.100:5000)

 

3)      Port Restricted Cone

這種NAT內(nèi)部的機(jī)器A連接過外網(wǎng)的機(jī)器C后,NAT打開一個(gè)端口.然后C可以用原來的端口和A通信.其他的外網(wǎng)機(jī)器不行.

例如 A:192.168.8.100 NAT:202.100.100.100 C:292.88.88.88

A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)

C(202.88.88.88:2000)發(fā)送到 NAT(202.100.100.100:8000)的數(shù)據(jù)都可以到達(dá)A(192.168.8.100:5000)

 

以上三種NAT通稱Cone NAT.我們只能用這種NAT進(jìn)行UDP打洞.

4)      Symmetic

對(duì)于這種NAT.連接不同的外部目標(biāo).原來NAT打開的端口會(huì)變化.而Cone NAT不會(huì).雖然可以用端口猜測.但是成功的概率很小.因此放棄這種NAT的UDP打洞.

2.       UDP hole punching

對(duì)于Cone NAT.要采用UDP打洞.需要一個(gè)公網(wǎng)機(jī)器C來充當(dāng)”介紹人”.內(nèi)網(wǎng)的A,B先分別和C通信.打開各自的NAT端口.C這個(gè)時(shí)候知道A,B的公網(wǎng)IP: Port. 現(xiàn)在A和B想直接連接.比如A給B發(fā).除非B是Full Cone.否則不能通信.反之亦然.但是我們可以這樣.

A要連接B.A給B發(fā)一個(gè)UDP包.同時(shí).A讓那個(gè)介紹人給B發(fā)一個(gè)命令,讓B同時(shí)給A發(fā)一個(gè)UDP包.這樣雙方的NAT都會(huì)記錄對(duì)方的IP,然后就會(huì)允許互相通信.

3.       同一個(gè)NAT后面的情況

如果A,B在同一個(gè)NAT后面.如果用上面的技術(shù)來進(jìn)行互連.那么如果NAT支持loopback(就是本地到本地的轉(zhuǎn)換),A,B可以連接,但是比較浪費(fèi)帶寬和NAT.有一種辦法是,A,B和介紹人通信的時(shí)候,同時(shí)把自己的local IP也告訴服務(wù)器.A,B通信的時(shí)候,同時(shí)發(fā)local ip和公網(wǎng)IP.誰先到就用哪個(gè)IP.但是local ip就有可能不知道發(fā)到什么地方去了.比如A,B在不同的NAT后面但是他們各自的local ip段一樣.A給B的local IP發(fā)的UDP就可能發(fā)給自己內(nèi)部網(wǎng)里面的某某某了.

還有一個(gè)辦法是服務(wù)器來判斷A,B是否在一個(gè)NAT后面.(網(wǎng)絡(luò)拓樸不同會(huì)不會(huì)有問題?)



攀升 2011-06-08 23:09 發(fā)表評(píng)論
]]>
(轉(zhuǎn))P2P之UDP穿透NAT的原理與實(shí)現(xiàn)(附源代碼)http://www.shnenglu.com/iuranus/archive/2011/05/19/146728.html攀升攀升Thu, 19 May 2011 00:37:00 GMThttp://www.shnenglu.com/iuranus/archive/2011/05/19/146728.htmlhttp://www.shnenglu.com/iuranus/comments/146728.htmlhttp://www.shnenglu.com/iuranus/archive/2011/05/19/146728.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/146728.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/146728.html
P2P 之 UDP穿透NAT的原理與實(shí)現(xiàn)(附源代碼)
原創(chuàng):shootingstars
參考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
論壇上經(jīng)常有對(duì)P2P原理的討論,但是討論歸討論,很少有實(shí)質(zhì)的東西產(chǎn)生(源代碼)。呵呵,在這里我就用自己實(shí)現(xiàn)的一個(gè)源代碼來說明UDP穿越NAT的原理。
首先先介紹一些基本概念:
    NAT(Network Address Translators),網(wǎng)絡(luò)地址轉(zhuǎn)換:網(wǎng)絡(luò)地址轉(zhuǎn)換是在IP地址日益缺乏的情況下產(chǎn)生的,它的主要目的就是為了能夠地址重用。NAT分為兩大類,基本的NAT和NAPT(Network Address/Port Translator)。
    最開始NAT是運(yùn)行在路由器上的一個(gè)功能模塊。
    
    最先提出的是基本的NAT,它的產(chǎn)生基于如下事實(shí):一個(gè)私有網(wǎng)絡(luò)(域)中的節(jié)點(diǎn)中只有很少的節(jié)點(diǎn)需要與外網(wǎng)連接(呵呵,這是在上世紀(jì)90年代中期提出的)。那么這個(gè)子網(wǎng)中其實(shí)只有少數(shù)的節(jié)點(diǎn)需要全球唯一的IP地址,其他的節(jié)點(diǎn)的IP地址應(yīng)該是可以重用的。
    因此,基本的NAT實(shí)現(xiàn)的功能很簡單,在子網(wǎng)內(nèi)使用一個(gè)保留的IP子網(wǎng)段,這些IP對(duì)外是不可見的。子網(wǎng)內(nèi)只有少數(shù)一些IP地址可以對(duì)應(yīng)到真正全球唯一的IP地址。如果這些節(jié)點(diǎn)需要訪問外部網(wǎng)絡(luò),那么基本NAT就負(fù)責(zé)將這個(gè)節(jié)點(diǎn)的子網(wǎng)內(nèi)IP轉(zhuǎn)化為一個(gè)全球唯一的IP然后發(fā)送出去。(基本的NAT會(huì)改變IP包中的原IP地址,但是不會(huì)改變IP包中的端口)
    關(guān)于基本的NAT可以參看RFC 1631
    
    另外一種NAT叫做NAPT,從名稱上我們也可以看得出,NAPT不但會(huì)改變經(jīng)過這個(gè)NAT設(shè)備的IP數(shù)據(jù)報(bào)的IP地址,還會(huì)改變IP數(shù)據(jù)報(bào)的TCP/UDP端口。基本NAT的設(shè)備可能我們見的不多(呵呵,我沒有見到過),NAPT才是我們真正討論的主角。看下圖:
                                Server S1                         
                         18.181.0.31:1235                          
                                      |
          ^  Session 1 (A-S1)  ^      |  
          |  18.181.0.31:1235  |      |   
          v 155.99.25.11:62000 v      |    
                                      |
                                     NAT
                                 155.99.25.11
                                      |
          ^  Session 1 (A-S1)  ^      |  
          |  18.181.0.31:1235  |      |  
          v   10.0.0.1:1234    v      |  
                                      |
                                   Client A
                                10.0.0.1:1234
    有一個(gè)私有網(wǎng)絡(luò)10.*.*.*,Client A是其中的一臺(tái)計(jì)算機(jī),這個(gè)網(wǎng)絡(luò)的網(wǎng)關(guān)(一個(gè)NAT設(shè)備)的外網(wǎng)IP是155.99.25.11(應(yīng)該還有一個(gè)內(nèi)網(wǎng)的IP地址,比如10.0.0.10)。如果Client A中的某個(gè)進(jìn)程(這個(gè)進(jìn)程創(chuàng)建了一個(gè)UDP Socket,這個(gè)Socket綁定1234端口)想訪問外網(wǎng)主機(jī)18.181.0.31的1235端口,那么當(dāng)數(shù)據(jù)包通過NAT時(shí)會(huì)發(fā)生什么事情呢?
    首先NAT會(huì)改變這個(gè)數(shù)據(jù)包的原IP地址,改為155.99.25.11。接著NAT會(huì)為這個(gè)傳輸創(chuàng)建一個(gè)Session(Session是一個(gè)抽象的概念,如果是TCP,也許Session是由一個(gè)SYN包開始,以一個(gè)FIN包結(jié)束。而UDP呢,以這個(gè)IP的這個(gè)端口的第一個(gè)UDP開始,結(jié)束呢,呵呵,也許是幾分鐘,也許是幾小時(shí),這要看具體的實(shí)現(xiàn)了)并且給這個(gè)Session分配一個(gè)端口,比如62000,然后改變這個(gè)數(shù)據(jù)包的源端口為62000。所以本來是(10.0.0.1:1234->18.181.0.31:1235)的數(shù)據(jù)包到了互聯(lián)網(wǎng)上變?yōu)榱耍?55.99.25.11:62000->18.181.0.31:1235)。
    一旦NAT創(chuàng)建了一個(gè)Session后,NAT會(huì)記住62000端口對(duì)應(yīng)的是10.0.0.1的1234端口,以后從18.181.0.31發(fā)送到62000端口的數(shù)據(jù)會(huì)被NAT自動(dòng)的轉(zhuǎn)發(fā)到10.0.0.1上。(注意:這里是說18.181.0.31發(fā)送到62000端口的數(shù)據(jù)會(huì)被轉(zhuǎn)發(fā),其他的IP發(fā)送到這個(gè)端口的數(shù)據(jù)將被NAT拋棄)這樣Client A就與Server S1建立以了一個(gè)連接。
    呵呵,上面的基礎(chǔ)知識(shí)可能很多人都知道了,那么下面是關(guān)鍵的部分了。
    看看下面的情況:
    Server S1                                     Server S2
 18.181.0.31:1235                              138.76.29.7:1235
        |                                             |
        |                                             |
        +----------------------+----------------------+
                               |
   ^  Session 1 (A-S1)  ^      |      ^  Session 2 (A-S2)  ^
   |  18.181.0.31:1235  |      |      |  138.76.29.7:1235  |
   v 155.99.25.11:62000 v      |      v 155.99.25.11:62000 v
                               |
                            Cone NAT
                          155.99.25.11
                               |
   ^  Session 1 (A-S1)  ^      |      ^  Session 2 (A-S2)  ^
   |  18.181.0.31:1235  |      |      |  138.76.29.7:1235  |
   v   10.0.0.1:1234    v      |      v   10.0.0.1:1234    v
                               |
                            Client A
                         10.0.0.1:1234
    接上面的例子,如果Client A的原來那個(gè)Socket(綁定了1234端口的那個(gè)UDP Socket)又接著向另外一個(gè)Server S2發(fā)送了一個(gè)UDP包,那么這個(gè)UDP包在通過NAT時(shí)會(huì)怎么樣呢?
    這時(shí)可能會(huì)有兩種情況發(fā)生,一種是NAT再次創(chuàng)建一個(gè)Session,并且再次為這個(gè)Session分配一個(gè)端口號(hào)(比如:62001)。另外一種是NAT再次創(chuàng)建一個(gè)Session,但是不會(huì)新分配一個(gè)端口號(hào),而是用原來分配的端口號(hào)62000。前一種NAT叫做Symmetric NAT,后一種叫做Cone NAT。我們期望我們的NAT是第二種,呵呵,如果你的NAT剛好是第一種,那么很可能會(huì)有很多P2P軟件失靈。(可以慶幸的是,現(xiàn)在絕大多數(shù)的NAT屬于后者,即Cone NAT)
   
    好了,我們看到,通過NAT,子網(wǎng)內(nèi)的計(jì)算機(jī)向外連結(jié)是很容易的(NAT相當(dāng)于透明的,子網(wǎng)內(nèi)的和外網(wǎng)的計(jì)算機(jī)不用知道NAT的情況)。
    但是如果外部的計(jì)算機(jī)想訪問子網(wǎng)內(nèi)的計(jì)算機(jī)就比較困難了(而這正是P2P所需要的)。
    那么我們?nèi)绻霃耐獠堪l(fā)送一個(gè)數(shù)據(jù)報(bào)給內(nèi)網(wǎng)的計(jì)算機(jī)有什么辦法呢?首先,我們必須在內(nèi)網(wǎng)的NAT上打上一個(gè)“洞”(也就是前面我們說的在NAT上建立一個(gè)Session),這個(gè)洞不能由外部來打,只能由內(nèi)網(wǎng)內(nèi)的主機(jī)來打。而且這個(gè)洞是有方向的,比如從內(nèi)部某臺(tái)主機(jī)(比如:192.168.0.10)向外部的某個(gè)IP(比如:219.237.60.1)發(fā)送一個(gè)UDP包,那么就在這個(gè)內(nèi)網(wǎng)的NAT設(shè)備上打了一個(gè)方向?yàn)?19.237.60.1的“洞”,(這就是稱為UDP Hole Punching的技術(shù))以后219.237.60.1就可以通過這個(gè)洞與內(nèi)網(wǎng)的192.168.0.10聯(lián)系了。(但是其他的IP不能利用這個(gè)洞)。
呵呵,現(xiàn)在該輪到我們的正題P2P了。有了上面的理論,實(shí)現(xiàn)兩個(gè)內(nèi)網(wǎng)的主機(jī)通訊就差最后一步了:那就是雞生蛋還是蛋生雞的問題了,兩邊都無法主動(dòng)發(fā)出連接請(qǐng)求,誰也不知道誰的公網(wǎng)地址,那我們?nèi)绾蝸泶蜻@個(gè)洞呢?我們需要一個(gè)中間人來聯(lián)系這兩個(gè)內(nèi)網(wǎng)主機(jī)。
    現(xiàn)在我們來看看一個(gè)P2P軟件的流程,以下圖為例:
                       Server S (219.237.60.1)
                          |
                          |
   +----------------------+----------------------+
   |                                             |
 NAT A (外網(wǎng)IP:202.187.45.3)                 NAT B (外網(wǎng)IP:187.34.1.56)
   |   (內(nèi)網(wǎng)IP:192.168.0.1)                      | (內(nèi)網(wǎng)IP:192.168.0.1)
   |                                             |
Client A  (192.168.0.20:4000)             Client B (192.168.0.10:40000)
    首先,Client A登錄服務(wù)器,NAT A為這次的Session分配了一個(gè)端口60000,那么Server S收到的Client A的地址是202.187.45.3:60000,這就是Client A的外網(wǎng)地址了。同樣,Client B登錄Server S,NAT B給此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。
    此時(shí),Client A與Client B都可以與Server S通信了。如果Client A此時(shí)想直接發(fā)送信息給Client B,那么他可以從Server S那兒獲得B的公網(wǎng)地址187.34.1.56:40000,是不是Client A向這個(gè)地址發(fā)送信息Client B就能收到了呢?答案是不行,因?yàn)槿绻@樣發(fā)送信息,NAT B會(huì)將這個(gè)信息丟棄(因?yàn)檫@樣的信息是不請(qǐng)自來的,為了安全,大多數(shù)NAT都會(huì)執(zhí)行丟棄動(dòng)作)。現(xiàn)在我們需要的是在NAT B上打一個(gè)方向?yàn)?02.187.45.3(即Client A的外網(wǎng)地址)的洞,那么Client A發(fā)送到187.34.1.56:40000的信息,Client B就能收到了。這個(gè)打洞命令由誰來發(fā)呢,呵呵,當(dāng)然是Server S。
    總結(jié)一下這個(gè)過程:如果Client A想向Client B發(fā)送信息,那么Client A發(fā)送命令給Server S,請(qǐng)求Server S命令Client B向Client A方向打洞。呵呵,是不是很繞口,不過沒關(guān)系,想一想就很清楚了,何況還有源代碼呢(侯老師說過:在源代碼面前沒有秘密 8)),然后Client A就可以通過Client B的外網(wǎng)地址與Client B通信了。
    
    注意:以上過程只適合于Cone NAT的情況,如果是Symmetric NAT,那么當(dāng)Client B向Client A打洞的端口已經(jīng)重新分配了,Client B將無法知道這個(gè)端口(如果Symmetric NAT的端口是順序分配的,那么我們或許可以猜測這個(gè)端口號(hào),可是由于可能導(dǎo)致失敗的因素太多,我們不推薦這種猜測端口的方法)。
    
    下面是一個(gè)模擬P2P聊天的過程的源代碼,過程很簡單,P2PServer運(yùn)行在一個(gè)擁有公網(wǎng)IP的計(jì)算機(jī)上,P2PClient運(yùn)行在兩個(gè)不同的NAT后(注意,如果兩個(gè)客戶端運(yùn)行在一個(gè)NAT后,本程序很可能不能運(yùn)行正常,這取決于你的NAT是否支持loopback translation,詳見http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt,當(dāng)然,此問題可以通過雙方先嘗試連接對(duì)方的內(nèi)網(wǎng)IP來解決,但是這個(gè)代碼只是為了驗(yàn)證原理,并沒有處理這些問題),后登錄的計(jì)算機(jī)可以獲得先登錄計(jì)算機(jī)的用戶名,后登錄的計(jì)算機(jī)通過send username message的格式來發(fā)送消息。如果發(fā)送成功,說明你已取得了直接與對(duì)方連接的成功。
    程序現(xiàn)在支持三個(gè)命令:send , getu , exit
    
    send格式:send username message
    功能:發(fā)送信息給username
    
    getu格式:getu
    功能:獲得當(dāng)前服務(wù)器用戶列表
    
    exit格式:exit
    功能:注銷與服務(wù)器的連接(服務(wù)器不會(huì)自動(dòng)監(jiān)測客戶是否吊線)
        
    代碼很短,相信很容易懂,如果有什么問題,可以給我發(fā)郵件zhouhuis22@sina.com  或者在CSDN上發(fā)送短消息。同時(shí),歡迎轉(zhuǎn)發(fā)此文,但希望保留作者版權(quán)8-)。
    
    最后感謝CSDN網(wǎng)友 PiggyXP 和 Seilfer的測試幫助
P2PServer.c
/* P2P 程序服務(wù)端
 * 
 * 文件名:P2PServer.c
 *
 * 日期:2004-5-21
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 *
 */
#pragma comment(lib, "ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
UserList ClientList;
void InitWinSock()
{
 WSADATA wsaData;
 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
 {
  printf("Windows sockets 2.2 startup");
  throw Exception("");
 }
 else{
  printf("Using %s (Status: %s)\n",
   wsaData.szDescription, wsaData.szSystemStatus);
  printf("with API versions %d.%d to %d.%d\n\n",
   LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
   LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
  
 }
}
SOCKET mksock(int type)
{
 SOCKET sock = socket(AF_INET, type, 0);
 if (sock < 0)
 {
        printf("create socket error");
  throw Exception("");
 }
 return sock;
}
stUserListNode GetUser(char *username)
{
 for(UserList::iterator UserIterator=ClientList.begin();
      UserIterator!=ClientList.end();
       ++UserIterator)
 {
  if( strcmp( ((*UserIterator)->userName), username) == 0 )
   return *(*UserIterator);
 }
 throw Exception("not find this user");
}
int main(int argc, char* argv[])
{
 try{
  InitWinSock();
  
  SOCKET PrimaryUDP;
  PrimaryUDP = mksock(SOCK_DGRAM);
  sockaddr_in local;
  local.sin_family=AF_INET;
  local.sin_port= htons(SERVER_PORT); 
  local.sin_addr.s_addr = htonl(INADDR_ANY);
  int nResult=bind(PrimaryUDP,(sockaddr*)&local,sizeof(sockaddr));
  if(nResult==SOCKET_ERROR)
   throw Exception("bind error");
  sockaddr_in sender;
  stMessage recvbuf;
  memset(&recvbuf,0,sizeof(stMessage));
  // 開始主循環(huán).
  // 主循環(huán)負(fù)責(zé)下面幾件事情:
  // 一:讀取客戶端登陸和登出消息,記錄客戶列表
  // 二:轉(zhuǎn)發(fā)客戶p2p請(qǐng)求
  for(;;)
  {
   int dwSender = sizeof(sender);
   int ret = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(stMessage), 0, (sockaddr *)&sender, &dwSender);
   if(ret <= 0)
   {
    printf("recv error");
    continue;
   }
   else
   {
    int messageType = recvbuf.iMessageType;
    switch(messageType){
    case LOGIN:
     {
      //  將這個(gè)用戶的信息記錄到用戶列表中
      printf("has a user login : %s\n", recvbuf.message.loginmember.userName);
      stUserListNode *currentuser = new stUserListNode();
      strcpy(currentuser->userName, recvbuf.message.loginmember.userName);
      currentuser->ip = ntohl(sender.sin_addr.S_un.S_addr);
      currentuser->port = ntohs(sender.sin_port);
      
      ClientList.push_back(currentuser);
      // 發(fā)送已經(jīng)登陸的客戶信息
      int nodecount = (int)ClientList.size();
      sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
      for(UserList::iterator UserIterator=ClientList.begin();
        UserIterator!=ClientList.end();
        ++UserIterator)
      {
       sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender)); 
      }
      break;
     }
    case LOGOUT:
     {
      // 將此客戶信息刪除
      printf("has a user logout : %s\n", recvbuf.message.logoutmember.userName);
      UserList::iterator removeiterator = NULL;
      for(UserList::iterator UserIterator=ClientList.begin();
       UserIterator!=ClientList.end();
       ++UserIterator)
      {
       if( strcmp( ((*UserIterator)->userName), recvbuf.message.logoutmember.userName) == 0 )
       {
        removeiterator = UserIterator;
        break;
       }
      }
      if(removeiterator != NULL)
       ClientList.remove(*removeiterator);
      break;
     }
    case P2PTRANS:
     {
      // 某個(gè)客戶希望服務(wù)端向另外一個(gè)客戶發(fā)送一個(gè)打洞消息
      printf("%s wants to p2p %s\n",inet_ntoa(sender.sin_addr),recvbuf.message.translatemessage.userName);
      stUserListNode node = GetUser(recvbuf.message.translatemessage.userName);
      sockaddr_in remote;
      remote.sin_family=AF_INET;
      remote.sin_port= htons(node.port); 
      remote.sin_addr.s_addr = htonl(node.ip);
      in_addr tmp;
      tmp.S_un.S_addr = htonl(node.ip);
      printf("the address is %s,and port is %d\n",inet_ntoa(tmp), node.port);
      stP2PMessage transMessage;
      transMessage.iMessageType = P2PSOMEONEWANTTOCALLYOU;
      transMessage.iStringLen = ntohl(sender.sin_addr.S_un.S_addr);
      transMessage.Port = ntohs(sender.sin_port);
                        
      sendto(PrimaryUDP,(const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr *)&remote, sizeof(remote));
      break;
     }
    
    case GETALLUSER:
     {
      int command = GETALLUSER;
      sendto(PrimaryUDP, (const char*)&command, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
      int nodecount = (int)ClientList.size();
      sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
      for(UserList::iterator UserIterator=ClientList.begin();
        UserIterator!=ClientList.end();
        ++UserIterator)
      {
       sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender)); 
      }
      break;
     }
    }
   }
  }
 }
 catch(Exception &e)
 {
  printf(e.GetMessage());
  return 1;
 }
 return 0;
}
/* P2P 程序客戶端
 * 
 * 文件名:P2PClient.c
 *
 * 日期:2004-5-21
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 *
 */
#pragma comment(lib,"ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
#include <iostream>
using namespace std;
UserList ClientList;
 
#define COMMANDMAXC 256
#define MAXRETRY    5
SOCKET PrimaryUDP;
char UserName[10];
char ServerIP[20];
bool RecvedACK;
void InitWinSock()
{
 WSADATA wsaData;
 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
 {
  printf("Windows sockets 2.2 startup");
  throw Exception("");
 }
 else{
  printf("Using %s (Status: %s)\n",
   wsaData.szDescription, wsaData.szSystemStatus);
  printf("with API versions %d.%d to %d.%d\n\n",
   LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
   LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
 }
}
SOCKET mksock(int type)
{
 SOCKET sock = socket(AF_INET, type, 0);
 if (sock < 0)
 {
        printf("create socket error");
  throw Exception("");
 }
 return sock;
}
stUserListNode GetUser(char *username)
{
 for(UserList::iterator UserIterator=ClientList.begin();
      UserIterator!=ClientList.end();
       ++UserIterator)
 {
  if( strcmp( ((*UserIterator)->userName), username) == 0 )
   return *(*UserIterator);
 }
 throw Exception("not find this user");
}
void BindSock(SOCKET sock)
{
 sockaddr_in sin;
 sin.sin_addr.S_un.S_addr = INADDR_ANY;
 sin.sin_family = AF_INET;
 sin.sin_port = 0;
 
 if (bind(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0)
  throw Exception("bind error");
}
void ConnectToServer(SOCKET sock,char *username, char *serverip)
{
 sockaddr_in remote;
 remote.sin_addr.S_un.S_addr = inet_addr(serverip);
 remote.sin_family = AF_INET;
 remote.sin_port = htons(SERVER_PORT);
 
 stMessage sendbuf;
 sendbuf.iMessageType = LOGIN;
 strncpy(sendbuf.message.loginmember.userName, username, 10);
 sendto(sock, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote,sizeof(remote));
 int usercount;
 int fromlen = sizeof(remote);
 int iread = recvfrom(sock, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
 if(iread<=0)
 {
  throw Exception("Login error\n");
 }
 // 登錄到服務(wù)端后,接收服務(wù)端發(fā)來的已經(jīng)登錄的用戶的信息
 cout<<"Have "<<usercount<<" users logined server:"<<endl;
 for(int i = 0;i<usercount;i++)
 {
  stUserListNode *node = new stUserListNode;
  recvfrom(sock, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
  ClientList.push_back(node);
  cout<<"Username:"<<node->userName<<endl;
  in_addr tmp;
  tmp.S_un.S_addr = htonl(node->ip);
  cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
  cout<<"UserPort:"<<node->port<<endl;
  cout<<""<<endl;
 }
}
void OutputUsage()
{
 cout<<"You can input you command:\n"
  <<"Command Type:\"send\",\"exit\",\"getu\"\n"
  <<"Example : send Username Message\n"
  <<"          exit\n"
  <<"          getu\n"
  <<endl;
}
/* 這是主要的函數(shù):發(fā)送一個(gè)消息給某個(gè)用戶(C)
 *流程:直接向某個(gè)用戶的外網(wǎng)IP發(fā)送消息,如果此前沒有聯(lián)系過
 *      那么此消息將無法發(fā)送,發(fā)送端等待超時(shí)。
 *      超時(shí)后,發(fā)送端將發(fā)送一個(gè)請(qǐng)求信息到服務(wù)端,
 *      要求服務(wù)端發(fā)送給客戶C一個(gè)請(qǐng)求,請(qǐng)求C給本機(jī)發(fā)送打洞消息
 *      以上流程將重復(fù)MAXRETRY次
 */
bool SendMessageTo(char *UserName, char *Message)
{
 char realmessage[256];
 unsigned int UserIP;
 unsigned short UserPort;
 bool FindUser = false;
 for(UserList::iterator UserIterator=ClientList.begin();
      UserIterator!=ClientList.end();
      ++UserIterator)
 {
  if( strcmp( ((*UserIterator)->userName), UserName) == 0 )
  {
   UserIP = (*UserIterator)->ip;
   UserPort = (*UserIterator)->port;
   FindUser = true;
  }
 }
 if(!FindUser)
  return false;
 strcpy(realmessage, Message);
 for(int i=0;i<MAXRETRY;i++)
 {
  RecvedACK = false;
  sockaddr_in remote;
  remote.sin_addr.S_un.S_addr = htonl(UserIP);
  remote.sin_family = AF_INET;
  remote.sin_port = htons(UserPort);
  stP2PMessage MessageHead;
  MessageHead.iMessageType = P2PMESSAGE;
  MessageHead.iStringLen = (int)strlen(realmessage)+1;
  int isend = sendto(PrimaryUDP, (const char *)&MessageHead, sizeof(MessageHead), 0, (const sockaddr*)&remote, sizeof(remote));
  isend = sendto(PrimaryUDP, (const char *)&realmessage, MessageHead.iStringLen, 0, (const sockaddr*)&remote, sizeof(remote));
  
  // 等待接收線程將此標(biāo)記修改
  for(int j=0;j<10;j++)
  {
   if(RecvedACK)
    return true;
   else
    Sleep(300);
  }
  // 沒有接收到目標(biāo)主機(jī)的回應(yīng),認(rèn)為目標(biāo)主機(jī)的端口映射沒有
  // 打開,那么發(fā)送請(qǐng)求信息給服務(wù)器,要服務(wù)器告訴目標(biāo)主機(jī)
  // 打開映射端口(UDP打洞)
  sockaddr_in server;
  server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
  server.sin_family = AF_INET;
  server.sin_port = htons(SERVER_PORT);
 
  stMessage transMessage;
  transMessage.iMessageType = P2PTRANS;
  strcpy(transMessage.message.translatemessage.userName, UserName);
  sendto(PrimaryUDP, (const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr*)&server, sizeof(server));
  Sleep(100);// 等待對(duì)方先發(fā)送信息。
 }
 return false;
}
// 解析命令,暫時(shí)只有exit和send命令
// 新增getu命令,獲取當(dāng)前服務(wù)器的所有用戶
void ParseCommand(char * CommandLine)
{
 if(strlen(CommandLine)<4)
  return;
 char Command[10];
 strncpy(Command, CommandLine, 4);
 Command[4]='\0';
 if(strcmp(Command,"exit")==0)
 {
  stMessage sendbuf;
  sendbuf.iMessageType = LOGOUT;
  strncpy(sendbuf.message.logoutmember.userName, UserName, 10);
  sockaddr_in server;
  server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
  server.sin_family = AF_INET;
  server.sin_port = htons(SERVER_PORT);
  sendto(PrimaryUDP,(const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr *)&server, sizeof(server));
  shutdown(PrimaryUDP, 2);
  closesocket(PrimaryUDP);
  exit(0);
 }
 else if(strcmp(Command,"send")==0)
 {
  char sendname[20];
  char message[COMMANDMAXC];
  int i;
  for(i=5;;i++)
  {
   if(CommandLine[i]!=' ')
    sendname[i-5]=CommandLine[i];
   else
   {
    sendname[i-5]='\0';
    break;
   }
  }
  strcpy(message, &(CommandLine[i+1]));
  if(SendMessageTo(sendname, message))
   printf("Send OK!\n");
  else 
   printf("Send Failure!\n");
 }
 else if(strcmp(Command,"getu")==0)
 {
  int command = GETALLUSER;
  sockaddr_in server;
  server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
  server.sin_family = AF_INET;
  server.sin_port = htons(SERVER_PORT);
  sendto(PrimaryUDP,(const char*)&command, sizeof(command), 0, (const sockaddr *)&server, sizeof(server));
 }
}
// 接受消息線程
DWORD WINAPI RecvThreadProc(LPVOID lpParameter)
{
 sockaddr_in remote;
 int sinlen = sizeof(remote);
 stP2PMessage recvbuf;
 for(;;)
 {
  int iread = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(recvbuf), 0, (sockaddr *)&remote, &sinlen);
  if(iread<=0)
  {
   printf("recv error\n");
   continue;
  }
  switch(recvbuf.iMessageType)
  {
  case P2PMESSAGE:
   {
    // 接收到P2P的消息
    char *comemessage= new char[recvbuf.iStringLen];
    int iread1 = recvfrom(PrimaryUDP, comemessage, 256, 0, (sockaddr *)&remote, &sinlen);
    comemessage[iread1-1] = '\0';
    if(iread1<=0)
     throw Exception("Recv Message Error\n");
    else
    {
     printf("Recv a Message:%s\n",comemessage);
     
     stP2PMessage sendbuf;
     sendbuf.iMessageType = P2PMESSAGEACK;
     sendto(PrimaryUDP, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote, sizeof(remote));
    }
    delete []comemessage;
    break;
   }
  case P2PSOMEONEWANTTOCALLYOU:
   {
    // 接收到打洞命令,向指定的IP地址打洞
    printf("Recv p2someonewanttocallyou data\n");
    sockaddr_in remote;
    remote.sin_addr.S_un.S_addr = htonl(recvbuf.iStringLen);
    remote.sin_family = AF_INET;
    remote.sin_port = htons(recvbuf.Port);
    // UDP hole punching
    stP2PMessage message;
    message.iMessageType = P2PTRASH;
    sendto(PrimaryUDP, (const char *)&message, sizeof(message), 0, (const sockaddr*)&remote, sizeof(remote));
                
    break;
   }
  case P2PMESSAGEACK:
   {
    // 發(fā)送消息的應(yīng)答
    RecvedACK = true;
    break;
   }
  case P2PTRASH:
   {
    // 對(duì)方發(fā)送的打洞消息,忽略掉。
    //do nothing ...
    printf("Recv p2ptrash data\n");
    break;
   }
  case GETALLUSER:
   {
    int usercount;
    int fromlen = sizeof(remote);
    int iread = recvfrom(PrimaryUDP, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
    if(iread<=0)
    {
     throw Exception("Login error\n");
    }
    
    ClientList.clear();
    cout<<"Have "<<usercount<<" users logined server:"<<endl;
    for(int i = 0;i<usercount;i++)
    {
     stUserListNode *node = new stUserListNode;
     recvfrom(PrimaryUDP, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
     ClientList.push_back(node);
     cout<<"Username:"<<node->userName<<endl;
     in_addr tmp;
     tmp.S_un.S_addr = htonl(node->ip);
     cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
     cout<<"UserPort:"<<node->port<<endl;
     cout<<""<<endl;
    }
    break;
   }
  }
 }
}

int main(int argc, char* argv[])
{
 try
 {
  InitWinSock();
  
  PrimaryUDP = mksock(SOCK_DGRAM);
  BindSock(PrimaryUDP);
  cout<<"Please input server ip:";
  cin>>ServerIP;
  cout<<"Please input your name:";
  cin>>UserName;
  ConnectToServer(PrimaryUDP, UserName, ServerIP);
  HANDLE threadhandle = CreateThread(NULL, 0, RecvThreadProc, NULL, NULL, NULL);
  CloseHandle(threadhandle);
  OutputUsage();
  for(;;)
  {
   char Command[COMMANDMAXC];
   gets(Command);
   ParseCommand(Command);
  }
 }
 catch(Exception &e)
 {
  printf(e.GetMessage());
  return 1;
 }
 return 0;
}
/* 異常類
 *
 * 文件名:Exception.h
 *
 * 日期:2004.5.5
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 */
#ifndef __HZH_Exception__
#define __HZH_Exception__
#define EXCEPTION_MESSAGE_MAXLEN 256
#include "string.h"
class Exception
{
private:
 char m_ExceptionMessage[EXCEPTION_MESSAGE_MAXLEN];
public:
 Exception(char *msg)
 {
  strncpy(m_ExceptionMessage, msg, EXCEPTION_MESSAGE_MAXLEN);
 }
 char *GetMessage()
 {
  return m_ExceptionMessage;
 }
};
#endif
/* P2P 程序傳輸協(xié)議
 * 
 * 日期:2004-5-21
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 *
 */
#pragma once
#include <list>
// 定義iMessageType的值
#define LOGIN 1
#define LOGOUT 2
#define P2PTRANS 3
#define GETALLUSER  4
// 服務(wù)器端口
#define SERVER_PORT 2280
// Client登錄時(shí)向服務(wù)器發(fā)送的消息
struct stLoginMessage
{
 char userName[10];
 char password[10];
};
// Client注銷時(shí)發(fā)送的消息
struct stLogoutMessage
{
 char userName[10];
};
// Client向服務(wù)器請(qǐng)求另外一個(gè)Client(userName)向自己方向發(fā)送UDP打洞消息
struct stP2PTranslate
{
 char userName[10];
};
// Client向服務(wù)器發(fā)送的消息格式
struct stMessage
{
 int iMessageType;
 union _message
 {
  stLoginMessage loginmember;
  stLogoutMessage logoutmember;
  stP2PTranslate translatemessage;
 }message;
};
// 客戶節(jié)點(diǎn)信息
struct stUserListNode
{
 char userName[10];
 unsigned int ip;
 unsigned short port;
};
// Server向Client發(fā)送的消息
struct stServerToClient
{
 int iMessageType;
 union _message
 {
  stUserListNode user;
 }message;
};
//======================================
// 下面的協(xié)議用于客戶端之間的通信
//======================================
#define P2PMESSAGE 100               // 發(fā)送消息
#define P2PMESSAGEACK 101            // 收到消息的應(yīng)答
#define P2PSOMEONEWANTTOCALLYOU 102  // 服務(wù)器向客戶端發(fā)送的消息
                                     // 希望此客戶端發(fā)送一個(gè)UDP打洞包
#define P2PTRASH        103          // 客戶端發(fā)送的打洞包,接收端應(yīng)該忽略此消息
// 客戶端之間發(fā)送消息格式
struct stP2PMessage
{
 int iMessageType;
 int iStringLen;         // or IP address
 unsigned short Port; 
};
using namespace std;
typedef list<stUserListNode *> UserList;


攀升 2011-05-19 08:37 發(fā)表評(píng)論
]]>
(轉(zhuǎn))P2P之UDP穿透NAT的原理與實(shí)現(xiàn)(附源代碼)http://www.shnenglu.com/iuranus/archive/2011/05/19/146727.html攀升攀升Thu, 19 May 2011 00:35:00 GMThttp://www.shnenglu.com/iuranus/archive/2011/05/19/146727.htmlhttp://www.shnenglu.com/iuranus/comments/146727.htmlhttp://www.shnenglu.com/iuranus/archive/2011/05/19/146727.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/146727.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/146727.html
P2P 之 UDP穿透NAT的原理與實(shí)現(xiàn)(附源代碼)
原創(chuàng):shootingstars
參考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
論壇上經(jīng)常有對(duì)P2P原理的討論,但是討論歸討論,很少有實(shí)質(zhì)的東西產(chǎn)生(源代碼)。呵呵,在這里我就用自己實(shí)現(xiàn)的一個(gè)源代碼來說明UDP穿越NAT的原理。
首先先介紹一些基本概念:
    NAT(Network Address Translators),網(wǎng)絡(luò)地址轉(zhuǎn)換:網(wǎng)絡(luò)地址轉(zhuǎn)換是在IP地址日益缺乏的情況下產(chǎn)生的,它的主要目的就是為了能夠地址重用。NAT分為兩大類,基本的NAT和NAPT(Network Address/Port Translator)。
    最開始NAT是運(yùn)行在路由器上的一個(gè)功能模塊。
    
    最先提出的是基本的NAT,它的產(chǎn)生基于如下事實(shí):一個(gè)私有網(wǎng)絡(luò)(域)中的節(jié)點(diǎn)中只有很少的節(jié)點(diǎn)需要與外網(wǎng)連接(呵呵,這是在上世紀(jì)90年代中期提出的)。那么這個(gè)子網(wǎng)中其實(shí)只有少數(shù)的節(jié)點(diǎn)需要全球唯一的IP地址,其他的節(jié)點(diǎn)的IP地址應(yīng)該是可以重用的。
    因此,基本的NAT實(shí)現(xiàn)的功能很簡單,在子網(wǎng)內(nèi)使用一個(gè)保留的IP子網(wǎng)段,這些IP對(duì)外是不可見的。子網(wǎng)內(nèi)只有少數(shù)一些IP地址可以對(duì)應(yīng)到真正全球唯一的IP地址。如果這些節(jié)點(diǎn)需要訪問外部網(wǎng)絡(luò),那么基本NAT就負(fù)責(zé)將這個(gè)節(jié)點(diǎn)的子網(wǎng)內(nèi)IP轉(zhuǎn)化為一個(gè)全球唯一的IP然后發(fā)送出去。(基本的NAT會(huì)改變IP包中的原IP地址,但是不會(huì)改變IP包中的端口)
    關(guān)于基本的NAT可以參看RFC 1631
    
    另外一種NAT叫做NAPT,從名稱上我們也可以看得出,NAPT不但會(huì)改變經(jīng)過這個(gè)NAT設(shè)備的IP數(shù)據(jù)報(bào)的IP地址,還會(huì)改變IP數(shù)據(jù)報(bào)的TCP/UDP端口。基本NAT的設(shè)備可能我們見的不多(呵呵,我沒有見到過),NAPT才是我們真正討論的主角。看下圖:
                                Server S1                         
                         18.181.0.31:1235                          
                                      |
          ^  Session 1 (A-S1)  ^      |  
          |  18.181.0.31:1235  |      |   
          v 155.99.25.11:62000 v      |    
                                      |
                                     NAT
                                 155.99.25.11
                                      |
          ^  Session 1 (A-S1)  ^      |  
          |  18.181.0.31:1235  |      |  
          v   10.0.0.1:1234    v      |  
                                      |
                                   Client A
                                10.0.0.1:1234
    有一個(gè)私有網(wǎng)絡(luò)10.*.*.*,Client A是其中的一臺(tái)計(jì)算機(jī),這個(gè)網(wǎng)絡(luò)的網(wǎng)關(guān)(一個(gè)NAT設(shè)備)的外網(wǎng)IP是155.99.25.11(應(yīng)該還有一個(gè)內(nèi)網(wǎng)的IP地址,比如10.0.0.10)。如果Client A中的某個(gè)進(jìn)程(這個(gè)進(jìn)程創(chuàng)建了一個(gè)UDP Socket,這個(gè)Socket綁定1234端口)想訪問外網(wǎng)主機(jī)18.181.0.31的1235端口,那么當(dāng)數(shù)據(jù)包通過NAT時(shí)會(huì)發(fā)生什么事情呢?
    首先NAT會(huì)改變這個(gè)數(shù)據(jù)包的原IP地址,改為155.99.25.11。接著NAT會(huì)為這個(gè)傳輸創(chuàng)建一個(gè)Session(Session是一個(gè)抽象的概念,如果是TCP,也許Session是由一個(gè)SYN包開始,以一個(gè)FIN包結(jié)束。而UDP呢,以這個(gè)IP的這個(gè)端口的第一個(gè)UDP開始,結(jié)束呢,呵呵,也許是幾分鐘,也許是幾小時(shí),這要看具體的實(shí)現(xiàn)了)并且給這個(gè)Session分配一個(gè)端口,比如62000,然后改變這個(gè)數(shù)據(jù)包的源端口為62000。所以本來是(10.0.0.1:1234->18.181.0.31:1235)的數(shù)據(jù)包到了互聯(lián)網(wǎng)上變?yōu)榱耍?55.99.25.11:62000->18.181.0.31:1235)。
    一旦NAT創(chuàng)建了一個(gè)Session后,NAT會(huì)記住62000端口對(duì)應(yīng)的是10.0.0.1的1234端口,以后從18.181.0.31發(fā)送到62000端口的數(shù)據(jù)會(huì)被NAT自動(dòng)的轉(zhuǎn)發(fā)到10.0.0.1上。(注意:這里是說18.181.0.31發(fā)送到62000端口的數(shù)據(jù)會(huì)被轉(zhuǎn)發(fā),其他的IP發(fā)送到這個(gè)端口的數(shù)據(jù)將被NAT拋棄)這樣Client A就與Server S1建立以了一個(gè)連接。
    呵呵,上面的基礎(chǔ)知識(shí)可能很多人都知道了,那么下面是關(guān)鍵的部分了。
    看看下面的情況:
    Server S1                                     Server S2
 18.181.0.31:1235                              138.76.29.7:1235
        |                                             |
        |                                             |
        +----------------------+----------------------+
                               |
   ^  Session 1 (A-S1)  ^      |      ^  Session 2 (A-S2)  ^
   |  18.181.0.31:1235  |      |      |  138.76.29.7:1235  |
   v 155.99.25.11:62000 v      |      v 155.99.25.11:62000 v
                               |
                            Cone NAT
                          155.99.25.11
                               |
   ^  Session 1 (A-S1)  ^      |      ^  Session 2 (A-S2)  ^
   |  18.181.0.31:1235  |      |      |  138.76.29.7:1235  |
   v   10.0.0.1:1234    v      |      v   10.0.0.1:1234    v
                               |
                            Client A
                         10.0.0.1:1234
    接上面的例子,如果Client A的原來那個(gè)Socket(綁定了1234端口的那個(gè)UDP Socket)又接著向另外一個(gè)Server S2發(fā)送了一個(gè)UDP包,那么這個(gè)UDP包在通過NAT時(shí)會(huì)怎么樣呢?
    這時(shí)可能會(huì)有兩種情況發(fā)生,一種是NAT再次創(chuàng)建一個(gè)Session,并且再次為這個(gè)Session分配一個(gè)端口號(hào)(比如:62001)。另外一種是NAT再次創(chuàng)建一個(gè)Session,但是不會(huì)新分配一個(gè)端口號(hào),而是用原來分配的端口號(hào)62000。前一種NAT叫做Symmetric NAT,后一種叫做Cone NAT。我們期望我們的NAT是第二種,呵呵,如果你的NAT剛好是第一種,那么很可能會(huì)有很多P2P軟件失靈。(可以慶幸的是,現(xiàn)在絕大多數(shù)的NAT屬于后者,即Cone NAT)
   
    好了,我們看到,通過NAT,子網(wǎng)內(nèi)的計(jì)算機(jī)向外連結(jié)是很容易的(NAT相當(dāng)于透明的,子網(wǎng)內(nèi)的和外網(wǎng)的計(jì)算機(jī)不用知道NAT的情況)。
    但是如果外部的計(jì)算機(jī)想訪問子網(wǎng)內(nèi)的計(jì)算機(jī)就比較困難了(而這正是P2P所需要的)。
    那么我們?nèi)绻霃耐獠堪l(fā)送一個(gè)數(shù)據(jù)報(bào)給內(nèi)網(wǎng)的計(jì)算機(jī)有什么辦法呢?首先,我們必須在內(nèi)網(wǎng)的NAT上打上一個(gè)“洞”(也就是前面我們說的在NAT上建立一個(gè)Session),這個(gè)洞不能由外部來打,只能由內(nèi)網(wǎng)內(nèi)的主機(jī)來打。而且這個(gè)洞是有方向的,比如從內(nèi)部某臺(tái)主機(jī)(比如:192.168.0.10)向外部的某個(gè)IP(比如:219.237.60.1)發(fā)送一個(gè)UDP包,那么就在這個(gè)內(nèi)網(wǎng)的NAT設(shè)備上打了一個(gè)方向?yàn)?19.237.60.1的“洞”,(這就是稱為UDP Hole Punching的技術(shù))以后219.237.60.1就可以通過這個(gè)洞與內(nèi)網(wǎng)的192.168.0.10聯(lián)系了。(但是其他的IP不能利用這個(gè)洞)。
呵呵,現(xiàn)在該輪到我們的正題P2P了。有了上面的理論,實(shí)現(xiàn)兩個(gè)內(nèi)網(wǎng)的主機(jī)通訊就差最后一步了:那就是雞生蛋還是蛋生雞的問題了,兩邊都無法主動(dòng)發(fā)出連接請(qǐng)求,誰也不知道誰的公網(wǎng)地址,那我們?nèi)绾蝸泶蜻@個(gè)洞呢?我們需要一個(gè)中間人來聯(lián)系這兩個(gè)內(nèi)網(wǎng)主機(jī)。
    現(xiàn)在我們來看看一個(gè)P2P軟件的流程,以下圖為例:
                       Server S (219.237.60.1)
                          |
                          |
   +----------------------+----------------------+
   |                                             |
 NAT A (外網(wǎng)IP:202.187.45.3)                 NAT B (外網(wǎng)IP:187.34.1.56)
   |   (內(nèi)網(wǎng)IP:192.168.0.1)                      | (內(nèi)網(wǎng)IP:192.168.0.1)
   |                                             |
Client A  (192.168.0.20:4000)             Client B (192.168.0.10:40000)
    首先,Client A登錄服務(wù)器,NAT A為這次的Session分配了一個(gè)端口60000,那么Server S收到的Client A的地址是202.187.45.3:60000,這就是Client A的外網(wǎng)地址了。同樣,Client B登錄Server S,NAT B給此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。
    此時(shí),Client A與Client B都可以與Server S通信了。如果Client A此時(shí)想直接發(fā)送信息給Client B,那么他可以從Server S那兒獲得B的公網(wǎng)地址187.34.1.56:40000,是不是Client A向這個(gè)地址發(fā)送信息Client B就能收到了呢?答案是不行,因?yàn)槿绻@樣發(fā)送信息,NAT B會(huì)將這個(gè)信息丟棄(因?yàn)檫@樣的信息是不請(qǐng)自來的,為了安全,大多數(shù)NAT都會(huì)執(zhí)行丟棄動(dòng)作)。現(xiàn)在我們需要的是在NAT B上打一個(gè)方向?yàn)?02.187.45.3(即Client A的外網(wǎng)地址)的洞,那么Client A發(fā)送到187.34.1.56:40000的信息,Client B就能收到了。這個(gè)打洞命令由誰來發(fā)呢,呵呵,當(dāng)然是Server S。
    總結(jié)一下這個(gè)過程:如果Client A想向Client B發(fā)送信息,那么Client A發(fā)送命令給Server S,請(qǐng)求Server S命令Client B向Client A方向打洞。呵呵,是不是很繞口,不過沒關(guān)系,想一想就很清楚了,何況還有源代碼呢(侯老師說過:在源代碼面前沒有秘密 8)),然后Client A就可以通過Client B的外網(wǎng)地址與Client B通信了。
    
    注意:以上過程只適合于Cone NAT的情況,如果是Symmetric NAT,那么當(dāng)Client B向Client A打洞的端口已經(jīng)重新分配了,Client B將無法知道這個(gè)端口(如果Symmetric NAT的端口是順序分配的,那么我們或許可以猜測這個(gè)端口號(hào),可是由于可能導(dǎo)致失敗的因素太多,我們不推薦這種猜測端口的方法)。
    
    下面是一個(gè)模擬P2P聊天的過程的源代碼,過程很簡單,P2PServer運(yùn)行在一個(gè)擁有公網(wǎng)IP的計(jì)算機(jī)上,P2PClient運(yùn)行在兩個(gè)不同的NAT后(注意,如果兩個(gè)客戶端運(yùn)行在一個(gè)NAT后,本程序很可能不能運(yùn)行正常,這取決于你的NAT是否支持loopback translation,詳見http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt,當(dāng)然,此問題可以通過雙方先嘗試連接對(duì)方的內(nèi)網(wǎng)IP來解決,但是這個(gè)代碼只是為了驗(yàn)證原理,并沒有處理這些問題),后登錄的計(jì)算機(jī)可以獲得先登錄計(jì)算機(jī)的用戶名,后登錄的計(jì)算機(jī)通過send username message的格式來發(fā)送消息。如果發(fā)送成功,說明你已取得了直接與對(duì)方連接的成功。
    程序現(xiàn)在支持三個(gè)命令:send , getu , exit
    
    send格式:send username message
    功能:發(fā)送信息給username
    
    getu格式:getu
    功能:獲得當(dāng)前服務(wù)器用戶列表
    
    exit格式:exit
    功能:注銷與服務(wù)器的連接(服務(wù)器不會(huì)自動(dòng)監(jiān)測客戶是否吊線)
        
    代碼很短,相信很容易懂,如果有什么問題,可以給我發(fā)郵件zhouhuis22@sina.com  或者在CSDN上發(fā)送短消息。同時(shí),歡迎轉(zhuǎn)發(fā)此文,但希望保留作者版權(quán)8-)。
    
    最后感謝CSDN網(wǎng)友 PiggyXP 和 Seilfer的測試幫助
P2PServer.c
/* P2P 程序服務(wù)端
 * 
 * 文件名:P2PServer.c
 *
 * 日期:2004-5-21
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 *
 */
#pragma comment(lib, "ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
UserList ClientList;
void InitWinSock()
{
 WSADATA wsaData;
 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
 {
  printf("Windows sockets 2.2 startup");
  throw Exception("");
 }
 else{
  printf("Using %s (Status: %s)\n",
   wsaData.szDescription, wsaData.szSystemStatus);
  printf("with API versions %d.%d to %d.%d\n\n",
   LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
   LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
  
 }
}
SOCKET mksock(int type)
{
 SOCKET sock = socket(AF_INET, type, 0);
 if (sock < 0)
 {
        printf("create socket error");
  throw Exception("");
 }
 return sock;
}
stUserListNode GetUser(char *username)
{
 for(UserList::iterator UserIterator=ClientList.begin();
      UserIterator!=ClientList.end();
       ++UserIterator)
 {
  if( strcmp( ((*UserIterator)->userName), username) == 0 )
   return *(*UserIterator);
 }
 throw Exception("not find this user");
}
int main(int argc, char* argv[])
{
 try{
  InitWinSock();
  
  SOCKET PrimaryUDP;
  PrimaryUDP = mksock(SOCK_DGRAM);
  sockaddr_in local;
  local.sin_family=AF_INET;
  local.sin_port= htons(SERVER_PORT); 
  local.sin_addr.s_addr = htonl(INADDR_ANY);
  int nResult=bind(PrimaryUDP,(sockaddr*)&local,sizeof(sockaddr));
  if(nResult==SOCKET_ERROR)
   throw Exception("bind error");
  sockaddr_in sender;
  stMessage recvbuf;
  memset(&recvbuf,0,sizeof(stMessage));
  // 開始主循環(huán).
  // 主循環(huán)負(fù)責(zé)下面幾件事情:
  // 一:讀取客戶端登陸和登出消息,記錄客戶列表
  // 二:轉(zhuǎn)發(fā)客戶p2p請(qǐng)求
  for(;;)
  {
   int dwSender = sizeof(sender);
   int ret = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(stMessage), 0, (sockaddr *)&sender, &dwSender);
   if(ret <= 0)
   {
    printf("recv error");
    continue;
   }
   else
   {
    int messageType = recvbuf.iMessageType;
    switch(messageType){
    case LOGIN:
     {
      //  將這個(gè)用戶的信息記錄到用戶列表中
      printf("has a user login : %s\n", recvbuf.message.loginmember.userName);
      stUserListNode *currentuser = new stUserListNode();
      strcpy(currentuser->userName, recvbuf.message.loginmember.userName);
      currentuser->ip = ntohl(sender.sin_addr.S_un.S_addr);
      currentuser->port = ntohs(sender.sin_port);
      
      ClientList.push_back(currentuser);
      // 發(fā)送已經(jīng)登陸的客戶信息
      int nodecount = (int)ClientList.size();
      sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
      for(UserList::iterator UserIterator=ClientList.begin();
        UserIterator!=ClientList.end();
        ++UserIterator)
      {
       sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender)); 
      }
      break;
     }
    case LOGOUT:
     {
      // 將此客戶信息刪除
      printf("has a user logout : %s\n", recvbuf.message.logoutmember.userName);
      UserList::iterator removeiterator = NULL;
      for(UserList::iterator UserIterator=ClientList.begin();
       UserIterator!=ClientList.end();
       ++UserIterator)
      {
       if( strcmp( ((*UserIterator)->userName), recvbuf.message.logoutmember.userName) == 0 )
       {
        removeiterator = UserIterator;
        break;
       }
      }
      if(removeiterator != NULL)
       ClientList.remove(*removeiterator);
      break;
     }
    case P2PTRANS:
     {
      // 某個(gè)客戶希望服務(wù)端向另外一個(gè)客戶發(fā)送一個(gè)打洞消息
      printf("%s wants to p2p %s\n",inet_ntoa(sender.sin_addr),recvbuf.message.translatemessage.userName);
      stUserListNode node = GetUser(recvbuf.message.translatemessage.userName);
      sockaddr_in remote;
      remote.sin_family=AF_INET;
      remote.sin_port= htons(node.port); 
      remote.sin_addr.s_addr = htonl(node.ip);
      in_addr tmp;
      tmp.S_un.S_addr = htonl(node.ip);
      printf("the address is %s,and port is %d\n",inet_ntoa(tmp), node.port);
      stP2PMessage transMessage;
      transMessage.iMessageType = P2PSOMEONEWANTTOCALLYOU;
      transMessage.iStringLen = ntohl(sender.sin_addr.S_un.S_addr);
      transMessage.Port = ntohs(sender.sin_port);
                        
      sendto(PrimaryUDP,(const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr *)&remote, sizeof(remote));
      break;
     }
    
    case GETALLUSER:
     {
      int command = GETALLUSER;
      sendto(PrimaryUDP, (const char*)&command, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
      int nodecount = (int)ClientList.size();
      sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
      for(UserList::iterator UserIterator=ClientList.begin();
        UserIterator!=ClientList.end();
        ++UserIterator)
      {
       sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender)); 
      }
      break;
     }
    }
   }
  }
 }
 catch(Exception &e)
 {
  printf(e.GetMessage());
  return 1;
 }
 return 0;
}
/* P2P 程序客戶端
 * 
 * 文件名:P2PClient.c
 *
 * 日期:2004-5-21
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 *
 */
#pragma comment(lib,"ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
#include <iostream>
using namespace std;
UserList ClientList;
 
#define COMMANDMAXC 256
#define MAXRETRY    5
SOCKET PrimaryUDP;
char UserName[10];
char ServerIP[20];
bool RecvedACK;
void InitWinSock()
{
 WSADATA wsaData;
 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
 {
  printf("Windows sockets 2.2 startup");
  throw Exception("");
 }
 else{
  printf("Using %s (Status: %s)\n",
   wsaData.szDescription, wsaData.szSystemStatus);
  printf("with API versions %d.%d to %d.%d\n\n",
   LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
   LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
 }
}
SOCKET mksock(int type)
{
 SOCKET sock = socket(AF_INET, type, 0);
 if (sock < 0)
 {
        printf("create socket error");
  throw Exception("");
 }
 return sock;
}
stUserListNode GetUser(char *username)
{
 for(UserList::iterator UserIterator=ClientList.begin();
      UserIterator!=ClientList.end();
       ++UserIterator)
 {
  if( strcmp( ((*UserIterator)->userName), username) == 0 )
   return *(*UserIterator);
 }
 throw Exception("not find this user");
}
void BindSock(SOCKET sock)
{
 sockaddr_in sin;
 sin.sin_addr.S_un.S_addr = INADDR_ANY;
 sin.sin_family = AF_INET;
 sin.sin_port = 0;
 
 if (bind(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0)
  throw Exception("bind error");
}
void ConnectToServer(SOCKET sock,char *username, char *serverip)
{
 sockaddr_in remote;
 remote.sin_addr.S_un.S_addr = inet_addr(serverip);
 remote.sin_family = AF_INET;
 remote.sin_port = htons(SERVER_PORT);
 
 stMessage sendbuf;
 sendbuf.iMessageType = LOGIN;
 strncpy(sendbuf.message.loginmember.userName, username, 10);
 sendto(sock, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote,sizeof(remote));
 int usercount;
 int fromlen = sizeof(remote);
 int iread = recvfrom(sock, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
 if(iread<=0)
 {
  throw Exception("Login error\n");
 }
 // 登錄到服務(wù)端后,接收服務(wù)端發(fā)來的已經(jīng)登錄的用戶的信息
 cout<<"Have "<<usercount<<" users logined server:"<<endl;
 for(int i = 0;i<usercount;i++)
 {
  stUserListNode *node = new stUserListNode;
  recvfrom(sock, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
  ClientList.push_back(node);
  cout<<"Username:"<<node->userName<<endl;
  in_addr tmp;
  tmp.S_un.S_addr = htonl(node->ip);
  cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
  cout<<"UserPort:"<<node->port<<endl;
  cout<<""<<endl;
 }
}
void OutputUsage()
{
 cout<<"You can input you command:\n"
  <<"Command Type:\"send\",\"exit\",\"getu\"\n"
  <<"Example : send Username Message\n"
  <<"          exit\n"
  <<"          getu\n"
  <<endl;
}
/* 這是主要的函數(shù):發(fā)送一個(gè)消息給某個(gè)用戶(C)
 *流程:直接向某個(gè)用戶的外網(wǎng)IP發(fā)送消息,如果此前沒有聯(lián)系過
 *      那么此消息將無法發(fā)送,發(fā)送端等待超時(shí)。
 *      超時(shí)后,發(fā)送端將發(fā)送一個(gè)請(qǐng)求信息到服務(wù)端,
 *      要求服務(wù)端發(fā)送給客戶C一個(gè)請(qǐng)求,請(qǐng)求C給本機(jī)發(fā)送打洞消息
 *      以上流程將重復(fù)MAXRETRY次
 */
bool SendMessageTo(char *UserName, char *Message)
{
 char realmessage[256];
 unsigned int UserIP;
 unsigned short UserPort;
 bool FindUser = false;
 for(UserList::iterator UserIterator=ClientList.begin();
      UserIterator!=ClientList.end();
      ++UserIterator)
 {
  if( strcmp( ((*UserIterator)->userName), UserName) == 0 )
  {
   UserIP = (*UserIterator)->ip;
   UserPort = (*UserIterator)->port;
   FindUser = true;
  }
 }
 if(!FindUser)
  return false;
 strcpy(realmessage, Message);
 for(int i=0;i<MAXRETRY;i++)
 {
  RecvedACK = false;
  sockaddr_in remote;
  remote.sin_addr.S_un.S_addr = htonl(UserIP);
  remote.sin_family = AF_INET;
  remote.sin_port = htons(UserPort);
  stP2PMessage MessageHead;
  MessageHead.iMessageType = P2PMESSAGE;
  MessageHead.iStringLen = (int)strlen(realmessage)+1;
  int isend = sendto(PrimaryUDP, (const char *)&MessageHead, sizeof(MessageHead), 0, (const sockaddr*)&remote, sizeof(remote));
  isend = sendto(PrimaryUDP, (const char *)&realmessage, MessageHead.iStringLen, 0, (const sockaddr*)&remote, sizeof(remote));
  
  // 等待接收線程將此標(biāo)記修改
  for(int j=0;j<10;j++)
  {
   if(RecvedACK)
    return true;
   else
    Sleep(300);
  }
  // 沒有接收到目標(biāo)主機(jī)的回應(yīng),認(rèn)為目標(biāo)主機(jī)的端口映射沒有
  // 打開,那么發(fā)送請(qǐng)求信息給服務(wù)器,要服務(wù)器告訴目標(biāo)主機(jī)
  // 打開映射端口(UDP打洞)
  sockaddr_in server;
  server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
  server.sin_family = AF_INET;
  server.sin_port = htons(SERVER_PORT);
 
  stMessage transMessage;
  transMessage.iMessageType = P2PTRANS;
  strcpy(transMessage.message.translatemessage.userName, UserName);
  sendto(PrimaryUDP, (const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr*)&server, sizeof(server));
  Sleep(100);// 等待對(duì)方先發(fā)送信息。
 }
 return false;
}
// 解析命令,暫時(shí)只有exit和send命令
// 新增getu命令,獲取當(dāng)前服務(wù)器的所有用戶
void ParseCommand(char * CommandLine)
{
 if(strlen(CommandLine)<4)
  return;
 char Command[10];
 strncpy(Command, CommandLine, 4);
 Command[4]='\0';
 if(strcmp(Command,"exit")==0)
 {
  stMessage sendbuf;
  sendbuf.iMessageType = LOGOUT;
  strncpy(sendbuf.message.logoutmember.userName, UserName, 10);
  sockaddr_in server;
  server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
  server.sin_family = AF_INET;
  server.sin_port = htons(SERVER_PORT);
  sendto(PrimaryUDP,(const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr *)&server, sizeof(server));
  shutdown(PrimaryUDP, 2);
  closesocket(PrimaryUDP);
  exit(0);
 }
 else if(strcmp(Command,"send")==0)
 {
  char sendname[20];
  char message[COMMANDMAXC];
  int i;
  for(i=5;;i++)
  {
   if(CommandLine[i]!=' ')
    sendname[i-5]=CommandLine[i];
   else
   {
    sendname[i-5]='\0';
    break;
   }
  }
  strcpy(message, &(CommandLine[i+1]));
  if(SendMessageTo(sendname, message))
   printf("Send OK!\n");
  else 
   printf("Send Failure!\n");
 }
 else if(strcmp(Command,"getu")==0)
 {
  int command = GETALLUSER;
  sockaddr_in server;
  server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
  server.sin_family = AF_INET;
  server.sin_port = htons(SERVER_PORT);
  sendto(PrimaryUDP,(const char*)&command, sizeof(command), 0, (const sockaddr *)&server, sizeof(server));
 }
}
// 接受消息線程
DWORD WINAPI RecvThreadProc(LPVOID lpParameter)
{
 sockaddr_in remote;
 int sinlen = sizeof(remote);
 stP2PMessage recvbuf;
 for(;;)
 {
  int iread = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(recvbuf), 0, (sockaddr *)&remote, &sinlen);
  if(iread<=0)
  {
   printf("recv error\n");
   continue;
  }
  switch(recvbuf.iMessageType)
  {
  case P2PMESSAGE:
   {
    // 接收到P2P的消息
    char *comemessage= new char[recvbuf.iStringLen];
    int iread1 = recvfrom(PrimaryUDP, comemessage, 256, 0, (sockaddr *)&remote, &sinlen);
    comemessage[iread1-1] = '\0';
    if(iread1<=0)
     throw Exception("Recv Message Error\n");
    else
    {
     printf("Recv a Message:%s\n",comemessage);
     
     stP2PMessage sendbuf;
     sendbuf.iMessageType = P2PMESSAGEACK;
     sendto(PrimaryUDP, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote, sizeof(remote));
    }
    delete []comemessage;
    break;
   }
  case P2PSOMEONEWANTTOCALLYOU:
   {
    // 接收到打洞命令,向指定的IP地址打洞
    printf("Recv p2someonewanttocallyou data\n");
    sockaddr_in remote;
    remote.sin_addr.S_un.S_addr = htonl(recvbuf.iStringLen);
    remote.sin_family = AF_INET;
    remote.sin_port = htons(recvbuf.Port);
    // UDP hole punching
    stP2PMessage message;
    message.iMessageType = P2PTRASH;
    sendto(PrimaryUDP, (const char *)&message, sizeof(message), 0, (const sockaddr*)&remote, sizeof(remote));
                
    break;
   }
  case P2PMESSAGEACK:
   {
    // 發(fā)送消息的應(yīng)答
    RecvedACK = true;
    break;
   }
  case P2PTRASH:
   {
    // 對(duì)方發(fā)送的打洞消息,忽略掉。
    //do nothing ...
    printf("Recv p2ptrash data\n");
    break;
   }
  case GETALLUSER:
   {
    int usercount;
    int fromlen = sizeof(remote);
    int iread = recvfrom(PrimaryUDP, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
    if(iread<=0)
    {
     throw Exception("Login error\n");
    }
    
    ClientList.clear();
    cout<<"Have "<<usercount<<" users logined server:"<<endl;
    for(int i = 0;i<usercount;i++)
    {
     stUserListNode *node = new stUserListNode;
     recvfrom(PrimaryUDP, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
     ClientList.push_back(node);
     cout<<"Username:"<<node->userName<<endl;
     in_addr tmp;
     tmp.S_un.S_addr = htonl(node->ip);
     cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
     cout<<"UserPort:"<<node->port<<endl;
     cout<<""<<endl;
    }
    break;
   }
  }
 }
}

int main(int argc, char* argv[])
{
 try
 {
  InitWinSock();
  
  PrimaryUDP = mksock(SOCK_DGRAM);
  BindSock(PrimaryUDP);
  cout<<"Please input server ip:";
  cin>>ServerIP;
  cout<<"Please input your name:";
  cin>>UserName;
  ConnectToServer(PrimaryUDP, UserName, ServerIP);
  HANDLE threadhandle = CreateThread(NULL, 0, RecvThreadProc, NULL, NULL, NULL);
  CloseHandle(threadhandle);
  OutputUsage();
  for(;;)
  {
   char Command[COMMANDMAXC];
   gets(Command);
   ParseCommand(Command);
  }
 }
 catch(Exception &e)
 {
  printf(e.GetMessage());
  return 1;
 }
 return 0;
}
/* 異常類
 *
 * 文件名:Exception.h
 *
 * 日期:2004.5.5
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 */
#ifndef __HZH_Exception__
#define __HZH_Exception__
#define EXCEPTION_MESSAGE_MAXLEN 256
#include "string.h"
class Exception
{
private:
 char m_ExceptionMessage[EXCEPTION_MESSAGE_MAXLEN];
public:
 Exception(char *msg)
 {
  strncpy(m_ExceptionMessage, msg, EXCEPTION_MESSAGE_MAXLEN);
 }
 char *GetMessage()
 {
  return m_ExceptionMessage;
 }
};
#endif
/* P2P 程序傳輸協(xié)議
 * 
 * 日期:2004-5-21
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 *
 */
#pragma once
#include <list>
// 定義iMessageType的值
#define LOGIN 1
#define LOGOUT 2
#define P2PTRANS 3
#define GETALLUSER  4
// 服務(wù)器端口
#define SERVER_PORT 2280
// Client登錄時(shí)向服務(wù)器發(fā)送的消息
struct stLoginMessage
{
 char userName[10];
 char password[10];
};
// Client注銷時(shí)發(fā)送的消息
struct stLogoutMessage
{
 char userName[10];
};
// Client向服務(wù)器請(qǐng)求另外一個(gè)Client(userName)向自己方向發(fā)送UDP打洞消息
struct stP2PTranslate
{
 char userName[10];
};
// Client向服務(wù)器發(fā)送的消息格式
struct stMessage
{
 int iMessageType;
 union _message
 {
  stLoginMessage loginmember;
  stLogoutMessage logoutmember;
  stP2PTranslate translatemessage;
 }message;
};
// 客戶節(jié)點(diǎn)信息
struct stUserListNode
{
 char userName[10];
 unsigned int ip;
 unsigned short port;
};
// Server向Client發(fā)送的消息
struct stServerToClient
{
 int iMessageType;
 union _message
 {
  stUserListNode user;
 }message;
};
//======================================
// 下面的協(xié)議用于客戶端之間的通信
//======================================
#define P2PMESSAGE 100               // 發(fā)送消息
#define P2PMESSAGEACK 101            // 收到消息的應(yīng)答
#define P2PSOMEONEWANTTOCALLYOU 102  // 服務(wù)器向客戶端發(fā)送的消息
                                     // 希望此客戶端發(fā)送一個(gè)UDP打洞包
#define P2PTRASH        103          // 客戶端發(fā)送的打洞包,接收端應(yīng)該忽略此消息
// 客戶端之間發(fā)送消息格式
struct stP2PMessage
{
 int iMessageType;
 int iStringLen;         // or IP address
 unsigned short Port; 
};
using namespace std;
typedef list<stUserListNode *> UserList;


攀升 2011-05-19 08:35 發(fā)表評(píng)論
]]>
(轉(zhuǎn))UNIX IO---再談文件描述符 http://www.shnenglu.com/iuranus/archive/2009/12/22/103681.html攀升攀升Tue, 22 Dec 2009 03:20:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/12/22/103681.htmlhttp://www.shnenglu.com/iuranus/comments/103681.htmlhttp://www.shnenglu.com/iuranus/archive/2009/12/22/103681.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/103681.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/103681.html在C程序中,文件由文件指針或者文件描述符表示。ISO C的標(biāo)準(zhǔn)I/0庫函數(shù)(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指針,UNIX的I/O函數(shù)(open, close, read, write, ioctl)使用文件描述符。下面重點(diǎn)來說下,文件描述符是如何工作的。
 
文件描述符相當(dāng)于一個(gè)邏輯句柄,而open,close等函數(shù)則是將文件或者物理設(shè)備與句柄相關(guān)聯(lián)。句柄是一個(gè)整數(shù),可以理解為進(jìn)程特定的文件 描述符表的索引。先介紹下面三個(gè)概念,后面講下open、close等操作以后,文件和文件描述符產(chǎn)生什么關(guān)系,以及fork后文件描述符的繼承等問題。
 
文件描述符表 :用戶區(qū)的一部分,除非通過使用文件描述符的函數(shù),否則程序無法對(duì)其進(jìn)行訪問。對(duì)進(jìn)程中每個(gè)打開的文件,文件描述符表都包含一個(gè)條目。
 
系統(tǒng)文件表 :為系統(tǒng)中所有的進(jìn)程共享。對(duì)每個(gè)活動(dòng)的open, 它都包含一個(gè)條目。每個(gè)系統(tǒng)文件表的條目都包含文件偏移量、訪問模式(讀、寫、or 讀-寫)以及指向它的文件描述符表的條目計(jì)數(shù)。
 
內(nèi)存索引節(jié)點(diǎn)表: 對(duì)系統(tǒng)中的每個(gè)活動(dòng)的文件(被某個(gè)進(jìn)程打開了),內(nèi)存中索引節(jié)點(diǎn)表都包含一個(gè)條目。幾個(gè)系統(tǒng)文件表?xiàng)l目可能對(duì)應(yīng)于同一個(gè)內(nèi)存索引節(jié)點(diǎn)表(不同進(jìn)程打開同一個(gè)文件)。
 
1、舉例: 執(zhí)行myfd = open( "/home/lucy/my.dat", O_RDONLY); 以后,上述3個(gè)表的關(guān)系原理圖如下:
http://keren.blog.51cto.com/
                                                                                  圖1
 
系統(tǒng)文件表包含一個(gè)偏移量,給出了文件當(dāng)前的位置。若2個(gè)進(jìn)程同時(shí)打開一個(gè)文件(如上圖A,B)做讀操作,每個(gè)進(jìn)程都有自己相對(duì)于文件的偏移 量,而且讀入整個(gè)文件是獨(dú)立于另一個(gè)進(jìn)程的;如果2個(gè)進(jìn)程打開同一個(gè)文件做寫操作,寫操作是相互獨(dú)立的,每個(gè)進(jìn)程都可以重寫另一個(gè)進(jìn)程寫入的內(nèi)容。
 
如果上面進(jìn)程在open以后又執(zhí)行了close()函數(shù),操作系統(tǒng)會(huì)刪除文件描述符表的第四個(gè)條目,和系統(tǒng)文件表的對(duì)應(yīng)條目(若指向它的描述符 表唯一),并對(duì)內(nèi)存索引節(jié)點(diǎn)表?xiàng)l目中的計(jì)數(shù)減1,如果自減以后變?yōu)?,說明沒有其他進(jìn)程鏈接此文件,將索引節(jié)點(diǎn)表?xiàng)l目也刪除,而這里進(jìn)程B也在open這 個(gè)文件,所以索引節(jié)點(diǎn)表?xiàng)l目保留。
 
2、文件描述符的繼承
通過fork()創(chuàng)建子進(jìn)程時(shí),子進(jìn)程繼承父進(jìn)程環(huán)境和上下文的大部分內(nèi)容的拷貝,其中就包括文件描述符表。
 
(1)對(duì)于父進(jìn)程在fork()之前打開的文件來說,子進(jìn)程都會(huì)繼承,與父進(jìn)程共享相同的文件偏移量。如下圖所示(0-1-2 表示 標(biāo)準(zhǔn)輸入-輸出-錯(cuò)誤):
                                  圖2 fork()之前打開my.dat
 
系統(tǒng)文件表位于系統(tǒng)空間中,不會(huì)被fork()復(fù)制,但是系統(tǒng)文件表中的條目會(huì)保存指向它的文件描述符表的計(jì)數(shù),fork()時(shí)需要對(duì)這個(gè)計(jì)數(shù) 進(jìn)行維護(hù),以體現(xiàn)子進(jìn)程對(duì)應(yīng)的新的文件描述符表也指向它。程序關(guān)閉文件時(shí),也是將系統(tǒng)文件表?xiàng)l目內(nèi)部的計(jì)數(shù)減一,當(dāng)計(jì)數(shù)值減為0時(shí),才將其刪除。
 
(2)相反,如果父進(jìn)程先進(jìn)程fork,再打開my.dat,這時(shí)父子進(jìn)程關(guān)于my.dat 的文件描述符表指向不同的系統(tǒng)文件表?xiàng)l目,也不再共享文件偏移量(fork以后2個(gè)進(jìn)程分別open,在系統(tǒng)文件表中創(chuàng)建2個(gè)條目);但是關(guān)于標(biāo)準(zhǔn)輸入, 標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)錯(cuò)誤,父子進(jìn)程還是共享的。
                      圖3   fork()以后打開my.dat
 
 

本文出自 “淡泊明志,寧靜致遠(yuǎn)” 博客,請(qǐng)務(wù)必保留此出處http://keren.blog.51cto.com/720558/170822




攀升 2009-12-22 11:20 發(fā)表評(píng)論
]]>
dup與賦值語句用于文件描述符的區(qū)別(聚合)http://www.shnenglu.com/iuranus/archive/2009/12/22/103672.html攀升攀升Tue, 22 Dec 2009 02:10:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/12/22/103672.htmlhttp://www.shnenglu.com/iuranus/comments/103672.htmlhttp://www.shnenglu.com/iuranus/archive/2009/12/22/103672.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/103672.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/103672.html
    進(jìn)程要對(duì)文件進(jìn)行操作,一般使用open調(diào)用打開一個(gè)文件進(jìn)行訪問,每個(gè)進(jìn)程都有一個(gè)文件描述符表,該表中存放打開的文件描述符。用戶使用open等調(diào)用得到的文件描述符其實(shí)是文件描述符在該表中的索引號(hào),該表項(xiàng)的內(nèi)容是一個(gè)指向文件表的指針。應(yīng)用程序只要使用該描述符就可以對(duì)指定文件進(jìn)行操作。
   
    為了了解dup與賦值語句用于文件描述符的區(qū)別,請(qǐng)看如下程序。

程序描述:

    打開一個(gè)文件描述符,分別適用dup和賦值語句進(jìn)行復(fù)制,復(fù)制之后,打印原始和被復(fù)制的文件描述符id,看看是否具有相同的值,然后關(guān)閉文件,測試關(guān)閉是否成功。

程序示例:

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h>

#include <unistd.h>

 

int sys_err(char *str)

{

    puts(str);

    exit(0);

}

 

int main(void)

{

    int p,q;

 

    if((p=open("c_fid.c", O_RDONLY)) == -1)

        sys_err("open error");

    q = dup(p);

    puts("dup:");

    printf("file p,q fd is:%d %d\n", q, p);

    printf("close file p ok?: %d\n", close(p));

    printf("close file q ok?: %d\n", close(q));

 

    if((p=open("c_fid.c", O_RDONLY)) == -1)

        sys_err("open error");

    q = p;

    puts("=:");

    printf("file p,q fd is:%d %d\n", q, p);

    printf("close file p ok?: %d\n", close(p));

    printf("close file q ok?: %d\n", close(q));

 

    return 0;

}

 

程序運(yùn)行結(jié)果:

dup:

file p,q fd is:4 3   //文件p,q使用不同的文件描述符

close file p ok?: 0

close file q ok?: 0 //文件關(guān)閉成功

=:

file p,q fd is:3 3 //簡單復(fù)制

close file p ok?: 0

close file q ok?: -1//關(guān)閉失敗,原因是此描述符已經(jīng)被關(guān)閉了

 

    由此證明,dup是產(chǎn)生一個(gè)新的文件描述符id和指針在進(jìn)程表項(xiàng)中,但是他們共用文件表,這時(shí),關(guān)閉一個(gè)文件描述符,另外一個(gè)仍舊可用,文件表并不會(huì)被釋放。而賦值語句不同,它只是簡單的在另外一個(gè)變量中記錄原始文件指針等,2個(gè)變量的文件描述符相同,進(jìn)程表項(xiàng)中并不產(chǎn)生新的項(xiàng)目。

關(guān)于socket的文件描述符

    socket接口增加了網(wǎng)絡(luò)通信操作的抽象定義,與文件操作一樣,每個(gè)打開的socket都對(duì)應(yīng)一個(gè)整數(shù),我們稱它為socket描述符,該整數(shù)也是socket描述符在文件描述符表中的索引值。但socket描述符在描述符表中的表項(xiàng)并不指向文件表,而是指向一個(gè)與該socket有關(guān)的數(shù)據(jù)結(jié)構(gòu)。BSD UNIX中新增加了一個(gè)socket調(diào)用,應(yīng)用程序可以調(diào)用它來新建一個(gè)socket描述符,注意進(jìn)程用open只能產(chǎn)生文件描述符,而不能產(chǎn)生socket描述符。socket調(diào)用只能完成建立通信的部分工作,一旦建立了一個(gè)socket,應(yīng)用程序可以使用其他特定的調(diào)用來為它添加其他詳細(xì)信息,以完成建立通信的過程。



攀升 2009-12-22 10:10 發(fā)表評(píng)論
]]>
Linux進(jìn)程通信:管道要點(diǎn)http://www.shnenglu.com/iuranus/archive/2009/08/09/92717.html攀升攀升Sun, 09 Aug 2009 09:24:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/08/09/92717.htmlhttp://www.shnenglu.com/iuranus/comments/92717.htmlhttp://www.shnenglu.com/iuranus/archive/2009/08/09/92717.html#Feedback2http://www.shnenglu.com/iuranus/comments/commentRss/92717.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/92717.html        管道的認(rèn)識(shí)從command1 | command2 認(rèn)識(shí)開始,到現(xiàn)在做A2DP升華,寫一些使用FIFO的要點(diǎn)下來。
        
      1   管道一般用于進(jìn)程間通信,把一個(gè)進(jìn)程的輸出通過管道送給另一個(gè)進(jìn)程。
      2   可以通過popen,pclose嘗試實(shí)現(xiàn)command1 | command2 。
            File *popen(const char * command, const char *open_mode);
            open_mode: r or w
            File a =popen("uname -a", "r");
            fread(buffer, 1, BUFSIZE, a);
            printf("%s", buffer);
            >> Linux Ubuntu 8.09..................

      3   pipe創(chuàng)建管道
           #include <unistd.h>

           int pipe(int file_description[2]);
           pipe的參數(shù)是由兩個(gè)文件描述符組成的數(shù)組。file_description[0] 用于讀管道, file_description[1] 用來寫管道。
       4   命名管道:mkfifo
            #include <sys/types.h>
            #include <sys/stat.h>
            int mkfifo(const char * filename, mode_t mode);
            mode: O_RDONLY, O_WRONLY, O_NONBLOCK.
            共四種組合:
            O_RDONLY:阻塞讀方式打開,除非有進(jìn)程以寫方式打開,不然阻塞。
            O_RDONLY|O_NONBLOCK:  不論怎樣,立即返回,總是成功
            O_WRONLY: 阻塞寫方式打開,直到有人來讀,不然阻塞
            O_WRONLY|O_NONBLOCK: 立即返回,但如果沒人以讀方式打開,返回-1錯(cuò)誤
            FIFO SIZE:#include <limites.h>, PIPE_BUF, default 4096
            
            多個(gè)進(jìn)程可以寫同一個(gè)管道。

攀升 2009-08-09 17:24 發(fā)表評(píng)論
]]>
firmware是什么http://www.shnenglu.com/iuranus/archive/2009/07/19/90536.html攀升攀升Sun, 19 Jul 2009 11:54:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/07/19/90536.htmlhttp://www.shnenglu.com/iuranus/comments/90536.htmlhttp://www.shnenglu.com/iuranus/archive/2009/07/19/90536.html#Feedback3http://www.shnenglu.com/iuranus/comments/commentRss/90536.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/90536.html閱讀全文

攀升 2009-07-19 19:54 發(fā)表評(píng)論
]]>
glibc: getopt()http://www.shnenglu.com/iuranus/archive/2009/07/17/90337.html攀升攀升Fri, 17 Jul 2009 06:29:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/07/17/90337.htmlhttp://www.shnenglu.com/iuranus/comments/90337.htmlhttp://www.shnenglu.com/iuranus/archive/2009/07/17/90337.html#Feedback1http://www.shnenglu.com/iuranus/comments/commentRss/90337.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/90337.html以后可以用它來直接讀了。

#include <unistd.h>
#include <stdio.h>
int main(int argc, char * argv[])
{
    int aflag=0, bflag=0, cflag=0;
    int ch;
    while ((ch = getopt(argc, argv, "ab:c")) != -1)
    {
        printf("optind: %d\n", optind);
        switch (ch) {
        case 'a':
            printf("HAVE option: -a\n");
            aflag = 1;
            break;
        case 'b':
            printf("HAVE option: -b\n");
            bflag = 1;
            printf("The argument of -b is %s\n", optarg);
            break;
        case 'c':
            printf("HAVE option: -c");
            cflag = 1;
            break;
        case '?':
            printf("Unknown option: %c\n",(char)optopt);
            break;
        }
    }
}

int getopt( int argc, char *const argv[], const char *optstring );

給定了命令參數(shù)的數(shù)量 (argc)、指向這些參數(shù)的數(shù)組 (argv) 和選項(xiàng)字符串 (optstring) 后,getopt() 將返回第一個(gè)選項(xiàng),并設(shè)置一些全局變量。使用相同的參數(shù)再次調(diào)用該函數(shù)時(shí),它將返回下一個(gè)選項(xiàng),并設(shè)置相應(yīng)的全局變量。如果不再有識(shí)別到的選項(xiàng),將返回 -1,此任務(wù)就完成了。

getopt() 所設(shè)置的全局變量包括:

  • optarg——指向當(dāng)前選項(xiàng)參數(shù)(如果有)的指針。
  • optind——再次調(diào)用 getopt() 時(shí)的下一個(gè) argv 指針的索引。
  • optopt——最后一個(gè)已知選項(xiàng)。

 ./getopt -a -d -b foo
optind: 2
HAVE option: -a
./getopt: invalid option -- d
optind: 3
Unknown option: d
optind: 5
HAVE option: -b
The argument of -b is foo



攀升 2009-07-17 14:29 發(fā)表評(píng)論
]]>
FileSystem: Initializationhttp://www.shnenglu.com/iuranus/archive/2009/07/12/89898.html攀升攀升Sun, 12 Jul 2009 14:50:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/07/12/89898.htmlhttp://www.shnenglu.com/iuranus/comments/89898.htmlhttp://www.shnenglu.com/iuranus/archive/2009/07/12/89898.html#Feedback1http://www.shnenglu.com/iuranus/comments/commentRss/89898.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/89898.html       After the kernel started up,following with file system.

   init
is the first user space process spawned by the kernel after completion of the boot process. And init can exist in a single runlevel(7 levels) at any given time.  Init invokes some scripts under a directory called /etc/rc.d/init.d that includes every service script like nfs,syslog. Init also reads the system configuration file /etc/inittab. Refer to man init and man inittab if want details.

    The initrd is a temporary filesystem commonly used in the boot process of the Linux kernel. It is typically used for making preparations before the real root file can be mounted.



攀升 2009-07-12 22:50 發(fā)表評(píng)論
]]>
Linux Kernel Image: initializehttp://www.shnenglu.com/iuranus/archive/2009/07/11/89806.html攀升攀升Sat, 11 Jul 2009 09:40:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/07/11/89806.htmlhttp://www.shnenglu.com/iuranus/comments/89806.htmlhttp://www.shnenglu.com/iuranus/archive/2009/07/11/89806.html#Feedback2http://www.shnenglu.com/iuranus/comments/commentRss/89806.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/89806.html1. 編譯kernel  
      編譯linux時(shí)其實(shí)會(huì)又很多產(chǎn)物,因?yàn)閍ndroid讓我接觸了linux 內(nèi)核的一些資料。

貼最后一段make的Log
$ make ARCH=arm CROSS_COMPILE=/dev/.....
...   < many build steps omitted for clarity>
  LD          vmlinux                                                  Kernel proper,EFL format,最原始的kernel
  SYSMAP      System.map                                 
  OBJCOPY     arch/arm/boot/Image                     去掉 symbols, notes, and comments.
  Kernel:     arch/arm/boot/Image is ready                objcopy to generate a binary file, Image   
  AS          arch/arm/boot/compressed/head.o         ARM-specific startup code generic to ARM processors,與arm相關(guān)的一些重要的啟動(dòng)時(shí)要用
  GZIP        arch/arm/boot/compressed/piggy.gz      gzip打包
  AS          arch/arm/boot/compressed/piggy.o        加載piggy.S,initializes the processor,required memory regions
  CC          arch/arm/boot/compressed/misc.o        Routines used for decompressing the kernel image 
  AS          arch/arm/boot/compressed/head-xscale.o   
  AS          arch/arm/boot/compressed/big-endian.o
  LD          arch/arm/boot/compressed/vmlinux         這容易搞亂,這個(gè)vmlinux和第一個(gè)Kernel proper是不一樣的。
  OBJCOPY     arch/arm/boot/zImage                     Final composite kernel image,loaded by bootload.
  Kernel:     arch/arm/boot/zImage is ready
boot-strap Loader :misc.o head-xscale.o big-endian.o

2. Initialization
    Power on-> bootloader ->head-xscale.o(boot-strap )-> head.o(vmlinux)->main.o(kernel)

3. start_kernel();
   啟動(dòng)Init()process.init初始化之前注冊的函數(shù),最后釋放資源。內(nèi)核級(jí)別啟動(dòng)常報(bào)的錯(cuò)就是“No init found.  Try passing init= option to kernel”
這主要是因?yàn)橥ㄟ^run_init_process執(zhí)行系統(tǒng)級(jí)別的/init時(shí)失敗,返回。如果成功,該函數(shù)不會(huì)返回。
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s.  Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found.  Try passing init= option to kernel.");

PS:
start_kernel(): .../init/main.c 
Most of the Linux kernel initialization takes place in this routine
setup_arch(&command_line):  start_kernel() function is the call to setup_arch(), .../arch/arm/kernel/setup.c



攀升 2009-07-11 17:40 發(fā)表評(píng)論
]]>
(轉(zhuǎn))SUSE 10 添加 telnet 和 samba 用戶http://www.shnenglu.com/iuranus/archive/2009/06/05/86868.html攀升攀升Fri, 05 Jun 2009 12:02:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/06/05/86868.htmlhttp://www.shnenglu.com/iuranus/comments/86868.htmlhttp://www.shnenglu.com/iuranus/archive/2009/06/05/86868.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/86868.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/86868.htmlsu - <-- 切換到admin賬戶

mkdir /newroot/nfsroot/xxx   <- 建立用戶的home 路徑
adduser xxx -d /newroot/nfsroot/xxx/ -g pnd  <- 添加用戶到組
passwd wyn   <- 設(shè)置用戶密碼

chown xxx:pnd /newroot/nfsroot/xxx/  <- 更改home路徑擁有者和組

echo xxx     ALL=(ALL) NOPASSWD:NOPASSWD:ALL   >> /etc/sudoers  <- 添加sudo權(quán)限

 

smbpasswd -a xxx  <- 添加samba 用戶

 

ps: /etc/samba/smb.conf 的內(nèi)容:
[global]
        workgroup = TUX-NET
        printing = cups
        printcap name = cups
        printcap cache time = 750
        cups options = raw
        map to guest = Bad User
        include = /etc/samba/dhcp.conf
        logon path = \\%L\profiles\.msprofile
        logon home = \\%L\%U\.9xprofile
        logon drive = P:
        usershare allow guests = Yes
        add machine script = /usr/sbin/useradd  -c Machine -d /var/lib/nobody -s /bin/false %m$
        domain logons = Yes
        domain master = Yes
        local master = Yes
        os level = 65
        preferred master = Yes
        security = user
[homes]
        comment = Home Directories
        valid users = %S, %D%w%S
        browseable = No
        read only = No
        inherit acls = Yes

 

本文來自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/stevenliyong/archive/2009/03/12/3984926.aspx



攀升 2009-06-05 20:02 發(fā)表評(píng)論
]]>
修改Grub啟動(dòng)http://www.shnenglu.com/iuranus/archive/2009/03/29/78286.html攀升攀升Sun, 29 Mar 2009 11:31:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/03/29/78286.htmlhttp://www.shnenglu.com/iuranus/comments/78286.htmlhttp://www.shnenglu.com/iuranus/archive/2009/03/29/78286.html#Feedback3http://www.shnenglu.com/iuranus/comments/commentRss/78286.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/78286.html         之前裝了雙系統(tǒng),默認(rèn)是linux啟動(dòng),作開發(fā)用,后來GF霸占了,但是每次啟動(dòng)時(shí)都要手動(dòng)調(diào)整到windows,今天我又換回來了,也就是通過修改/boot/grub/grub.conf(/boot/grub/menu.list同一個(gè)文件).

         修改default, default值以0、1、2…表示默認(rèn)啟動(dòng)隨后的第1、2或第3個(gè)操作系統(tǒng),以前默認(rèn)是0,因?yàn)榈?個(gè)title就是我的Linux,第1個(gè)是Windows,所以吧default改為1.

         reboot,OK



攀升 2009-03-29 19:31 發(fā)表評(píng)論
]]>
(轉(zhuǎn))Linux時(shí)間函數(shù)http://www.shnenglu.com/iuranus/archive/2009/03/01/75232.html攀升攀升Sun, 01 Mar 2009 03:37:00 GMThttp://www.shnenglu.com/iuranus/archive/2009/03/01/75232.htmlhttp://www.shnenglu.com/iuranus/comments/75232.htmlhttp://www.shnenglu.com/iuranus/archive/2009/03/01/75232.html#Feedback1http://www.shnenglu.com/iuranus/comments/commentRss/75232.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/75232.html

asctime(將時(shí)間和日期以字符串格式表示)

 

 

相關(guān)函數(shù)

time,ctime,gmtime,localtime
表頭文件
#include<time.h>
定義函數(shù)
char * asctime(const struct tm * timeptr);
函數(shù)說明
asctime()將參數(shù)timeptr所指的tm結(jié)構(gòu)中的信息轉(zhuǎn)換成真實(shí)世界所使用的時(shí)間日期表示方法,然后將結(jié)果以字符串形態(tài)返回。此函數(shù)已經(jīng)由時(shí)區(qū)轉(zhuǎn)換成當(dāng)?shù)貢r(shí)間,字符串格式為:“Wed Jun 30 21:49:08 1993\n”
返回值
若再調(diào)用相關(guān)的時(shí)間日期函數(shù),此字符串可能會(huì)被破壞。此函數(shù)與ctime不同處在于傳入的參數(shù)是不同的結(jié)構(gòu)。
附加說明
返回一字符串表示目前當(dāng)?shù)氐臅r(shí)間日期。
范例
#include <time.h>
main()
{
time_t timep;
time (&timep);
printf(“%s”,asctime(gmtime(&timep)));
}
執(zhí)行
Sat Oct 28 02:10:06 2000
 



ctime(將時(shí)間和日期以字符串格式表示)
相關(guān)函數(shù)
time,asctime,gmtime,localtime
表頭文件
#include<time.h>
定義函數(shù)
char *ctime(const time_t *timep);
函數(shù)說明
ctime()將參數(shù)timep所指的time_t結(jié)構(gòu)中的信息轉(zhuǎn)換成真實(shí)世界所使用的時(shí)間日期表示方法,然后將結(jié)果以字符串形態(tài)返回。此函數(shù)已經(jīng)由時(shí)區(qū)轉(zhuǎn)換成當(dāng)?shù)貢r(shí)間,字符串格式為“Wed Jun 30 21 :49 :08 1993\n”。若再調(diào)用相關(guān)的時(shí)間日期函數(shù),此字符串可能會(huì)被破壞。
返回值
返回一字符串表示目前當(dāng)?shù)氐臅r(shí)間日期。
范例
#include<time.h>
main()
{
time_t timep;
time (&timep);
printf(“%s”,ctime(&timep));
}
執(zhí)行
Sat Oct 28 10 : 12 : 05 2000
 



gettimeofday(取得目前的時(shí)間)
相關(guān)函數(shù)
time,ctime,ftime,settimeofday
表頭文件
#include <sys/time.h>
#include <unistd.h>
定義函數(shù)
int gettimeofday ( struct timeval * tv , struct timezone * tz )
函數(shù)說明
gettimeofday()會(huì)把目前的時(shí)間有tv所指的結(jié)構(gòu)返回,當(dāng)?shù)貢r(shí)區(qū)的信息則放到tz所指的結(jié)構(gòu)中。
timeval結(jié)構(gòu)定義為:
struct timeval{
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};
timezone 結(jié)構(gòu)定義為:
struct timezone{
int tz_minuteswest; /*和Greenwich 時(shí)間差了多少分鐘*/
int tz_dsttime; /*日光節(jié)約時(shí)間的狀態(tài)*/
};
上述兩個(gè)結(jié)構(gòu)都定義在/usr/include/sys/time.h。tz_dsttime 所代表的狀態(tài)如下
DST_NONE /*不使用*/
DST_USA /*美國*/
DST_AUST /*澳洲*/
DST_WET /*西歐*/
DST_MET /*中歐*/
DST_EET /*東歐*/
DST_CAN /*加拿大*/
DST_GB /*大不列顛*/
DST_RUM /*羅馬尼亞*/
DST_TUR /*土耳其*/
DST_AUSTALT /*澳洲(1986年以后)*/
返回值
成功則返回0,失敗返回-1,錯(cuò)誤代碼存于errno。附加說明EFAULT指針tv和tz所指的內(nèi)存空間超出存取權(quán)限。
范例
#include<sys/time.h>
#include<unistd.h>
main(){
struct timeval tv;
struct timezone tz;
gettimeofday (&tv , &tz);
printf(“tv_sec; %d\n”, tv,.tv_sec) ;
printf(“tv_usec; %d\n”,tv.tv_usec);
printf(“tz_minuteswest; %d\n”, tz.tz_minuteswest);
printf(“tz_dsttime, %d\n”,tz.tz_dsttime);
}
執(zhí)行
tv_sec: 974857339
tv_usec:136996
tz_minuteswest:-540
tz_dsttime:0
 



gmtime(取得目前時(shí)間和日期)
相關(guān)函數(shù)
time,asctime,ctime,localtime
表頭文件
#include<time.h>
定義函數(shù)
struct tm*gmtime(const time_t*timep);
函數(shù)說明
gmtime()將參數(shù)timep 所指的time_t 結(jié)構(gòu)中的信息轉(zhuǎn)換成真實(shí)世界所使用的時(shí)間日期表示方法,然后將結(jié)果由結(jié)構(gòu)tm返回。
結(jié)構(gòu)tm的定義為
struct tm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
int tm_sec 代表目前秒數(shù),正常范圍為0-59,但允許至61秒
int tm_min 代表目前分?jǐn)?shù),范圍0-59
int tm_hour 從午夜算起的時(shí)數(shù),范圍為0-23
int tm_mday 目前月份的日數(shù),范圍01-31
int tm_mon 代表目前月份,從一月算起,范圍從0-11
int tm_year 從1900 年算起至今的年數(shù)
int tm_wday 一星期的日數(shù),從星期一算起,范圍為0-6
int tm_yday 從今年1月1日算起至今的天數(shù),范圍為0-365
int tm_isdst 日光節(jié)約時(shí)間的旗標(biāo)
此函數(shù)返回的時(shí)間日期未經(jīng)時(shí)區(qū)轉(zhuǎn)換,而是UTC時(shí)間。
返回值
返回結(jié)構(gòu)tm代表目前UTC 時(shí)間
范例
#include <time.h>
main(){
char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
time_t timep;
struct tm *p;
time(&timep);
p=gmtime(&timep);
printf(“%d%d%d”,(1900+p->tm_year), (1+p->tm_mon),p->tm_mday);
printf(“%s%d;%d;%d\n”, wday[p->tm_wday], p->tm_hour, p->tm_min, p->tm_sec);
}
執(zhí)行
2000/10/28 Sat 8:15:38
 



localtime(取得當(dāng)?shù)啬壳皶r(shí)間和日期)
相關(guān)函數(shù)
time, asctime, ctime, gmtime
表頭文件
#include<time.h>
定義函數(shù)
struct tm *localtime(const time_t * timep);
函數(shù)說明
localtime()將參數(shù)timep所指的time_t結(jié)構(gòu)中的信息轉(zhuǎn)換成真實(shí)世界所使用的時(shí)間日期表示方法,然后將結(jié)果由結(jié)構(gòu)tm返回。結(jié)構(gòu)tm的定義請(qǐng)參考gmtime()。此函數(shù)返回的時(shí)間日期已經(jīng)轉(zhuǎn)換成當(dāng)?shù)貢r(shí)區(qū)。
返回值
返回結(jié)構(gòu)tm代表目前的當(dāng)?shù)貢r(shí)間。
范例
#include<time.h>
main(){
char *wday[]={“Sun”,”Mon”,”Tue”,”Wed”,”Thu”,”Fri”,”Sat”};
time_t timep;
struct tm *p;
time(&timep);
p=localtime(&timep); /*取得當(dāng)?shù)貢r(shí)間*/
printf (“%d%d%d ”, (1900+p->tm_year),( l+p->tm_mon), p->tm_mday);
printf(“%s%d:%d:%d\n”, wday[p->tm_wday],p->tm_hour, p->tm_min, p->tm_sec);
}
執(zhí)行
2000/10/28 Sat 11:12:22
 



mktime(將時(shí)間結(jié)構(gòu)數(shù)據(jù)轉(zhuǎn)換成經(jīng)過的秒數(shù))
相關(guān)函數(shù)
time,asctime,gmtime,localtime
表頭文件
#include<time.h>
定義函數(shù)
time_t mktime(strcut tm * timeptr);
函數(shù)說明
mktime()用來將參數(shù)timeptr所指的tm結(jié)構(gòu)數(shù)據(jù)轉(zhuǎn)換成從公元1970年1月1日0時(shí)0分0 秒算起至今的UTC時(shí)間所經(jīng)過的秒數(shù)。
返回值
返回經(jīng)過的秒數(shù)。
范例
/* 用time()取得時(shí)間(秒數(shù)),利用localtime()
轉(zhuǎn)換成struct tm 再利用mktine()將struct tm轉(zhuǎn)換成原來的秒數(shù)*/
#include<time.h>
main()
{
time_t timep;
strcut tm *p;
time(&timep);
printf(“time() : %d \n”,timep);
p=localtime(&timep);
timep = mktime(p);
printf(“time()->localtime()->mktime():%d\n”,timep);
}
執(zhí)行
time():974943297
time()->localtime()->mktime():974943297
 



settimeofday(設(shè)置目前時(shí)間)
相關(guān)函數(shù)
time,ctime,ftime,gettimeofday
表頭文件
#include<sys/time.h>
#include<unistd.h>
定義函數(shù)
int settimeofday ( const struct timeval *tv,const struct timezone *tz);
函數(shù)說明
settimeofday()會(huì)把目前時(shí)間設(shè)成由tv所指的結(jié)構(gòu)信息,當(dāng)?shù)貢r(shí)區(qū)信息則設(shè)成tz所指的結(jié)構(gòu)。詳細(xì)的說明請(qǐng)參考gettimeofday()。注意,只有root權(quán)限才能使用此函數(shù)修改時(shí)間。
返回值
成功則返回0,失敗返回-1,錯(cuò)誤代碼存于errno。
錯(cuò)誤代碼
EPERM 并非由root權(quán)限調(diào)用settimeofday(),權(quán)限不夠。
EINVAL 時(shí)區(qū)或某個(gè)數(shù)據(jù)是不正確的,無法正確設(shè)置時(shí)間。
 



time(取得目前的時(shí)間)
相關(guān)函數(shù)
ctime,ftime,gettimeofday
表頭文件
#include<time.h>
定義函數(shù)
time_t time(time_t *t);
函數(shù)說明
此函數(shù)會(huì)返回從公元1970年1月1日的UTC時(shí)間從0時(shí)0分0秒算起到現(xiàn)在所經(jīng)過的秒數(shù)。如果t 并非空指針的話,此函數(shù)也會(huì)將返回值存到t指針?biāo)傅膬?nèi)存。
返回值
成功則返回秒數(shù),失敗則返回((time_t)-1)值,錯(cuò)誤原因存于errno中。
范例
#include<time.h>
mian()
{
int seconds= time((time_t*)NULL);
printf(“%d\n”,seconds);
}
執(zhí)行
9.73E+08

原文地址: http://hi.baidu.com/peruke/blog/item/753192a88ee685b7cb130cf5.html

在這里我再補(bǔ)充一個(gè):
函數(shù)說明:ftime()將目前日期由tp所指的結(jié)構(gòu)返回。tp結(jié)構(gòu)定義:

struct  timeb{
       time_t  time;                      /* 為1970-01-01至今的秒數(shù)*/
       unsigned  short  millitm;       /*  千分之一秒 */
       short  timezonel;                 /* 為目前時(shí)區(qū)和Greenwich相差的時(shí)間,單位為分鐘 */
       short  dstflag;                    /* 為日光節(jié)約時(shí)間的修正狀態(tài),如果為非0代表啟用日光節(jié)約時(shí)間修正 */
};

返回值 :無論成功或失敗都返回0

范例:
#include <sys/timeb.h>
main()
{
     struct  timeb  tp;
     ftime(&tp);
     printf("%d\n", tp.time);
}



攀升 2009-03-01 11:37 發(fā)表評(píng)論
]]>
fopen打開文件方式http://www.shnenglu.com/iuranus/archive/2008/12/25/70315.html攀升攀升Thu, 25 Dec 2008 04:40:00 GMThttp://www.shnenglu.com/iuranus/archive/2008/12/25/70315.htmlhttp://www.shnenglu.com/iuranus/comments/70315.htmlhttp://www.shnenglu.com/iuranus/archive/2008/12/25/70315.html#Feedback5http://www.shnenglu.com/iuranus/comments/commentRss/70315.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/70315.html     最近寫一個(gè)文件操作類,fopen的參數(shù)著實(shí)讓我搞了半天,因?yàn)橐郧熬褪枪潭ǖ姆绞阶x寫文件的,現(xiàn)在要做靈活了,所以就有些參數(shù)理解不夠準(zhǔn)確。以下是關(guān)于mode參數(shù)的定義。

'r' 只讀方式打開,將文件指針指向文件頭,如果文件不存在,則File返回空。
'r+' 讀寫方式打開,將文件指針指向文件頭,如果文件不存在,則File返回空。
'w' 寫入方式打開,將文件指針指向文件頭并將文件大小截為零。如果文件不存在則嘗試創(chuàng)建之。
'w+' 讀寫方式打開,將文件指針指向文件頭并將文件大小截為零。如果文件不存在則嘗試創(chuàng)建之。
'a' 寫入方式打開,將文件指針指向文件末尾。如果文件不存在則嘗試創(chuàng)建之。
'a+' 讀寫方式打開,將文件指針指向文件末尾。如果文件不存在則嘗試創(chuàng)建之。
'x' 創(chuàng)建并以寫入方式打開,將文件指針指向文件頭。如果文件已存在,則 fopen() 調(diào)用失敗并返回 FALSE。
'x' 創(chuàng)建并以寫入方式打開,將文件指針指向文件頭。如果文件已存在,則 fopen() 調(diào)用失敗并返回 FALSE。
'b' 使用字符b作為文件類型的判斷,是否是binary文件。

還有在讀文件時(shí)最好先判斷下該文件是否存在
bool ClassA::IsFileExisted(const char* filePath)
{
   struct stat info;
   if(stat(filePath, &info) != 0)
   {
      return false;
   }
   else
      return true;
}



攀升 2008-12-25 12:40 發(fā)表評(píng)論
]]>
DBus 介紹http://www.shnenglu.com/iuranus/archive/2008/12/20/69894.html攀升攀升Sat, 20 Dec 2008 04:23:00 GMThttp://www.shnenglu.com/iuranus/archive/2008/12/20/69894.htmlhttp://www.shnenglu.com/iuranus/comments/69894.htmlhttp://www.shnenglu.com/iuranus/archive/2008/12/20/69894.html#Feedback7http://www.shnenglu.com/iuranus/comments/commentRss/69894.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/69894.html      dbus是freedesktop下開源的Linux IPC通信機(jī)制,本身Linux 的IPC通信機(jī)制包括,管道(fifo),共享內(nèi)存,信號(hào)量,消息隊(duì)列,Socket等。 像現(xiàn)在流行的moblin平臺(tái)就使用了DBUS通信,還有我最近看的bluez 4 也是通過DBUS來交互的。
 
      它是個(gè)3層架構(gòu)的進(jìn)程間通信系統(tǒng),包括:   

            1.   函數(shù)庫libdbus,用于兩個(gè)應(yīng)用程序呼叫聯(lián)系和交互消息。

            2.   Message bus daemon,總線守護(hù)進(jìn)程可同時(shí)與多個(gè)應(yīng)用程序相連,并能把來自一個(gè)應(yīng)用程序的消息路由到0或者多個(gè)其他程序。

            3.   一系列基于特定應(yīng)用程序框架的Wrapper庫。 比如libdbus-glib, libdbus-python.

      參看圖1-1,  Bus Daemon Process就是運(yùn)行在linux的daemon(dbus-daemon, 用戶可以在/etc/init.d/dbus 操作,stop, start等等),  dbus-daemon運(yùn)行時(shí)會(huì)調(diào)用libdus的庫。 在Application Process1里面就是應(yīng)用層的東西了,應(yīng)用程序調(diào)用特定的應(yīng)用程序框架的Wrapper庫與dbus-daemon進(jìn)行通信。

      我前段時(shí)間就是用Python寫程序與dbus-daemon通信,所以就需要libdbus-python,后來又用c寫程序,又裝了libdus-glib。實(shí)質(zhì)上在dbus主頁上(http://www.freedesktop.org/wiki/Software/dbus)提供了很多Wrapper庫, for QT4, JAVA, Perl, C++, Pascal, QT3, .NET, Ruby等等。這個(gè)Wrapper庫呢其實(shí)就是對(duì)dbus下層調(diào)用做了封裝,給上層暴露一個(gè)友好的接口。dbus的底層其實(shí)也是通過socket通信的

                                                                         圖 1-1    
      我再給一張bluez的例子讓大家更理解dbus; 有四個(gè)應(yīng)用想與bluz的damon通信,bluez注冊到dbus中,其它的應(yīng)用只需要向dbus要bluez的數(shù)據(jù),
dbus負(fù)責(zé)再和bluez溝通了,但是bluez一定要把接口告訴其它應(yīng)用。


      理解有限,先說到這。

 



攀升 2008-12-20 12:23 發(fā)表評(píng)論
]]>
(轉(zhuǎn)載)linux select使用 http://www.shnenglu.com/iuranus/archive/2008/12/18/69718.html攀升攀升Thu, 18 Dec 2008 02:44:00 GMThttp://www.shnenglu.com/iuranus/archive/2008/12/18/69718.htmlhttp://www.shnenglu.com/iuranus/comments/69718.htmlhttp://www.shnenglu.com/iuranus/archive/2008/12/18/69718.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/69718.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/69718.html

select系統(tǒng)調(diào)用是用來讓我們的程序監(jiān)視多個(gè)文件句柄(file descriptor)的狀態(tài)變化的。程序會(huì)停在select這里等待,直到被監(jiān)視的文件句柄有某一個(gè)或多個(gè)發(fā)生了狀態(tài)改變。 文件在句柄在Linux里很多,如果你man某個(gè)函數(shù),在函數(shù)返回值部分說到成功后有一個(gè)文件句柄被創(chuàng)建的都是的,如man socket可以看到“On success, a file descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其實(shí)文件句柄就是一個(gè)整數(shù),看socket函數(shù)的聲明就明白了:

int socket(int domain, int type, int protocol);

當(dāng)然,我們最熟悉的句柄是0、1、2三個(gè),0是標(biāo)準(zhǔn)輸入,1是標(biāo)準(zhǔn)輸出,2是標(biāo)準(zhǔn)錯(cuò)誤輸出。0、1、2是整數(shù)表示的,對(duì)應(yīng)的FILE *結(jié)構(gòu)的表示就是stdin、stdout、stderr,0就是stdin,1就是stdout,2就是stderr。比如下面這兩段代碼都是從標(biāo)準(zhǔn)輸入讀入9個(gè)字節(jié)字符:

#include <stdio.h>

#include <unistd.h>

#include <string.h>

int main(int argc, char ** argv)

{       

    char buf[10] = "";        

    read(0, buf, 9); /* 從標(biāo)準(zhǔn)輸入 0 讀入字符 */        

     fprintf(stdout, "%s\n", buf); /* 向標(biāo)準(zhǔn)輸出 stdout 寫字符 */       

 

    return 0;

}

/* **上面和下面的代碼都可以用來從標(biāo)準(zhǔn)輸入讀用戶輸入的9個(gè)字符** */

#include <stdio.h>

#include <unistd.h>

#include <string.h>

int main(int argc, char ** argv)

{        

char buf[10] = "";        

fread(buf, 9, 1, stdin); /* 從標(biāo)準(zhǔn)輸入 stdin 讀入字符 */        

write(1, buf, strlen(buf));        

 

return 0;

}

繼續(xù)上面說的select,就是用來監(jiān)視某個(gè)或某些句柄的狀態(tài)變化的。

select函數(shù)原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函 數(shù)的最后一個(gè)參數(shù)timeout顯然是一個(gè)超時(shí)時(shí)間值,其類型是struct timeval *,即一個(gè)struct timeval結(jié)構(gòu)的變量的指針,所以我們在程序里要申明一個(gè)struct timeval tv;然后把變量tv的地址&tv傳遞給select函數(shù)。struct timeval結(jié)構(gòu)如下:

struct timeval

{             

long    tv_sec;         /* seconds */             

long    tv_usec;        /* microseconds */         

};

第2、 3、4三個(gè)參數(shù)是一樣的類型: fd_set *,即我們在程序里要申明幾個(gè)fd_set類型的變量,比如rdfds, wtfds, exfds,然后把這個(gè)變量的地址&rdfds, &wtfds, &exfds 傳遞給select函數(shù)。

      這三個(gè)參數(shù)都是一個(gè)句柄的集合,第一個(gè)rdfds是用來保存這樣的句柄的:當(dāng)句柄的狀態(tài)變成可讀的時(shí)系統(tǒng)就會(huì)告訴select函數(shù)返回,同理第二個(gè)wtfds是指有句柄狀態(tài)變成可寫的時(shí)系統(tǒng)就會(huì)告訴select函數(shù)返回,同理第三個(gè)參數(shù)exfds是特殊情況,即句柄上有特殊情況發(fā)生時(shí)系統(tǒng)會(huì)告訴select函數(shù)返回。特殊情況比如對(duì)方通過一個(gè)socket句柄發(fā)來了緊急數(shù)據(jù)。如果我們程序里只想檢測某個(gè)socket是否有數(shù)據(jù)可讀,我們可以這樣:

fd_set rdfds; /* 先申明一個(gè) fd_set 集合來保存我們要檢測的 socket句柄 */

struct timeval tv; /* 申明一個(gè)時(shí)間變量來保存時(shí)間 */

int ret; /* 保存返回值 */

FD_ZERO(&rdfds); /* 用select函數(shù)之前先把集合清零 */

FD_SET(socket, &rdfds); /* 把要檢測的句柄socket加入到集合里 */

 

tv.tv_sec = 1;

tv.tv_usec = 500; /* 設(shè)置select等待的最大時(shí)間為1秒加500微秒 */

 

ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 檢測我們上面設(shè)置到集合rdfds里的句柄是否有可讀信息 */

if(ret < 0)

    perror("select");/* 這說明select函數(shù)出錯(cuò) */

else if(ret == 0)

   printf("超時(shí)\n"); /* 說明在我們設(shè)定的時(shí)間值1秒加500毫秒的時(shí)間內(nèi),socket的狀態(tài)沒有發(fā)生變化 */

else

{ /* 說明等待時(shí)間還未到1秒加500毫秒,socket的狀態(tài)發(fā)生了變化 */    

    printf("ret=%d\n", ret); /* ret這個(gè)返回值記錄了發(fā)生狀態(tài)變化的句柄的數(shù)目,由于我們只監(jiān)視了socket這一個(gè)句柄,所以這里一定ret=1,如果同時(shí)有多個(gè)句柄發(fā)生變化返回的就是句柄的總和了 */

    /* 這里我們就應(yīng)該從socket這個(gè)句柄里讀取數(shù)據(jù)了,因?yàn)閟elect函數(shù)已經(jīng)告訴我們這個(gè)句柄里有數(shù)據(jù)可讀 */   

if(FD_ISSET(socket, &rdfds)) { /* 先判斷一下socket這外被監(jiān)視的句柄是否真的變成可讀的了 */        

   /* 讀取socket句柄里的數(shù)據(jù) */        

    recv(...);    

}

}

注意select函數(shù)的第一個(gè)參數(shù),是所有加入集合的句柄值的最大那個(gè)值還要加1。比如我們創(chuàng)建了3個(gè)句柄:

 

int sa, sb, sc;

sa = socket(...); /* 分別創(chuàng)建3個(gè)句柄并連接到服務(wù)器上 */

connect(sa,...);

sb = socket(...);

connect(sb,...);

sc = socket(...);

connect(sc,...);

FD_SET(sa, &rdfds);/* 分別把3個(gè)句柄加入讀監(jiān)視集合里去 */

FD_SET(sb, &rdfds);

FD_SET(sc, &rdfds);

在使用select函數(shù)之前,一定要找到3個(gè)句柄中的最大值是哪個(gè),我們一般定義一個(gè)變量來保存最大值,取得最大socket值如下:

int maxfd = 0;

if(sa > maxfd)

     maxfd = sa;

if(sb > maxfd)

    maxfd = sb;

if(sc > maxfd)

    maxfd = sc;

然后調(diào)用select函數(shù):

ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值還要加1 */

同樣的道理,如果我們要檢測用戶是否按了鍵盤進(jìn)行輸入,我們就應(yīng)該把標(biāo)準(zhǔn)輸入0這個(gè)句柄放到select里來檢測,如下:

FD_ZERO(&rdfds);

FD_SET(0, &rdfds);

tv.tv_sec = 1;

tv.tv_usec = 0;

ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值還要加1 */

if(ret < 0)

     perror("select");/* 出錯(cuò) */

else if(ret == 0)

     printf("超時(shí)\n"); /* 在我們設(shè)定的時(shí)間tv內(nèi),用戶沒有按鍵盤 */

else { /* 用戶有按鍵盤,要讀取用戶的輸入 */   

scanf("%s", buf); }



攀升 2008-12-18 10:44 發(fā)表評(píng)論
]]>
(轉(zhuǎn)載)Linux操作系統(tǒng)的Configure參數(shù)解釋說明http://www.shnenglu.com/iuranus/archive/2008/12/17/69689.html攀升攀升Wed, 17 Dec 2008 14:30:00 GMThttp://www.shnenglu.com/iuranus/archive/2008/12/17/69689.htmlhttp://www.shnenglu.com/iuranus/comments/69689.htmlhttp://www.shnenglu.com/iuranus/archive/2008/12/17/69689.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/69689.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/69689.html

Linux環(huán)境下的軟件安裝,并不是一件容易的事情;如果通過源代碼編譯后在安裝,當(dāng)然事情就更為復(fù)雜一些;現(xiàn)在安裝各種軟件的教程都非常普遍;但萬變不離其中,對(duì)基礎(chǔ)知識(shí)的扎實(shí)掌握,安裝各種軟件的問題就迎刃而解了。Configure腳本配置工具就是基礎(chǔ)之一,它是autoconf的工具的基本應(yīng)用。


與一些技巧相比,Configure顯得基礎(chǔ)一些,當(dāng)然使用和學(xué)習(xí)起來就顯得枯燥乏味一些,當(dāng)然要成為高手,對(duì)基礎(chǔ)的熟悉不能超越哦。


為此我轉(zhuǎn)載了一篇關(guān)于Configure選項(xiàng)配置的詳細(xì)介紹。供大家參考


'configure'腳本有大量的命令行選項(xiàng)。對(duì)不同的軟件包來說,這些選項(xiàng)可能會(huì)有變化,但是許多基本的選項(xiàng)是不會(huì)改變的。帶上'--help'選項(xiàng)執(zhí)行'configure'腳本可以看到可用的所有選項(xiàng)。盡管許多選項(xiàng)是很少用到的,但是當(dāng)你為了特殊的需求而configure一個(gè)包時(shí),知道他們的存在是很有益處的。下面對(duì)每一個(gè)選項(xiàng)進(jìn)行簡略的介紹:

--cache-file=FILE

'configure'會(huì)在你的系統(tǒng)上測試存在的特性(或者bug!)。為了加速隨后進(jìn)行的配置,測試的結(jié)果會(huì)存儲(chǔ)在一個(gè)cache file里。當(dāng)configure一個(gè)每個(gè)子樹里都有'configure'腳本的復(fù)雜的源碼樹時(shí),一個(gè)很好的cache file的存在會(huì)有很大幫助。

--help

輸出幫助信息。即使是有經(jīng)驗(yàn)的用戶也偶爾需要使用使用'--help'選項(xiàng),因?yàn)橐粋€(gè)復(fù)雜的項(xiàng)目會(huì)包含附加的選項(xiàng)。例如,GCC包里的'configure'腳本就包含了允許你控制是否生成和在GCC中使用GNU匯編器的選項(xiàng)。


--no-create


'configure'中的一個(gè)主要函數(shù)會(huì)制作輸出文件。此選項(xiàng)阻止'configure'生成這個(gè)文件。你可以認(rèn)為這是一種演習(xí)(dry run),盡管緩存(cache)仍然被改寫了。


--quiet

--silent


當(dāng)'configure'進(jìn)行他的測試時(shí),會(huì)輸出簡要的信息來告訴用戶正在作什么。這樣作是因?yàn)?configure'可能會(huì)比較慢,沒有這種輸出的話用戶將會(huì)被扔在一旁疑惑正在發(fā)生什么,使用這兩個(gè)選項(xiàng)中的任何一個(gè)都會(huì)把你扔到一旁。(譯注:這兩句話比較有意思,原文是這樣的:If there was no such output, the user would be left wondering what is happening. By using this option, you too can be left wondering!)


--version


打印用來產(chǎn)生'configure'腳本的Autoconf的版本號(hào)。


--prefix=PEWFIX


'--prefix'是最常用的選項(xiàng)。制作出的'Makefile'會(huì)查看隨此選項(xiàng)傳遞的參數(shù),當(dāng)一個(gè)包在安裝時(shí)可以徹底的重新安置他的結(jié)構(gòu)獨(dú)立部分。舉一個(gè)例子,當(dāng)安裝一個(gè)包,例如說Emacs,下面的命令將會(huì)使Emacs Lisp file被安裝到"/opt/gnu/share":

$ ./configure --prefix=/opt/gnu


--exec-prefix=EPREFIX


與'--prefix'選項(xiàng)類似,但是他是用來設(shè)置結(jié)構(gòu)倚賴的文件的安裝位置,編譯好的'emacs'二進(jìn)制文件就是這樣一個(gè)問件。如果沒有設(shè)置這個(gè)選項(xiàng)的話,默認(rèn)使用的選項(xiàng)值將被設(shè)為和'--prefix'選項(xiàng)值一樣。


--bindir=DIR


指定二進(jìn)制文件的安裝位置,這里的二進(jìn)制文件定義為可以被用戶直接執(zhí)行的程序。


--sbindir=DIR


指定超級(jí)二進(jìn)制文件的安裝位置。這是一些通常只能由超級(jí)用戶執(zhí)行的程序。


--libexecdir=DIR


指定可執(zhí)行支持文件的安裝位置。與二進(jìn)制文件相反,這些文件從來不直接由用戶執(zhí)行,但是可以被上面提到的二進(jìn)制文件所執(zhí)行。


--datadir=DIR


指定通用數(shù)據(jù)文件的安裝位置。


--sysconfdir=DIR


指定在單個(gè)機(jī)器上使用的只讀數(shù)據(jù)的安裝位置。


--sharedstatedir=DIR

指定可以在多個(gè)機(jī)器上共享的可寫數(shù)據(jù)的安裝位置。


--localstatedir=DIR

指定只能單機(jī)使用的可寫數(shù)據(jù)的安裝位置。

--libdir=DIR

指定庫文件的安裝位置。


--includedir=DIR

指定C頭文件的安裝位置。其他語言如C++的頭文件也可以使用此選項(xiàng)。


--oldincludedir=DIR

指定為除GCC外編譯器安裝的C頭文件的安裝位置。


--infodir=DIR

指定Info格式文檔的安裝位置.Info是被GNU工程所使用的文檔格式。


--mandir=DIR

指定手冊頁的安裝位置。


--srcdir=DIR

這個(gè)選項(xiàng)對(duì)安裝沒有作用,他會(huì)告訴'configure'源碼的位置。一般來說不用指定此選項(xiàng),因?yàn)?configure'腳本一般和源碼文件在同一個(gè)目錄下。


--program-prefix=PREFIX

指定將被加到所安裝程序的名字上的前綴。例如,使用'--program-prefix=g'來configure一個(gè)名為'tar'的程序?qū)?huì)使安裝的程序被命名為'gtar'。當(dāng)和其他的安裝選項(xiàng)一起使用時(shí),這個(gè)選項(xiàng)只有當(dāng)他被`Makefile.in'文件使用時(shí)才會(huì)工作。


--program-suffix=SUFFIX

指定將被加到所安裝程序的名字上的后綴。


--program-transform-name=PROGRAM

這里的PROGRAM是一個(gè)sed腳本。當(dāng)一個(gè)程序被安裝時(shí),他的名字將經(jīng)過`sed -e PROGRAM'來產(chǎn)生安裝的名字。


--build=BUILD

指定軟件包安裝的系統(tǒng)平臺(tái)。如果沒有指定,默認(rèn)值將是'--host'選項(xiàng)的值。


--host=HOST

指定軟件運(yùn)行的系統(tǒng)平臺(tái)。如果沒有指定。將會(huì)運(yùn)行`config.guess'來檢測。


--target=GARGET

指定軟件面向(target to)的系統(tǒng)平臺(tái)。這主要在程序語言工具如編譯器和匯編器上下文中起作用。如果沒有指定,默認(rèn)將使用'--host'選項(xiàng)的值。


--disable-FEATURE

一些軟件包可以選擇這個(gè)選項(xiàng)來提供為大型選項(xiàng)的編譯時(shí)配置,例如使用Kerberos認(rèn)證系統(tǒng)或者一個(gè)實(shí)驗(yàn)性的編譯器最優(yōu)配置。如果默認(rèn)是提供這些特性,可以使用'--disable-FEATURE'來禁用它,這里'FEATURE'是特性的名字,例如:

$ ./configure --disable-gui


-enable-FEATURE[=ARG]

相反的,一些軟件包可能提供了一些默認(rèn)被禁止的特性,可以使用'--enable-FEATURE'來起用它。這里'FEATURE'是特性的名字。一個(gè)特性可能會(huì)接受一個(gè)可選的參數(shù)。例如:

$ ./configure --enable-buffers=128

`--enable-FEATURE=no'與上面提到的'--disable-FEATURE'是同義的。


--with-PACKAGE[=ARG]

在自由軟件社區(qū)里,有使用已有軟件包和庫的優(yōu)秀傳統(tǒng)。當(dāng)用'configure'來配置一個(gè)源碼樹時(shí),可以提供其他已經(jīng)安裝的軟件包的信息。例如,倚賴于Tcl和Tk的BLT器件工具包。要配置BLT,可能需要給'configure'提供一些關(guān)于我們把Tcl和Tk裝的何處的信息:

$ ./configure --with-tcl=/usr/local --with-tk=/usr/local

'--with-PACKAGE=no'與下面將提到的'--without-PACKAGE'是同義的。


--without-PACKAGE

有時(shí)候你可能不想讓你的軟件包與系統(tǒng)已有的軟件包交互。例如,你可能不想讓你的新編譯器使用GNU ld。通過使用這個(gè)選項(xiàng)可以做到這一點(diǎn):

$ ./configure --without-gnu-ld


--x-includes=DIR

這個(gè)選項(xiàng)是'--with-PACKAGE'選項(xiàng)的一個(gè)特例。在Autoconf最初被開發(fā)出來時(shí),流行使用'configure'來作為Imake的一個(gè)變通方法來制作運(yùn)行于X的軟件。'--x-includes'選項(xiàng)提供了向'configure'腳本指明包含X11頭文件的目錄的方法。


--x-libraries=DIR

類似的,'--x-libraries'選項(xiàng)提供了向'configure'腳本指明包含X11庫的目錄的方法。


在源碼樹中運(yùn)行'configure'是不必要的同時(shí)也是不好的。一個(gè)由'configure'產(chǎn)生的良好的'Makefile'可以構(gòu)筑源碼屬于另一棵樹的軟件包。在一個(gè)獨(dú)立于源碼的樹中構(gòu)筑派生的文件的好處是很明顯的:派生的文件,如目標(biāo)文件,會(huì)凌亂的散布于源碼樹。這也使在另一個(gè)不同的系統(tǒng)或用不同的配置選項(xiàng)構(gòu)筑同樣的目標(biāo)文件非常困難。建議使用三棵樹:一棵源碼樹(source tree),一棵構(gòu)筑樹(build tree),一棵安裝樹(install tree)。這里有一個(gè)很接近的例子,是使用這種方法來構(gòu)筑GNU malloc包:

$ gtar zxf mmalloc-1.0.tar.gz

$ mkdir build && cd build

$ ../mmalloc-1.0/configure

creating cache ./config.cache

checking for gcc... gcc

checking whether the C compiler (gcc ) works... yes

checking whether the C compiler (gcc ) is a cross-compiler... no

checking whether we are using GNU C... yes

checking whether gcc accepts -g... yes

checking for a BSD compatible install... /usr/bin/install -c

checking host system type... i586-pc-linux-gnu

checking build system type... i586-pc-linux-gnu

checking for ar... ar

checking for ranlib... ranlib

checking how to run the C preprocessor... gcc -E

checking for unistd.h... yes

checking for getpagesize... yes

checking for working mmap... yes

checking for limits.h... yes

checking for stddef.h... yes

updating cache ../config.cache

creating ./config.status

這樣這棵構(gòu)筑樹就被配置了,下面可以繼續(xù)構(gòu)筑和安裝這個(gè)包到默認(rèn)的位置'/usr/local':

$ make all && make install



攀升 2008-12-17 22:30 發(fā)表評(píng)論
]]>
(轉(zhuǎn)載)GUN gcc 中文手冊http://www.shnenglu.com/iuranus/archive/2008/12/17/69687.html攀升攀升Wed, 17 Dec 2008 14:09:00 GMThttp://www.shnenglu.com/iuranus/archive/2008/12/17/69687.htmlhttp://www.shnenglu.com/iuranus/comments/69687.htmlhttp://www.shnenglu.com/iuranus/archive/2008/12/17/69687.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/69687.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/69687.html閱讀全文

攀升 2008-12-17 22:09 發(fā)表評(píng)論
]]>
簡述藍(lán)牙協(xié)議棧-完整版http://www.shnenglu.com/iuranus/archive/2008/12/14/69391.html攀升攀升Sun, 14 Dec 2008 03:50:00 GMThttp://www.shnenglu.com/iuranus/archive/2008/12/14/69391.htmlhttp://www.shnenglu.com/iuranus/comments/69391.htmlhttp://www.shnenglu.com/iuranus/archive/2008/12/14/69391.html#Feedback4http://www.shnenglu.com/iuranus/comments/commentRss/69391.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/69391.html         項(xiàng)目剛好做到藍(lán)牙了,也不是很忙,講講自己最近一段時(shí)間做的東西。

         提到協(xié)議棧,都會(huì)想到與開放式系統(tǒng)互聯(lián)(OSI)協(xié)議棧的 ,OSI協(xié)議棧定義了廠商們?nèi)绾尾拍苌a(chǎn)可以與其它廠商的產(chǎn)品一起工作的產(chǎn)品。協(xié)議棧是指一組協(xié)議的集合,舉個(gè)例子,把大象裝到冰箱里,總共要3步。每步就是一個(gè)協(xié)議,3步組成一個(gè)協(xié)議棧。把應(yīng)用層數(shù)據(jù)包發(fā)出去,也要好幾步,TCP/UDP頭,IP頭,ether頭,每步也是一個(gè)協(xié)議。另外每層都有一些特殊的協(xié)議。所有這些統(tǒng)稱協(xié)議棧。
        
         簡單的來說,藍(lán)牙協(xié)議棧就是SIG(Special Intersted Group)定義的一組協(xié)議的規(guī)范,目標(biāo)是允許遵循規(guī)范的藍(lán)牙應(yīng)用應(yīng)用能夠進(jìn)行相互間操作,圖1-1就是完整的藍(lán)牙協(xié)議棧和部分profile:

                                             圖1-1
         接著介紹下藍(lán)牙里面profile的定義,profile既是配置文件,配置文件定義了可能的應(yīng)用,藍(lán)牙配置文件表達(dá)了一般行為,藍(lán)牙設(shè)備可以通過這些行為與其它設(shè)備進(jìn)行通信。藍(lán)牙技術(shù)定義了廣泛的配置文件,描述了許多不同類型的使用案例。按照藍(lán)牙規(guī)格中提供的指導(dǎo),開發(fā)商可以創(chuàng)建應(yīng)用程序以與其它符合藍(lán)牙規(guī)格的設(shè)備協(xié)同工作。 到目前為止,藍(lán)牙一共有22個(gè)profile,在這里我就不詳細(xì)介紹圖1-1的協(xié)議和每個(gè)Profile了,在www.bluetooth.com上有詳細(xì)的文檔說明。

         在這里我想詳細(xì)介紹下已經(jīng)實(shí)現(xiàn)了r的協(xié)議棧。

  1. Widcomm:  第一個(gè)windows上的協(xié)議棧,由Widcomm公司開發(fā),也就是現(xiàn)在的Broadcom .
  2. Microsoft Windows stack: Windows XP SP2中包括了這個(gè)內(nèi)建的協(xié)議棧,開發(fā)者也可以調(diào)用其API開發(fā)第三方軟件。
  3. Toshiba stack: 它也是基于Windows的,不支持第三方開發(fā),但它把協(xié)議棧授權(quán)給一些laptop商(sony, asus等,我的本本上就是Toshiba的)。它支持的Profile有: SPP, DUN, FAX, LAP, OPP, FTP, HID, HCRP, PAN, BIP, HSP, HFP , A2DP, AVRCP, GAVDP
  4. BlueSoleil: 著名的IVT公司的產(chǎn)品,這個(gè)應(yīng)該是個(gè)中國公司,值得自豪。該產(chǎn)品可以用于桌面和嵌入式,他也支持第三方開發(fā),DUN, FAX, HFP, HSP, LAP, OBEX, OPP, PAN SPP, AV, BIP, FTP, GAP, HID, SDAP, and SYNC。
  5. Bluez: Linux官方協(xié)議棧,該協(xié)議棧的上層用Socket封裝,便于開發(fā)者使用,通過DBUS與其它應(yīng)用程序通信。那么最近我的工作就是移植bluez 4.x到板子上。
  6.  Affix: NOKIA公司的協(xié)議棧,在Symbian系統(tǒng)上運(yùn)行,具體的沒找到資料
  7. BlueDragon:東軟公司產(chǎn)品,值得驕傲,好像2002年6月就通過了藍(lán)牙的認(rèn)證,支持的Profile:SDP、Serial-DevB、AVCTP、AVRCP-Controller、AVRCP-Target、Headset-AG、Headset-HS、OPP-Client、OPP-Server、CT-GW、CT-Term、Intercom、FT-Server、FT-Client、GAP、SDAP、Serial-DevA、AVDTP、GAVDP、A2DP-Source、A2DP-Sink,但到現(xiàn)在我沒怎么聽過這個(gè)協(xié)議棧的應(yīng)用,難得是個(gè)爛尾樓??
  8. BlueMagic:美國Open Interface 公司for portable embedded divce的協(xié)議棧,iphone(apple),nav-u(sony)等很多電子產(chǎn)品都用該商業(yè)的協(xié)議棧,BlueMagic 3.0是第一個(gè)通過bluetooth 協(xié)議棧1.1認(rèn)證的協(xié)議棧,那么我現(xiàn)在就在用它,那么該棧用起來簡單,API清晰明了。實(shí)現(xiàn)了的profile有:HCI,L2CAP,RFCOMM,A/V,Remote,Control,A/V,Streaming,BIP,BPP,DUN,FAX,FTP,GAP,Hands-Free,and,Headset,HCRP,HID,OBEX,OPP,PAN,BNEP,PBAP,SAP,SPP,Synchronization,SyncML,Telephony,XML.
  9. BCHS-Bluecore Host Software: 藍(lán)牙芯片CSR的協(xié)議棧,同時(shí)他也提供了一些上層應(yīng)用的Profile的庫,當(dāng)然了它也是為嵌入式產(chǎn)品了,支持的Profile有:A2DP,AVRCP,PBAP,BIP,BPP,CTP,DUN,FAX,FM API,FTP GAP,GAVDP,GOEP,HCRP,Headset,HF1.5,HID,ICP,JSR82,LAP Message Access Profile,OPP,PAN,SAP,SDAP,SPP,SYNC,SYNC ML。
  10. Windows CE:微軟給Windows CE開發(fā)的協(xié)議棧,但是windows ce本身也支持其它的協(xié)議棧
  11. BlueLet:IVT公司for embedded product的清量級(jí)協(xié)議棧。

         我們是基于BlueMagic3的,最近呢也在研究bluez 4的移植和profile工作,后面我會(huì)再針對(duì)bluez做詳細(xì)介紹。

         時(shí)間有限,簡單的寫了下,如果各位網(wǎng)友知道一些協(xié)議棧的動(dòng)態(tài),或?qū)ξ覍懙挠醒a(bǔ)充,請(qǐng)給我留言,我會(huì)及時(shí)改正,



    ]]>
    (轉(zhuǎn)載)怎么源碼安裝 PKG_CONFIG_PATH設(shè)置http://www.shnenglu.com/iuranus/archive/2008/12/10/69035.html攀升攀升Wed, 10 Dec 2008 03:15:00 GMThttp://www.shnenglu.com/iuranus/archive/2008/12/10/69035.htmlhttp://www.shnenglu.com/iuranus/comments/69035.htmlhttp://www.shnenglu.com/iuranus/archive/2008/12/10/69035.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/69035.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/69035.html閱讀全文

    攀升 2008-12-10 11:15 發(fā)表評(píng)論
    ]]>
    多國語言惹得禍http://www.shnenglu.com/iuranus/archive/2007/11/09/36242.html攀升攀升Fri, 09 Nov 2007 10:53:00 GMThttp://www.shnenglu.com/iuranus/archive/2007/11/09/36242.htmlhttp://www.shnenglu.com/iuranus/comments/36242.htmlhttp://www.shnenglu.com/iuranus/archive/2007/11/09/36242.html#Feedback5http://www.shnenglu.com/iuranus/comments/commentRss/36242.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/36242.html        上面下指令說要做多國語言版的程序,這可是個(gè)不小的改動(dòng)呀。于是我就拿當(dāng)前的程序運(yùn)行,一系列的問題隨之出現(xiàn)了。
            話分兩頭,先說跑在Windows上的,最基本的就是讀各種語言的文件名,正常英文還是正常的,但遇到俄文,法文,德文時(shí)就出現(xiàn)問題,正常的字符都變成了"?",跟蹤內(nèi)存發(fā)現(xiàn)讀入內(nèi)存的字符已經(jīng)變成了3f00,也就是"?"的unicode,可見是讀入目錄到內(nèi)存的函數(shù)出了問題,如下代碼:
       long filehandle;
        //the structure of file
        struct _finddata_t entry;
        //"*" means get all the file and directory
        // Get the first file
        if((filehandle = _findfirst( "*", &entry )) != -1)    //-1 means the directory is null
        {
            tree* child;
            do{
                if(entry.attrib&FILE_ATTRIBUTE_DIRECTORY){
                    if ( strcmp("..", entry.name) != 0 && strcmp(".", entry.name) != 0){
                        //printf("%*s%s\\\n", depth, "", entry.name);
                        if(treenode)
                        {
                            char name[MAX_LOCALPATH_FOLDERNAME_LENGTH+1];
                            strncpy(name,entry.name,MAX_LOCALPATH_FOLDERNAME_LENGTH-1);
                            strcat(name,"\\");
                            child=new tree(name);
                            treenode->AddChild(child);
                        }
                        //Recursively processes directories
                        //printdir(entry.name, depth + 4,child);
                    }
                } else{
                    //printf("%*s%s\n", depth, "", entry.name);
                    if(treenode)
                    {
                        child=new tree(entry.name);
                        treenode->AddChild(child);
                    }
                }
            }while( (_findnext(filehandle,&entry)) ==0 );
        }

        chdir("..");
        _findclose(filehandle);
            通過查證MSDN得知類似于_findfirst,findnext都是針對(duì)ASCII碼的,要讀unicode(windows默認(rèn)字符集),就得用_wfindfirst,_wfindnext等讀寬字符的操作函數(shù),最終解決問題,但我沒有松氣,因?yàn)槌绦蛑饕沁\(yùn)行在linux中的,Linux真不知道怎么整了。
            Linux上讀多國語言的文件和目錄就需要對(duì)Linux系統(tǒng)深入了解,因?yàn)槲乙x的文件是usb上的文件,所以得先掛載到一個(gè)目錄,
            mount -t vfat /dev/sda1 /mnt/usb,然后readdir讀入文件,與Windows上同樣的錯(cuò),讀入的是"?",我想和windows一樣去找一個(gè)類似wreaddir,但是沒有。于是應(yīng)該從掛載著手,目前在NTFS和FAT32/VFAT下的文件系統(tǒng)上都使用了Unicode,這就需要系統(tǒng)在讀取這些文件名時(shí)動(dòng)態(tài)將其轉(zhuǎn)換為相應(yīng)的語言編碼,也就是說掛載的時(shí)候要把usb上的編碼轉(zhuǎn)化成16位的Unicode編碼,改命令如下后成功。
            mount -o iocharset = utf8 /dev/sda1 /mnt/usb.
            Linux對(duì)iocharset的解釋如下:
            Character set to use for converting between 8 bit characters and 16 bit unicode charaters.The default is iso8859-1. Long filenames are stored on disk in Unicode format.
            至此終于解決了多國語言的問題,接著我無法想象還有什么問題會(huì)出來,但我準(zhǔn)備好了。



    攀升 2007-11-09 18:53 發(fā)表評(píng)論
    ]]>
    dos2unixhttp://www.shnenglu.com/iuranus/archive/2007/07/05/27564.html攀升攀升Thu, 05 Jul 2007 12:07:00 GMThttp://www.shnenglu.com/iuranus/archive/2007/07/05/27564.htmlhttp://www.shnenglu.com/iuranus/comments/27564.htmlhttp://www.shnenglu.com/iuranus/archive/2007/07/05/27564.html#Feedback1http://www.shnenglu.com/iuranus/comments/commentRss/27564.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/27564.html    說起來都不好意思,今天搞了半天,最后發(fā)現(xiàn)竟然是vss上check out的文件沒有dos2unix,無語,其實(shí)主要是沒有認(rèn)真考慮整個(gè)程序的運(yùn)行全過程,僅在看代碼,向錦錦一樣。

        說起錦錦,他就慘了,廣東話+english,我看他怎么過。呵呵,不過還好了,看這個(gè)家伙怎么適應(yīng)這個(gè)生活,畢業(yè)了,大家都散了,老的走了,新的來了,我們都好好努力把。

        事情還挺多的,辜子怎么就把老板炒了,那個(gè)公司好就好在很自由,適合他的性格,看他找到下一個(gè)能不能適應(yīng)了。

        好了,回家了,這兩天籌劃寫些設(shè)計(jì)模式的東西,希望早點(diǎn)寫出來。

     

    攀升 2007-07-05 20:07 發(fā)表評(píng)論
    ]]>
    suse嘗鮮http://www.shnenglu.com/iuranus/archive/2006/11/10/17081.html攀升攀升Fri, 10 Nov 2006 05:15:00 GMThttp://www.shnenglu.com/iuranus/archive/2006/11/10/17081.htmlhttp://www.shnenglu.com/iuranus/comments/17081.htmlhttp://www.shnenglu.com/iuranus/archive/2006/11/10/17081.html#Feedback0http://www.shnenglu.com/iuranus/comments/commentRss/17081.htmlhttp://www.shnenglu.com/iuranus/services/trackbacks/17081.html

    ubuntu已經(jīng)被我從硬盤上擦除了,呵呵,因?yàn)橐恍﹩栴},找了張suse,安裝時(shí)選了KDE,現(xiàn)在突然感覺不錯(cuò),從可用性上,界面上都不再是那個(gè)死板的Linux,而且我沒裝什么驅(qū)動(dòng)用跑起來也沒問題(對(duì)于菜鳥來說這個(gè)比較重要),然后U盤也不用掛載就可用,不會(huì)出中文問題,就感覺還可以了,先給張圖片大家看看。





    攀升 2006-11-10 13:15 發(fā)表評(píng)論
    ]]>
    久久青青草视频| 久久精品国产色蜜蜜麻豆| 999久久久免费精品国产| 久久精品人成免费| 国产精品成人99久久久久91gav| 88久久精品无码一区二区毛片 | 久久只这里是精品66| 久久精品免费一区二区| 久久国产乱子伦精品免费强| 久久影视国产亚洲| 欧美噜噜久久久XXX| 久久久精品国产亚洲成人满18免费网站| 久久人人爽人人爽AV片| 久久久久成人精品无码中文字幕| 久久99国产一区二区三区| 亚洲精品乱码久久久久久按摩| 久久美女人爽女人爽| 久久精品国产亚洲αv忘忧草| 中文字幕成人精品久久不卡| 久久丫忘忧草产品| 久久精品国产精品亚洲人人 | 亚洲欧美日韩精品久久| 午夜久久久久久禁播电影| 久久久久无码专区亚洲av| 久久99精品久久久久久久不卡| 久久人人爽人人精品视频| 国产 亚洲 欧美 另类 久久| 久久亚洲中文字幕精品有坂深雪 | 狠狠色婷婷久久一区二区三区| 亚洲精品美女久久久久99小说| 91久久精品电影| 国产精品xxxx国产喷水亚洲国产精品无码久久一区 | 亚洲七七久久精品中文国产| 国产成人久久AV免费| 久久综合久久自在自线精品自| 久久99中文字幕久久| 99国产精品久久| 色综合久久综精品| 91精品国产色综久久| 91久久精品无码一区二区毛片| 久久综合丁香激情久久|