網絡編程入門建議

幾種網絡編程方式:

ISAPI、CGI、WinInet、Winsock

它們之間的區別:

1)  ISAPI主要是開發基於瀏覽器客戶端與服務器端程序。效率比CGI方式高,而且也擴展了CGI沒有的一些功能。(基於TCP/IP模型中的應用層)

2)  CGI主要是開發基於瀏覽器客戶端與服務器端程序。(基於TCP/IP模型中的應用層)

3)  WinInet主要是開發客戶端程序。(基於TCP/IP模型中的應用層)

4)  Winsock主要是基於socket來開發客戶端與服務器端程序。(基於TCP/IP模型中的各層)要想開發低層協議的程序的話就要了解協議的報文格式。

 

網絡基礎知識:

網絡硬件

數據通訊原理 (詳見http://download.csdn.net/source/1196517)

OSI七層網絡模型與TCP/IP四層網絡模型 (詳見http://bbs.51cto.com/topic/thread-396621.html

網絡原理和協議 (詳見http://www.cnpaf.net/

Winsock

 

網絡編程:

建議,把機械工業出版社出的《Windows網絡編程技術》看N遍後,再利用MFC或者SDK編寫一些小的通信例程,然後編寫較大規模的網絡程序,最後你就明白了網絡編程了!

 

《Windows網絡編程技術》專門討論Windows網絡編程技術,覆蓋Windows 95/98/NT 4/2000/CE平臺。內容包括NetBIOS和Windows重定向器方法、Winsock方法、客戶端遠程訪問服務器方法。本書論述深入淺出、用大量實例詳解了微軟網絡API函數的應用。

《TCP/IP詳解,卷1:協議》是一本完整而詳細的TCP/IP協議指南。描述了屬於每一層的各個協議以及它們如何在不同操作系統中運行。

《網絡通信編程實用案例精選》是一本介紹利用vlsuaIC++進行網絡通信程序開發的書籍。書中精選了大量網絡實例,涵蓋了本地汁算機網絡編程、局域網網絡通信編程、IE編程、網絡通信協議編程、串口通信編程、代理服務器編程和高級網絡通信編程.

RFC文檔目錄:http://oss.org.cn/man/develop/rfc/default.htm

ACE:ACE自適配通信環境(ADAPTIVE   Communication   Environment)是可以自由使用、開放源碼的面向對象框架,在其中實現了許多用於併發通信軟件的核心模式。ACE提供了一組豐富的可複用C++  Wrapper   Facade(包裝外觀)和框架組件,可跨越多種平臺完成常見的通信軟件任務,其中包括:事件多路分離和事件處理器分派、信號處理、服務初始化、進程間通信、共享內存管理、消息路由、分佈式服務動態(重)配置、併發執行和同步,等等。ACE資料參考:http://docs.huihoo.com/ace_tao/index.html

建議在www.codeproject.comhttp://www.codeguru.com/網站上找些老外寫的網絡代碼研究研究,最好能參加實際的網絡項目,這樣能見識更多成熟的網絡類庫。最好能參加實際的網絡項目,這樣能見識更多成熟的網絡類庫。  

 開源網絡封裝庫 : ACE,ICE,asio,cppsocket,netclass,poco,SimpleSocket,socketman,Sockets  開源下載工具  fdm, eMulePlus,eMule  開源FTP  FileZilla  開源服務器  Apache  網遊服務器開源框架  GNE,HawkNL,RakNet,SDL_net

 

網絡協議分析軟件:

Sniffer工具

Wireshark 開源的經典的協議分析工具Wireshark, http://www.wireshark.org/

WPE -------抓包

Ethereal  -------協議分析  SockMon5  -------抓包及錯誤分析

 

Windows網絡編程細節問題:     1. 如果在已經處於 ESTABLISHED狀態下的socket(一般由端口號和標誌符區分)調用closesocket(一般不會立即關閉而經歷TIME_WAIT的過程)後想繼續重用該socket:

    BOOL bReuseaddr=TRUE;

  setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof

(BOOL));

    2. 如果要已經處於連接狀態的soket在調用closesocket後強制關閉,不經歷TIME_WAIT的過程:

    BOOL bDontLinger = FALSE;

    setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

    3.在send(),recv()過程中有時由於網絡狀況等原因,發收不能預期進行,而設置收發時限:

  int nNetTimeout=1000;//1秒

  //發送時限

  setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));

  //接收時限

  setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

    4.在send()的時候,返回的是實際發送出去的字節(同步)或發送到socket緩衝區的字節(異步);系統默認的狀態發送和接收一次爲8688字節(約爲8.5K);在實際的過程中發送數據和接收數據量比較大,可以設置socket緩衝區,而避免了send(),recv()不斷的循環收發:

  // 接收緩衝區

  int nRecvBuf=32*1024;//設置爲32K

  setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

  //發送緩衝區

  int nSendBuf=32*1024;//設置爲32K

  setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

  5. 如果在發送數據的時,希望不經歷由系統緩衝區到socket緩衝區的拷貝而影響程序的性能:

  int nZero=0;

  setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));

  6.同上在recv()完成上述功能(默認情況是將socket緩衝區的內容拷貝到系統緩衝區):

  int nZero=0;

  setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

  7.一般在發送UDP數據報的時候,希望該socket發送的數據具有廣播特性:

  BOOL bBroadcast=TRUE;

  setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

  8.在client連接服務器過程中,如果處於非阻塞模式下的socket在connect()的過程中可以設置connect()延時,直到accpet()被呼叫(本函數設置只有在非阻塞的過程中有顯著的作用,在阻塞的函數調用中作用不大)

  BOOL bConditionalAccept=TRUE;

  setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)

