影音先锋亚洲精品,黄色成人在线网站,一区在线视频http://www.shnenglu.com/iuranus/category/4279.html<br><font color="#ADFF2F">Something Different,Something New</font>zh-cnTue, 07 Aug 2012 08:08:22 GMTTue, 07 Aug 2012 08:08:22 GMT60(轉)MongoDB與內存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 發表評論
]]>
./configure -build,-host,-target設置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:執行代碼編譯的主機,正常的話就是你的主機系統。這個參數一般由config.guess來猜就可以。當然自己指定也可以。
host:編譯出來的二進制程序所執行的主機,因為絕大多數是如果本機編譯,本機執行。所以這個值就等于build。只有交叉編譯的時候(也就是本機編譯,其他系統機器執行)才會build和host不同。用host指定運行主機。
target:這個選項只有在建立交叉編譯環境的時候用到,正常編譯和交叉編譯都不會用到。他用build主機上的編譯器,編譯一個新的編譯器(
binutils, gcc,gdb等),這個新的編譯器將來編譯出來的其他程序將運行在target指定的系統上。
讓我們以編譯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的交叉編譯環境”。

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的交叉編譯環境,但是交叉編譯環境在mipsel-linux 編譯出來,安裝到i386-linux主機上,估計沒有多少人會這么用吧

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



攀升 2011-07-22 11:10 發表評論
]]>
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分類

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

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


1)      Full Cone

這種NAT內部的機器A連接過外網機器C后,NAT會打開一個端口.然后外網的任何發到這個打開的端口的UDP數據報都可以到達A.不管是不是C發過來的.

例如 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)

任何發送到 NAT(202.100.100.100:8000)的數據都可以到達A(192.168.8.100:5000)

2)      Restricted Cone

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

例如 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發送到 NAT(202.100.100.100:8000)的數據都可以到達A(192.168.8.100:5000)

 

3)      Port Restricted Cone

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

例如 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)發送到 NAT(202.100.100.100:8000)的數據都可以到達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打洞.需要一個公網機器C來充當”介紹人”.內網的A,B先分別和C通信.打開各自的NAT端口.C這個時候知道A,B的公網IP: Port. 現在A和B想直接連接.比如A給B發.除非B是Full Cone.否則不能通信.反之亦然.但是我們可以這樣.

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

3.       同一個NAT后面的情況

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

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



攀升 2011-06-08 23:09 發表評論
]]>
(轉)P2P之UDP穿透NAT的原理與實現(附源代碼)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的原理與實現(附源代碼)
原創:shootingstars
參考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
論壇上經常有對P2P原理的討論,但是討論歸討論,很少有實質的東西產生(源代碼)。呵呵,在這里我就用自己實現的一個源代碼來說明UDP穿越NAT的原理。
首先先介紹一些基本概念:
    NAT(Network Address Translators),網絡地址轉換:網絡地址轉換是在IP地址日益缺乏的情況下產生的,它的主要目的就是為了能夠地址重用。NAT分為兩大類,基本的NAT和NAPT(Network Address/Port Translator)。
    最開始NAT是運行在路由器上的一個功能模塊。
    
    最先提出的是基本的NAT,它的產生基于如下事實:一個私有網絡(域)中的節點中只有很少的節點需要與外網連接(呵呵,這是在上世紀90年代中期提出的)。那么這個子網中其實只有少數的節點需要全球唯一的IP地址,其他的節點的IP地址應該是可以重用的。
    因此,基本的NAT實現的功能很簡單,在子網內使用一個保留的IP子網段,這些IP對外是不可見的。子網內只有少數一些IP地址可以對應到真正全球唯一的IP地址。如果這些節點需要訪問外部網絡,那么基本NAT就負責將這個節點的子網內IP轉化為一個全球唯一的IP然后發送出去。(基本的NAT會改變IP包中的原IP地址,但是不會改變IP包中的端口)
    關于基本的NAT可以參看RFC 1631
    
    另外一種NAT叫做NAPT,從名稱上我們也可以看得出,NAPT不但會改變經過這個NAT設備的IP數據報的IP地址,還會改變IP數據報的TCP/UDP端口。基本NAT的設備可能我們見的不多(呵呵,我沒有見到過),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
    有一個私有網絡10.*.*.*,Client A是其中的一臺計算機,這個網絡的網關(一個NAT設備)的外網IP是155.99.25.11(應該還有一個內網的IP地址,比如10.0.0.10)。如果Client A中的某個進程(這個進程創建了一個UDP Socket,這個Socket綁定1234端口)想訪問外網主機18.181.0.31的1235端口,那么當數據包通過NAT時會發生什么事情呢?
    首先NAT會改變這個數據包的原IP地址,改為155.99.25.11。接著NAT會為這個傳輸創建一個Session(Session是一個抽象的概念,如果是TCP,也許Session是由一個SYN包開始,以一個FIN包結束。而UDP呢,以這個IP的這個端口的第一個UDP開始,結束呢,呵呵,也許是幾分鐘,也許是幾小時,這要看具體的實現了)并且給這個Session分配一個端口,比如62000,然后改變這個數據包的源端口為62000。所以本來是(10.0.0.1:1234->18.181.0.31:1235)的數據包到了互聯網上變為了(155.99.25.11:62000->18.181.0.31:1235)。
    一旦NAT創建了一個Session后,NAT會記住62000端口對應的是10.0.0.1的1234端口,以后從18.181.0.31發送到62000端口的數據會被NAT自動的轉發到10.0.0.1上。(注意:這里是說18.181.0.31發送到62000端口的數據會被轉發,其他的IP發送到這個端口的數據將被NAT拋棄)這樣Client A就與Server S1建立以了一個連接。
    呵呵,上面的基礎知識可能很多人都知道了,那么下面是關鍵的部分了。
    看看下面的情況:
    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發送了一個UDP包,那么這個UDP包在通過NAT時會怎么樣呢?
    這時可能會有兩種情況發生,一種是NAT再次創建一個Session,并且再次為這個Session分配一個端口號(比如:62001)。另外一種是NAT再次創建一個Session,但是不會新分配一個端口號,而是用原來分配的端口號62000。前一種NAT叫做Symmetric NAT,后一種叫做Cone NAT。我們期望我們的NAT是第二種,呵呵,如果你的NAT剛好是第一種,那么很可能會有很多P2P軟件失靈。(可以慶幸的是,現在絕大多數的NAT屬于后者,即Cone NAT)
   
    好了,我們看到,通過NAT,子網內的計算機向外連結是很容易的(NAT相當于透明的,子網內的和外網的計算機不用知道NAT的情況)。
    但是如果外部的計算機想訪問子網內的計算機就比較困難了(而這正是P2P所需要的)。
    那么我們如果想從外部發送一個數據報給內網的計算機有什么辦法呢?首先,我們必須在內網的NAT上打上一個“洞”(也就是前面我們說的在NAT上建立一個Session),這個洞不能由外部來打,只能由內網內的主機來打。而且這個洞是有方向的,比如從內部某臺主機(比如:192.168.0.10)向外部的某個IP(比如:219.237.60.1)發送一個UDP包,那么就在這個內網的NAT設備上打了一個方向為219.237.60.1的“洞”,(這就是稱為UDP Hole Punching的技術)以后219.237.60.1就可以通過這個洞與內網的192.168.0.10聯系了。(但是其他的IP不能利用這個洞)。
