網絡編程

OSI(Open System Interconnection)參考模型將網絡的不同功能劃分爲7層。
應用層  ---》處理網絡應用---》遠程登錄協議Telnet、文件傳輸協議FTP、 超文本傳輸協議HTTP、域名服務DNS、簡單郵件傳輸協議SMTP、郵局協議POP3等。
表示層  ---》數據表示
會話層  ---》主機間通信
傳輸層  ---》端到端的連接---》傳輸控制協議TCP、用戶數據報協議UDP。TCP:面向連接的可靠的傳輸協議。 UDP:是無連接的,不可靠的傳輸協議。
網絡層  ---》尋址和最短路徑---》網際協議IP、Internet互聯網控制報文協議ICMP、Internet組管理協議IGMP。
數據鏈路層  ---》介質訪問(接入)
物理層  ---》二進制傳輸

OSI參考模型中,對等層協議之間交換的信息單元統稱爲協議數據單元(PDU,Protocol Data Unit)。

TCP/IP模型包括4個層次:
應用層----類似於會話層 、表示層 、應用層
傳輸層
網絡層
網絡接口----類似於數據鏈路層、物理層


端口
按照OSI七層模型的描述,傳輸層提供進程(應用程序)通信的能力。爲 了標識通信實體中進行通信的進程(應用程序),TCP/IP協議提出了協議端口(protocol port,簡稱端口)的概念


端口是一種抽象的軟件結構(包括一些數據結構和I/O緩衝區)。應用程序通過系統調用與某端口建立連接(binding)後,傳輸層傳給該端口的數據都被相應的進程所接收,相應進程發給傳

輸層的數據都通過該端口輸出。
端口用一個整數型標識符來表示,即端口號。端口號跟協議相關,TCP/IP傳輸層的兩個協議TCP和UDP是完全獨立的兩個軟件模塊,因此各自的端口號也相互獨立。
端口使用一個16位的數字來表示,它的範圍是0~65535,1024以下的端口號保留給預定義的服務。例如:http使用80端口。

SOCKET的引入
爲了能夠方便的開發網絡應用軟件,由美國伯克利大學在Unix上推出了一種應用程序訪問通信協議的操作系統調用socket(套接字)。socket的出現,使程序員可以很方便地訪問TCP/IP,從而

開發各種網絡應用的程序。
隨着Unix的應用推廣,套接字在編寫網絡軟件中得到了極大的普及。後來,套接字又被引進了Windows等操作系統,成爲開發網絡應用程序的非常有效快捷的工具。
套接字存在於通信區域中。通信區域也叫地址族,它是一個抽象的概念,主要用於將通過套接字通信的進程的共有特性綜合在一起。套接字通常只與同一區域的套接字交換數據(也有可能跨

區域通信,但這隻在執行了某種轉換進程後才能實現)。Windows Sockets只支持一個通信區域:網際域( AF_INET),這個域被使用網際協議簇通信的進程使用。

網絡字節的順序
不同的計算機存放多字節值的順序不同,有的機器在起始地址存放低位字節(低位先存),有的機器在起始地址存放高位字節(高位先存)。基於Intel的CPU,即我們常用的PC機採用的是低位先

存。爲保證數據的正確性,在網絡協議中需要指定網絡字節順序。TCP/IP協議使用16位整數和32位整數的高位先存格式。


套接字的類型
流式套接字(SOCK_STREAM)
 提供面向連接、可靠的數據傳輸服務,數據無差錯、無重複的發送,且按發送順序接收。
數據報式套接字(SOCK_DGRAM)
 提供無連接服務。數據包以獨立包形式發送,不提供無錯保證,數據可能丟失或重複,並且接收順序混亂。
原始套接字(SOCK_RAW)。


基於TCP的SOCKET編程

