程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 【vc】14_網絡編程_socket編程,網絡編程_socket

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

編輯:C++入門知識

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


1、計算機網絡基本知識

  最簡單的網絡程序如圖:

  提示:IP地址就相當於一個公司的總機號碼,端口號就相當於分機號碼。在打電話時,撥通總機後,還需要轉到分機上。

(1)協議

    ·為進行網絡中的數據交換(通信)而建立的規則、標准或約定(=語義+語法+規則);

    ·不同層具有各自不同的協議;

(2)網路的狀況

    ·多種通信媒介---有線、無線···

    ·不同種類的設備---通用、專用···

    ·不同的操作系統---UNIX、Windows···

    ·不同的應用環境---固定、移動···

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

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

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

   他們互相交織,形成了非常復雜的系統應用環境。    

(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

 

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

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

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

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

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

    ⑤關閉服務器;

  客戶方:

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

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

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

2、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地址相當於總機號碼,端口號相當於分機。

3、相關函數

  (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網絡字節順序;

 

4、基於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

 

5、基於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


VC++ SOCKET網絡編程

我是初學者,最好能從基礎講起.不知道你有好基礎,基本什麼都不用就用孫鑫的 VC深入詳解 很好的入門書!
 

VC++ Socket 網絡編程

是QQ群哦!我加了!
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved