標籤:應用程式 網路 tcp api 結構
最近看了《Window程式設計》感覺在網路方面講的不錯,講的很通俗易懂,與大家一同交流
轉載請註明出處:http://blog.csdn.net/u010484477謝謝^_^
使用 Winsock 編程的一般步驟是比較固定的。
1.Winsock 庫的裝入、初始化和釋放
所有的 WinSock 函數都是從 WS2_32.DLL 庫匯出的,VC++在預設情況下並沒有串連到該庫,如果想使用 Winsock API,就必須包含相應的庫檔案。
#pragma commment(lib, "wsock32.lib")
WSAstartup 必須是應用程式首先調用的 Winsock 函數。它允許應用程式指定所需的Windows Sockets API 的版本,擷取特定 Winsock 實現的詳細資料。僅當這個函數成功執行之後,應用程式才能調用其他 Winsock API。
int WSAStartup(
WORD wVersionRequested, // 應用程式支援的最高WinSock庫版本。高位元組為次版本號碼,低位元組為主要版本號
LPWSADATA lpWSAData // 一個指向WSADATA結構的指標。它用來返回DLL 庫的詳細資料
);
lpWSAData 參數用來取得 DLL 庫的詳細資料,結構定義如下。
typedef struct WSAData {
WORD wVersion;// 庫檔案建議應用程式使用的版本
WORD wHighVersion;// 庫檔案支援的最高版本
char szDescription[WSADESCRIPTION_LEN+1]; // 庫描述字串
char szSystemStatus[WSASYS_STATUS_LEN+1]; // 系統狀態字串
unsigned short iMaxSockets;// 同時支援的最大套節字的數量
unsigned short iMaxUdpDg;// 2.0 版中已廢棄的參數
char FAR * lpVendorInfo;// 2.0 版中已廢棄的參數
} WSADATA, FAR * LPWSADATA;
函數調用成功返回 0。否則要調用 WSAGetLastError 函數查看出錯的原因。此函數的作用相當於 Win32 API GetLastError,它取得最後發生錯誤的代碼。
每一個對WSAStartup的調用必須對應一個對WSACleanup的調用,這個函數釋放Winsock庫。
int WSACleanup(void);
2.套節字的建立和關閉
使用套節字之前,必須調用 socket 函數建立一個套節字對象,此函數調用成功將返回套節字控制代碼。
SOCKET socket(
int af,// 用來指定套節示使用的地址格式,WinSock中只支援AF_INET
int type,// 用來指定套節字的類型
int protocol// 配合type 參數使用,用來指定使用的協議類型。可以是IPPROTO_TCP等
);
type 參數用來指定套節字的類型。套節字有流套節字、資料報套節字和原始套節字等,
下面是常見的幾種套節字類型定義:
SOCK_STREAM 流套節字,使用 TCP 協議提供有串連的可靠的傳輸
SOCK_DGRAM 資料報套節字,使用 UDP 協議提供不需連線的不可靠的傳輸
SOCK_RAW 原始套節字,WinSock 介面並不使用某種特定的協議去封裝它,而是有程式自行處理資料報以及協議首部
當 type 參數指定為 SOCK_STREAM 和 SOCK_DGRAM 時,系統已經明確確定使用 TCP和 UDP 協議來工作,所以 protocol 參數可以指定為 0。
函數執行失敗返回 INVALID_SOCKET(即-1),可以通過調用 WSAGetLastError 取得錯誤碼。
當不使用 socket 建立的套節字時,應該調用 closesocket 函數將它關閉。如果沒有錯誤發生,函數返回 0,否則返回 SOCKET_ERROR。
closesocket函數用法如下。
int closesocket(SOCKET s); // 函數惟一的參數就是要關閉的套節字的控制代碼
3.綁定套節字到指定的 IP 位址和連接埠號碼
為套節字關聯本地地址的函數是 bind,用法如下。
int bind(
SOCKET s,// 套節字控制代碼
const struct sockaddr* name,// 要關聯的本地地址
int namelen// 地址的長度
);
bind 函數用在沒有建立串連的套節字上,它的作用是綁定連線導向的或者不需連線的套節字。當一個套節字被 socket 函數建立以後,他存在於指定的地址家族裡,但是它是未命名的。bind 函數通過安排一個本地名稱到未命名的 socket 建立此 socket 的本地關聯。
本地名稱包含 3個部分:主機地址、協議號(分別為 UDP 或 TCP)和連接埠號碼。
// 填充sockaddr_in結構
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
// 綁定這個套節字到一個本地地址
if(::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("Failed bind() \n");
::WSACleanup();
return 0;
}
sockaddr_in 結構中的 sin_familly 欄位用來指定地址家族,該欄位和 socket 函數中的 af 參數的含義相同,所以惟一可以使用的值就是 AF_INET。
sin_port 欄位和 sin_addr 欄位分別指定套節字需要綁定的連接埠號碼和IP 位址。放入這兩個欄位的資料的位元組順序必須是網路位元組順序。
由於網路位元組順序和 Intel CPU 的位元組順序剛好相反,所以必須首先用 htons 函數進行轉換。如果應用程式不關心所使用的地址,可以為互連網地址指定 INADDR_ANY,為連接埠號碼指定 0。如果互連網地址等於 INADDR_ANY,系統會自動使用當前主機配置的所有 IP 位址,這簡化了程式設計;如果連接埠號碼等於 0,程式執行時系統會分配一個惟一的連接埠號碼到這個應用程式,其值在 1024 到 5000 之間。應用程式可以在 bind 之後使用 getsockname 來知道為它分配的地址。但是要注意,直到套節字串連上之後 getsockname 才可能填寫互連網地址,因為對一個主機來說可能有多個地址是可用的。
4.設定套節字進入監聽狀態
listen 函數置套節字進入監聽狀態。
int listen(
SOCKET s,// 套節字控制代碼
int backlog// 監聽隊列中允許保持的尚未處理的最大串連數量
);
為了接受串連,首先使用 socket 函數建立一個套節字,然後使用bind 函數綁定它到一個本地地址,再用 listen 函數為到達的串連指定一個 backlog,最後使用 accept 接受請求的串連。listen 僅應用在支援串連的套節字上,如 SOCK_STREAM 類型。函數成功執行之後,套節字 s 進入了被動模式,到來的串連會被通知,排隊等候接受處理。在同一時間處理多個串連請求的伺服器通常使用 listen 函數:如果一個串連請求到達,並且排隊已滿,用戶端將接接收 WSAECONNREFUSED 錯誤。
5.接受串連請求
accept 函數用於接受到來的串連。
SOCKET accept(
SOCKET s,// 套節字控制代碼
struct sockaddr* addr,// 一個指向sockaddr_in結構的指標,用於取得對方的地址資訊
int* addrlen// 是一個指向地址長度的指標
);
該函數在 s 上取出未處理串連中的第一個串連,然後為這個串連建立一個新的套節字,返回它的控制代碼。新建立的套節字是處理實際串連的套節字,它與 s 有相同的屬性。
程式預設工作在阻塞模式下,這種方式下如果沒有未處理的串連存在,accept 函數會一直等待下去直到有新的串連發生才返回。
addrlen 參數用於指定 addr 所指空間的大小,也用於返回返回地址的實際長度。如果 addr或者 addrlen 是 NULL,則沒有關於遠程地址的資訊返回。用戶端程式在建立套節字之後,要使用 connect 函數請求與伺服器串連,函數原型如下。
int connect(
SOCKET s,// 套節字控制代碼
const struct sockaddr FAR * name, // 一個指向 sockaddr_in 結構的指標,包含了要已連線的服務器的地址資訊。
int namelen// sockaddr_in結構的長度
);
第一個參數 s 是此串連使用的用戶端套節字。另兩個參數 name 和 namelen 用來定址遠程套節字(正在監聽的伺服器套節字)。
6.收發資料
對流套節字來說,一般使用 send 和 recv 函數來收發資料。
int send(
SOCKET s,// 套節字控制代碼
const char FAR * buf,// 要發送資料的緩衝區地址
int len,// 緩衝區長度
int flags// 指定了調用方式,通常設位0
);
int recv( SOCKET s, char FAR * buf, int len, int );
send 函數在一個串連的套節字上發送緩衝區內的資料,返回傳送資料的實際位元組數。
recv函數從對方接收數據,並儲存它到指定的緩衝區。flags 參數在這兩函數中通常設為 0。
在阻塞模式下,send 將會阻塞線程的執行直到所有的資料發送完畢(或者一個錯誤發生),
recv 函數將返回儘可能多的當前可用資訊,一直到緩衝區指定的大小。
為伺服器程式和客戶程式的建立過程:
最後是送大家一句我很喜歡的話:
你必須用自己的努力換取成功,然後成功就會像一個大巴掌,打在那些曾經看不起你的人臉上