呵呵,現在該輪到我們的正題P2P了。有了上面的理論,實現兩個內網的主機通訊就差最后一步了:那就是雞生蛋還是蛋生雞的問題了,兩邊都無法主動發出連接請求,誰也不知道誰的公網地址,那我們如何來打這個洞呢?我們需要一個中間人來聯系這兩個內網主機。
    現在我們來看看一個P2P軟件的流程,以下圖為例:
                       Server S (219.237.60.1)
                          |
                          |
   +----------------------+----------------------+
   |                                             |
 NAT A (外網IP:202.187.45.3)                 NAT B (外網IP:187.34.1.56)
   |   (內網IP:192.168.0.1)                      | (內網IP:192.168.0.1)
   |                                             |
Client A  (192.168.0.20:4000)             Client B (192.168.0.10:40000)
    首先,Client A登錄服務器,NAT A為這次的Session分配了一個端口60000,那么Server S收到的Client A的地址是202.187.45.3:60000,這就是Client A的外網地址了。同樣,Client B登錄Server S,NAT B給此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。
    此時,Client A與Client B都可以與Server S通信了。如果Client A此時想直接發送信息給Client B,那么他可以從Server S那兒獲得B的公網地址187.34.1.56:40000,是不是Client A向這個地址發送信息Client B就能收到了呢?答案是不行,因為如果這樣發送信息,NAT B會將這個信息丟棄(因為這樣的信息是不請自來的,為了安全,大多數NAT都會執行丟棄動作)。現在我們需要的是在NAT B上打一個方向為202.187.45.3(即Client A的外網地址)的洞,那么Client A發送到187.34.1.56:40000的信息,Client B就能收到了。這個打洞命令由誰來發呢,呵呵,當然是Server S。
    總結一下這個過程:如果Client A想向Client B發送信息,那么Client A發送命令給Server S,請求Server S命令Client B向Client A方向打洞。呵呵,是不是很繞口,不過沒關系,想一想就很清楚了,何況還有源代碼呢(侯老師說過:在源代碼面前沒有秘密 8)),然后Client A就可以通過Client B的外網地址與Client B通信了。
    
    注意:以上過程只適合于Cone NAT的情況,如果是Symmetric NAT,那么當Client B向Client A打洞的端口已經重新分配了,Client B將無法知道這個端口(如果Symmetric NAT的端口是順序分配的,那么我們或許可以猜測這個端口號,可是由于可能導致失敗的因素太多,我們不推薦這種猜測端口的方法)。
    
    下面是一個模擬P2P聊天的過程的源代碼,過程很簡單,P2PServer運行在一個擁有公網IP的計算機上,P2PClient運行在兩個不同的NAT后(注意,如果兩個客戶端運行在一個NAT后,本程序很可能不能運行正常,這取決于你的NAT是否支持loopback translation,詳見http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt,當然,此問題可以通過雙方先嘗試連接對方的內網IP來解決,但是這個代碼只是為了驗證原理,并沒有處理這些問題),后登錄的計算機可以獲得先登錄計算機的用戶名,后登錄的計算機通過send username message的格式來發送消息。如果發送成功,說明你已取得了直接與對方連接的成功。
    程序現在支持三個命令:send , getu , exit
    
    send格式:send username message
    功能:發送信息給username
    
    getu格式:getu
    功能:獲得當前服務器用戶列表
    
    exit格式:exit
    功能:注銷與服務器的連接(服務器不會自動監測客戶是否吊線)
        
    代碼很短,相信很容易懂,如果有什么問題,可以給我發郵件zhouhuis22@sina.com  或者在CSDN上發送短消息。同時,歡迎轉發此文,但希望保留作者版權8-)。
    
    最后感謝CSDN網友 PiggyXP 和 Seilfer的測試幫助
P2PServer.c
/* P2P 程序服務端
 * 
 * 文件名: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));
  // 開始主循環.
  // 主循環負責下面幾件事情:
  // 一:讀取客戶端登陸和登出消息,記錄客戶列表
  // 二:轉發客戶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);
      // 發送已經登陸的客戶信息
      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:
     {
      // 某個客戶希望服務端向另外一個客戶發送一個打洞消息
      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");
 }
 // 登錄到服務端后,接收服務端發來的已經登錄的用戶的信息
 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;
}
/* 這是主要的函數:發送一個消息給某個用戶(C)
 *流程:直接向某個用戶的外網IP發送消息,如果此前沒有聯系過
 *      那么此消息將無法發送,發送端等待超時。
 *      超時后,發送端將發送一個請求信息到服務端,
 *      要求服務端發送給客戶C一個請求,請求C給本機發送打洞消息
 *      以上流程將重復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);
  }
  // 沒有接收到目標主機的回應,認為目標主機的端口映射沒有
  // 打開,那么發送請求信息給服務器,要服務器告訴目標主機
  // 打開映射端口(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);// 等待對方先發送信息。
 }
 return false;
}
// 解析命令,暫時只有exit和send命令
// 新增getu命令,獲取當前服務器的所有用戶
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:
   {
    // 發送消息的應答
    RecvedACK = true;
    break;
   }
  case P2PTRASH:
   {
    // 對方發送的打洞消息,忽略掉。
    //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 程序傳輸協議
 * 
 * 日期:2004-5-21
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 *
 */
