【vc】14_網絡編程_socket編程

一、計算機網絡基本知識

  最簡單的網絡程序如圖:編程

  提示:IP地址就至關於一個公司的總機號碼,端口號就至關於分機號碼。在打電話時,撥通總機後,還須要轉到分機上。服務器

(1)協議網絡

    ·爲進行網絡中的數據交換(通訊)而創建的規則、標準或約定(=語義+語法+規則);併發

    ·不一樣層具備各自不一樣的協議;socket

(2)網路的情況ide

    ·多種通訊媒介---有線、無線···函數

    ·不一樣種類的設備---通用、專用···this

    ·不一樣的操做系統---UNIX、Windows···spa

    ·不一樣的應用環境---固定、移動···操作系統

    ·不一樣的業務種類---分時、交互、實時···

    ·寶貴的投資和積累---有形、無形···

    ·用戶業務的延續性---不容許出現大的跌巖起伏;

   他們互相交織,造成了很是複雜的系統應用環境。    

(3)ISO/OSI七層參考模型

    ·物理層:提供二進制傳輸,肯定在通訊信道上如何傳輸比特流;

    ·數據鏈路層:提供介質訪問,增強物理層的傳輸功能,創建一條無差錯的傳輸線路;

    ·網絡層:提供IP尋址和路由(網絡上數據能夠經由多條線路到達目的地,網絡層負責找出最佳的傳輸線路);

    ·傳輸層:爲源端主機到目的端主機提供可靠的數據傳輸服務,隔離網絡的上下層協議,使得網絡應用與下層協議無關;

    ·會話層:在兩個相互通訊的應用進程之間創建、組織和協調其相互之間的通訊;

    ·表示層:處理被傳送數據的表示問題,即信息的語法和語義;若有必要,可以使用一種通用的數據表示格式,在多種數據表示之間進行轉換。例如在日期、貨幣、數值等本地數據表示格式和標準數據表示格式之間進行轉換,還有數據的加解密、壓縮和解壓縮等;

    ·應用層:爲用戶的網絡應用程序提供網絡通訊的服務;

   注意:在進行一次網絡通訊時,每一層爲本次通訊提供本次的服務(通訊實體的對等體之間不容許直接通訊);

      各層之間是嚴格單向依賴;

      上層使用下層提供的服務---Service user;

      下層向上層提供服務---Service provider;

(圖片引用於別處)

  應用層、傳輸層、網絡層各使用的協議

    應用層:Telnet(遠程登陸協議)、FTP(文件傳輸協議)、HTTP(超文本傳輸協議)、DNS(域名服務)、SMTP(簡單郵件傳輸協議)、POP3(郵局協議)等;

    傳輸層:TCP(傳輸控制協議)、UDP(用戶數據報協議);

    網絡層:網際協議IP、Internet互聯網控制報文協議ICMP、Internet組管理協議IGMP;

(4)數據封裝

    ·一臺計算機要想另外一臺計算機發送數據,首先必須將該數據打包,打包的過程成爲封裝(即:在數據前面加上特定的協議頭部);

    ·PDU(協議數據單元):對等層協議之間交換的信息單元的統稱;

    ·頭部含有的數據中含有王城數據傳輸所需的控制信息;

(5)TCP/IP模型

(6)端口

    ·傳輸層提供進程(活動的應用程序)通訊的能力;爲了標識通訊實體中進行通訊的進程(應用程序),TCP/IP協議提出了協議端口(protocol port,簡稱端口)的概念;

    ·端口用一個整數型標識符來表示,即端口號;端口號跟協議相關,TCP/IP傳輸層的兩個協議TCP和UDP是徹底獨立的兩個軟件模塊,所以各自的端口號也相互獨立;

    ·咱們在編寫網絡應用程序時,要爲程序指定1024以上的端口號;1024如下端口號保留給預約義的服務;

