【網絡編程學習筆記01】Socket套接字編程(TCP/IP)

前置知識

1.有一定C++編程基礎

2.套接字的主要類型

有兩種:一種是流式套接字(SOCK_STREAM),一種是數據報套接字(SOCK_DGRAM),

分別用於TCP/IP協議和UDP協議進行通訊的應用程序中。

3.TCP/IP尋址方式

  用戶使用IP地址和端口號進行確定通信雙方。

-----------------------------------------------------------------------------

一、Winsock編程流程

1.配置編程環境

由於所有的Winsock函數均是從動態鏈接庫WS2_32.DLL中導出的,而VC默認是沒有與該庫進行連接,所以我們需要進行編程環境設置。添加方法是選擇“工程--->設置--->連接”

2.初始化套接字庫

調用WSAStartup()對該庫進行初始化,查詢MSDN後得知原型如下

int WSAStartup(WORD wVersionRequested , LPWSADATA lpWSAData);

該函數調用成功,將返回0。

參數wVersionRequested表示當前套接字庫的版本號。

例如當前套接字版本號爲2.0,則將該參數設置爲2.0,代碼如下:

<pre class="cpp" name="codeWORD wVersionRequested=MAKEWORD(2,0);

參數lpWSAData指向結構體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;	

例1:有了上述知識,就可以開始初始化套接字庫了,代碼如下:

WSADATA data;								//定義WSAData指針對象
WORD w=MAKEWORD(2,0);						//定義當前套接字庫版本號
::WSAStartup(w,&data);						//初始化套接字庫,爲默認參數

3.創建套接字句柄

創建套接字句柄的函數是socket(),查詢MSDN後得知該函數原型如下

SOCKET socket(
  int af,       //指定套接字所使用的地址格式,TCP/IP設置爲AF_INET
  int type,     //套接字類型,見前置知識第2條。
  int protocol  //一般設置爲0,想深究,請百度。
);
該函數執行成功,將返回新創建的套接字句柄。否則,將返回INVALID_SOCKET。

例2:創建流式套接字(TCP/IP)句柄的代碼如下:

SOCKET s;								//定義套接字句柄對象
s=::socket(AF_INET,SOCK_STREAM,0); 		//創建並返回套接字句柄
4.構造套接字地址結構

sockaddr_in是TCP/IP地址家族中統一的套接字地址結構,查詢MSDN得知原型如下

struct sockaddr_in{
    short 		   sin_family;	   //指定地址家族,TCP/IP設置爲AF_INET
    unsigned short   sin_port;	   //端口
    struct in_addr   sin_addr;	   // IP地址
    char			   sin_zero[8];	   //一般設置爲0
};

該結構成員變量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;
};

通常,我們在編寫網絡程序時,使用一個u_long類型的字符串進行描述IP地址。

例3:127.0.0.1是回送地址,指向本機,一般可以用來測試,代碼如下:

sockaddr_in addr;									//定義結構對象
addr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");

編程知識普及:字節順序

字節順序分爲:Big-Endian和Little-Endian。以unsigned int val=0x12345678爲例

Intel CPU(Little-Endian)存放的字節順序爲0x78、0x56、0x34、0x12。

而網絡的字節順序爲(Big-Endian) 0x12、0x34、0x56、0x78。正好相反,但更符合人

們的閱讀情況。所以編程人員將數據通過網絡發送時,需要將存儲在本地計算機上的數

據轉換成以網絡字節順序排列的數據。從數據的角度來說,網絡字節順序將最重要的數

據先進行存儲,而Intel CPU字節順序則將不重要的字節首先儲存。

字節順序轉換函數

u_short htons(u_short hostshort);
//將一個u_short類型的端口號從主機字節順序轉換到網絡字節順序。
u_short ntohs(u_short netshort); 
//將一個u_short類型的端口號從網絡字節順序轉換到主機字節順序。
u_long htonl(u_long hostlong); 
//將一個u_long類型的IP地址從主機字節順序轉換到網絡字節順序。
u_long ntohl(u_long netlong);
//將一個u_long類型的IP地址從網絡字節順序轉換到主機字節順序。
unsigned long inet_addr(const char FAR *cp);
//將一個字符串IP轉換到以網絡字節順序排列的IP地址。
char FAR * inet_ntoa(struct in_addr in);
//將一個以網絡字節順序排列的IP地址轉化爲一個字符串IP。

例4:在構造套接字地址結構中使用字節順序轉換函數

sockaddr_in addr;				//定義套接字地址結構對象
addr.sin_family=AF_INET;			//指定地址家族爲TCP/IP
addr.sin_port=htons(80);			//指定端口號
addr.sin_addr.S_un.S_addr="127.0.0.1";	//將字符串IP轉換爲網絡字節順序排列的IP
char addRes[]=inet_ntoa(addr.sin_addr.S_un.S_addr);	//將網絡字節順序排列的IP轉換爲字符串IP