服務器端程序:
1、創建套接字(socket)。 
2、將套接字綁定到一個本地地址和端口上(bind)。
3、將套接字設爲監聽模式,準備接收客戶請求(listen)。
4、等待客戶請求到來;當請求到來後,接受連接請求,返回一個新的對應於此次連接的套接字(accept)。
5、用返回的套接字和客戶端進行通信(send/recv)。
6、返回,等待另一客戶請求。
7、關閉套接字。


客戶端程序:
1、創建套接字(socket)。 
2、向服務器發出連接請求(connect)。
3、和服務器端進行通信(send/recv)。
4、關閉套接字。

基於UDP的SOCKET編程

服務器端(接收端)程序:
1、創建套接字(socket)。 
2、將套接字綁定到一個本地地址和端口上(bind)。
3、等待接收數據(recvfrom)。
4、關閉套接字。

客戶端(發送端)程序:
1、創建套接字(socket)。 
2、向服務器發送數據(sendto)。
3、關閉套接字。

int WSAStartup(
  WORD wVersionRequested,----》參數用於指定準備加載的Winsock庫的版本。高位字節指定所需要的Winsock庫的副版本,而低位字節則是主版本。可用MAKEWORD(x,y)(其中,x是高位字節

,y是低位字節)方便地獲得wVersionRequested的正確值。
  LPWSADATA lpWSAData----》參數是指向WSADATA結構的指針,WSAStartup用其加載的庫版本有關的信息填在這個結構中。
); ----》1.加載套接字庫2.進行套接字庫的版本協商即確定使用哪一個版本的套接字庫


WSADATA結構定義如下:
typedef struct WSAData {
  WORD wVersion;
  WORD wHighVersion;
  char szDescription[WSADESCRIPTION_LEN+1];
  char szSystemStatus[WSASYS_STATUS_LEN+1];
  unsigned short iMaxSockets;
  unsigned short iMaxUdpDg;
  char FAR * lpVendorInfo;
} WSADATA, *LPWSADATA;
              WSAStartup把第一個字段wVersion設成打算使用的Winsock版本。wHighVersion 參數容納的是現有的Winsock庫的最高版本。記住,這兩個字段中,高位字節代表的是Winsock

副版本,而低位字節代表的則是Winsock主版本。szDescription和szSystemStatus這兩個字段由特定的Winsock實施方案設定,事實上沒有用。不要使用下面這兩個字段:iMaxSockets和

iMaxUdpDg,它們是假定同時最多可打開多少套接字和數據報的最大長度。然而,要知道數據報的最大長度應該通過WSAEnumProtocols來查詢協議信息。同時最多可打開套接字的數目不是固

定的,很大程度上和可用物理內存的多少有關。最後,lpVendorInfo字段是爲Winsock實施方案有關的指定廠商信息預留的。任何一個Win32平臺上都沒有使用這個字段。
               如果WinSock.dll或底層網絡子系統沒有被正確初始化或沒有被找到,WSAStartup將返回WSASYSNOTREADY。此外這個函數允許你的應用程序協商使用某種版本的WinSock規範

,如果請求的版本等於或高於DLL所支持的最低版本,WSAData的wVersion成員中將包含你的應用程序應該使用的版本,它是DLL所支持的最高版本與請求版本中較小的那個。反之,如果請求

的版本低於DLL所支持的最低版本,WSAStartup將返回WSAVERNOTSUPPORTED。關於WSAStartup更詳細的信息,請查閱MSDN中的相關部分。
              對於每一個WSAStartup的成功調用(成功加載WinSock DLL後),在最後都對應一個WSACleanUp調用,以便釋放爲該應用程序分配的資源。