&bConditionalAccept,sizeof(BOOL));

  9.如果在發送數據的過程中(send()沒有完成,還有數據沒發送)而調用了closesocket(),以前我們一般採取的措施是"從容關閉"shutdown(s,SD_BOTH),但是數據是肯定丟失了,如何設置讓程序滿足具體應用的要求(即讓沒發完的數據發送出去後在關閉socket)?

  struct linger {

  u_short l_onoff;

  u_short l_linger;

  };

  linger m_sLinger;

  m_sLinger.l_onoff=1;//(在closesocket()調用,但是還有數據沒發送完畢的時候容許逗留)

  // 如果m_sLinger.l_onoff=0;則功能和B)作用相同;

  m_sLinger.l_linger=5;//(容許逗留的時間爲5秒)

  setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof

(linger));

  注意點:

      A.在設置了逗留延時,用於一個非阻塞的socket是作用不大的,最好不用;

  B.如果想要程序不經歷SO_LINGER需要設置SO_DONTLINGER,或者設置l_onoff=0;

  10.一個用的比較少的是在SDI或者是Dialog的程序中,可以記錄socket的調試信息:

  BOOL bDebug=TRUE;

  setsockopt(s,SOL_SOCKET,SO_DEBUG,(const char*)&bDebug,sizeof(BOOL));

  11.往往通過setsockopt()設置了緩衝區大小,但還不能滿足數據的傳輸需求,一般習慣是自己寫個處理網絡緩衝的類,動態分配內存。

    12、#include <Afxsock.h>,#include<winsock2.h>衝突問題

解決方法:在StdAfx.h 頭文件中添加winsock2.h,Afxsock.h

先#include <winsock2.h> 再#include <Afxsock.h>

    13、獲取數據包,一般來說想獲取數據包可以使用IP_HDRINCL選項,但是在Windows 2000/XP中setsockopt()中IP_HDRINCL是個不合法的選項,但是可以使用 WSAIoctl() 函數調用SIO_RCVALL捕獲IP數據包。簡單步驟如下: 1)、Create a raw socket.  2)、Bind the socket to the local IP over which the traffic is to be sniffed.  3)、WSAIoctl() the socket with SIO_RCVALL to give it sniffing powers.  4)、Put the socket in an infinite loop of recvfrom.  5)、n' joy! the Buffer from recvfrom.

    14、IP、TCP、UDP、ICMP數據包格式 /*The IP header */ typedef struct tagIPHEADER{  unsigned char version:4;  unsigned char header_len:4;  unsigned char tos;  unsigned short total_len;  unsigned short ident;  unsigned short flags;  unsigned char ttl;  unsigned char proto;  unsigned short checksum;  unsigned int sourceIP;  unsigned int destIP; }IPHEADER;

struct TCPPacketHead{  WORD SourPort;  WORD DestPort;  DWORD SeqNo;  DWORD AckNo;  BYTE HLen;  BYTE Flag;  WORD WndSize;  WORD ChkSum;  WORD UrgPtr; };