#pragma once
#include <list>
// 定義iMessageType的值
#define LOGIN 1
#define LOGOUT 2
#define P2PTRANS 3
#define GETALLUSER  4
// 服務器端口
#define SERVER_PORT 2280
// Client登錄時向服務器發送的消息
struct stLoginMessage
{
 char userName[10];
 char password[10];
};
// Client注銷時發送的消息
struct stLogoutMessage
{
 char userName[10];
};
// Client向服務器請求另外一個Client(userName)向自己方向發送UDP打洞消息
struct stP2PTranslate
{
 char userName[10];
};
// Client向服務器發送的消息格式
struct stMessage
{
 int iMessageType;
 union _message
 {
  stLoginMessage loginmember;
  stLogoutMessage logoutmember;
  stP2PTranslate translatemessage;
 }message;
};
// 客戶節點信息
struct stUserListNode
{
 char userName[10];
 unsigned int ip;
 unsigned short port;
};
// Server向Client發送的消息
struct stServerToClient
{
 int iMessageType;
 union _message
 {
  stUserListNode user;
 }message;
};
//======================================
// 下面的協議用于客戶端之間的通信
//======================================
#define P2PMESSAGE 100               // 發送消息
#define P2PMESSAGEACK 101            // 收到消息的應答
#define P2PSOMEONEWANTTOCALLYOU 102  // 服務器向客戶端發送的消息
                                     // 希望此客戶端發送一個UDP打洞包
#define P2PTRASH        103          // 客戶端發送的打洞包,接收端應該忽略此消息
// 客戶端之間發送消息格式
struct stP2PMessage
{
 int iMessageType;
 int iStringLen;         // or IP address
 unsigned short Port; 
};
using namespace std;
typedef list<stUserListNode *> UserList;


攀升 2011-05-19 08:37 發表評論
]]>
(轉)P2P之UDP穿透NAT的原理與實現(附源代碼)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的原理與實現(附源代碼)
原創:shootingstars
參考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
論壇上經常有對P2P原理的討論,但是討論歸討論,很少有實質的東西產生(源代碼)。呵呵,在這里我就用自己實現的一個源代碼來說明UDP穿越NAT的原理。
首先先介紹一些基本概念:
    NAT(Network Address Translators),網絡地址轉換:網絡地址轉換是在IP地址日益缺乏的情況下產生的,它的主要目的就是為了能夠地址重用。NAT分為兩大類,基本的NAT和NAPT(Network Address/Port Translator)。
    最開始NAT是運行在路由器上的一個功能模塊。
    
    最先提出的是基本的NAT,它的產生基于如下事實:一個私有網絡(域)中的節點中只有很少的節點需要與外網連接(呵呵,這是在上世紀90年代中期提出的)。那么這個子網中其實只有少數的節點需要全球唯一的IP地址,其他的節點的IP地址應該是可以重用的。
    因此,基本的NAT實現的功能很簡單,在子網內使用一個保留的IP子網段,這些IP對外是不可見的。子網內只有少數一些IP地址可以對應到真正全球唯一的IP地址。如果這些節點需要訪問外部網絡,那么基本NAT就負責將這個節點的子網內IP轉化為一個全球唯一的IP然后發送出去。(基本的NAT會改變IP包中的原IP地址,但是不會改變IP包中的端口)
    關于基本的NAT可以參看RFC 1631
    
    另外一種NAT叫做NAPT,從名稱上我們也可以看得出,NAPT不但會改變經過這個NAT設備的IP數據報的IP地址,還會改變IP數據報的TCP/UDP端口。基本NAT的設備可能我們見的不多(呵呵,我沒有見到過),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
    有一個私有網絡10.*.*.*,Client A是其中的一臺計算機,這個網絡的網關(一個NAT設備)的外網IP是155.99.25.11(應該還有一個內網的IP地址,比如10.0.0.10)。如果Client A中的某個進程(這個進程創建了一個UDP Socket,這個Socket綁定1234端口)想訪問外網主機18.181.0.31的1235端口,那么當數據包通過NAT時會發生什么事情呢?
    首先NAT會改變這個數據包的原IP地址,改為155.99.25.11。接著NAT會為這個傳輸創建一個Session(Session是一個抽象的概念,如果是TCP,也許Session是由一個SYN包開始,以一個FIN包結束。而UDP呢,以這個IP的這個端口的第一個UDP開始,結束呢,呵呵,也許是幾分鐘,也許是幾小時,這要看具體的實現了)并且給這個Session分配一個端口,比如62000,然后改變這個數據包的源端口為62000。所以本來是(10.0.0.1:1234->18.181.0.31:1235)的數據包到了互聯網上變為了(155.99.25.11:62000->18.181.0.31:1235)。
    一旦NAT創建了一個Session后,NAT會記住62000端口對應的是10.0.0.1的1234端口,以后從18.181.0.31發送到62000端口的數據會被NAT自動的轉發到10.0.0.1上。(注意:這里是說18.181.0.31發送到62000端口的數據會被轉發,其他的IP發送到這個端口的數據將被NAT拋棄)這樣Client A就與Server S1建立以了一個連接。
    呵呵,上面的基礎知識可能很多人都知道了,那么下面是關鍵的部分了。
    看看下面的情況:
    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發送了一個UDP包,那么這個UDP包在通過NAT時會怎么樣呢?
    這時可能會有兩種情況發生,一種是NAT再次創建一個Session,并且再次為這個Session分配一個端口號(比如:62001)。另外一種是NAT再次創建一個Session,但是不會新分配一個端口號,而是用原來分配的端口號62000。前一種NAT叫做Symmetric NAT,后一種叫做Cone NAT。我們期望我們的NAT是第二種,呵呵,如果你的NAT剛好是第一種,那么很可能會有很多P2P軟件失靈。(可以慶幸的是,現在絕大多數的NAT屬于后者,即Cone NAT)
   
    好了,我們看到,通過NAT,子網內的計算機向外連結是很容易的(NAT相當于透明的,子網內的和外網的計算機不用知道NAT的情況)。
    但是如果外部的計算機想訪問子網內的計算機就比較困難了(而這正是P2P所需要的)。
    那么我們如果想從外部發送一個數據報給內網的計算機有什么辦法呢?首先,我們必須在內網的NAT上打上一個“洞”(也就是前面我們說的在NAT上建立一個Session),這個洞不能由外部來打,只能由內網內的主機來打。而且這個洞是有方向的,比如從內部某臺主機(比如:192.168.0.10)向外部的某個IP(比如:219.237.60.1)發送一個UDP包,那么就在這個內網的NAT設備上打了一個方向為219.237.60.1的“洞”,(這就是稱為UDP Hole Punching的技術)以后219.237.60.1就可以通過這個洞與內網的192.168.0.10聯系了。(但是其他的IP不能利用這個洞)。