SOCKET socket( int af, int type, int protocol );
該函數接收三個參數。
第一個參數af指定地址族,對於TCP/IP協議的套接字,它只能是AF_INET(也可寫成PF_INET)。
第二個參數指定Socket類型,對於1.1版本的Socket,它只支持兩種類型的套接字,SOCK_STREAM指定產生流式套接字,SOCK_DGRAM產生數據報套接字。
第三個參數是與特定的地址家族相關的協議,如果指定爲0,那麼它就會根據地址格式和套接字類別,自動爲你選擇一個合適的協議。這是推薦使用的一種選擇協議的方法。
如果這個函數調用成功,它將返回一個新的SOCKET數據類型的套接字描述符。如果調用失敗,這個函數就會返回一個INVALID_SOCKET,錯誤信息可以通過WSAGetLastError函數返回。


   int bind( SOCKET s, const struct sockaddr FAR *name, int namelen );
這個函數接收三個參數。
第一個參數s指定要綁定的套接字,
第二個參數指定了該套接字的本地地址信息,是指向sockaddr結構的指針變量,由於該地址結構是爲所有的地址家族準備的,這個結構可能(通常會)隨所使用的網絡協議不同而不同,所以

,要用第三個參數指定該地址結構的長度。 sockaddr結構定義如下:
    struct sockaddr {
      u_short sa_family;
      char sa_data[14];
    };
sockaddr的第一個字段sa_family指定該地址家族,在這裏必須設爲AF_INET。
sa_data僅僅是表示要求一塊內存分配區,起到佔位的作用,該區域中指定與協議相關的具體地址信息。由於實際要求的只是內存區,所以對於不同的協議家族,用不同的結構來替換

sockaddr。除了sa_family外,sockaddr是按網絡字節順序表示的。在TCP/IP中,我們可以用sockaddr_in結構替換sockaddr,以方便我們填寫地址信息。

sockaddr_in的定義如下:
  struct sockaddr_in{
  short sin_family;
  unsigned short sin_port;
  struct   in_addr sin_addr;
  char sin_zero[8];
};
  sin_family表示地址族,對於IP地址,sin_family成員將一直是AF_INET。
 成員sin_port指定的是將要分配給套接字的端口。
成員sin_addr給出的是套接字的主機IP地址。
成員sin_zero只是一個填充數,以使sockaddr_in結構和sockaddr結構的長度一樣。如果這個函數調用成功,它將返回0。如果調用失敗,這個函數就會返回一個SOCKET_ERROR,錯誤信息可以

通過WSAGetLastError函數返回。

             將IP地址指定爲INADDR_ANY,允許套接字向任何分配給本地機器的IP地址發送或接收數據。多數情況下,每個機器只有一個IP地址,但有的機器可能會有多個網卡,每個網卡

都可以有自己的IP地址,用INADDR_ANY可以簡化應用程序的編寫。將地址指定爲INADDR_ANY,允許一個獨立應用接受發自多個接口的迴應。如果我們只想讓套接字使用多個IP中的一個地址,

就必須指定實際地址,要做到這一點,可以用inet_addr()函數,這個函數需要一個字符串作爲其參數,該字符串指定了以點分十進制格式表示的IP地址(如192.168.0.16)。而且inet_addr()

函數會返回一個適合分配給S_addr的u_long類型的數值。inet_ntoa()函數會完成相反的轉換,它接受一個in_addr結構體類型的參數並返回一個以點分十進制格式表示的IP地址字符串。

u_long htonl(
  u_long hostlong 
);----》將一個主機字節序轉換爲一個TCP/IP字節序


u_short htons(
  u_short hostshort 
);---》和上述類似


int listen(
  SOCKET s,   
  int backlog  ---》可連接隊列的最大請求數目
);


SOCKET accept(
  SOCKET s,
  struct sockaddr FAR *addr,
  int FAR *addrlen
);

int send(
  SOCKET s,             
  const char FAR *buf, 
  int len,              
  int flags             
);


int recv(
  SOCKET s,      
  char FAR *buf, 
  int len,       
  int flags      
);

在Project Settings -》Link-》Object/library modules 添加 ws2_32.lib
TCP服務端:
#include <Winsock2.h>
#include <stdio.h>

