作者:Nishant S
原文連結:http://www.codeproject.com/internet/winsockintro02.asp
下載下傳工程
二、簡單的TCP用戶端
介紹
本文是《Winsock程式設計入門(1) - 簡單的TCP伺服器》一文的結局,如果你還沒有讀過第1部分的話,我還是建議你首先讀一下。在本文中,我将示範給你如何編寫一個簡單的TCP用戶端程式。我們要編寫一個程式,這個程式将連接配接到一個HTTP伺服器,并獲得一個檔案。
一個簡單的TCP用戶端程式流程
1、使用WSAStartup()初始化WinSock庫。
2、使用socket()建立一個IPPROTO_TCP SOCKET。
3、使用gethostbyname()/gethostbyaddr()擷取主機資訊。
4、使用connect()和我們建立的套接字連接配接伺服器。
5、使用send()/recv()發送和接收資料,直到我們的TCP會話結束。
6、使用closesocket()關閉套接字連接配接。
7、使用WSACleanup()釋放WinSock。
初始化WinSock
正如其它每個WinSock程式一樣,我們需要初始化WinSock庫。這也基本上是一種檢查WinSock是否在目前系統可用的方法,對于以前的版本,我們當然希望是這樣。
int wsaret=WSAStartup(0x101,&wsaData);
if(wsaret)
return;
建立SOCKET
套 接字是一種實體,它擔當了用戶端和伺服器之間的端點。當用戶端連接配接到伺服器之後,就會存在兩個套接字——用戶端一邊的套接字和相應的伺服器一邊的套接字。 讓我們來稱它們為CLIENTSOCK和SERVERSOCK。當用戶端在CLIENTSOCK使用send()時,伺服器可以在SERVERSOCK使 用recv()來接收用戶端所發送的資料,反之亦然。對于我們的目的,我們使用一個名為socket()的函數來建立套接字。
SOCKET conn;
conn=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(conn==INVALID_SOCKET)
return;
擷取主機資訊
顯 然,我們在連接配接到主機(伺服器)之前,要擷取它的資訊。我們可以使用兩個函數——gethostbyname()和gethostbyaddr()。當我 們擁有伺服器的DNS名稱時,我們可以使用gethostbyname()函數,例如codeproject.com或ftp.myserver.org 之類的名稱。當我們擁有要連接配接的伺服器的IP位址時,可以使用gethostbyaddr()函數,例如192.168.1.1或 202.54.1.100。
顯然,我們希望能使我們的最終使用者既能使用DNS名稱,也能使用IP位址。那麼,為了這些工作對他來說透明,我們需要 像下面這樣玩一個小把戲。我們對入口字元串使用inet_addr(),這個函數會把一個IP位址轉換成一個标準的網絡位址格式。這樣一來,如果它傳回失 敗,我們就可以知道這個字元串不是一個IP位址,如果它成功的話,我們就可以假設它是一個有效的IP位址了。
if(inet_addr(servername)==INADDR_NONE)
{
hp=gethostbyname(servername);
}
else
{
addr=inet_addr(servername);
hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
}
if(hp==NULL)
{
closesocket(conn);
return;
}
連接配接到伺服器
connect()函數用于向目标伺服器建立連接配接。我們向它傳遞我們先前建立的套接字和一個sockaddr結構。我們使用由gethostbyname()/gethostbyaddr()傳回的主機位址為sockaddr成員指派,并輸入一個要連接配接的有效端口。
server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
server.sin_family=AF_INET;
server.sin_port=htons(80);
if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
{
closesocket(conn);
return;
}
會話
當 套接字連接配接建立後,用戶端和伺服器就可以通過send()和recv()來發送/接收資料了。這通常稱為TCP會話。對于我們的特定情況,我們需要進行 HTTP會話。和那些複雜的SMTP或POP3協定相比,它還是比較簡單的。HTTP的GET指令用于從HTTP伺服器上擷取檔案。這個檔案可以是 HTML檔案、圖像檔案、壓縮檔案、MP3檔案等等。這樣,這個檔案就會被發送了(這是它最簡單的形式)。當然,還有一些更複雜的方法來使用這個指令。
GET http-path-to-file/r/n/r/n
在我們的程式中,我們像這樣來發送GET指令:
sprintf(buff,"GET %s/r/n/r/n",filepath);
send(conn,buff,strlen(buff),0);
當 我們發送了這個指令的時候,我們就應該知道伺服器就要開始把我們所請求的檔案發送給我們了。就像我們使用send()來發送我們的指令一樣,我們可以使用 recv()來接收伺服器發送給我們的資料。我們循環調用recv(),直到它傳回零,這時候我們就會知道伺服器已經将資料發送完畢了。并且,對于我們的 特定情況,我們可以将這些資料寫入檔案,就像我們要下載下傳并儲存這個檔案一樣。
while(y=recv(conn,buff,512,0))
{
f.Write(buff,y);
}
關閉連接配接
現 在我們的會話結束了,我們必須關閉連接配接。在我們的情況下,HTTP連接配接在檔案發送完畢之後就會被伺服器關閉了,但是這不要緊,我們仍然需要關閉我們的套接 字并釋放資源。在更加複雜的會話中,我們通常在調用closesocket()之前調用shutdown()來确定緩沖區已經被重新整理,否則可能會有部分數 據丢失。
closesocket(conn);
釋放WinSock
我們調用WSACleanup()來結束WinSock的使用。
WSACleanup();