呵呵,現在該輪到我們的正題P2P了。有了上面的理論,實現兩個內網的主機通訊就差最后一步了:那就是雞生蛋還是蛋生雞的問題了,兩邊都無法主動發出連接請求,誰也不知道誰的公網地址,那我們如何來打這個洞呢?我們需要一個中間人來聯系這兩個內網主機。
    現在我們來看看一個P2P軟件的流程,以下圖為例:
                       Server S (219.237.60.1)
                          |
                          |
   +----------------------+----------------------+
   |                                             |
 NAT A (外網IP:202.187.45.3)                 NAT B (外網IP:187.34.1.56)
   |   (內網IP:192.168.0.1)                      | (內網IP:192.168.0.1)
   |                                             |
Client A  (192.168.0.20:4000)             Client B (192.168.0.10:40000)
    首先,Client A登錄服務器,NAT A為這次的Session分配了一個端口60000,那么Server S收到的Client A的地址是202.187.45.3:60000,這就是Client A的外網地址了。同樣,Client B登錄Server S,NAT B給此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。
    此時,Client A與Client B都可以與Server S通信了。如果Client A此時想直接發送信息給Client B,那么他可以從Server S那兒獲得B的公網地址187.34.1.56:40000,是不是Client A向這個地址發送信息Client B就能收到了呢?答案是不行,因為如果這樣發送信息,NAT B會將這個信息丟棄(因為這樣的信息是不請自來的,為了安全,大多數NAT都會執行丟棄動作)。現在我們需要的是在NAT B上打一個方向為202.187.45.3(即Client A的外網地址)的洞,那么Client A發送到187.34.1.56:40000的信息,Client B就能收到了。這個打洞命令由誰來發呢,呵呵,當然是Server S。
    總結一下這個過程:如果Client A想向Client B發送信息,那么Client A發送命令給Server S,請求Server S命令Client B向Client A方向打洞。呵呵,是不是很繞口,不過沒關系,想一想就很清楚了,何況還有源代碼呢(侯老師說過:在源代碼面前沒有秘密 8)),然后Client A就可以通過Client B的外網地址與Client B通信了。
    
    注意:以上過程只適合于Cone NAT的情況,如果是Symmetric NAT,那么當Client B向Client A打洞的端口已經重新分配了,Client B將無法知道這個端口(如果Symmetric NAT的端口是順序分配的,那么我們或許可以猜測這個端口號,可是由于可能導致失敗的因素太多,我們不推薦這種猜測端口的方法)。
    
    下面是一個模擬P2P聊天的過程的源代碼,過程很簡單,P2PServer運行在一個擁有公網IP的計算機上,P2PClient運行在兩個不同的NAT后(注意,如果兩個客戶端運行在一個NAT后,本程序很可能不能運行正常,這取決于你的NAT是否支持loopback translation,詳見http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt,當然,此問題可以通過雙方先嘗試連接對方的內網IP來解決,但是這個代碼只是為了驗證原理,并沒有處理這些問題),后登錄的計算機可以獲得先登錄計算機的用戶名,后登錄的計算機通過send username message的格式來發送消息。如果發送成功,說明你已取得了直接與對方連接的成功。
    程序現在支持三個命令:send , getu , exit
    
    send格式:send username message
    功能:發送信息給username
    
    getu格式:getu
    功能:獲得當前服務器用戶列表
    
    exit格式:exit
    功能:注銷與服務器的連接(服務器不會自動監測客戶是否吊線)
        
    代碼很短,相信很容易懂,如果有什么問題,可以給我發郵件zhouhuis22@sina.com  或者在CSDN上發送短消息。同時,歡迎轉發此文,但希望保留作者版權8-)。
    
    最后感謝CSDN網友 PiggyXP 和 Seilfer的測試幫助
P2PServer.c
/* P2P 程序服務端
 * 
 * 文件名: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));
  // 開始主循環.
  // 主循環負責下面幾件事情:
  // 一:讀取客戶端登陸和登出消息,記錄客戶列表
  // 二:轉發客戶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);
      // 發送已經登陸的客戶信息
      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:
     {
      // 某個客戶希望服務端向另外一個客戶發送一個打洞消息
      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");
 }
 // 登錄到服務端后,接收服務端發來的已經登錄的用戶的信息
 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;
}
/* 這是主要的函數:發送一個消息給某個用戶(C)
 *流程:直接向某個用戶的外網IP發送消息,如果此前沒有聯系過
 *      那么此消息將無法發送,發送端等待超時。
 *      超時后,發送端將發送一個請求信息到服務端,
 *      要求服務端發送給客戶C一個請求,請求C給本機發送打洞消息
 *      以上流程將重復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);
  }
  // 沒有接收到目標主機的回應,認為目標主機的端口映射沒有
  // 打開,那么發送請求信息給服務器,要服務器告訴目標主機
  // 打開映射端口(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);// 等待對方先發送信息。
 }
 return false;
}
// 解析命令,暫時只有exit和send命令
// 新增getu命令,獲取當前服務器的所有用戶
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:
   {
    // 發送消息的應答
    RecvedACK = true;
    break;
   }
  case P2PTRASH:
   {
    // 對方發送的打洞消息,忽略掉。
    //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 程序傳輸協議
 * 
 * 日期:2004-5-21
 *
 * 作者:shootingstars(zhouhuis22@sina.com)
 *
 */
#pragma once
#include <list>
// 定義iMessageType的值
#define LOGIN 1
#define LOGOUT 2
#define P2PTRANS 3
#define GETALLUSER  4
// 服務器端口
#define SERVER_PORT 2280
// Client登錄時向服務器發送的消息
struct stLoginMessage
{
 char userName[10];
 char password[10];
};
// Client注銷時發送的消息
struct stLogoutMessage
{
 char userName[10];
};
// Client向服務器請求另外一個Client(userName)向自己方向發送UDP打洞消息
struct stP2PTranslate
{
 char userName[10];
};
// Client向服務器發送的消息格式
struct stMessage
{
 int iMessageType;
 union _message
 {
  stLoginMessage loginmember;
  stLogoutMessage logoutmember;
  stP2PTranslate translatemessage;
 }message;
};
// 客戶節點信息
struct stUserListNode
{
 char userName[10];
 unsigned int ip;
 unsigned short port;
};
// Server向Client發送的消息
struct stServerToClient
{
 int iMessageType;
 union _message
 {
  stUserListNode user;
 }message;
};
//======================================
// 下面的協議用于客戶端之間的通信
//======================================
#define P2PMESSAGE 100               // 發送消息
#define P2PMESSAGEACK 101            // 收到消息的應答
#define P2PSOMEONEWANTTOCALLYOU 102  // 服務器向客戶端發送的消息
                                     // 希望此客戶端發送一個UDP打洞包
#define P2PTRASH        103          // 客戶端發送的打洞包,接收端應該忽略此消息
// 客戶端之間發送消息格式
struct stP2PMessage
{
 int iMessageType;
 int iStringLen;         // or IP address
 unsigned short Port; 
};
using namespace std;
typedef list<stUserListNode *> UserList;


攀升 2011-05-19 08:35 發表評論
]]>
(轉)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庫函數(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指針,UNIX的I/O函數(open, close, read, write, ioctl)使用文件描述符。下面重點來說下,文件描述符是如何工作的。
 
文件描述符相當于一個邏輯句柄,而open,close等函數則是將文件或者物理設備與句柄相關聯。句柄是一個整數,可以理解為進程特定的文件 描述符表的索引。先介紹下面三個概念,后面講下open、close等操作以后,文件和文件描述符產生什么關系,以及fork后文件描述符的繼承等問題。
 
文件描述符表 :用戶區的一部分,除非通過使用文件描述符的函數,否則程序無法對其進行訪問。對進程中每個打開的文件,文件描述符表都包含一個條目。
 
系統文件表 :為系統中所有的進程共享。對每個活動的open, 它都包含一個條目。每個系統文件表的條目都包含文件偏移量、訪問模式(讀、寫、or 讀-寫)以及指向它的文件描述符表的條目計數。
 
內存索引節點表: 對系統中的每個活動的文件(被某個進程打開了),內存中索引節點表都包含一個條目。幾個系統文件表條目可能對應于同一個內存索引節點表(不同進程打開同一個文件)。
 
1、舉例: 執行myfd = open( "/home/lucy/my.dat", O_RDONLY); 以后,上述3個表的關系原理圖如下:
http://keren.blog.51cto.com/
                                                                                  圖1
 
系統文件表包含一個偏移量,給出了文件當前的位置。若2個進程同時打開一個文件(如上圖A,B)做讀操作,每個進程都有自己相對于文件的偏移 量,而且讀入整個文件是獨立于另一個進程的;如果2個進程打開同一個文件做寫操作,寫操作是相互獨立的,每個進程都可以重寫另一個進程寫入的內容。
 
如果上面進程在open以后又執行了close()函數,操作系統會刪除文件描述符表的第四個條目,和系統文件表的對應條目(若指向它的描述符 表唯一),并對內存索引節點表條目中的計數減1,如果自減以后變為0,說明沒有其他進程鏈接此文件,將索引節點表條目也刪除,而這里進程B也在open這 個文件,所以索引節點表條目保留。
 
2、文件描述符的繼承
通過fork()創建子進程時,子進程繼承父進程環境和上下文的大部分內容的拷貝,其中就包括文件描述符表。
 
(1)對于父進程在fork()之前打開的文件來說,子進程都會繼承,與父進程共享相同的文件偏移量。如下圖所示(0-1-2 表示 標準輸入-輸出-錯誤):
                                  圖2 fork()之前打開my.dat
 
系統文件表位于系統空間中,不會被fork()復制,但是系統文件表中的條目會保存指向它的文件描述符表的計數,fork()時需要對這個計數 進行維護,以體現子進程對應的新的文件描述符表也指向它。程序關閉文件時,也是將系統文件表條目內部的計數減一,當計數值減為0時,才將其刪除。
 
(2)相反,如果父進程先進程fork,再打開my.dat,這時父子進程關于my.dat 的文件描述符表指向不同的系統文件表條目,也不再共享文件偏移量(fork以后2個進程分別open,在系統文件表中創建2個條目);但是關于標準輸入, 標準輸出,標準錯誤,父子進程還是共享的。
                      圖3   fork()以后打開my.dat
 
 

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




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

程序描述:

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

程序示例:

#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;

}

 

程序運行結果:

dup:

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

close file p ok?: 0

close file q ok?: 0 //文件關閉成功

=:

file p,q fd is:3 3 //簡單復制

close file p ok?: 0

close file q ok?: -1//關閉失敗,原因是此描述符已經被關閉了

 

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

關于socket的文件描述符

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