(7)套接字的引入

    ·套接字存在於通訊區域中;通訊區域也叫地址族,主要用於將經過套接字通訊的進程的共有特性綜合在一塊兒;

    ·套接字一般只與同一區域的套接字交換數據(也有可能跨區域通訊,但這隻在執行了某種轉換進程後才能實現);

    ·Windows Sockets只支持一個通訊區域:網際域(AF_INET),這個域被使用網際協議簇通訊的進程使用;

(8)網絡字節順序

    ·不一樣的計算機存放多字節的順序不一樣;

    ·基於Inter的CPU,採用的是低位先存。爲保證數據的正確性,在網絡協議中須要指定網絡字節順序,TCP/IP協議使用16位整數和32位整數的高位先存格式;

    ·網絡中不一樣主機間進行通訊時,要同一採用網絡字節順序

(9)客戶機/服務器模式

圖片來自:http://pic002.cnblogs.com/images/2012/387401/2012111509190090.jpg

 

   客戶機/服務器在操做過程當中採用主動請求的方式,首先服務器方要先啓動,並根據請求提供相應的服務:

    ①打開一個通訊通道並告知本地主機,他願意在某一地址可端口上接收客戶請求;

    ②等待客戶請求到達該端口;

    ③接收到重複服務請求,處理請求併發送應答信息。接收到併發起服務請求,要激活一個新的進程(或線程)來處理這個客戶請求。新進程(或線程)處理此客戶請求,並不須要對其餘請求作出應答。服務完成後,關閉此新進程與客戶的通訊連接,並終止;

    ④返回第二步,等待另外一客戶請求;

    ⑤關閉服務器;

  客戶方:

    ①打開一個通訊通道,並鏈接到服務器所在主機的特定端口;

    ②想服務器發送服務請求報文,等待並接收應答;繼續提出請求;

    ③請求結束後關閉通訊通道並終止;

二、Windows Sockets的實現

  Socket是鏈接應用程序與網絡驅動程序的橋樑,Socket在應用程序中建立,經過綁定操做與驅動程序創建關係。此後,應用程序送給Socket的數據,由Socket交給驅動程序向網絡上發送出去。計算機從網絡上收到與該Socket綁定的IP地址和端口號相關的數據後,由驅動程序交給Socket。應用程序即可從該Socket中提取接收到的數據。

(1)套接字的類型

    ·流式套接字(SOCK_STREAM)

       提供面向鏈接、可靠的數據傳輸服務,數據烏差錯、無重複的發送,且按接發送順序接收;SOCK_STREAM是基於TCP協議實現的;

    ·數據報式套接字(SOCK_DGRAM)

       提供無鏈接服務;數據包以獨立包形式發送,不提供無差錯保證,數據可能丟失和重複,而且接收順序混亂;SOCK_DGRAM是基於 UDP協議實現的;

    ·原始套接字(SOCK_RAW)

(2)基於TCP(面向鏈接)的Socket編程

  基於基於TCP(面向鏈接)的Socket編程的服務端程序流程以下:

    ①建立套接字(socket);

    ②將套接字綁定到一個本地地址和端口上(bind);(解釋:告訴本地主機它打算在哪一個IP地址和哪一個端口上等待客戶請求)

    ③將套接字設爲監聽模式,準備接收客戶請求(listen);

    ④等待客戶請求到來;當請求到來後,接受鏈接請求,返回一個新的對應於此鏈接的套接字(accept);

    ⑤用返回的套接字和客戶端進行通訊(send/recv);

    ⑥返回,等待另外一個客戶請求;

    ⑦關閉套接字;

  基於基於TCP(面向鏈接)的Socket編程的客戶端程序流程以下:

    ①建立套接字(socket);

    ②向服務器發出鏈接請求(connect);

    ③和服務器端進行通訊(send/recv);

    ④關閉套接字;

  在服務器端,當調用accept函數時,程序就會等待,等待客戶端調用connect函數發出鏈接請求,而後服務器端接收該請求,因而雙方就創建了鏈接,以後,服務器端和客戶端就能夠利用send和recv函數進行通訊了。

