【windows socket+HTTP伺服器客戶端】

Love_Hulidear發表於2014-05-10

Windows Socket+HTTP伺服器客戶端


     Winsock是 Windows下套接字標準
        
       1.HTTP協議

         HTTP是基於客戶端/伺服器的請求,響應協議。

       請求:由客戶端向伺服器發起,指定了要從伺服器獲取的資源。請求包含了協議首部,指明瞭客戶端處理能力資訊,如可以處理的檔案型別,支援的語言,編碼方式等。
       響應:伺服器收到客戶端的請求後,解析這個請求,構造響應,併傳送給客戶端。響應同樣包含了協議首部,指明瞭伺服器的相關資訊。
         
       2.簡易HTTP伺服器與客戶端

        實際中的HTTP協議,考慮到多種需求,協議具有一定的複雜性,這裡我們只實現一個簡單的HTTP伺服器與客戶端,重在理解HTTP協議的工作原理。
         Winsock程式設計下,客戶端通過socket向客戶端傳送一段資料(即請求),這段資料包含了客戶端請求的資源(即檔案)。
        客戶端收到這段資料後,對這段資料進行處理(即解析URL),提取客戶端請求的資源名,根據資源名找到伺服器資源,將資源與其他資訊處理後(即響應)傳送給客戶端。
         
       3.HTTP伺服器與客戶端實質

        HTTP協議是建立在socket之上的,本質上是兩個程式通過socket相互傳送資料。HTTP協議,規定了傳送方傳送資料的格式以及接受方如何使用接受的資料。實現HTTP伺服器與客戶端,HTTP協議的實現體現在雙發對傳送與接受資料的處理上。最簡單的例子,客戶端向伺服器傳送一個"GET 1.html"資料,伺服器收到資料後,解讀"GET 1.html",明白客戶端想得(GET)到1.html檔案,伺服器將1.html檔案的內容傳送給客戶端,客戶端接收到含1.html檔案內容的資料後,新建1.html檔案並寫入伺服器端傳送來的資料。

         4.牛刀小試
         先在VC6.0中執行伺服器,再開啟一個VC6.0執行客戶端。


         執行效果:
          常用的瀏覽器也是客戶端,顯然我們的客戶端只是簡單的將html檔案內容列印,瀏覽器則會按HTML規則處理html檔案然後顯示。

伺服器與客戶端參考了《Winsock網路程式設計經絡》
客戶端程式:
#include
#include
#pragma comment(lib,"ws2_32.lib")

char *http_req_hdr_tmp1="GET %s HTTP/1.1\r\n"
                        "Accept:image/gif,image/jpeg,*/*\r\nAccept-language:zh-ch\r\n"
						"Accept-Encoding:gzip,deflate\r\nHost:%s:%d\r\n"
						"Uert-Agent:Jiangwei's Browser<0.1>\r\nConnection:keep-Alive\r\n\r\n";

void http_pares_request_url(char *buf,char *host,
							int *port,char *file_name)
{
	int length=0;
	char port_buf[8];
	char *buf_end=(char *)(buf+strlen(buf));
	char *begin,*host_end,*colon,*file;

    //尋找主機開始位置
	begin = strstr(buf,"//");
	begin = (begin ? begin+2:buf);

	colon = strchr(begin,':');
	host_end = strchr(begin,'/');

	if(host_end == NULL)
	{
		host_end = buf_end;	
	}
	else
	{
		//得到檔名
		file = strrchr(host_end,'/');
		if(file && (file+1)!=buf_end)
		{
			strcpy(file_name,file+1);
		}
	}

	if(colon)
	{
		//得到埠號
		colon++;
		length = host_end-colon;
		memcpy(port_buf,colon,length);
		port_buf[length]=0;
		*port = atoi(port_buf);

		host_end = colon -1;
	}

	//得到主機
	length = host_end-begin;
	memcpy(host,begin,length);
	host[length]=0;
}

