簡單的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

套接字是一種實體,它擔當了客戶端和服務器之間的端點。當客戶端連接到服務器之後,就會存在兩個套接字——客戶端一邊的套接字和相應的服務器一邊的套接字。讓我們來稱它們爲CLIENTSOCKSERVERSOCK。當客戶端在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.comftp.myserver.org之類的名稱。當我們擁有要連接的服務器的IP地址時,可以使用gethostbyaddr()函數,例如192.168.1.1202.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會話。和那些複雜的SMTPPOP3協議相比,它還是比較簡單的。HTTPGET命令用於從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();

感謝您的閱讀。

 

 

發佈了25 篇原創文章 · 獲贊 0 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章