(3)基於UDP(面向無鏈接)的socket編程

  接收端(服務器端):先啓動的一端;發送端(客戶端):發送數據的一端;

  接收端程序的編寫:

    ①建立套接字(socket);

    ②將套接字綁定到一個本地地址和端口上(bind);(解釋:接收端告訴本地主機,它是在哪一個地址和端口上等待數據的到來)

    ③等待接收數據(recvfrom);

    ④關閉套接字;

  客戶端程序的編寫:

    ①建立套接字(socket);

    ②向服務器發送數據(sendto);

    ③關閉套接字;

提示:套接字表示了通訊的端點;利用套接字通訊與利用電話機通訊是同樣的,套接字至關於電話機,IP地址至關於總機號碼,端口號至關於分機。

三、相關函數

  (1)WSAStartup函數

int WSAStartup(
  WORD wVersionRequested,  //指定準備加載的Winsock庫的版本;
  LPWSADATA lpWSAData       //是一個返回值,指向WSADATA結構的指針
);
//lpWSAdata:這是一個返回值,指向WSADATA結構的指針,WSAStartup函數用其加載的庫版本有關的信息填在這個結構中;

    ·功能:①加載套接字庫;

        ②進行套接字庫的版本的協商(肯定將使用的socket版本);

    ·對於每個WSAStartup函數的成功調用(即成功加載WinSock動態庫後),在最後對應一個WASCleanUp調用,來釋放該程序佔用的資源,終止對WinSock動態庫的使用。

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; 
View Code

    ·WSAStartup函數把WSADate結構中的第一個字段wVersion設置爲打算使用的Winsock版本,wHighVersion字段容納的是現有的Winsock庫的最高版本;

    ·注意:這兩個字段中,高位字節表明的是Winsock副版本,而低字節表明的則是Winsock朱版本;

 

  (2)socket函數

SOCKET socket(
  int af,       //指定地址簇,對於TCP/IP協議的套接字,它只能是AF_INET(或PF_INET);
  int type,     //指定socket類型(SOCK_STREAM、SOCK_DGRAM)
  int protocol  //與特定的地址家族相關的協議;
);

    ·若是socket函數調用成功,它就會返回一個新的SOCKET數據類型的套接字描述符;調用失敗,返回一個INVALID_SOCKET值,錯誤信息能夠經過WSAGetLastError函數返回。

 

  (3)bind函數

int bind(
  SOCKET s,   //指定要綁定的套接字;                       
  const struct sockaddr FAR *name,  //指向sockaddr結構的指針變量,指定了該套接字的本地地址信息; 
  int namelen    //指定sockaddr地址結構的長度;                    
);

    ·name:指定了該套接字的本地地址信息;指向sockaddr結構的指針變量,因爲該地址結構是爲全部的地址家族準備的,這個結構可能隨所使用的網絡協議不一樣而不一樣,故:第三個參數(namelen)指定改地址結構的長度;

    ·功能:建立套接字成功以後,將該套接字綁定到本地的某個地址和端口上;

sockaddr結構定義以下:

struct sockaddr {
  u_short    sa_family;
  char       sa_data[14];
};  

    ·sockaddr結構的第一個字段(sa_family)指定地址家族,對於TCP/IP協議的套接字,必須設置爲AF_INET;
    ·第二個地段(sa_data)僅僅是表示要求一塊內存分配區,起到佔用的做用,該區域中指定與協議相關的具體地址信息;

  ·注意:因爲實際要求的只是內存去,因此對於不一樣的協議家族,用不一樣的結構來替代sockaddr。處理sa_family外,sockaddr是按網絡字節順序表示的。

      **在基於TCP/IP的socket編輯過程當中,能夠用sockaddr_in結構替換sockaddr以方便咱們填寫地址信息**。

