亚洲午夜精品久久久久久浪潮,思思久久99热只有频精品66,思思久久99热只有频精品66http://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ā)表評論
]]>
./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í)行代碼編譯的主機,正常的話就是你的主機系統(tǒng)。這個參數(shù)一般由config.guess來猜就可以。當然自己指定也可以。
host:編譯出來的二進制程序所執(zhí)行的主機,因為絕大多數(shù)是如果本機編譯,本機執(zhí)行。所以這個值就等于build。只有交叉編譯的時候(也就是本機編譯,其他系統(tǒng)機器執(zhí)行)才會build和host不同。用host指定運行主機。
target:這個選項只有在建立交叉編譯環(huán)境的時候用到,正常編譯和交叉編譯都不會用到。他用build主機上的編譯器,編譯一個新的編譯器(
binutils, gcc,gdb等),這個新的編譯器將來編譯出來的其他程序?qū)⑦\行在target指定的系統(tǒng)上。
讓我們以編譯binutils為例:
1. `./configure --build=mipsel-linux --host=mipsel-linux --target=mipsel-linux' 
說明我們利用
mipsel-linux的編譯器對binutils進行編譯,編譯出來的binutils運行在mipsel-linux,這個binutils用來編譯能夠在mipsel-linux運行的代碼。“當然沒有人會用這個選項來編譯binutils”
2. `./configure --build=i386-linux --host=mipsel-linux
--target=mipsel-linux' will cross-build native mipsel-linux binutils oni386-linux.

說明我們利用i386-linux的編譯器對binutils進行編譯,編譯出來的binutils運行在mipsel-linux,這個binutils用來編譯能夠在mipsel-linux運行的代碼。“這個選項可以用來為其他的機器編譯它的編譯器”。