int main()
{
	WSADATA wsa;
	WSAStartup(MAKEWORD(2,0),&wsa);

	SOCKET http_sock;
	SOCKADDR_IN serveraddr;
	struct hostent *host_ent;

	int result;
	int Send_len;
	char data_buf[1024];
	char host[256]="127.0.0.1";
	int port=8080;
	unsigned long addr;
	char file_name[256]="index.html";
	FILE *file_web;
    
	/*input為URL
	 5.html為請求的檔案
	 此時伺服器程式資料夾下要要有5.html
	*/
	char *input="http://127.0.0.1:8080/5.html";
	http_pares_request_url(input,host,&port,file_name);
    
	//判斷地址是否可用
	addr=inet_addr(host);
	if(addr==INADDR_NONE)
	{
		host_ent=gethostbyname(host);
		if(!host_ent)
		{
			printf("伺服器不可用!\n");
			return -1;
		}
		memcpy(&addr,host_ent->h_addr_list[0],host_ent->h_length);
	}
    
	//根據輸入的地址資訊,初始化IP,Port
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(port);
	serveraddr.sin_addr.s_addr = addr;
    
	//建立伺服器socket
	http_sock = socket(AF_INET,SOCK_STREAM,0);
    
	//請求連線伺服器socket
	result = connect(http_sock,(SOCKADDR *)&serveraddr,sizeof(serveraddr));
	if(result==SOCKET_ERROR)
	{
		closesocket(http_sock);
		printf("[Web]連線失敗!");
		return -1;
	}
    
    //傳送http請求
	Send_len=sprintf(data_buf,http_req_hdr_tmp1,input,host,port);
	result=send(http_sock,data_buf,Send_len,0);
	if(result==SOCKET_ERROR)
	{
		printf("[Web]傳送失敗!\n");
		return -1;
	}

	file_web=fopen(file_name,"w+");

	//迴圈接收伺服器發來的資料
	do
	{
		result = recv(http_sock,data_buf,1024,0);
		if(result > 0)
		{
			//將接收的資料寫入檔案
			fwrite(data_buf,1,result,file_web);
			//列印接收的資料
			data_buf[result]=0;
			printf("%s",data_buf);
		}
	}while(result>0);
	
	//關閉檔案
	fclose(file_web);
	//關閉套接字
	closesocket(http_sock);
	WSACleanup();

	return 0;
}
伺服器程式:
#include
#include
#pragma comment(lib,"ws2_32.lib")

//定義檔案型別對應的content-tyoe
struct doc_type{
	char *suffix;
	char *type;
};

//檔案
struct doc_type file_type[]=
{
	{"html",  "text/html"},
	{"gif",   "imag/gif"},
	{"jpeg",  "imag/jpeg"},
	{NULL,    NULL}
};

//響應首部內容
char *http_res_hdr_tmp1="HTTP/1.1 200 OK \r\nServer:Jiangwei's Server<0.1>\r\n"
                        "Accept-Ranges:bytes\r\nContent-Length:%d\r\nConnection:close\r\n"
						"Content-Type:%s\r\n\r\n";

//通過字尾,查詢到對應的content-type
char *http_get_type_by_suffix(const char *suffix)
{
	struct doc_type *type;
	for(type=file_type;type->suffix;type++)
	{
		if(strcmp(type->suffix,suffix)==0)
			return type->type;
	}
	return NULL;
}


//解析客戶端傳送過來的請求
void http_parse_request_cmd(char *buf,int buflen,char *file_name,char *suffix)
{
	int length=0;
	char *begin,*end,*bias;

	//查詢URL開始位置
	begin=strchr(buf,' ');
	begin++;
    
	//查詢URL結束位置
	end=strchr(begin,' ');
	*end=0;
    
	bias=strrchr(begin,'/');
	length=end-bias;

	//找到檔名開始的位置
	if((*bias=='/')||(*bias=='\\'))
	{
		bias++;
		length--;
	}

	//得到客戶端請求的檔名
	if(length>0)
	{
		memcpy(file_name,bias,length);
		file_name[length]=0;

		begin = strchr(file_name,'.');
		if(begin)
			strcpy(suffix,begin+1);
	}
}