struct ICMPPacketHead {  BYTE Type;  BYTE Code;  WORD ChkSum; };

struct UDPPacketHead {  WORD SourPort;  WORD DestPort;  WORD Len;  WORD ChkSum; };

 

    15、幾種winsock I/O模型比較: select模型核心就是select函數,它可用於判斷套接字上是否存在數據,或者能否向一個套接字寫入數據。這個函數可以有效地防止應用程序在套接字處於阻塞模式中時,send或recv進入阻塞狀態;同時也可以防止產生大量的WSAEWOULDBLOCK錯誤select的優勢是能夠從單個線程的多個套接字上進行多重連接及I/O。

WSAAsyncSelect 模型是以消息機制爲基礎,能夠處理一定的客戶連接量,但是擴展性也不是很好。因爲消息泵很快就會阻塞,降低了消息處理的速度。WSAAsyncSelect和WSAEventSelect模型提供了讀寫數據能力的異步通知,但他們不提供異步數據傳送,而重疊及完成端口提供異步數據的傳送。

WSAEventSelect 模型以時間爲基礎的網絡事件通知,但是與WSAAsyncSelect不同的是,它主要是由事件對象句柄完成的,而不是通過窗口。但是一個線程只能等待64個事件(需要開闢多個線程解決),伸縮性不如完成端口。

重疊模型可以使程序能達到更佳的系統性能。基本設計原理就是讓應用程序使用重疊的數據結構,一次投遞一個或多個I/O請求。針對這些提交的請求,在他們完成之後,應用程序可爲他們提供服務。它又分爲兩種實現方法:事件通知和完成例程。重疊I/O模型事件通知依賴於等待事件通知的線程數(WSAWaitForMultipleEvents調用的每個線程,該I/O模型一次最多都只能支持6 4個套接字。),處理客戶通信時,大量線程上下文的切換是它們共同的制約因素。

完成端口提供了最好的伸縮性,往往可以使系統達到最好的性能,是處理成千上萬的套接字的首選。從本質上說,完成端口模型要求創建一個windows完成端口對象,該對象通過指定數量的線程,對重疊I/O請求進行管理,以便爲已經完成的重疊I/O請求提供服務。但是完成端口只是支持NT系統、WIN2000系統。

重疊模型和完成端口模型的應用程序通知緩衝區收發系統直接使用數據,也就是說,如果應用程序投遞了一個10KB大小的緩衝區來接收數據,且數據已經到達套接字,則該數據將直接被拷貝到投遞的緩衝區。 而select模型、WSAAsyncSelect 模型、WSAEventSelect 模型,數據到達並拷貝到單套接字接收緩衝區中,此時應用程序會被告知可以讀入的容量。當應用程序調用接收函數之後,數據才從單套接字緩衝區拷貝到應用程序的緩衝區,差別就體現出來了。

 

    16、服務器與客戶端IO模型選擇

對於如何挑選最適合自己應用程序的I/O模型已經很明晰了。同開發一個簡單的運行多線程的鎖定模式應用相比,其他每種I/O模型都需要更爲複雜的編程工作。因此,針對客戶機和服務器應用開發模型的選擇,有以下原則。

1). 客戶端

若打算開發一個客戶機應用,令其同時管理一個或多個套接字,那麼建議採用重疊I/O或WSAEventSelect模型

,以便在一定程度上提升性能。然而,假如開發的是一個以Windows爲基礎的應用程序,要進行窗口消息的管理,那麼WSAAsyncSelect模型恐怕是一種最好的選擇,因爲WSAAsyncSelect本身便是從Windows消息模型借鑑來的。採用這種模型,程序需具備消息處理功能。

2). 服務器端

若開發的是一個服務器應用,要在一個給定的時間,同時控制多個套接字,建議採用重疊I/O模型,這同樣是從性能角度考慮的。但是,如果服務器在任何給定的時間,都會爲大量I/O請求提供服務,便應考慮使用I/O完成端口模型,從而獲得更佳的性能。

    17、shutdown、closesocket區別

