1. 程式人生 > >HTTP代理伺服器的實現

HTTP代理伺服器的實現

一.套接字程式設計

API函式介紹

SOCKET accept( SOCKET s , struct sockaddr_in FAR * addr ,int Far *addlen ) ; 

函式說明:當沒有連線請求時,對於阻塞式套接字,如果程式呼叫了accept函式,那麼執行緒將進入等待狀態,知道有一個連線請求到達為止,accept在接收到連線請求時,會為這個連線建立起一個新的套接字,該套接字負責和客戶單進行通訊,常被稱為“會話套接字。此前呼叫的socket函式返回的套接字負責監聽和接收連線請求,因此被稱為“監聽套接字”。

引數說明:引數s是此前用於監聽和接收連線的“監聽套接字”。

          引數addr

和引數addlen是用於返回客戶機的資訊,如果服務機對此不關心,那        麼可以都設定為NULL

int connect( SOCKET s , struct sockaddr_in FAR * name , int namelen ) ;

引數說明:引數s是呼叫socket函式返回的套接字。

引數name 是服務機的地址,namelen屬於服務機地址型別的大小。

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

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

引數說明:引數s在伺服器端指呼叫accept函式接收客戶端的連線請求後返回的會話套接字,而客戶端只有一個會話套接字(因為客戶端不用監聽連線請求,因此沒有監聽套接字)

引數buf是指要傳送和接收資料的緩衝區。

引數lenbuf的大小。

如果函式呼叫成功,會返回實際傳送或接收的位元組數。

如果函式呼叫失敗,那麼將返回錯誤SOCKET_ERROR

二.利用socket實現代理伺服器的基本框架

代理伺服器是基於socket套接字程式設計實現的,現在可以考慮實現三個端,一個是client , 一個是server,最後一個是proxy server

其中proxy server就是我們要實現的代理伺服器,在這一步中實現的主要功能是並沒有實現能夠解析

HTTP請求的機制(這個功能實際上是比較簡單的),但是大體框架是利用多執行緒程式設計接收客戶端的請求,傳送給伺服器,接收伺服器的響應,傳送給客戶端。

三個基本程式都是基於多執行緒的套接字程式設計。下一步將加入HTTP協議的內容,那麼就可以實現HTTP代理的作用了。

主要的程式碼需呀說明一下:

proxyThread是一個執行緒函式,是這個代理伺服器的主體框架和核心程式碼。

// 代理伺服器執行緒,處理與客戶端和目標伺服器的連線

DWORD WINAPI proxyThread( LPVOID lp )

{

ThreadPara para = *(ThreadPara*)lp ;

SOCKET clientsocket = para.clientsocket ;

SOCKET serversocket = para.serversocket ;

SOCKET proxysocket ;  // 建立一個監聽套接字,使用者接受目標伺服器的響應,處理與目標伺服器的通訊

//定義接收來至客戶端的資料

char client_buffer[4096] ;

int len = recv( clientsocket , client_buffer , MAXBYTE , NULL ) ;

if( len < 0 )

{

printf("接收客戶端資料失敗!\n") ;

return 0 ;

}

client_buffer[len] = '\0' ;

//輸出客戶端發來的資料

printf("client addr :%d , port = %d\n" , para.client_addr.sin_addr.S_un.S_addr , para.client_addr.sin_port ) ;

printf("from client data :\n%s\n" , client_buffer ) ;

// 填充目標伺服器的地址

sockaddr_in server_addr ;

server_addr.sin_family = AF_INET ;

server_addr.sin_addr.S_un.S_addr = inet_addr( "127.0.0.1" ) ;

server_addr.sin_port = htons( 1234 ) ;

// 連線目標伺服器

// 為了與目標伺服器建立連線,必須再建立一個與套接字serversocket不同的套接字來實現

proxysocket = socket( AF_INET , SOCK_STREAM , 0 ) ;

connect( proxysocket , (SOCKADDR*)&server_addr , sizeof( SOCKADDR ) ) ;

// 把接收來至客戶端的資料發給目標伺服器

send( proxysocket , client_buffer , sizeof( client_buffer ) , NULL ) ;

// 接收來至目標伺服器的響應

char server_buffer[4096] ;

int len2 = recv( proxysocket , server_buffer , MAXBYTE , NULL ) ;

if( len2 < 0 )

{

printf("接收伺服器端的資料失敗!\n") ;

return 0 ;

}

server_buffer[len] = '\0' ;

// 輸出目標伺服器端的響應

printf("server address:%d , port = %d\n" , server_addr.sin_addr.S_un.S_addr , server_addr.sin_port ) ;

printf("from server data : \n%s\n" , server_buffer ) ;

// 將接收來至伺服器端的資料發給相應的客戶端

send( clientsocket , server_buffer , sizeof( server_buffer ) , NULL ) ;

// 關閉套接字

closesocket( clientsocket ) ;

closesocket( serversocket ) ;

closesocket( proxysocket ) ;

return 0 ;

}

為了方便程式設計,設計了多執行緒引數型別:

// 定義執行緒引數的型別

