Windows下C語言網絡編程快速入門

  C語言的學習,一般的方式是,先學C,然後是C++,最好還要有彙編語言和微機原理基礎,然後才是Visual C++。這樣的方式,對學習者來說,要花費很多時間和耐力。而在學校教學中,也沒有時間深入學習Windows編程的實用技術了。
  其實,具有了C語言基礎後,再有一些基本的C++類的概念,就可以直接學習Windows C編程了。
  一、走近Windows C語言
  很多語言都把顯示一個「Hello,World!」做爲第一個入門程序, C語言的第一個程序是這樣的:
  #include<stdio.h>
  main()
  {
  PRintf(「Hello,World!」);
  }
  假如把main函數寫成帶參數的main函數,應該是:
  #include<stdio.h>
  main(int arge,char *argv[])
  {
  printf(「Hello,World!」);
  }
  Windows C的第一個程序和這個程序在形式和原理上都是一致的,只是有兩點不同:
  1. 主函數接收的形參不只是命令行中的字符串的個數和字符串的首地址。
  2. C語言的很多函數在Windows C中都可以繼續使用,但象printf()屏幕顯示等函數就不能繼續使用了。因爲Windows是多任務操作系統,屏幕已不再爲某一個應用程序所獨有,Windows C應用程序要顯示字符串,需要使用Windows提供的API函數,開自己的窗口
  下面是一個最簡單的,顯示「Hello,World!」的Windows C程序:
  #include<windows.h>
  APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
  LPSTR lpCmdLine,int nCmdShow)
  {
  MessageBox(NULL,"Hello,World!","第一個Windows C程序",MB_OKMB_ICONASTERISK);
  }
  主函數的形參有四個:
  1) Hinstance:接收程序運行時當前實例的句柄;
  2) HprivInstance:前一個實例的句柄;
  3) LpCmdLine:程序命令行指針;
  4) NcmdShow:一個用來指定窗口顯示方式的整數。
  這幾個參數的使用我們會在深入的學習中介紹的。
  顯示Hello,Word!字符串,我們使用了一個MessageBox函數,這個函數會在屏幕上顯示一個對話框,它的原型是:
  int MessageBox(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UNIT uType)
  四個參數分別是:
  1) HWnd:父窗口的句柄;
  2) LpText:要顯示字符串的指針;
  3) LpCaption:對話框標題字符串的指針;
  4) UType:顯示在對話框上的小圖標的類型。
  使用這個函數要包含windows.h頭文件。
  調試一下,怎麽樣?窗口上彈出了一個「第一個Windows C程序」對話框,上面有一行字:「Hello,World!」。
  世界真的很美好啊!!
  深入編程:
  在C語言中,函數的聲明,假如沒有指明返回值類型,缺省值爲void,這個程序的主函數就沒有返回值。不過,在Windows編程時,我們最好養成個好習慣,指明函數的返回值類型,因爲在C++中,函數返回值類型是不可以缺省的。而我們在Windows C編程時,還是會用到C++的一些概念,這樣做,有利于以後深入地學習。
  規範一點的程序應該是這樣的:
  #include<windows.h>
  int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
  LPSTR lpCmdLine,int nCmdShow)
  {
  MessageBox(NULL,"Hello,World!","第一個Windows C程序",MB_OKMB_ICONASTERISK);
  return 0;
  }
  這裏,我們聲明的類型爲int型,並且返回一個值0,這樣的函數就可以使用在複雜一點的函數調用中了。
  在這一節中,我們有幾處都提到了句柄的概念,句柄和指針的概念不同,它是作爲操作系統內部索引表中的一個值來使用的,這樣可以防止應用程序直接訪問名對象的內部結構,體現了Windows資源治理的優越性。譬如說,一個窗口找開之後,好對應內存中的一個內存塊,這個窗口所在的內存快地址往往會由操作系統做動態的調整,但其卻不會隨之變化。不過,通過它可以訪問這個窗口,所以在使用的時候,可以把它當做指針一樣看待。
  二、 獲取本地計算機的主機名和ip地址
  和C語言一樣,函數是Windows C編程的最基本的單位。不過,Windows C主要使用API函數,而網絡編程則主要使用Winsock提供的API函數。
  Winsock是90年代初,爲了方便網絡編程,由Microsoft聯合了其他幾家公司共同制定的一套WINDOWS下的網絡編程接口,它是通過C語言的動態鏈接庫方式提供給用戶及軟件開發者的,主要由winsock.h頭文件和動態鏈接庫winsock.dll組成,目前有兩個版本:Winsock1.1和Winsock2.0。
  
  
  
  在Win32平台上,訪問衆多的基層網絡協議,Winsock是首選接口。
  用Visual C++6.0編譯Windows C程序,使用Winsock API函數時,首先要把wsock32.lib添加到它的庫模塊中,否剛在鏈接的時候,會出現「error LNK2001」錯誤。添加wsock32.lib的具體步驟是:打開工程菜單,選擇設置,在彈出的Project settings對話框中,點擊link選項卡,然後在對象/庫模塊文本框中添加wsock32.lib。
  最簡單的網絡編程是獲取本機的主機名和IP地址,這個程序使用了WSAStart()、WSAClenaup()、gethostname()、gethostbyname()四個winsock API函數,這四個函數的功能和使用方法介紹如下:
  1. WSAStartup():
  【函數原型】
  int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);
  【使用說明】
  每一個使用winsock的應用程序,都必須進行WSAStart函數調用,並且只有在調用成功之後才能使用其它的winsock網絡操作函數。
  WVersionRequired:<輸入>表示欲使用的Winsock版本,這是一個WORD類型的整數,它的高位字節定義的是次版本號,低位字節定義的是主版本號。
  LpWSAData:<輸出>是一個指向WSADATA資料的指針。這個資料我們一般不使用。
  返回值:調用成功返回0;否則,返回出錯信息。
  2. WSAClenaup():
  【函數原型】
  int PASCAL FAR WSACleanup(void);
  【使用說明】
  winsock使用後,要調用WSACleanup函數關閉網絡設備,以便釋放其占用的資源。
  3.gethostname()
  【函數原型】
  int PASCAL FAR gethostname (char FAR * name, int namelen);
  【使用說明】
  該函數可以獲取本地主機的主機名,其中:
  name:<輸出>用于指向所獲取的主機名的緩沖區的指針。
  Namelen:<輸入>緩沖區的大小,以字節爲單位。
  返回值:若無錯誤,返回0;否則,返回錯誤代嗎。
  4.gethostbyname()
  【函數原型】
  strUCt hostent FAR * PASCAL FAR gethostbyname(const char FAR * name);
  【使用說明】
  該函數可以從主機名數據庫中得到對應的「主機」。
  該函數唯一的參數name就是前面調用函數gethostname()得到的主機名。若無錯誤,剛返回一個指向hostent結構的批針,它可以標識一個「主機」列表。
  Hostent結構定義如下:
  Struct hostent
  {
  char FAR * h_name;
  char FAR FAR ** h_aliases;
  short h_addrtype;
  char FAR FAR ** h_addr_list;
  }
  其中:
  h_name:<輸入>主機名地址(PC)。
  h_aliases:一個由主機備用名組成的空中止數組。
  H_addrtype:返回地址的類型,對于Winsock,這個域總是PF_INET。
  H_lenth:每個地址的長度(字節數),對應于PF_INET域應該爲4。
  H_addr_list:應該以空指針結尾的主機地址的列表,返回的地址是以網絡順序排列的。
  其中,h_addr_list[0]存放的就是本地主機的4個字節的IP地址,即:
  h_addr_list[0][0].h_addr_list[0][1].h_addr_list[0][2].h_addr_list[0][3]
  一個簡單的用消息框顯示主機名和IP地址的源程序如下:
  #include<winsock.h>
  int WSA_return;
  WSADATA WSAData;
  HOSTENT *host_entry;
  char host_name[256];
  char host_address[256];
  int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
  LPSTR lpCmdLine,int nCmdShow)
  {
  WSA_return=WSAStartup(0x0101,&WSAData);
  if(WSA_return==0)
  {
  gethostname(host_name,256);
  host_entry=gethostbyname(host_name);
  if(host_entry!=0)
  {
  wsprintf(host_address,"%d.%d.%d.%d",
  (host_entry->h_addr_list[0][0]&0x00ff),
  (host_entry->h_addr_list[0][1]&0x00ff),
  (host_entry->h_addr_list[0][2]&0x00ff),
  (host_entry->h_addr_list[0][3]&0x00ff));
  MessageBox(NULL,host_address,host_name,MB_OK);
  
  
   }
  }
  WSACleanup();
  return 0;
  }
  深入編程:
  前面顯示IP地址的時候,我們使用的是消息框,規範一點的編程應該使用對話框,如何編輯一個對話框,很多書中都有介紹,編輯的對話框可參考圖5的運行界面。
  頭文件Get_IP.h如下:
  BOOL APIENTRY Hostname_ipDlgPro(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam);
  這個程序只使用了一個對話框過程,一般把這個過程的聲明放在頭文件中。
  源程序Get_IP.c:
  #include<winsock2.h>
  #include"Get_IP.h"
  #include"resource.h" //這個頭文件在創建資源的時候會自動生成,
  //並會在插入資源時自動生成控件標識號.
  int WSA_return;
  WSADATA WSAData;
  HOSTENT *host_entry;
  char host_name[256];
  char host_address[256];
  int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
  LPSTR lpCmdLine,int nCmdShow)
  {
  WSA_return=WSAStartup(0x0101,&WSAData);
  if(WSA_return==0)
  {
  gethostname(host_name,256);
  host_entry=gethostbyname(host_name);
  if(host_entry!=0)
  {
  wsprintf(host_address,"%d.%d.%d.%d",
  (host_entry->h_addr_list[0][0]&0x00ff),
  (host_entry->h_addr_list[0][1]&0x00ff),
  (host_entry->h_addr_list[0][2]&0x00ff),
  (host_entry->h_addr_list[0][3]&0x00ff));
  }
  }
  WSACleanup();
  DialogBox(hInstance,"DIALOG1",NULL,(DLGPROC)Hostname_ipDlgPro);
  return 0;
  }
  BOOL APIENTRY Hostname_ipDlgPro(HWND hDlg,UINT message,
  WPARAM wParam,LPARAM lParam)
  {
  switch(message)
  {
  case WM_INITDIALOG:
  return(TRUE);
  case WM_COMMAND:
  if(LOWORD(wParam)==IDOK)
  {
  SetDlgItemText(hDlg,IDC_EDIT1,host_name);
  SetDlgItemText(hDlg,IDC_EDIT2,host_address);
  SetDlgItemText(hDlg,IDCANCEL,"確定");
  }
  if(LOWORD(wParam)==IDCANCEL)
  EndDialog(hDlg,TRUE);
  return(TRUE);
  break;
  }
  return(FALSE);
  }
  三、利用VisualC++6.0編譯Windows C程序
  利用Visual C++6.0編譯Windows C程序一般要經過以下四個步驟:新建項目、添加代碼、添加資源和編譯鏈接。下面我們簡單地介紹一下程序上面介紹的規範的獲取本機的主機名和IP地址程序的編譯過程:
  (一) 新建項目
  1.啓動MicrosoftVisualC++,然後在【文件】菜單中先擇【新建】命令,彈出如圖1所示的【新建】對話框:
  