int http_send_response(SOCKET soc,char *buf,int buf_len)
{
	int read_len,file_len,hdr_len,send_len;
	char *type;
	char read_buf[1024];
	char http_header[1024];
	char file_name[256]="index.html";
	char suffix[16]="html";
	FILE *res_file;

	//通過解析URL,得到檔名
	http_parse_request_cmd(buf,buf_len,file_name,suffix);

	//開啟檔案
	res_file=fopen(file_name,"rb+");

	if(res_file==NULL)
	{
		printf("[Web]檔案:%s 不存在!\n",file_name);
		return 0;
	}

	//計算檔案大小
	fseek(res_file,0,SEEK_END);
	file_len=ftell(res_file);
	fseek(res_file,0,SEEK_SET);

	//獲得檔案content-type
	type=http_get_type_by_suffix(suffix);
    
	if(type==NULL)
	{
		printf("[Web]沒有相關的檔案型別!\n");
		return 0;
	}
    
	//構造響應首部,加入檔案長度,content-type資訊
	hdr_len=sprintf(http_header,http_res_hdr_tmp1,file_len,type);
	send_len=send(soc,http_header,hdr_len,0);
	
	if(send_len==SOCKET_ERROR)
	{
		fclose(res_file);
		printf("[Web]傳送失敗,錯誤:%d\n",WSAGetLastError());
		return 0;
	}

	//傳送檔案
	do
	{
		read_len=fread(read_buf,sizeof(char),1024,res_file);
		if(read_len>0)
		{
			send_len=send(soc,read_buf,read_len,0);
			file_len-=read_len;
		}
	}while((read_len>0) && (file_len>0));
	fclose(res_file);
    return 1;
}
int main(){
	WSADATA wsa;
	WSAStartup(MAKEWORD(2,0),&wsa);
	SOCKET serversoc,acceptsoc;
	SOCKADDR_IN serveraddr;
	SOCKADDR_IN fromaddr;
	char Recv_buf[1024];
	int from_len=sizeof(fromaddr);
	int result;
	int Recv_len;

	//建立socket
	serversoc = socket(AF_INET,SOCK_STREAM,0);
	if(serversoc==INVALID_SOCKET)
    {
		printf("[Web]建立套接字失敗!");
		return -1;
	}

	//初始化伺服器IP,Port
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(8080);
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

	//繫結socket
	result=bind(serversoc,(SOCKADDR *)&serveraddr,sizeof(serveraddr));
	if(result==SOCKET_ERROR)
	{
		closesocket(serversoc);
		printf("[Web]繫結套接字失敗!");
		return -1;
	}
    
	//監聽socket請求
	result = listen(serversoc,3);
	printf("[Web]伺服器正在執行.....\n");

	while(1)
	{
		//接收請求
		acceptsoc = accept(serversoc,(SOCKADDR *)&fromaddr,&from_len);
		if(acceptsoc==INVALID_SOCKET)
		{
			printf("[Web]接收請求失敗!");
			break;
		}
		printf("[Web]連線來自 IP:  %s  Port:  %d  \n",inet_ntoa(fromaddr.sin_addr),ntohs(fromaddr.sin_port));
		
		//接收來自客戶端的請求
        Recv_len=recv(acceptsoc,Recv_buf,1024,0);
		if(Recv_len==SOCKET_ERROR)
		{
			printf("[Web]接收資料失敗!");
			break;
		}
		Recv_buf[Recv_len]=0;
		//向客戶端傳送響應資料
		result = http_send_response(acceptsoc,Recv_buf,Recv_len);

		closesocket(acceptsoc);
	}

	closesocket(serversoc);
	WSACleanup();

	return 0;
}


相關文章