攀升 2009-12-22 10:10 發表評論
]]>
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 認識開始,到現在做A2DP升華,寫一些使用FIFO的要點下來。
        
      1   管道一般用于進程間通信,把一個進程的輸出通過管道送給另一個進程。
      2   可以通過popen,pclose嘗試實現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創建管道
           #include <unistd.h>

           int pipe(int file_description[2]);
           pipe的參數是由兩個文件描述符組成的數組。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 發表評論
]]>
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 發表評論
]]>
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 );

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

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

  • optarg——指向當前選項參數(如果有)的指針。
  • optind——再次調用 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 發表評論
]]>
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 發表評論
]]>
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時其實會又很多產物,因為android讓我接觸了linux 內核的一些資料。

貼最后一段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相關的一些重要的啟動時要用
  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初始化之前注冊的函數,最后釋放資源。內核級別啟動常報的錯就是“No init found.  Try passing init= option to kernel”
這主要是因為通過run_init_process執行系統級別的/init時失敗,返回。如果成功,該函數不會返回。
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 發表評論
]]>
(轉)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   <- 設置用戶密碼

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

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

 

smbpasswd -a xxx  <- 添加samba 用戶

 

ps: /etc/samba/smb.conf 的內容:
[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博客,轉載請標明出處:http://blog.csdn.net/stevenliyong/archive/2009/03/12/3984926.aspx



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

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

         reboot,OK



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

 

 

相關函數

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



ctime(將時間和日期以字符串格式表示)
相關函數
time,asctime,gmtime,localtime
表頭文件
#include<time.h>
定義函數
char *ctime(const time_t *timep);
函數說明
ctime()將參數timep所指的time_t結構中的信息轉換成真實世界所使用的時間日期表示方法,然后將結果以字符串形態返回。此函數已經由時區轉換成當地時間,字符串格式為“Wed Jun 30 21 :49 :08 1993\n”。若再調用相關的時間日期函數,此字符串可能會被破壞。
返回值
返回一字符串表示目前當地的時間日期。
范例
#include<time.h>
main()
{
time_t timep;
time (&timep);
printf(“%s”,ctime(&timep));
}
執行
Sat Oct 28 10 : 12 : 05 2000
 



gettimeofday(取得目前的時間)
相關函數
time,ctime,ftime,settimeofday
表頭文件
#include <sys/time.h>
#include <unistd.h>
定義函數
int gettimeofday ( struct timeval * tv , struct timezone * tz )
函數說明
gettimeofday()會把目前的時間有tv所指的結構返回,當地時區的信息則放到tz所指的結構中。
timeval結構定義為:
struct timeval{
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};
timezone 結構定義為:
struct timezone{
int tz_minuteswest; /*和Greenwich 時間差了多少分鐘*/
int tz_dsttime; /*日光節約時間的狀態*/
};
上述兩個結構都定義在/usr/include/sys/time.h。tz_dsttime 所代表的狀態如下
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所指的內存空間超出存取權限。
范例
#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);
}
執行
tv_sec: 974857339
tv_usec:136996
tz_minuteswest:-540
tz_dsttime:0
 



gmtime(取得目前時間和日期)
相關函數
time,asctime,ctime,localtime
表頭文件
#include<time.h>
定義函數
struct tm*gmtime(const time_t*timep);
函數說明
gmtime()將參數timep 所指的time_t 結構中的信息轉換成真實世界所使用的時間日期表示方法,然后將結果由結構tm返回。
結構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 代表目前秒數,正常范圍為0-59,但允許至61秒
int tm_min 代表目前分數,范圍0-59
int tm_hour 從午夜算起的時數,范圍為0-23
int tm_mday 目前月份的日數,范圍01-31
int tm_mon 代表目前月份,從一月算起,范圍從0-11
int tm_year 從1900 年算起至今的年數
int tm_wday 一星期的日數,從星期一算起,范圍為0-6
int tm_yday 從今年1月1日算起至今的天數,范圍為0-365
int tm_isdst 日光節約時間的旗標
此函數返回的時間日期未經時區轉換,而是UTC時間。
返回值
返回結構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);
}
執行
2000/10/28 Sat 8:15:38
 



localtime(取得當地目前時間和日期)
相關函數
time, asctime, ctime, gmtime
表頭文件
#include<time.h>
定義函數
struct tm *localtime(const time_t * timep);
函數說明
localtime()將參數timep所指的time_t結構中的信息轉換成真實世界所使用的時間日期表示方法,然后將結果由結構tm返回。結構tm的定義請參考gmtime()。此函數返回的時間日期已經轉換成當地時區。
返回值
返回結構tm代表目前的當地時間。
范例
#include<time.h>
main(){
char *wday[]={“Sun”,”Mon”,”Tue”,”Wed”,”Thu”,”Fri”,”Sat”};
time_t timep;
struct tm *p;
time(&timep);
p=localtime(&timep); /*取得當地時間*/
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);
}
執行
2000/10/28 Sat 11:12:22
 



mktime(將時間結構數據轉換成經過的秒數)
相關函數
time,asctime,gmtime,localtime
表頭文件
#include<time.h>
定義函數
time_t mktime(strcut tm * timeptr);
函數說明
mktime()用來將參數timeptr所指的tm結構數據轉換成從公元1970年1月1日0時0分0 秒算起至今的UTC時間所經過的秒數。
返回值
返回經過的秒數。
范例
/* 用time()取得時間(秒數),利用localtime()
轉換成struct tm 再利用mktine()將struct tm轉換成原來的秒數*/
#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);
}
執行
time():974943297
time()->localtime()->mktime():974943297
 



settimeofday(設置目前時間)
相關函數
time,ctime,ftime,gettimeofday
表頭文件
#include<sys/time.h>
#include<unistd.h>
定義函數
int settimeofday ( const struct timeval *tv,const struct timezone *tz);
函數說明
settimeofday()會把目前時間設成由tv所指的結構信息,當地時區信息則設成tz所指的結構。詳細的說明請參考gettimeofday()。注意,只有root權限才能使用此函數修改時間。
返回值
成功則返回0,失敗返回-1,錯誤代碼存于errno。
錯誤代碼
EPERM 并非由root權限調用settimeofday(),權限不夠。
EINVAL 時區或某個數據是不正確的,無法正確設置時間。
 



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

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

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

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

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

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