void main()
{
 WORD wVersionRequested;
 WSADATA wsaData;
 int err;
 
 wVersionRequested = MAKEWORD( 1, 1 );
 
 err = WSAStartup( wVersionRequested, &wsaData );
 if ( err != 0 ) {
  return;
 }
 

 if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
  WSACleanup( );
  return;
 }
 SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);

 SOCKADDR_IN addrSrv;
 addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
 addrSrv.sin_family=AF_INET;
 addrSrv.sin_port=htons(6000);

 bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

 listen(sockSrv,5);

 SOCKADDR_IN addrClient;
 int len=sizeof(SOCKADDR);

 while(1)
 {
  SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
  char sendBuf[100];
  sprintf(sendBuf,"Welcome %s to http://www.sunxin.org",
   inet_ntoa(addrClient.sin_addr));
  send(sockConn,sendBuf,strlen(sendBuf)+1,0);
  char recvBuf[100];
  recv(sockConn,recvBuf,100,0);
  printf("%s/n",recvBuf);
  closesocket(sockConn);
 }
}

TCP客戶端:

int connect(
  SOCKET s,                         
  const struct sockaddr FAR *name, 
  int namelen                       
);

#include <Winsock2.h>
#include <stdio.h>

void main()
{
 WORD wVersionRequested;
 WSADATA wsaData;
 int err;
 
 wVersionRequested = MAKEWORD( 1, 1 );
 
 err = WSAStartup( wVersionRequested, &wsaData );
 if ( err != 0 ) {
  return;
 }
 

 if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
  WSACleanup( );
  return;
 }
 SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);

 SOCKADDR_IN addrSrv;
 addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
 addrSrv.sin_family=AF_INET;
 addrSrv.sin_port=htons(6000);
 connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

 char recvBuf[100];
 recv(sockClient,recvBuf,100,0);
 printf("%s/n",recvBuf);
 send(sockClient,"This is lisi",strlen("This is lisi")+1,0);

 closesocket(sockClient);
 WSACleanup();
}

 


基於UDP的SOCKET
服務端
int recvfrom(
  SOCKET s,                  
  char FAR* buf,             
  int len,                   
  int flags,                 
  struct sockaddr FAR *from, 
  int FAR *fromlen           
);
#include <Winsock2.h>
#include <stdio.h>

void main()
{
 WORD wVersionRequested;
 WSADATA wsaData;
 int err;
 
 wVersionRequested = MAKEWORD( 1, 1 );
 
 err = WSAStartup( wVersionRequested, &wsaData );
 if ( err != 0 ) {
  return;
 }
 

 if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
  WSACleanup( );
  return;
 }

 SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
 SOCKADDR_IN addrSrv;
 addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
 addrSrv.sin_family=AF_INET;
 addrSrv.sin_port=htons(6000);

 bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

 SOCKADDR_IN addrClient;
 int len=sizeof(SOCKADDR);
 char recvBuf[100];

 recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
 printf("%s/n",recvBuf);
 closesocket(sockSrv);
 WSACleanup();
}


客戶端
int sendto(
  SOCKET s,                       
  const char FAR *buf,           
  int len,                        
  int flags,                      
  const struct sockaddr FAR *to, 
  int tolen                       
);

#include <Winsock2.h>
#include <stdio.h>

void main()
{
 WORD wVersionRequested;
 WSADATA wsaData;
 int err;
 
 wVersionRequested = MAKEWORD( 1, 1 );
 
 err = WSAStartup( wVersionRequested, &wsaData );
 if ( err != 0 ) {
  return;
 }
 

 if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
  WSACleanup( );
  return;
 }

 SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
 SOCKADDR_IN addrSrv;
 addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
 addrSrv.sin_family=AF_INET;
 addrSrv.sin_port=htons(6000);

 sendto(sockClient,"Hello",strlen("Hello")+1,0,
  (SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
 closesocket(sockClient);
 WSACleanup();
}

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