shutdown 從容關閉,爲了保證通信雙方都能夠收到應用程序發出的所有數據,一個合格的應用程序的做法是通知接受雙發都不在發送數據!這就是所謂的“正常關閉”套接字的方法,而這個方法就是由shutdown函數,傳遞給它的參數有SD_RECEIVE,SD_SEND,SD_BOTH三種,如果是SD_RECEIVE就表示不允許再對此套接字調用接受函數。這對於協議層沒有影響,另外對於tcp套接字來說,無論數據是在等候接受還是即將抵達,都要重置連接(注意對於udp協議來說,仍然接受並排列傳入的數據,因此udp套接字而言shutdown毫無意義)。如果選擇SE_SEND,則表示不允許再調用發送函數。對於tcp套接字來說,這意味着會在所有數據發送出並得到接受端確認後產生一個FIN包。如果指定SD_BOTH,答案不言而喻。    closesocket 正式關閉,關閉連接,釋放所有相關的資源。因爲無連接協議沒有連接,所以不會有正式關閉和從容關閉,直接調用closesocket函數。

    18、TCP鏈接三次握手、終止鏈接四次握手

 

 

 

 

    19、getpeername 、getsockname

getpeername 函數用於獲得通信方的套接字地址信息,該信息上關於已建立連接的那個套接字的。 getsockname 函數是getpeername的對應函數。它返回的是指定套接字的本地接口的地址信息。

    20、MFC下CSocket編程注意事項

1)、在使用MFC編寫socket程序時,必須要包含<afxsock.h>都文件。 2)、AfxSocketInit() 這個函數,在使用CSocket前一定要先調用該函數,否則使用CSocket會出錯。 3)、CSocket::Create 的接口就是, 實現上還執行了 CSocket::Bind , 非常不容易被發現。如果是以 Create 方法初始化的前提下不能再調用 Bind ,要不一定出錯。一般寫服務器程序都不要用Create 爲好,用下面的

CSocket::Socket 初始化然後Bind。

    21、winsock   有兩個不同的版本