Windows下C語言網絡編程快速入門

  點擊查看大圖
  圖1
  2.在【新建】對話框中,系統打開的是默認的【工程】選項卡,【工程】選項卡左側的列表框中有多種建立工程的方式,我們選中「Win32 application」選項。
  3. 在【位置】文本框中輸入新建工程的路徑(例如:F:\),在【工程】文本框中輸入工程名稱(例如:Get_IP)。
  4. 選中【平台】列表框中的Win32複選框,然後單擊【確定】按鈕。
  5. 在隨後的對話框中,都選擇默認設置,完成後,進入圖2示界面:
  
Windows下C語言網絡編程快速入門

  點擊查看大圖
  圖2
  (二) 添加代碼
  在VisualC++6.0中,源代碼一般存放在源代碼文件和頭文件中,往項目中添加源代碼是非常方便的,爲項目新建一個源代碼文件一般要按下述方法操作:
  1. 選擇【工程】【添加工程】【新建】選項,彈出圖3所示【新建】對話框:
  
Windows下C語言網絡編程快速入門

  點擊查看大圖
  圖3
  2. 在對話框的【文件】選項卡中,左側的列表框選中「C++ Source File」選項,右側選中【添加工程】複選框,並在【文件】文本框中輸入源文件名(例如:Get_IP.c)。
  
  
  
  3. 單擊【確定】按鈕,【新建】對話框將被閉,用戶就可以在新建的Get_IP.c中輸入程序的源代碼了。
  4. 添加頭文件Get_IP.h的方法和上面所述過程一樣,只是在【文件】選項卡中,左側的列表框要先中「C/C++ Header File」選項。在【文件】文本框中輸入頭文件名(例如:Get_IP.h)。
  (三) 添加資源
  在添加資源前,必須在項目中先添加一個資源文件,然後可利用Visual C++6.0提供的資源編輯器爲項目新建一個資源,具體步驟如下:
  1. 選擇【工程】【添加工程】【新建】選項,彈出圖3所示【新建】對話框。
  2. 在對話框的【文件】選項卡中,左側的列表框選中「Rsource Script」選項,右側選中【添加工程】複選框,並在【文件】文本框中輸入資源文件名(例如:Get_IP.rc)。
  3. 單擊確定,回到主窗口後,選擇【插入】【資源】選項,打開【插入資源】對話框,如圖4所示, 在【資源類型】列表框中選中「Dialog」選項,單擊【新建】按鈕,返回主窗口後,即可利用對話框編輯器進行編輯了。編輯後的對話框如圖
  