sockaddr_in結構體的定義以下:

struct sockaddr_in{
 short sin_family;         //表示地址族;(對於IP地址,改變量一直是AF_INET)
 unsigned short sin_port; //指定將要分配給套接字的端口;
 IN_ADDR sin_addr;      //給出套接字的主機IP地址;
 char sin_zero[8];};      //填充數(使sockaddr_in和sockaddr長度同樣);

    ·sockaddr_in結構中sin_addr成員的類型是in_addr,該結構的定義以下所示:

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

  ·提示:in_addr結構其實是一個聯合,一般利用這個結構將一個點分十進制格式的IP地址轉換爲u_long類型,並將結果賦給成員S_addr。

 

  (4)inet_addrinet_intoa函數

unsigned long inet_addr(  const char   FAR *cp  );

    ·inet_addr函數須要一個字符串做爲其參數,該字符串指定了以點分十進制格式表示的IP地址(如192.168.0.16);並且inet_addr函數會返回一個適合分配給S_addr的u_long類型的數值;

char FAR * inet_ntoa(  struct   in_addr in  );

    ·inet_ntoa函數完成與inet_addr相反的轉換,它接收一個in_addr結構體類型的參數並返回一個以點分十進制格式表示的IP地址字符串;

 

  (5)listen函數

int listen(
  SOCKET s,   // 套接字描述符; 
  int backlog // 等待**鏈接隊列**的最大長度;
);
View Code

    ·做用:將指定的套接字設置爲監聽模式;

 

  (6)accept函數

SOCKET accept(
  SOCKET s,   //套接字描述符,該套接字已經經過listen函數將其設置爲監聽狀態;
  struct sockaddr FAR *addr,  
  int FAR *addrlen  //是一個返回值,指向一個整型的指針,返回包含地址信息的長度;
);

    ·功能:接受客戶端發出的鏈接請求;

    ·addr參數:指向一個緩衝區的指針,該緩衝區用來接收鏈接實體的地址,也就是當客戶端服務器發起鏈接,服務器接受這個鏈接時,保存發起鏈接的這個客戶端的IP地址信息和端口信息;

  (7)send函數

int send(
  SOCKET s,             //一個已經創建鏈接的套接字;     
  const char FAR *buf,  //buf指向一個緩衝區,該緩衝區包含將要傳遞的數據;
  int len,              //len是緩衝區的長度;
  int flags             //flogs:設定的值將影響函數的行爲,通常將器設置爲0便可;
);

    ·功能:經過一個已創建鏈接的套接字發送數據;

 

  (8)recv函數

int recv(
  SOCKET s,       //s:創建鏈接後準備接收數據的那個套接字;
  char FAR *buf,  //buf:指向緩衝區的指針,用來保存接收的數據;
  int len,        //len:緩衝區的長度;
  int flags       //同send的flags;
);

    ·功能:從一個已鏈接的套接字接收數據;

 

  (9)connect

int connect(
  SOCKET s,                        //s:即將在其上創建鏈接的那個套接字; 
  const struct sockaddr FAR *name, //name:設定鏈接的服務器地址信息;
  int namelen                      //namelen:指定服務器端地址的長度;
);

    ·功能:將與一個特定的套接字創建鏈接;

 

  (10)recvfrom

int recvfrom(
  SOCKET s,                   //s:準備接收數據的套接字;
  char FAR* buf,              //buf:指向緩衝區的指針,該緩衝區用來接收數據;
  int len,                    //len:緩衝區長度;
  int flags,                  //不解釋
  struct sockaddr FAR *from,  //from:是一個指向地址結構的指針,主要是用來接收發送數據方的地址信息;
  int FAR *fromlen            //整型指針,且是一個in/out類型的參數;
);

    ·功能:將接收一個數據報信息並保存源地址;

    ·fromlen:是一個in/out類型的參數,代表在調用前須要給它指定一個初始值,當函數調用以後,會經過這個參數返回一個值,該返回值是底地址結構的大小;

 

  (11)sendto