3. `./configure --build=i386-linux --host=i386-linux
--target=mipsel-linux' will build mipsel-linux cross-binutils on i386-linux.
說明我們利用i386-linux的編譯器對binutils進行編譯,編譯出來的binutils運行在i386-linux,這個binutils用來編譯能夠在mipsel-linux運行的代碼。“這個選項用來在i386主機上建立一個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的編譯器對binutils進行編譯,編譯出來的binutils運行在i386-linux,這個binutils用來編譯能夠在mipsel-linux運行的代碼。“這個選項可以用來在i386主機上建立一個mipsel-linux的交叉編譯環(huán)境,但是交叉編譯環(huán)境在mipsel-linux 編譯出來,安裝到i386-linux主機上,估計沒有多少人會這么用吧

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



攀升 2011-07-22 11:10 發(fā)表評論
]]>
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)部的機器A連接過外網(wǎng)機器C后,NAT會打開一個端口.然后外網(wǎng)的任何發(fā)到這個打開的端口的UDP數(shù)據(jù)報都可以到達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ù)都可以到達A(192.168.8.100:5000)

2)      Restricted Cone

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

例如 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ù)都可以到達A(192.168.8.100:5000)

 

3)      Port Restricted Cone

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

例如 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ù)都可以到達A(192.168.8.100:5000)

 

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

4)      Symmetic

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

2.       UDP hole punching

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

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

3.       同一個NAT后面的情況

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

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



攀升 2011-06-08 23:09 發(fā)表評論
]]>
(轉(zhuǎn))P2P之UDP穿透NAT的原理與實現(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的原理與實現(xiàn)(附源代碼)
原創(chuàng):shootingstars
參考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
論壇上經(jīng)常有對P2P原理的討論,但是討論歸討論,很少有實質(zhì)的東西產(chǎn)生(源代碼)。呵呵,在這里我就用自己實現(xiàn)的一個源代碼來說明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是運行在路由器上的一個功能模塊。
    
    最先提出的是基本的NAT,它的產(chǎn)生基于如下事實:一個私有網(wǎng)絡(luò)(域)中的節(jié)點中只有很少的節(jié)點需要與外網(wǎng)連接(呵呵,這是在上世紀90年代中期提出的)。那么這個子網(wǎng)中其實只有少數(shù)的節(jié)點需要全球唯一的IP地址,其他的節(jié)點的IP地址應(yīng)該是可以重用的。
    因此,基本的NAT實現(xiàn)的功能很簡單,在子網(wǎng)內(nèi)使用一個保留的IP子網(wǎng)段,這些IP對外是不可見的。子網(wǎng)內(nèi)只有少數(shù)一些IP地址可以對應(yīng)到真正全球唯一的IP地址。如果這些節(jié)點需要訪問外部網(wǎng)絡(luò),那么基本NAT就負責將這個節(jié)點的子網(wǎng)內(nèi)IP轉(zhuǎn)化為一個全球唯一的IP然后發(fā)送出去。(基本的NAT會改變IP包中的原IP地址,但是不會改變IP包中的端口)
    關(guān)于基本的NAT可以參看RFC 1631
    
    另外一種NAT叫做NAPT,從名稱上我們也可以看得出,NAPT不但會改變經(jīng)過這個NAT設(shè)備的IP數(shù)據(jù)報的IP地址,還會改變IP數(shù)據(jù)報的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
    有一個私有網(wǎng)絡(luò)10.*.*.*,Client A是其中的一臺計算機,這個網(wǎng)絡(luò)的網(wǎng)關(guān)(一個NAT設(shè)備)的外網(wǎng)IP是155.99.25.11(應(yīng)該還有一個內(nèi)網(wǎng)的IP地址,比如10.0.0.10)。如果Client A中的某個進程(這個進程創(chuàng)建了一個UDP Socket,這個Socket綁定1234端口)想訪問外網(wǎng)主機18.181.0.31的1235端口,那么當數(shù)據(jù)包通過NAT時會發(fā)生什么事情呢?
    首先NAT會改變這個數(shù)據(jù)包的原IP地址,改為155.99.25.11。接著NAT會為這個傳輸創(chuàng)建一個Session(Session是一個抽象的概念,如果是TCP,也許Session是由一個SYN包開始,以一個FIN包結(jié)束。而UDP呢,以這個IP的這個端口的第一個UDP開始,結(jié)束呢,呵呵,也許是幾分鐘,也許是幾小時,這要看具體的實現(xiàn)了)并且給這個Session分配一個端口,比如62000,然后改變這個數(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)建了一個Session后,NAT會記住62000端口對應(yīng)的是10.0.0.1的1234端口,以后從18.181.0.31發(fā)送到62000端口的數(shù)據(jù)會被NAT自動的轉(zhuǎn)發(fā)到10.0.0.1上。(注意:這里是說18.181.0.31發(fā)送到62000端口的數(shù)據(jù)會被轉(zhuǎn)發(fā),其他的IP發(fā)送到這個端口的數(shù)據(jù)將被NAT拋棄)這樣Client A就與Server S1建立以了一個連接。
    呵呵,上面的基礎(chǔ)知識可能很多人都知道了,那么下面是關(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的原來那個Socket(綁定了1234端口的那個UDP Socket)又接著向另外一個Server S2發(fā)送了一個UDP包,那么這個UDP包在通過NAT時會怎么樣呢?
    這時可能會有兩種情況發(fā)生,一種是NAT再次創(chuàng)建一個Session,并且再次為這個Session分配一個端口號(比如:62001)。另外一種是NAT再次創(chuàng)建一個Session,但是不會新分配一個端口號,而是用原來分配的端口號62000。前一種NAT叫做Symmetric NAT,后一種叫做Cone NAT。我們期望我們的NAT是第二種,呵呵,如果你的NAT剛好是第一種,那么很可能會有很多P2P軟件失靈。(可以慶幸的是,現(xiàn)在絕大多數(shù)的NAT屬于后者,即Cone NAT)
   
    好了,我們看到,通過NAT,子網(wǎng)內(nèi)的計算機向外連結(jié)是很容易的(NAT相當于透明的,子網(wǎng)內(nèi)的和外網(wǎng)的計算機不用知道NAT的情況)。
    但是如果外部的計算機想訪問子網(wǎng)內(nèi)的計算機就比較困難了(而這正是P2P所需要的)。
    那么我們?nèi)绻霃耐獠堪l(fā)送一個數(shù)據(jù)報給內(nèi)網(wǎng)的計算機有什么辦法呢?首先,我們必須在內(nèi)網(wǎng)的NAT上打上一個“洞”(也就是前面我們說的在NAT上建立一個Session),這個洞不能由外部來打,只能由內(nèi)網(wǎng)內(nèi)的主機來打。而且這個洞是有方向的,比如從內(nèi)部某臺主機(比如:192.168.0.10)向外部的某個IP(比如:219.237.60.1)發(fā)送一個UDP包,那么就在這個內(nèi)網(wǎng)的NAT設(shè)備上打了一個方向為219.237.60.1的“洞”,(這就是稱為UDP Hole Punching的技術(shù))以后219.237.60.1就可以通過這個洞與內(nèi)網(wǎng)的192.168.0.10聯(lián)系了。(但是其他的IP不能利用這個洞)。
呵呵,現(xiàn)在該輪到我們的正題P2P了。有了上面的理論,實現(xiàn)兩個內(nèi)網(wǎng)的主機通訊就差最后一步了:那就是雞生蛋還是蛋生雞的問題了,兩邊都無法主動發(fā)出連接請求,誰也不知道誰的公網(wǎng)地址,那我們?nèi)绾蝸泶蜻@個洞呢?我們需要一個中間人來聯(lián)系這兩個內(nèi)網(wǎng)主機。
    現(xiàn)在我們來看看一個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分配了一個端口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。
    此時,Client A與Client B都可以與Server S通信了。如果Client A此時想直接發(fā)送信息給Client B,那么他可以從Server S那兒獲得B的公網(wǎng)地址187.34.1.56:40000,是不是Client A向這個地址發(fā)送信息Client B就能收到了呢?答案是不行,因為如果這樣發(fā)送信息,NAT B會將這個信息丟棄(因為這樣的信息是不請自來的,為了安全,大多數(shù)NAT都會執(zhí)行丟棄動作)。現(xiàn)在我們需要的是在NAT B上打一個方向為202.187.45.3(即Client A的外網(wǎng)地址)的洞,那么Client A發(fā)送到187.34.1.56:40000的信息,Client B就能收到了。這個打洞命令由誰來發(fā)呢,呵呵,當然是Server S。
    總結(jié)一下這個過程:如果Client A想向Client B發(fā)送信息,那么Client A發(fā)送命令給Server S,請求Server S命令Client B向Client A方向打洞。呵呵,是不是很繞口,不過沒關(guān)系,想一想就很清楚了,何況還有源代碼呢(侯老師說過:在源代碼面前沒有秘密 8)),然后Client A就可以通過Client B的外網(wǎng)地址與Client B通信了。
    
    注意:以上過程只適合于Cone NAT的情況,如果是Symmetric NAT,那么當Client B向Client A打洞的端口已經(jīng)重新分配了,Client B將無法知道這個端口(如果Symmetric NAT的端口是順序分配的,那么我們或許可以猜測這個端口號,可是由于可能導(dǎo)致失敗的因素太多,我們不推薦這種猜測端口的方法)。
    
    下面是一個模擬P2P聊天的過程的源代碼,過程很簡單,P2PServer運行在一個擁有公網(wǎng)IP的計算機上,P2PClient運行在兩個不同的NAT后(注意,如果兩個客戶端運行在一個NAT后,本程序很可能不能運行正常,這取決于你的NAT是否支持loopback translation,詳見http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt,當然,此問題可以通過雙方先嘗試連接對方的內(nèi)網(wǎng)IP來解決,但是這個代碼只是為了驗證原理,并沒有處理這些問題),后登錄的計算機可以獲得先登錄計算機的用戶名,后登錄的計算機通過send username message的格式來發(fā)送消息。如果發(fā)送成功,說明你已取得了直接與對方連接的成功。
    程序現(xiàn)在支持三個命令:send , getu , exit
    
    send格式:send username message
    功能:發(fā)送信息給username
    
    getu格式:getu
    功能:獲得當前服務(wù)器用戶列表
    
    exit格式:exit
    功能:注銷與服務(wù)器的連接(服務(wù)器不會自動監(jiān)測客戶是否吊線)
        
    代碼很短,相信很容易懂,如果有什么問題,可以給我發(fā)郵件zhouhuis22@sina.com  或者在CSDN上發(fā)送短消息。同時,歡迎轉(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)負責下面幾件事情:
  // 一:讀取客戶端登陸和登出消息,記錄客戶列表
  // 二:轉(zhuǎn)發(fā)客戶p2p請求
  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:
     {
      //  將這個用戶的信息記錄到用戶列表中
      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:
     {
      // 某個客戶希望服務(wù)端向另外一個客戶發(fā)送一個打洞消息
      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ā)送一個消息給某個用戶(C)
 *流程:直接向某個用戶的外網(wǎng)IP發(fā)送消息,如果此前沒有聯(lián)系過
 *      那么此消息將無法發(fā)送,發(fā)送端等待超時。
 *      超時后,發(fā)送端將發(fā)送一個請求信息到服務(wù)端,
 *      要求服務(wù)端發(fā)送給客戶C一個請求,請求C給本機發(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));
  
  // 等待接收線程將此標記修改
  for(int j=0;j<10;j++)
  {
   if(RecvedACK)
    return true;
   else
    Sleep(300);
  }
  // 沒有接收到目標主機的回應(yīng),認為目標主機的端口映射沒有
  // 打開,那么發(fā)送請求信息給服務(wù)器,要服務(wù)器告訴目標主機
  // 打開映射端口(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);// 等待對方先發(fā)送信息。
 }
 return false;
}
// 解析命令,暫時只有exit和send命令
// 新增getu命令,獲取當前服務(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:
   {
    // 對方發(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登錄時向服務(wù)器發(fā)送的消息
struct stLoginMessage
{
 char userName[10];
 char password[10];
};
// Client注銷時發(fā)送的消息
struct stLogoutMessage
{
 char userName[10];
};
// Client向服務(wù)器請求另外一個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é)點信息
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ā)送一個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ā)表評論
]]>
(轉(zhuǎn))P2P之UDP穿透NAT的原理與實現(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的原理與實現(xiàn)(附源代碼)
原創(chuàng):shootingstars
參考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
論壇上經(jīng)常有對P2P原理的討論,但是討論歸討論,很少有實質(zhì)的東西產(chǎn)生(源代碼)。呵呵,在這里我就用自己實現(xiàn)的一個源代碼來說明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是運行在路由器上的一個功能模塊。
    
    最先提出的是基本的NAT,它的產(chǎn)生基于如下事實:一個私有網(wǎng)絡(luò)(域)中的節(jié)點中只有很少的節(jié)點需要與外網(wǎng)連接(呵呵,這是在上世紀90年代中期提出的)。那么這個子網(wǎng)中其實只有少數(shù)的節(jié)點需要全球唯一的IP地址,其他的節(jié)點的IP地址應(yīng)該是可以重用的。
    因此,基本的NAT實現(xiàn)的功能很簡單,在子網(wǎng)內(nèi)使用一個保留的IP子網(wǎng)段,這些IP對外是不可見的。子網(wǎng)內(nèi)只有少數(shù)一些IP地址可以對應(yīng)到真正全球唯一的IP地址。如果這些節(jié)點需要訪問外部網(wǎng)絡(luò),那么基本NAT就負責將這個節(jié)點的子網(wǎng)內(nèi)IP轉(zhuǎn)化為一個全球唯一的IP然后發(fā)送出去。(基本的NAT會改變IP包中的原IP地址,但是不會改變IP包中的端口)
    關(guān)于基本的NAT可以參看RFC 1631
    
    另外一種NAT叫做NAPT,從名稱上我們也可以看得出,NAPT不但會改變經(jīng)過這個NAT設(shè)備的IP數(shù)據(jù)報的IP地址,還會改變IP數(shù)據(jù)報的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
    有一個私有網(wǎng)絡(luò)10.*.*.*,Client A是其中的一臺計算機,這個網(wǎng)絡(luò)的網(wǎng)關(guān)(一個NAT設(shè)備)的外網(wǎng)IP是155.99.25.11(應(yīng)該還有一個內(nèi)網(wǎng)的IP地址,比如10.0.0.10)。如果Client A中的某個進程(這個進程創(chuàng)建了一個UDP Socket,這個Socket綁定1234端口)想訪問外網(wǎng)主機18.181.0.31的1235端口,那么當數(shù)據(jù)包通過NAT時會發(fā)生什么事情呢?
    首先NAT會改變這個數(shù)據(jù)包的原IP地址,改為155.99.25.11。接著NAT會為這個傳輸創(chuàng)建一個Session(Session是一個抽象的概念,如果是TCP,也許Session是由一個SYN包開始,以一個FIN包結(jié)束。而UDP呢,以這個IP的這個端口的第一個UDP開始,結(jié)束呢,呵呵,也許是幾分鐘,也許是幾小時,這要看具體的實現(xiàn)了)并且給這個Session分配一個端口,比如62000,然后改變這個數(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)建了一個Session后,NAT會記住62000端口對應(yīng)的是10.0.0.1的1234端口,以后從18.181.0.31發(fā)送到62000端口的數(shù)據(jù)會被NAT自動的轉(zhuǎn)發(fā)到10.0.0.1上。(注意:這里是說18.181.0.31發(fā)送到62000端口的數(shù)據(jù)會被轉(zhuǎn)發(fā),其他的IP發(fā)送到這個端口的數(shù)據(jù)將被NAT拋棄)這樣Client A就與Server S1建立以了一個連接。
    呵呵,上面的基礎(chǔ)知識可能很多人都知道了,那么下面是關(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的原來那個Socket(綁定了1234端口的那個UDP Socket)又接著向另外一個Server S2發(fā)送了一個UDP包,那么這個UDP包在通過NAT時會怎么樣呢?
    這時可能會有兩種情況發(fā)生,一種是NAT再次創(chuàng)建一個Session,并且再次為這個Session分配一個端口號(比如:62001)。另外一種是NAT再次創(chuàng)建一個Session,但是不會新分配一個端口號,而是用原來分配的端口號62000。前一種NAT叫做Symmetric NAT,后一種叫做Cone NAT。我們期望我們的NAT是第二種,呵呵,如果你的NAT剛好是第一種,那么很可能會有很多P2P軟件失靈。(可以慶幸的是,現(xiàn)在絕大多數(shù)的NAT屬于后者,即Cone NAT)
   
    好了,我們看到,通過NAT,子網(wǎng)內(nèi)的計算機向外連結(jié)是很容易的(NAT相當于透明的,子網(wǎng)內(nèi)的和外網(wǎng)的計算機不用知道NAT的情況)。
    但是如果外部的計算機想訪問子網(wǎng)內(nèi)的計算機就比較困難了(而這正是P2P所需要的)。
    那么我們?nèi)绻霃耐獠堪l(fā)送一個數(shù)據(jù)報給內(nèi)網(wǎng)的計算機有什么辦法呢?首先,我們必須在內(nèi)網(wǎng)的NAT上打上一個“洞”(也就是前面我們說的在NAT上建立一個Session),這個洞不能由外部來打,只能由內(nèi)網(wǎng)內(nèi)的主機來打。而且這個洞是有方向的,比如從內(nèi)部某臺主機(比如:192.168.0.10)向外部的某個IP(比如:219.237.60.1)發(fā)送一個UDP包,那么就在這個內(nèi)網(wǎng)的NAT設(shè)備上打了一個方向為219.237.60.1的“洞”,(這就是稱為UDP Hole Punching的技術(shù))以后219.237.60.1就可以通過這個洞與內(nèi)網(wǎng)的192.168.0.10聯(lián)系了。(但是其他的IP不能利用這個洞)。
呵呵,現(xiàn)在該輪到我們的正題P2P了。有了上面的理論,實現(xiàn)兩個內(nèi)網(wǎng)的主機通訊就差最后一步了:那就是雞生蛋還是蛋生雞的問題了,兩邊都無法主動發(fā)出連接請求,誰也不知道誰的公網(wǎng)地址,那我們?nèi)绾蝸泶蜻@個洞呢?我們需要一個中間人來聯(lián)系這兩個內(nèi)網(wǎng)主機。
    現(xiàn)在我們來看看一個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分配了一個端口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。
    此時,Client A與Client B都可以與Server S通信了。如果Client A此時想直接發(fā)送信息給Client B,那么他可以從Server S那兒獲得B的公網(wǎng)地址187.34.1.56:40000,是不是Client A向這個地址發(fā)送信息Client B就能收到了呢?答案是不行,因為如果這樣發(fā)送信息,NAT B會將這個信息丟棄(因為這樣的信息是不請自來的,為了安全,大多數(shù)NAT都會執(zhí)行丟棄動作)。現(xiàn)在我們需要的是在NAT B上打一個方向為202.187.45.3(即Client A的外網(wǎng)地址)的洞,那么Client A發(fā)送到187.34.1.56:40000的信息,Client B就能收到了。這個打洞命令由誰來發(fā)呢,呵呵,當然是Server S。
    總結(jié)一下這個過程:如果Client A想向Client B發(fā)送信息,那么Client A發(fā)送命令給Server S,請求Server S命令Client B向Client A方向打洞。呵呵,是不是很繞口,不過沒關(guān)系,想一想就很清楚了,何況還有源代碼呢(侯老師說過:在源代碼面前沒有秘密 8)),然后Client A就可以通過Client B的外網(wǎng)地址與Client B通信了。
    
    注意:以上過程只適合于Cone NAT的情況,如果是Symmetric NAT,那么當Client B向Client A打洞的端口已經(jīng)重新分配了,Client B將無法知道這個端口(如果Symmetric NAT的端口是順序分配的,那么我們或許可以猜測這個端口號,可是由于可能導(dǎo)致失敗的因素太多,我們不推薦這種猜測端口的方法)。
    
    下面是一個模擬P2P聊天的過程的源代碼,過程很簡單,P2PServer運行在一個擁有公網(wǎng)IP的計算機上,P2PClient運行在兩個不同的NAT后(注意,如果兩個客戶端運行在一個NAT后,本程序很可能不能運行正常,這取決于你的NAT是否支持loopback translation,詳見http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt,當然,此問題可以通過雙方先嘗試連接對方的內(nèi)網(wǎng)IP來解決,但是這個代碼只是為了驗證原理,并沒有處理這些問題),后登錄的計算機可以獲得先登錄計算機的用戶名,后登錄的計算機通過send username message的格式來發(fā)送消息。如果發(fā)送成功,說明你已取得了直接與對方連接的成功。
    程序現(xiàn)在支持三個命令:send , getu , exit
    
    send格式:send username message
    功能:發(fā)送信息給username
    
    getu格式:getu
    功能:獲得當前服務(wù)器用戶列表
    
    exit格式:exit
    功能:注銷與服務(wù)器的連接(服務(wù)器不會自動監(jiān)測客戶是否吊線)
        
    代碼很短,相信很容易懂,如果有什么問題,可以給我發(fā)郵件zhouhuis22@sina.com  或者在CSDN上發(fā)送短消息。同時,歡迎轉(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)負責下面幾件事情:
  // 一:讀取客戶端登陸和登出消息,記錄客戶列表
  // 二:轉(zhuǎn)發(fā)客戶p2p請求
  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:
     {
      //  將這個用戶的信息記錄到用戶列表中
      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:
     {
      // 某個客戶希望服務(wù)端向另外一個客戶發(fā)送一個打洞消息
      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ā)送一個消息給某個用戶(C)
 *流程:直接向某個用戶的外網(wǎng)IP發(fā)送消息,如果此前沒有聯(lián)系過
 *      那么此消息將無法發(fā)送,發(fā)送端等待超時。
 *      超時后,發(fā)送端將發(fā)送一個請求信息到服務(wù)端,
 *      要求服務(wù)端發(fā)送給客戶C一個請求,請求C給本機發(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));
  
  // 等待接收線程將此標記修改
  for(int j=0;j<10;j++)
  {
   if(RecvedACK)
    return true;
   else
    Sleep(300);
  }
  // 沒有接收到目標主機的回應(yīng),認為目標主機的端口映射沒有
  // 打開,那么發(fā)送請求信息給服務(wù)器,要服務(wù)器告訴目標主機
  // 打開映射端口(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);// 等待對方先發(fā)送信息。
 }
 return false;
}
// 解析命令,暫時只有exit和send命令
// 新增getu命令,獲取當前服務(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:
   {
    // 對方發(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登錄時向服務(wù)器發(fā)送的消息
struct stLoginMessage
{
 char userName[10];
 char password[10];
};
// Client注銷時發(fā)送的消息
struct stLogoutMessage
{
 char userName[10];
};
// Client向服務(wù)器請求另外一個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é)點信息
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ā)送一個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ā)表評論
]]>
(轉(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的標準I/0庫函數(shù)(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指針,UNIX的I/O函數(shù)(open, close, read, write, ioctl)使用文件描述符。下面重點來說下,文件描述符是如何工作的。
 
文件描述符相當于一個邏輯句柄,而open,close等函數(shù)則是將文件或者物理設(shè)備與句柄相關(guān)聯(lián)。句柄是一個整數(shù),可以理解為進程特定的文件 描述符表的索引。先介紹下面三個概念,后面講下open、close等操作以后,文件和文件描述符產(chǎn)生什么關(guān)系,以及fork后文件描述符的繼承等問題。
 
文件描述符表 :用戶區(qū)的一部分,除非通過使用文件描述符的函數(shù),否則程序無法對其進行訪問。對進程中每個打開的文件,文件描述符表都包含一個條目。
 
系統(tǒng)文件表 :為系統(tǒng)中所有的進程共享。對每個活動的open, 它都包含一個條目。每個系統(tǒng)文件表的條目都包含文件偏移量、訪問模式(讀、寫、or 讀-寫)以及指向它的文件描述符表的條目計數(shù)。
 
內(nèi)存索引節(jié)點表: 對系統(tǒng)中的每個活動的文件(被某個進程打開了),內(nèi)存中索引節(jié)點表都包含一個條目。幾個系統(tǒng)文件表條目可能對應(yīng)于同一個內(nèi)存索引節(jié)點表(不同進程打開同一個文件)。
 
1、舉例: 執(zhí)行myfd = open( "/home/lucy/my.dat", O_RDONLY); 以后,上述3個表的關(guān)系原理圖如下:
http://keren.blog.51cto.com/
                                                                                  圖1
 
系統(tǒng)文件表包含一個偏移量,給出了文件當前的位置。若2個進程同時打開一個文件(如上圖A,B)做讀操作,每個進程都有自己相對于文件的偏移 量,而且讀入整個文件是獨立于另一個進程的;如果2個進程打開同一個文件做寫操作,寫操作是相互獨立的,每個進程都可以重寫另一個進程寫入的內(nèi)容。
 
如果上面進程在open以后又執(zhí)行了close()函數(shù),操作系統(tǒng)會刪除文件描述符表的第四個條目,和系統(tǒng)文件表的對應(yīng)條目(若指向它的描述符 表唯一),并對內(nèi)存索引節(jié)點表條目中的計數(shù)減1,如果自減以后變?yōu)?,說明沒有其他進程鏈接此文件,將索引節(jié)點表條目也刪除,而這里進程B也在open這 個文件,所以索引節(jié)點表條目保留。
 
2、文件描述符的繼承
通過fork()創(chuàng)建子進程時,子進程繼承父進程環(huán)境和上下文的大部分內(nèi)容的拷貝,其中就包括文件描述符表。
 
(1)對于父進程在fork()之前打開的文件來說,子進程都會繼承,與父進程共享相同的文件偏移量。如下圖所示(0-1-2 表示 標準輸入-輸出-錯誤):
                                  圖2 fork()之前打開my.dat
 
系統(tǒng)文件表位于系統(tǒng)空間中,不會被fork()復(fù)制,但是系統(tǒng)文件表中的條目會保存指向它的文件描述符表的計數(shù),fork()時需要對這個計數(shù) 進行維護,以體現(xiàn)子進程對應(yīng)的新的文件描述符表也指向它。程序關(guān)閉文件時,也是將系統(tǒng)文件表條目內(nèi)部的計數(shù)減一,當計數(shù)值減為0時,才將其刪除。
 
(2)相反,如果父進程先進程fork,再打開my.dat,這時父子進程關(guān)于my.dat 的文件描述符表指向不同的系統(tǒng)文件表條目,也不再共享文件偏移量(fork以后2個進程分別open,在系統(tǒng)文件表中創(chuàng)建2個條目);但是關(guān)于標準輸入, 標準輸出,標準錯誤,父子進程還是共享的。
                      圖3   fork()以后打開my.dat
 
 

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




攀升 2009-12-22 11:20 發(fā)表評論
]]>
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
    進程要對文件進行操作,一般使用open調(diào)用打開一個文件進行訪問,每個進程都有一個文件描述符表,該表中存放打開的文件描述符。用戶使用open等調(diào)用得到的文件描述符其實是文件描述符在該表中的索引號,該表項的內(nèi)容是一個指向文件表的指針。應(yīng)用程序只要使用該描述符就可以對指定文件進行操作。
   
    為了了解dup與賦值語句用于文件描述符的區(qū)別,請看如下程序。

程序描述:

    打開一個文件描述符,分別適用dup和賦值語句進行復(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;

}

 

程序運行結(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)生一個新的文件描述符id和指針在進程表項中,但是他們共用文件表,這時,關(guān)閉一個文件描述符,另外一個仍舊可用,文件表并不會被釋放。而賦值語句不同,它只是簡單的在另外一個變量中記錄原始文件指針等,2個變量的文件描述符相同,進程表項中并不產(chǎn)生新的項目。

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

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



攀升 2009-12-22 10:10 發(fā)表評論
]]>
Linux進程通信:管道要點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        管道的認識從command1 | command2 認識開始,到現(xiàn)在做A2DP升華,寫一些使用FIFO的要點下來。
        
      1   管道一般用于進程間通信,把一個進程的輸出通過管道送給另一個進程。
      2   可以通過popen,pclose嘗試實現(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ù)是由兩個文件描述符組成的數(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:阻塞讀方式打開,除非有進程以寫方式打開,不然阻塞。
            O_RDONLY|O_NONBLOCK:  不論怎樣,立即返回,總是成功
            O_WRONLY: 阻塞寫方式打開,直到有人來讀,不然阻塞
            O_WRONLY|O_NONBLOCK: 立即返回,但如果沒人以讀方式打開,返回-1錯誤
            FIFO SIZE:#include <limites.h>, PIPE_BUF, default 4096
            
            多個進程可以寫同一個管道。

攀升 2009-08-09 17:24 發(fā)表評論
]]>
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ā)表評論
]]>
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) 和選項字符串 (optstring) 后,getopt() 將返回第一個選項,并設(shè)置一些全局變量。使用相同的參數(shù)再次調(diào)用該函數(shù)時,它將返回下一個選項,并設(shè)置相應(yīng)的全局變量。如果不再有識別到的選項,將返回 -1,此任務(wù)就完成了。

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

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

 ./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ā)表評論
]]>
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ā)表評論
]]>
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時其實會又很多產(chǎn)物,因為android讓我接觸了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)的一些重要的啟動時要用
  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         這容易搞亂,這個vmlinux和第一個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();
   啟動Init()process.init初始化之前注冊的函數(shù),最后釋放資源。內(nèi)核級別啟動常報的錯就是“No init found.  Try passing init= option to kernel”
這主要是因為通過run_init_process執(zhí)行系統(tǒng)級別的/init時失敗,返回。如果成功,該函數(shù)不會返回。
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ā)表評論
]]>
(轉(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)載請標明出處:http://blog.csdn.net/stevenliyong/archive/2009/03/12/3984926.aspx



攀升 2009-06-05 20:02 發(fā)表評論
]]>
修改Grub啟動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),默認是linux啟動,作開發(fā)用,后來GF霸占了,但是每次啟動時都要手動調(diào)整到windows,今天我又換回來了,也就是通過修改/boot/grub/grub.conf(/boot/grub/menu.list同一個文件).

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

         reboot,OK



攀升 2009-03-29 19:31 發(fā)表評論
]]>
(轉(zhuǎn))Linux時間函數(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(將時間和日期以字符串格式表示)

 

 

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



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



gettimeofday(取得目前的時間)
相關(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()會把目前的時間有tv所指的結(jié)構(gòu)返回,當?shù)貢r區(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 時間差了多少分鐘*/
int tz_dsttime; /*日光節(jié)約時間的狀態(tài)*/
};
上述兩個結(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,錯誤代碼存于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(取得目前時間和日期)
相關(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)換成真實世界所使用的時間日期表示方法,然后將結(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 代表目前分數(shù),范圍0-59
int tm_hour 從午夜算起的時數(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ù)返回的時間日期未經(jīng)時區(qū)轉(zhuǎn)換,而是UTC時間。
返回值
返回結(jié)構(gòu)tm代表目前UTC 時間
范例
#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(取得當?shù)啬壳皶r間和日期)
相關(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)換成真實世界所使用的時間日期表示方法,然后將結(jié)果由結(jié)構(gòu)tm返回。結(jié)構(gòu)tm的定義請參考gmtime()。此函數(shù)返回的時間日期已經(jīng)轉(zhuǎn)換成當?shù)貢r區(qū)。
返回值
返回結(jié)構(gòu)tm代表目前的當?shù)貢r間。
范例
#include<time.h>
main(){
char *wday[]={“Sun”,”Mon”,”Tue”,”Wed”,”Thu”,”Fri”,”Sat”};
time_t timep;
struct tm *p;
time(&timep);
p=localtime(&timep); /*取得當?shù)貢r間*/
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(將時間結(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時0分0 秒算起至今的UTC時間所經(jīng)過的秒數(shù)。
返回值
返回經(jīng)過的秒數(shù)。
范例
/* 用time()取得時間(秒數(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è)置目前時間)
相關(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()會把目前時間設(shè)成由tv所指的結(jié)構(gòu)信息,當?shù)貢r區(qū)信息則設(shè)成tz所指的結(jié)構(gòu)。詳細的說明請參考gettimeofday()。注意,只有root權(quán)限才能使用此函數(shù)修改時間。
返回值
成功則返回0,失敗返回-1,錯誤代碼存于errno。
錯誤代碼
EPERM 并非由root權(quán)限調(diào)用settimeofday(),權(quán)限不夠。
EINVAL 時區(qū)或某個數(shù)據(jù)是不正確的,無法正確設(shè)置時間。
 



time(取得目前的時間)
相關(guān)函數(shù)
ctime,ftime,gettimeofday
表頭文件
#include<time.h>
定義函數(shù)
time_t time(time_t *t);
函數(shù)說明
此函數(shù)會返回從公元1970年1月1日的UTC時間從0時0分0秒算起到現(xiàn)在所經(jīng)過的秒數(shù)。如果t 并非空指針的話,此函數(shù)也會將返回值存到t指針所指的內(nèi)存。
返回值
成功則返回秒數(shù),失敗則返回((time_t)-1)值,錯誤原因存于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

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

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

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

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



攀升 2009-03-01 11:37 發(fā)表評論
]]>
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     最近寫一個文件操作類,fopen的參數(shù)著實讓我搞了半天,因為以前就是固定的方式讀寫文件的,現(xiàn)在要做靈活了,所以就有些參數(shù)理解不夠準確。以下是關(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文件。

還有在讀文件時最好先判斷下該文件是否存在
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ā)表評論
]]>
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通信機制,本身Linux 的IPC通信機制包括,管道(fifo),共享內(nèi)存,信號量,消息隊列,Socket等。 像現(xiàn)在流行的moblin平臺就使用了DBUS通信,還有我最近看的bluez 4 也是通過DBUS來交互的。
 
      它是個3層架構(gòu)的進程間通信系統(tǒng),包括:   

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

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

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

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

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

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


      理解有限,先說到這。

 



攀升 2008-12-20 12:23 發(fā)表評論
]]>
(轉(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)視多個文件句柄(file descriptor)的狀態(tài)變化的。程序會停在select這里等待,直到被監(jiān)視的文件句柄有某一個或多個發(fā)生了狀態(tài)改變。 文件在句柄在Linux里很多,如果你man某個函數(shù),在函數(shù)返回值部分說到成功后有一個文件句柄被創(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ù),看socket函數(shù)的聲明就明白了:

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

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

#include <stdio.h>

#include <unistd.h>

#include <string.h>

int main(int argc, char ** argv)

{       

    char buf[10] = "";        

    read(0, buf, 9); /* 從標準輸入 0 讀入字符 */        

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

 

    return 0;

}

/* **上面和下面的代碼都可以用來從標準輸入讀用戶輸入的9個字符** */

#include <stdio.h>

#include <unistd.h>

#include <string.h>

int main(int argc, char ** argv)

{        

char buf[10] = "";        

fread(buf, 9, 1, stdin); /* 從標準輸入 stdin 讀入字符 */        

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

 

return 0;

}

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

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

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

函 數(shù)的最后一個參數(shù)timeout顯然是一個超時時間值,其類型是struct timeval *,即一個struct timeval結(jié)構(gòu)的變量的指針,所以我們在程序里要申明一個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三個參數(shù)是一樣的類型: fd_set *,即我們在程序里要申明幾個fd_set類型的變量,比如rdfds, wtfds, exfds,然后把這個變量的地址&rdfds, &wtfds, &exfds 傳遞給select函數(shù)。

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

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

struct timeval tv; /* 申明一個時間變量來保存時間 */

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

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

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

 

tv.tv_sec = 1;

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

 

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

if(ret < 0)

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

else if(ret == 0)

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

else

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

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

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

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

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

    recv(...);    

}

}

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

 

int sa, sb, sc;

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

connect(sa,...);

sb = socket(...);

connect(sb,...);

sc = socket(...);

connect(sc,...);

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

FD_SET(sb, &rdfds);

FD_SET(sc, &rdfds);

在使用select函數(shù)之前,一定要找到3個句柄中的最大值是哪個,我們一般定義一個變量來保存最大值,取得最大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 */

同樣的道理,如果我們要檢測用戶是否按了鍵盤進行輸入,我們就應(yīng)該把標準輸入0這個句柄放到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");/* 出錯 */

else if(ret == 0)

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

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

scanf("%s", buf); }



攀升 2008-12-18 10:44 發(fā)表評論
]]>
(轉(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)境下的軟件安裝,并不是一件容易的事情;如果通過源代碼編譯后在安裝,當然事情就更為復(fù)雜一些;現(xiàn)在安裝各種軟件的教程都非常普遍;但萬變不離其中,對基礎(chǔ)知識的扎實掌握,安裝各種軟件的問題就迎刃而解了。Configure腳本配置工具就是基礎(chǔ)之一,它是autoconf的工具的基本應(yīng)用。


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


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


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

--cache-file=FILE

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

--help

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


--no-create


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


--quiet

--silent


當'configure'進行他的測試時,會輸出簡要的信息來告訴用戶正在作什么。這樣作是因為'configure'可能會比較慢,沒有這種輸出的話用戶將會被扔在一旁疑惑正在發(fā)生什么,使用這兩個選項中的任何一個都會把你扔到一旁。(譯注:這兩句話比較有意思,原文是這樣的: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的版本號。


--prefix=PEWFIX


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

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


--exec-prefix=EPREFIX


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


--bindir=DIR


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


--sbindir=DIR


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


--libexecdir=DIR


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


--datadir=DIR


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


--sysconfdir=DIR


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


--sharedstatedir=DIR

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


--localstatedir=DIR

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

--libdir=DIR

指定庫文件的安裝位置。


--includedir=DIR

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


--oldincludedir=DIR

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


--infodir=DIR

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


--mandir=DIR

指定手冊頁的安裝位置。


--srcdir=DIR

這個選項對安裝沒有作用,他會告訴'configure'源碼的位置。一般來說不用指定此選項,因為'configure'腳本一般和源碼文件在同一個目錄下。


--program-prefix=PREFIX

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


--program-suffix=SUFFIX

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


--program-transform-name=PROGRAM

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


--build=BUILD

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


--host=HOST

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


--target=GARGET

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


--disable-FEATURE

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

$ ./configure --disable-gui


-enable-FEATURE[=ARG]

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

$ ./configure --enable-buffers=128

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


--with-PACKAGE[=ARG]

在自由軟件社區(qū)里,有使用已有軟件包和庫的優(yōu)秀傳統(tǒng)。當用'configure'來配置一個源碼樹時,可以提供其他已經(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

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

$ ./configure --without-gnu-ld


--x-includes=DIR

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


--x-libraries=DIR

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


在源碼樹中運行'configure'是不必要的同時也是不好的。一個由'configure'產(chǎn)生的良好的'Makefile'可以構(gòu)筑源碼屬于另一棵樹的軟件包。在一個獨立于源碼的樹中構(gòu)筑派生的文件的好處是很明顯的:派生的文件,如目標文件,會凌亂的散布于源碼樹。這也使在另一個不同的系統(tǒng)或用不同的配置選項構(gòu)筑同樣的目標文件非常困難。建議使用三棵樹:一棵源碼樹(source tree),一棵構(gòu)筑樹(build tree),一棵安裝樹(install tree)。這里有一個很接近的例子,是使用這種方法來構(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)筑和安裝這個包到默認的位置'/usr/local':

$ make all && make install



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

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

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

  1. Widcomm:  第一個windows上的協(xié)議棧,由Widcomm公司開發(fā),也就是現(xiàn)在的Broadcom .
  2. Microsoft Windows stack: Windows XP SP2中包括了這個內(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)品,這個應(yīng)該是個中國公司,值得自豪。該產(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)上運行,具體的沒找到資料
  7. BlueDragon:東軟公司產(chǎn)品,值得驕傲,好像2002年6月就通過了藍牙的認證,支持的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)在我沒怎么聽過這個協(xié)議棧的應(yīng)用,難得是個爛尾樓??
  8. BlueMagic:美國Open Interface 公司for portable embedded divce的協(xié)議棧,iphone(apple),nav-u(sony)等很多電子產(chǎn)品都用該商業(yè)的協(xié)議棧,BlueMagic 3.0是第一個通過bluetooth 協(xié)議棧1.1認證的協(xié)議棧,那么我現(xiàn)在就在用它,那么該棧用起來簡單,API清晰明了。實現(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: 藍牙芯片CSR的協(xié)議棧,同時他也提供了一些上層應(yīng)用的Profile的庫,當然了它也是為嵌入式產(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的清量級協(xié)議棧。

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

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



    攀升 2008-12-14 11:50 發(fā)表評論
    ]]>
    (轉(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ā)表評論
    ]]>
    多國語言惹得禍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        上面下指令說要做多國語言版的程序,這可是個不小的改動呀。于是我就拿當前的程序運行,一系列的問題隨之出現(xiàn)了。
            話分兩頭,先說跑在Windows上的,最基本的就是讀各種語言的文件名,正常英文還是正常的,但遇到俄文,法文,德文時就出現(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都是針對ASCII碼的,要讀unicode(windows默認字符集),就得用_wfindfirst,_wfindnext等讀寬字符的操作函數(shù),最終解決問題,但我沒有松氣,因為程序主要是運行在linux中的,Linux真不知道怎么整了。
            Linux上讀多國語言的文件和目錄就需要對Linux系統(tǒng)深入了解,因為我要讀的文件是usb上的文件,所以得先掛載到一個目錄,
            mount -t vfat /dev/sda1 /mnt/usb,然后readdir讀入文件,與Windows上同樣的錯,讀入的是"?",我想和windows一樣去找一個類似wreaddir,但是沒有。于是應(yīng)該從掛載著手,目前在NTFS和FAT32/VFAT下的文件系統(tǒng)上都使用了Unicode,這就需要系統(tǒng)在讀取這些文件名時動態(tài)將其轉(zhuǎn)換為相應(yīng)的語言編碼,也就是說掛載的時候要把usb上的編碼轉(zhuǎn)化成16位的Unicode編碼,改命令如下后成功。
            mount -o iocharset = utf8 /dev/sda1 /mnt/usb.
            Linux對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.
            至此終于解決了多國語言的問題,接著我無法想象還有什么問題會出來,但我準備好了。



    攀升 2007-11-09 18:53 發(fā)表評論
    ]]>
    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,無語,其實主要是沒有認真考慮整個程序的運行全過程,僅在看代碼,向錦錦一樣。

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

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

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

     

    攀升 2007-07-05 20:07 發(fā)表評論
    ]]>
    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)被我從硬盤上擦除了,呵呵,因為一些問題,找了張suse,安裝時選了KDE,現(xiàn)在突然感覺不錯,從可用性上,界面上都不再是那個死板的Linux,而且我沒裝什么驅(qū)動用跑起來也沒問題(對于菜鳥來說這個比較重要),然后U盤也不用掛載就可用,不會出中文問題,就感覺還可以了,先給張圖片大家看看。





    攀升 2006-11-10 13:15 發(fā)表評論
    ]]>
    午夜精品久久久久久影视777| 久久亚洲熟女cc98cm| 久久男人AV资源网站| 日韩精品久久久肉伦网站| 国产成人综合久久精品尤物| 伊人久久大香线蕉综合影院首页| 国产精品热久久无码av| 久久A级毛片免费观看| 久久婷婷五月综合国产尤物app| 91久久精品国产91性色也| 久久精品国产清高在天天线| 亚洲精品99久久久久中文字幕| 伊人久久大香线焦综合四虎| 国内精品人妻无码久久久影院 | 久久久精品国产免大香伊| 国产香蕉97碰碰久久人人| 久久久国产精品福利免费| 久久久婷婷五月亚洲97号色| 国产精品亚洲综合久久| 欧美伊人久久大香线蕉综合69 | 久久99热狠狠色精品一区| 欧洲精品久久久av无码电影| 久久久久久午夜精品| 一本久久精品一区二区| 亚洲欧洲精品成人久久曰影片| 久久久久久国产精品无码下载| 51久久夜色精品国产| 97久久精品人人做人人爽| 亚洲成人精品久久| 成人午夜精品久久久久久久小说 | 久久精品国产精品国产精品污| 久久久无码精品亚洲日韩按摩| 久久久精品人妻一区二区三区蜜桃| 色综合久久夜色精品国产| 一级做a爰片久久毛片免费陪| 伊人色综合久久天天人守人婷 | 丁香久久婷婷国产午夜视频| 国产精品99久久久久久宅男| 国内精品久久久久久不卡影院| 久久国产福利免费| 亚洲精品97久久中文字幕无码|