typedef struct ThreadPara

{

SOCKET serversocket  ;  // 本地監聽套接字,用於處理與客戶端的連線

SOCKET clientsocket ;   // accept函式返回的套接字,用於處理與客戶端的連線

sockaddr_in client_addr ; // 客戶端地址   

} ThreadPara ;

主函式部分:

因為代理伺服器的作用是幫助客戶端去訪問目標伺服器,那麼在主函式中程式碼的作用就是在一個迴圈中不斷等待客戶端的連線,然後對每一個連線都建立一個執行緒去處理它。

int _tmain(int argc, _TCHAR* argv[])

{

WSADATA ws ;

WSAStartup( MAKEWORD( 2 , 2 ) , &ws ) ;

SOCKET serversocket , clientsocket  ;

serversocket = socket( AF_INET , SOCK_STREAM , 0 ) ;

if( INVALID_SOCKET == serversocket )

{

printf(" Create serversocket error !\n") ;

return 0 ;

}

// 填充本地地址並繫結

sockaddr_in local_addr ;

local_addr.sin_family = AF_INET ;

local_addr.sin_addr.S_un.S_addr = inet_addr( "127.0.0.1") ;

local_addr.sin_port = htons( 5678 ) ;

bind( serversocket , (SOCKADDR*)&local_addr , sizeof( SOCKADDR ) ) ;

//監聽

listen( serversocket , 10 ) ;

// 接收客戶端的請求並建立連線

sockaddr_in client_addr ;

int size = sizeof( SOCKADDR ) ;

whiletrue )

{

clientsocket = accept( serversocket , (SOCKADDR*)&client_addr , &size ) ;

if( INVALID_SOCKET == clientsocket )

{

printf("clientsocket error!\n") ;

return 0 ;

}

// 建立客戶端執行緒處理與客戶端的連線

ThreadPara clientPara ;

clientPara.clientsocket = clientsocket ;

clientPara.client_addr = client_addr ;

// 建立處理執行緒

HANDLE hClient = CreateThread( NULL , 0 , proxyThread , &clientPara , 0 , NULL ) ;

WaitForSingleObject( hClient , INFINITE ) ;  //等待所有執行緒結束

}

WSACleanup() ;

return 0;

}

三.HTTP代理伺服器的實現

為了說明實際中代理伺服器是如何工作的,現在來了解一下,瀏覽器訪問目標伺服器的整個過程。

在不使用代理伺服器的情況下,只需要在瀏覽器的搜尋框中輸入響應的URL,那麼就可以訪問到自己想要得到內容。在這個過程中,首先本地瀏覽器會解析URL,然後構造HTTP請求資料包,然後傳送給目標伺服器。

但是在使用代理伺服器的情況下,本地瀏覽器會把構造好的HTTP協議資料包發給代理伺服器,代理伺服器會解析這個HTTP流量包,然後把這個包發給目標伺服器。

根據這個過程,那麼就可以確定代理伺服器的具體功能了。

代理伺服器可以有各種各樣的功能,我們這裡只關心並只會實現這個功能,如何解析HTTP請求包。

演示:

為了方便演示,直接在客戶端的程式中給出請求包的內容:

char  buffer[] = { "Get / HTTP/1.1\nhost:127.0.0.1:1234\naccept:\n" } ;

這個是客戶端發給伺服器端的請求

這個是HTTP代理伺服器記錄的中間請求

這個是伺服器發給客戶端的響應

程式說明:

在代理伺服器中主要是對客戶單請求的解析,然後得到目標伺服器的IP地址和埠號,將請求轉發給目標伺服器。

為了方便程式設計,設計瞭如下資料結構:

// 定義解析出來的目標主機的地址

typedef struct ServerInfo

{

char IP[20] ; // 目標主機的IP地址

int port  ;     // 目標主機的埠號

} ServerInfo ;

解析請求的程式碼如下,都是字串的操作,在C語言程式設計中,字串的操作是很重要的,要熟練掌握。

bool MyHttpParaser( char * request , ServerInfo & serverInfo ) 

{

char requestLine[50] ;

char * pos = strstr( request , "host:" ) ;

pos = pos + 5 ;

int i = 0 ;

while( *pos != '\n' )

{

requestLine[i] = *pos ;

pos = pos + 1 ;

i++ ;

}

requestLine[i] = '\0' ;

pos = strstr( requestLine , ":" ) ;

*pos = '\0' ;

pos = pos + 1 ;

char port[20] ;

strcpy( port , pos ) ;

strcpy( serverInfo.IP , requestLine ) ;

serverInfo.port  = atoi( port ) ;  // 將埠號轉換為int

printf("serverInfo.IP = %s , serverInfo.Port = %d\n" , serverInfo.IP , serverInfo.port ) ;

return true ;

}

在伺服器端那麼就是對客戶端的請求作出解析後給出適當地響應。

——————————————————————————————————————————————————————————————————————————————

那麼現在,HTTP代理伺服器已經解析完畢,現在主要就是把它與瀏覽器來一起進行設定了,這個將在後續的文章中給出。