int sendto(
  SOCKET s,                        
  const char FAR *buf,            
  int len,                         
  int flags,                       
  const struct sockaddr FAR *to,  //可選的指針,指定目標套接字的地址;
  int tolen                       //tolen:是參數to中指定的地址的長度; 
);

    ·功能:將向一個特定的目的方發送數據;

 

  (12)htonshtonl 函數

u_short htons(
  u_short hostshort  //hostshort:是一個以主機字節順序表示的16爲數值;
);

 

    ·功能:(Windows Sockets的htons函數)將把一個u_short類型的值從主機字節順序轉換爲TCP/IP網絡字節順序;

 

u_long htonl(
  u_long hostlong  //是一個以主機字節順序表示的32位數值;
);

    ·功能:將把一個u_long類型的值從主機字節順序轉換爲TCP/IP網絡字節順序;

 

四、基於TCP的網絡應用程序的編寫

  服務器端程序:

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

void main()
{
    //加載套接字庫
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    
    wVersionRequested = MAKEWORD( 1, 1 );  //MAKEWORD宏建立一個包含一個請求版本號的WORD值;
                      //MAKEWORD(x,y)宏(x是高位字節, y是低位字節)能夠方便的獲取wVersionRequested的正確值;
    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.cnblog.com.aze-003", inet_ntoa(addrClient.sin_addr));
        
        send( sockConn, sendBuf, strlen(sendBuf)+1, 0);    //發送數據 注意:「+1」:表示增長1個「\0」結尾標誌;
        
        char recvBuf[100];
        recv( sockConn, recvBuf, 100, 0);    //接收數據
        printf("%s\n", recvBuf);    //打印接收的數據
        
        closesocket(sockConn);    //⑦關閉套接字
    }
}
/*    ④等待客戶請求到來;當請求到來後,接收鏈接請求,返回一個新的對應於這次鏈接的套接字(accept)
    ⑤用返回的套接字和客戶端進行鏈接通訊(send/recv);
    ⑥返回,等待另外一客戶請求;
    注意:在調用accept函數前,必須爲它的第三個參數賦予一個初始值,即:SOCKADDR_IN結構體的長度;

       進入循環,首先調用accept函數等待並接收客戶的鏈接請求,其中第一個參數是處於監聽狀態的套接字;
    第二個參數利用addrClient變量接收客戶端的地址信息。當客戶端鏈接請求到來時,該函數接受該請求,建
    立鏈接,同時它將返回一個相對於當前這個新鏈接的一個套接字描述符,保存於sockConn變量中,而後利用
    這個套i蛾子就能夠與客戶端進行通訊了,而咱們先前的套接字仍然繼續監聽客戶端的鏈接請求;
  */
TCP_Srv

 

  客戶端程序:

#include <stdio.h>
#include <Winsock2.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));    
    
    //③和服務器進行通訊(send/recv)
    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();
}
TCP_Client

 

五、基於UDP的網絡應用程序的編寫

  服務器端程序: 

//UDP_Srv.cpp
#include <stdio.h>
#include <Winsock2.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(ADDR_ANY);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);
    //②綁定套接字
    bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
    //③等待並接收數據 (UDP服務器就是一個接收端)
    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();
}
UDP_Srv.cpp

 

  客戶端程序:

//UDP_Client.cpp
#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 sockSrv;
    sockSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    sockSrv.sin_family = AF_INET;
    sockSrv.sin_port = htons(6000);
    //發送數據
    sendto(sockClient, "hello!", strlen("hello!")+1, 0, (SOCKADDR*)&sockSrv, sizeof(SOCKADDR));
    //關閉套接字
    closesocket(sockClient);
    WSACleanup();

}
UDP_Client.cpp

 

2014-08-14  

23:56:21

相關文章
相關標籤/搜索