winsock   有兩個不同的版本,第一版很old了,win95時代的,win2000後推崇第二版winsock   2,   出了主板本號外,還有子版本號,這些功能上有差別,winsock2   支持原始套接字編程,   MFC   還封裝了winsock,使用WINSOCK.h   要用到WSOCK32.LIB,   還有一些擴展api功能,需要MSWSOCK.h   MSWSOCK.DLL  。 現在winsock.h   winsock2.h   都用ws2_32.lib。

   22、sockaddr_in , sockaddr , in_addr區別 struct   sockaddr   {                   unsigned   short   sa_family;                      char   sa_data[14];              };     上面是通用的socket地址,具體到Internet   socket,用下面的結構,二者可以進行類型轉換               struct   sockaddr_in   {                   short   int   sin_family;                      unsigned   short   int   sin_port;                      struct   in_addr   sin_addr;                      unsigned   char   sin_zero[8];              };           struct   in_addr就是32位IP地址。           struct   in_addr   {                   union {                         struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;                         struct { u_short s_w1,s_w2; } S_un_w;                         u_long S_addr;                  } S_un;

                #define s_addr  S_un.S_addr         };      inet_addr()是將一個點分制的IP地址(如192.168.0.1)轉換爲上述結構中需要的32位IP地址(0xC0A80001)。

填值的時候使用sockaddr_in結構,而作爲函數(如socket, listen, bind等)的參數傳入的時候轉換成sockaddr結構就行了,畢竟都是16個字符長。

通常的用法是:     int   sockfd;     struct   sockaddr_in   my_addr;     sockfd   =   socket(AF_INET,   SOCK_STREAM,   0);            my_addr.sin_family   =   AF_INET;        my_addr.sin_port   =   htons(MYPORT);        my_addr.sin_addr.s_addr   =   inet_addr("192.168.0.1");         bzero(&(my_addr.sin_zero),   8);             bind(sockfd,   (struct   sockaddr   *)&my_addr,   sizeof(struct   sockaddr)); 想來你是要進行網絡編程,使用socket, listen, bind等函數。你只要記住,填值的時候使用sockaddr_in結構,而作爲函數的參數傳入的時候轉換成sockaddr結構就行了,畢竟都是16個字符長。      23、幾個特殊的地址

INADDR_LOOPBACK   (127.0.0.1)   總是代表經由迴環設備的本地主機;   INADDR_ANY  

(0.0.0.0)   表示任何可綁定的地址;   INADDR_BROADCAST   (255.255.255.255)   表示任何主機。 INADDR_ANY 的具體含義是,綁定到0.0.0.0。此時,對所有的地址都將是有效的,如果系統考慮冗餘,採用多個網卡的話,那麼使用此種bind,將在所有網卡上進行綁定。在這種情況下,你可以收到發送到所有有效地址上數據包。 例如: SOCKADDR_IN Local; Local.sin_addr.s_addr = htonl(INADDR_ANY); 另外一種方式如下: SOCKADDR_IN Local; hostent* thisHost = gethostbyname(""); char* ip = inet_ntoa(*(struct in_addr *)*thisHost->h_addr_list);  Local.sin_addr.s_addr = inet_addr(ip);  在這種方式下,將在系統中當前第一個可用地址上進行綁定。在多網卡的環境下,可能會出問題。

   24、常見協議

FTP協議:http://blog.csdn.net/superman419/archive/2009/04/10/4063476.aspx SMTP協議:http://www.cnpaf.net/class/smtp/ POP3協議:http://www.cnpaf.net/class/pop3/ http://www.yesky.com/20020305/1600243.shtml ICMP協議:http://blog.csdn.net/byxdaz/archive/2007/08/01/1720971.aspx RAS協議:http://blog.ixpub.net/html/94/10181094-31509.html TAPI協議:http://blog.csdn.net/chszs/archive/2008/12/08/3475908.aspx Telnet協議:http://www.cnblogs.com/liuweijian/archive/2005/09/12/235493.html HTTP協議:http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html 代理協議socks:http://blog.csdn.net/byxdaz/archive/2010/03/31/5439291.aspx

 25、爲什麼需要htons(), ntohl(), ntohs(),htons() 函數?  在C/C++寫網絡程序的時候,往往會遇到字節的網絡順序和主機順序的問題。這是就可能用到htons(), ntohl(), ntohs(),htons()這4個函數。

網絡字節順序與本地字節順序之間的轉換函數:

      htonl()--"Host to Network Long"       ntohl()--"Network to Host Long"       htons()--"Host to Network Short"       ntohs()--"Network to Host Short"   

之所以需要這些函數是因爲計算機數據表示存在兩種字節順序:NBO與HBO

網絡字節順序NBO(Network Byte Order):       按從高到低的順序存儲,在網絡上使用統一的網絡字節順序,可以避免兼容性問題。

主機字節順序(HBO,Host Byte Order):       不同的機器HBO不相同,與CPU設計有關,數據的順序是由cpu決定的,而與操作系統無關。  如 Intel   x86結構下,short型數0x1234表示爲34   12, int型數0x12345678表示爲78   56   34   12    如IBM   power PC結構下,short型數0x1234表示爲12   34, int型數0x12345678表示爲12   34   56   78 由於這個原因不同體系結構的機器之間無法通信,所以要轉換成一種約定的數序,也就是網絡字節順序,其實就是如同power   pc那樣的順序 。在PC開發中有ntohl和htonl函數可以用來進行網絡字節和主機字節的轉換。

 

 

26、如何查詢端口被佔用的程序

大家在啓動服務器時,有時正常啓動有時又啓動不了是怎麼回事呢??那爲什麼關掉迅雷等軟件就又好了呢??現在就來給大家講解一下,

 

 

 

 

這些端口如果被其他程序佔用就不能正常啓動,比如有時啓動時會提示WEB啓動失敗,其實就是80端口被佔用了,而迅雷等下載軟件恰恰就是佔用了80端口,關掉就行了。但有時迅雷等都沒有開也啓動不了,那就是別的東西佔用了,那怎麼辦呢?我來叫你查看端口並關掉的方法。 1.在開始--運行   裏面輸入cmd點回車,會出現運行窗口。 2.在提示符後輸入netstat -ano回車,找到tcp 80端口對應的pid,比如1484. 3.ctrl+alt+del打開任務管理器,選進程,這裏有很多正在運行的程序怎麼找?別急點上面的   查看--選擇列--在PID(進程標示符)前面打鉤。好了,下面的進程前面都有了PID號碼。這時上一步找到的PID就有用了,找到1484,比如PEER.EXE什麼的,結束進程吧。這時再開服務器,看WEB可以啓動了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章