Windows下C語言網絡編程快速入門

  圖4
  
Windows下C語言網絡編程快速入門

  圖5
  (四) 編譯鏈接
  在添加了源代碼與資源文件後,就可以對程序編譯連接了,可按Ctrl+F7鍵編譯,按F7鍵連接,按Ctrl+F5鍵運行程序。在連接前是要注重,資源文件Get_IP.rc也要進行編譯。
  由于這個程序引用了Winsock API函數,在編譯連接前,還要添加wsock32.dll,具體方法前面已經介紹過,這裏就不再贅述了。
  一點看法:
  利用C語言編寫Windows應用程序有兩種方式:一種是Windows C編程方式,另一種是Visual C++編程方式。在一般情況下,Visual C++編程方式編寫的程序源代碼量小、開發時的工作量小、工作難度也較小,但編譯後的代碼量較大,運行速度略低;而Windows C編程方式編寫的程序源代碼量雖然較大,但可執行代碼效率高。隨著技術的進步,Visual C++編程方式已被廣泛采用,但象網絡編程等一些對速度要求高、對硬件操作較多的程序,大多數還是用Windows C編程方式開發的。另外,學習Windows C程序設計,還有助于更深入地了解Windows的內幕和Windows API。
  從教學角度講,在學生具備了C語言和其它一些前導課程基礎後,直接進入Windows C網絡編程等實用編程技術課程,不僅可以讓學生盡早地接觸到前沿的實用編程技術,而且還可以極大地調動學生的學習積極性,在有限的時間裏,學到更多的知識和技術。