攀升 2009-03-01 11:37 發表評論
]]>
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的參數著實讓我搞了半天,因為以前就是固定的方式讀寫文件的,現在要做靈活了,所以就有些參數理解不夠準確。以下是關于mode參數的定義。

'r' 只讀方式打開,將文件指針指向文件頭,如果文件不存在,則File返回空。
'r+' 讀寫方式打開,將文件指針指向文件頭,如果文件不存在,則File返回空。
'w' 寫入方式打開,將文件指針指向文件頭并將文件大小截為零。如果文件不存在則嘗試創建之。
'w+' 讀寫方式打開,將文件指針指向文件頭并將文件大小截為零。如果文件不存在則嘗試創建之。
'a' 寫入方式打開,將文件指針指向文件末尾。如果文件不存在則嘗試創建之。
'a+' 讀寫方式打開,將文件指針指向文件末尾。如果文件不存在則嘗試創建之。
'x' 創建并以寫入方式打開,將文件指針指向文件頭。如果文件已存在,則 fopen() 調用失敗并返回 FALSE。
'x' 創建并以寫入方式打開,將文件指針指向文件頭。如果文件已存在,則 fopen() 調用失敗并返回 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 發表評論
]]>
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),共享內存,信號量,消息隊列,Socket等。 像現在流行的moblin平臺就使用了DBUS通信,還有我最近看的bluez 4 也是通過DBUS來交互的。
 
      它是個3層架構的進程間通信系統,包括:   

            1.   函數庫libdbus,用于兩個應用程序呼叫聯系和交互消息。

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

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

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

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

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


      理解有限,先說到這。

 



攀升 2008-12-20 12:23 發表評論
]]>
(轉載)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系統調用是用來讓我們的程序監視多個文件句柄(file descriptor)的狀態變化的。程序會停在select這里等待,直到被監視的文件句柄有某一個或多個發生了狀態改變。 文件在句柄在Linux里很多,如果你man某個函數,在函數返回值部分說到成功后有一個文件句柄被創建的都是的,如man socket可以看到“On success, a file descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其實文件句柄就是一個整數,看socket函數的聲明就明白了:

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

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

#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;

}

繼續上面說的select,就是用來監視某個或某些句柄的狀態變化的。

select函數原型如下:

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

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

struct timeval

{             

long    tv_sec;         /* seconds */             

long    tv_usec;        /* microseconds */         

};

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

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

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

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

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

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

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

 

tv.tv_sec = 1;

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

 

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

if(ret < 0)

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

else if(ret == 0)

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

else

{ /* 說明等待時間還未到1秒加500毫秒,socket的狀態發生了變化 */    

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

    /* 這里我們就應該從socket這個句柄里讀取數據了,因為select函數已經告訴我們這個句柄里有數據可讀 */   

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

   /* 讀取socket句柄里的數據 */        

    recv(...);    

}

}

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

 

int sa, sb, sc;

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

connect(sa,...);

sb = socket(...);

connect(sb,...);

sc = socket(...);

connect(sc,...);

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

FD_SET(sb, &rdfds);

FD_SET(sc, &rdfds);

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

int maxfd = 0;

if(sa > maxfd)

     maxfd = sa;

if(sb > maxfd)

    maxfd = sb;

if(sc > maxfd)

    maxfd = sc;

然后調用select函數:

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

同樣的道理,如果我們要檢測用戶是否按了鍵盤進行輸入,我們就應該把標準輸入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"); /* 在我們設定的時間tv內,用戶沒有按鍵盤 */

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

scanf("%s", buf); }



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


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


為此我轉載了一篇關于Configure選項配置的詳細介紹。供大家參考


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

--cache-file=FILE

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

--help

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


--no-create


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


--quiet

--silent


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


打印用來產生'configure'腳本的Autoconf的版本號。


--prefix=PEWFIX


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

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


--exec-prefix=EPREFIX


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


--bindir=DIR


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


--sbindir=DIR


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


--libexecdir=DIR


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


--datadir=DIR


指定通用數據文件的安裝位置。


--sysconfdir=DIR


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


--sharedstatedir=DIR

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


--localstatedir=DIR

指定只能單機使用的可寫數據的安裝位置。

--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'的程序將會使安裝的程序被命名為'gtar'。當和其他的安裝選項一起使用時,這個選項只有當他被`Makefile.in'文件使用時才會工作。


--program-suffix=SUFFIX

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


--program-transform-name=PROGRAM

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


--build=BUILD

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


--host=HOST

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


--target=GARGET

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


--disable-FEATURE

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

$ ./configure --disable-gui


-enable-FEATURE[=ARG]

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

$ ./configure --enable-buffers=128

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


--with-PACKAGE[=ARG]

在自由軟件社區里,有使用已有軟件包和庫的優秀傳統。當用'configure'來配置一個源碼樹時,可以提供其他已經安裝的軟件包的信息。例如,倚賴于Tcl和Tk的BLT器件工具包。要配置BLT,可能需要給'configure'提供一些關于我們把Tcl和Tk裝的何處的信息:

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

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


--without-PACKAGE

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

$ ./configure --without-gnu-ld


--x-includes=DIR

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


--x-libraries=DIR

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


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

這樣這棵構筑樹就被配置了,下面可以繼續構筑和安裝這個包到默認的位置'/usr/local':

$ make all && make install