5.綁定地址信息(服務端)

對於服務器,套接字創建成功後,還應該使用bind()函數將套接字與地址結構信息相

關聯,bind()函數原型如下:

int bind(
  SOCKET s,                          //套接字句柄
  const struct sockaddr FAR *name,   //地址結構信息
  int namelen                        //地址結構大小
);

該函數調用成功,則返回0。

例5:將套接字句柄綁定到本地地址,代碼如下:

sockaddr_in addr;						//定義套接字地址結構對象
addr.sin_family=AF_INET;				//指定地址家族爲TCP/IP
addr.sin_port=htons(80);				//指定端口號
addr.sin_addr.S_un.S_addr=INADDR_ANY;	//服務器能接受任何計算機發來的請求
::bind(s,(sockaddr*)&addr,sizeof(addr));//綁定套接字到指定地址結構

6.監聽端口信息(服務端)

服務器程序可以調用listen()函數實現監聽端口的功能,函數原型如下:

int listen(
  SOCKET s,    							//實現監聽功能的套接字句柄
  int backlog  							//指定監聽的最大連接數量
);

如果多個客戶端同時向服務器發出連接請求,並且超過了最大連接數,客戶端將返回錯誤代碼。

例6:在套接字上進行監聽,並且將最大監聽數量定義爲5:

::listen(s,5);

7.連接(客戶端)

客戶端連接服務端需要用到connect()函數,函數原型如下:

int connect(
  SOCKET s,                          //實現連接功能的套接字句柄
  const struct sockaddr FAR *name,   //將要連接的服務器地址信息的結構指針
  int namelen                        //服務器地址信息結構體長度
);

例7:連接地址爲"127.0.0.1",端口爲80的服務器。代碼如下:

sockaddr_in addr;					 //定義套接字地址結構對象
addr.sin_family=AF_INET;			 //指定服務器地址家族爲TCP/IP
addr.sin_port=htons(80);			 //指定要連接的端口號
addr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//指定服務器IP地址
::connect(s,(sockaddr*)&addr,sizeof(addr)); 	 //連接服務器

8.接受連接請求(服務端)

當服務端收到客戶端的連接請求時,則可以調用accept()函數接受該請求,函數原型:

SOCKET accept(
  SOCKET s,							 //套接字句柄
  struct sockaddr FAR *addr,		 //保存客戶端的地址信息於addr中
  int FAR *addrlen					 //保存客戶端的地址信息長度於addrlen中
);

例8:服務端套接字句柄爲s,使用accept()函數接收客戶端信息,代碼如下:

SOCKET s1;
sockaddr_in addrConnect;
int n=sizeof(addrConnect);
s1=::accept(s,(sockaddr*)&addrConnect,&n);

9.數據收發

當服務器和客戶端成功連接時,我們可以使用send()和recv()函數實現數據的發送和接收功能,函數原型如下:

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

兩個函數的各個參數以及表示的意義都是相同的,參數*buf是指向數據緩衝區的指針變量,參數len是數據的長度,參數flags一般設置爲0。

注意:對於服務端,參數s應該爲接收客戶端連接請求後,返回的新套接字句柄。

      對於客戶端,參數s應該爲客戶端創建的套接字句柄。

例9:使用send()、recv()函數來發送、接收數據,代碼如下:

char szText[]="Hello C++ Socket World";		//用於儲存發送的數據
::send(s1,szText,sizeof(szText),0);			//發送數據給客戶端s1
char szText2[100]={0};						//用於保存接收到的數據
::recv(s,szText2,sizeof(szText2),0);		//接收服務端發來的數據

10.關閉套接字

當套接字使用完畢或者程序退出時,應該調用函數closesocket()關閉套接字句柄,函數原型如下:

int closesocket(
  SOCKET s  			//需要關閉的套接字句柄
);

例10:關閉之前創建的套接字句柄s,代碼如下:

::closesocket(s);

11.釋放套接字庫

例11:當程序退出時,還應該記得調用WSACleanup()函數釋放該套接字庫,代碼如下:

::WSACleanup();

二、Winsock編寫過程

服務端編寫過程:                        客戶端編寫過程:

1.初始化套接字庫:WSAStartup()          1.初始化套接字庫:WSAStartup()

2.創建套接字句柄:socket()              2.創建套接字句柄:socket()

3.構造套接字地址結構:sockaddr_in       3.構造套接字地址結構:sockaddr_in

4.綁定地址信息:bind()                  4.連接服務端:connect

5.監聽端口信息:listen()                5.數據收發:send()、recv()

6.接受連接請求:accept()                6.關閉套接字:closesocket()

7.數據收發:send()、recv()              7.釋放套接字庫:WSACleanup()

8.關閉套接字:closesocket()

9.釋放套接字庫:WSACleanup()

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