攀升 2008-12-17 22:30 發表評論
]]>
(轉載)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 發表評論
]]>
簡述藍牙協議棧-完整版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         項目剛好做到藍牙了,也不是很忙,講講自己最近一段時間做的東西。

         提到協議棧,都會想到與開放式系統互聯(OSI)協議棧的 ,OSI協議棧定義了廠商們如何才能生產可以與其它廠商的產品一起工作的產品。協議棧是指一組協議的集合,舉個例子,把大象裝到冰箱里,總共要3步。每步就是一個協議,3步組成一個協議棧。把應用層數據包發出去,也要好幾步,TCP/UDP頭,IP頭,ether頭,每步也是一個協議。另外每層都有一些特殊的協議。所有這些統稱協議棧。
        
         簡單的來說,藍牙協議棧就是SIG(Special Intersted Group)定義的一組協議的規范,目標是允許遵循規范的藍牙應用應用能夠進行相互間操作,圖1-1就是完整的藍牙協議棧和部分profile:

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

         在這里我想詳細介紹下已經實現了r的協議棧。

  1. Widcomm:  第一個windows上的協議棧,由Widcomm公司開發,也就是現在的Broadcom .
  2. Microsoft Windows stack: Windows XP SP2中包括了這個內建的協議棧,開發者也可以調用其API開發第三方軟件。
  3. Toshiba stack: 它也是基于Windows的,不支持第三方開發,但它把協議棧授權給一些laptop商(sony, asus等,我的本本上就是Toshiba的)。它支持的Profile有: SPP, DUN, FAX, LAP, OPP, FTP, HID, HCRP, PAN, BIP, HSP, HFP , A2DP, AVRCP, GAVDP
  4. BlueSoleil: 著名的IVT公司的產品,這個應該是個中國公司,值得自豪。該產品可以用于桌面和嵌入式,他也支持第三方開發,DUN, FAX, HFP, HSP, LAP, OBEX, OPP, PAN SPP, AV, BIP, FTP, GAP, HID, SDAP, and SYNC。
  5. Bluez: Linux官方協議棧,該協議棧的上層用Socket封裝,便于開發者使用,通過DBUS與其它應用程序通信。那么最近我的工作就是移植bluez 4.x到板子上。
  6.  Affix: NOKIA公司的協議棧,在Symbian系統上運行,具體的沒找到資料
  7. BlueDragon:東軟公司產品,值得驕傲,好像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,但到現在我沒怎么聽過這個協議棧的應用,難得是個爛尾樓??
  8. BlueMagic:美國Open Interface 公司for portable embedded divce的協議棧,iphone(apple),nav-u(sony)等很多電子產品都用該商業的協議棧,BlueMagic 3.0是第一個通過bluetooth 協議棧1.1認證的協議棧,那么我現在就在用它,那么該棧用起來簡單,API清晰明了。實現了的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的協議棧,同時他也提供了一些上層應用的Profile的庫,當然了它也是為嵌入式產品了,支持的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開發的協議棧,但是windows ce本身也支持其它的協議棧
  11. BlueLet:IVT公司for embedded product的清量級協議棧。

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

         時間有限,簡單的寫了下,如果各位網友知道一些協議棧的動態,或對我寫的有補充,請給我留言,我會及時改正,



    攀升 2008-12-14 11:50 發表評論
    ]]>
    (轉載)怎么源碼安裝 PKG_CONFIG_PATH設置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 發表評論
    ]]>
    多國語言惹得禍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        上面下指令說要做多國語言版的程序,這可是個不小的改動呀。于是我就拿當前的程序運行,一系列的問題隨之出現了。
            話分兩頭,先說跑在Windows上的,最基本的就是讀各種語言的文件名,正常英文還是正常的,但遇到俄文,法文,德文時就出現問題,正常的字符都變成了"?",跟蹤內存發現讀入內存的字符已經變成了3f00,也就是"?"的unicode,可見是讀入目錄到內存的函數出了問題,如下代碼:
       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等讀寬字符的操作函數,最終解決問題,但我沒有松氣,因為程序主要是運行在linux中的,Linux真不知道怎么整了。
            Linux上讀多國語言的文件和目錄就需要對Linux系統深入了解,因為我要讀的文件是usb上的文件,所以得先掛載到一個目錄,
            mount -t vfat /dev/sda1 /mnt/usb,然后readdir讀入文件,與Windows上同樣的錯,讀入的是"?",我想和windows一樣去找一個類似wreaddir,但是沒有。于是應該從掛載著手,目前在NTFS和FAT32/VFAT下的文件系統上都使用了Unicode,這就需要系統在讀取這些文件名時動態將其轉換為相應的語言編碼,也就是說掛載的時候要把usb上的編碼轉化成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 發表評論
    ]]>
    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    說起來都不好意思,今天搞了半天,最后發現竟然是vss上check out的文件沒有dos2unix,無語,其實主要是沒有認真考慮整個程序的運行全過程,僅在看代碼,向錦錦一樣。

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

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

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

     

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





    攀升 2006-11-10 13:15 發表評論
    ]]>
    伊人久久大香线蕉综合Av| 久久99国产精品久久99果冻传媒| 久久久久国产| 2020国产成人久久精品| 97久久超碰国产精品旧版| 久久久久亚洲精品男人的天堂 | 久久av免费天堂小草播放| 久久夜色撩人精品国产小说| 日本久久久久亚洲中字幕| 婷婷久久综合九色综合绿巨人 | 国产精品久久久久天天影视| 久久久亚洲AV波多野结衣| 久久精品国产黑森林| 久久99国产综合精品女同| 久久久久久国产a免费观看黄色大片| WWW婷婷AV久久久影片| 热综合一本伊人久久精品| 99久久国产免费福利| 狠狠色丁香婷婷综合久久来| 午夜精品久久久久久99热| 一本色道久久88综合日韩精品| 91精品免费久久久久久久久| 99久久99久久久精品齐齐| 欧洲精品久久久av无码电影| 亚洲精品乱码久久久久久按摩| 欧美成a人片免费看久久| 久久久久无码精品| 精品视频久久久久| 久久亚洲精品无码播放| 热综合一本伊人久久精品| 欧美一级久久久久久久大| 久久99精品国产麻豆蜜芽| 国产成人无码精品久久久久免费 | 久久国产欧美日韩精品| 无码专区久久综合久中文字幕| 97精品依人久久久大香线蕉97 | 亚洲综合伊人久久大杳蕉| 久久精品桃花综合| 久久夜色精品国产噜噜麻豆| 国产亚洲美女精品久久久2020| 日产精品99久久久久久|