天天看點

通過c/c++在linux系統實作Mini型http伺服器的并發通路測試效果相關函數介紹程式測試源碼相關 html 資源檔案代碼總結

文章目錄

  • 測試效果
  • 相關函數介紹
    • pthread_create函數
    • stat函數
  • 程式測試源碼
  • 相關 html 資源檔案代碼
  • 總結

測試效果

測試平台:通過 MobaXterm Personal Edition 連接配接的本地 Ubuntu 4.2.0-27-generic

當伺服器端程式啟動後,在浏覽器網址搜尋欄輸入 192.168.60.132/qiniu.html登入到我的伺服器端. 192.168.60.132 是我電腦的ip位址.

在 linux shell 視窗 輸入 ip addr 可擷取本機ip

通過c/c++在linux系統實作Mini型http伺服器的并發通路測試效果相關函數介紹程式測試源碼相關 html 資源檔案代碼總結

編譯檔案

由于實作并發通路使用到線程函數 pthread_create 是以編譯時要在 -o 的前面加上 pthread這個函數的庫否則會報錯

通過c/c++在linux系統實作Mini型http伺服器的并發通路測試效果相關函數介紹程式測試源碼相關 html 資源檔案代碼總結

啟動伺服器端程式

通過c/c++在linux系統實作Mini型http伺服器的并發通路測試效果相關函數介紹程式測試源碼相關 html 資源檔案代碼總結

用戶端測試

在浏覽器網址欄 192.168.60.132/qiniu.html 前面是本機 ip 位址 後面通路的是 qiniu.html 這個檔案,這個檔案是設定的一個 html 格式的檔案用來顯示

用戶端使用并發測試,分别使用電腦的 谷歌浏覽器、火狐浏覽器、ubuntu下的telnet 和 Windows 下進行 ip 通路測試

火狐浏覽器測試 我們看到了打開了一個改伺服器網頁 伺服器網頁顯示的這個樣式與 這個qiniu.html 檔案有關

通過c/c++在linux系統實作Mini型http伺服器的并發通路測試效果相關函數介紹程式測試源碼相關 html 資源檔案代碼總結

伺服器響應

收到浏覽器的請求後 伺服器端列印 連接配接的用戶端ip 端口号 等相關資訊

通過c/c++在linux系統實作Mini型http伺服器的并發通路測試效果相關函數介紹程式測試源碼相關 html 資源檔案代碼總結

用戶端測試

使用谷歌浏覽器測試

通過c/c++在linux系統實作Mini型http伺服器的并發通路測試效果相關函數介紹程式測試源碼相關 html 資源檔案代碼總結

伺服器端的響應與上述火狐浏覽器相同

用戶端測試

Ubuntu 下的 telnet 測試

輸入 telnet 192.168.60.132 80 telnet 本機ip http 協定的 80端口

通過c/c++在linux系統實作Mini型http伺服器的并發通路測試效果相關函數介紹程式測試源碼相關 html 資源檔案代碼總結

用戶端成功連接配接後伺服器響應,同樣列印上述相關資訊

通過c/c++在linux系統實作Mini型http伺服器的并發通路測試效果相關函數介紹程式測試源碼相關 html 資源檔案代碼總結

說明,通過浏覽器通路伺服器,浏覽器就相當于是用戶端它内部有會對我們輸入的 ip 進行解析自動補全 http 協定的相關内容

相關函數介紹

pthread_create函數

建立一個新線程,并行的執行任務。

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

傳回值:成功:0; 失敗:錯誤号。

參數:

pthread_t:目前Linux中可了解為:typedef unsigned long int pthread_t;

參數1:傳出參數,儲存系統為我們配置設定好的線程ID

參數2:通常傳NULL,表示使用線程預設屬性。若想使用具體屬性也可以修改該參數。

參數3:函數指針,指向線程主函數(線程體),該函數運作結束,則線程結束。

參數4:線程主函數執行期間所使用的參數。

在一個線程中調用pthread_create()建立新的線程後,目前線程從pthread_create()傳回繼續往下執行,而新的線程所執行的代碼由我們傳給pthread_create的函數指針start_routine決定。start_routine函數接收一個參數,是通過pthread_create的arg參數傳遞給它的,該參數的類型為void *,這個指針按什麼類型解釋由調用者自己定義。start_routine的傳回值類型也是void *,這個指針的含義同樣由調用者自己定義。start_routine傳回時,這個線程就退出了,其它線程可以調用pthread_join得到start_routine的傳回值,以後再詳細介紹pthread_join。

pthread_create成功傳回後,新建立的線程的id被填寫到thread參數所指向的記憶體單元。

attr參數表示線程屬性,本節不深入讨論線程屬性,所有代碼例子都傳NULL給attr參數,表示線程屬性取預設值。

stat函數

stat函數

作用:傳回檔案的狀态資訊

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

int stat(const char *path, struct stat *buf);
   int fstat(int fd, struct stat *buf);
   int lstat(const char *path, struct stat *buf);
           

path:

檔案的路徑

buf:

傳入的儲存檔案狀态的指針,用于儲存檔案的狀态

傳回值:

成功傳回0,失敗傳回-1,設定errno

struct stat {

dev_t st_dev;

nlink_t   st_nlink;   /* number of hard links */
           uid_t     st_uid;     /* user ID of owner */
           gid_t     st_gid;     /* group ID of owner */
           dev_t     st_rdev;    /* device ID (if special file) */
           off_t     st_size;    /* total size, in bytes */
           blksize_t st_blksize; /* blocksize for filesystem I/O */
           blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
           time_t    st_atime;   /* time of last access */
           time_t    st_mtime;   /* time of last modification */
           time_t    st_ctime;   /* time of last status change */
       };
           

程式測試源碼

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include<string.h>
#include<errno.h>
#include<sys/stat.h>

#define SERVER_PORT 80

static int debug = 1;

void do_http_request(int client_sock);
int get_line(int sock,char *buf,int size);
void do_http_response1(int client_sock);
void do_http_response(int client_sock,const char*path);
int headers(int client_sock,FILE*resource);
void cat(int client_sock,FILE*resource);
void inner_error(int client_sock);
void not_found(int client_sock);
void unimplemented(int client_sock);

void bad_request(int client_sock);


int main(void){

    int sock;//代表信箱
    struct sockaddr_in server_addr;


    //1.美女建立信箱
    sock = socket(AF_INET, SOCK_STREAM, 0);

    //2.清空标簽,寫上位址和端口号
    bzero(&server_addr, sizeof(server_addr));

    server_addr.sin_family = AF_INET;//選擇協定族IPV4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//監聽本地所有IP位址
    server_addr.sin_port = htons(SERVER_PORT);//綁定端口号

    //實作标簽貼到收信得信箱上
    bind(sock, (struct sockaddr *)&server_addr,  sizeof(server_addr));

    //把信箱挂置到傳達室,這樣,就可以接收信件了
    listen(sock, 128);

    //萬事俱備,隻等來信
    printf("等待用戶端的連接配接\n");


    int done =1;

    while(done){
        struct sockaddr_in client;
        int client_sock, len, i;
        char client_ip[64];
        char buf[256];

        socklen_t  client_addr_len;
        client_addr_len = sizeof(client);
	printf("come here!\n");
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);

        //列印客服端IP位址和端口号
        printf("client ip: %s\t port : %d\n",
                 inet_ntop(AF_INET, &client.sin_addr.s_addr,client_ip,sizeof(client_ip)),
                 ntohs(client.sin_port));
        /*處理http請求,讀取用戶端的資料*/
		do_http_request(client_sock);
		close(client_sock);
        }


       


		close(sock);
		return 0;
}

void do_http_request(int client_sock){
	/*//1.讀取請求行
	int len = 0;
	char buf[256];
	do{
	len = get_line(client_sock,buf,sizeof(buf));
	printf("read line:%s\n",buf);
	}while(len>0);*/
	
	
	int len  = 0;
	char buf[256];
	char method[64];
	char url[256];
	
	char path[256];
	
	struct stat st;
	
	len = get_line(client_sock,buf,sizeof(buf));
	int i =0,j=0;
	if(len>0){//讀到請求行
	
		while(!isspace(buf[j])&&i<sizeof(method)-1){
			method[i] = buf[j];
			i++;
			j++;
			
		}
		
		method[i]='\0';
		if(debug) printf("request method:%s\n",method);
		
	
	//判斷method中的字元是否是 "GET"
	if(strncasecmp(method,"GET",i)==0){//字元串比較函數
		if(debug) printf("request method=GET\n");
		//擷取url
		
		//跳過前面get後面的空格
		while(isspace(buf[j++]));
		i = 0;
		
		while(!isspace(buf[j])&&(i<sizeof(url)-1)){
			url[i] = buf[j];
			i++;
			j++;
		}
		url[i]='\0';
		
		if(debug) printf("url:%s\n",url);
	
	//}
		do{
		len = get_line(client_sock,buf,sizeof(buf));
		if(debug) printf("read:%s\n",buf);
		
		}while(len>0);
	

	
	
			//處理url中的?号
		{
			char *pos =strchr(url,'?'); 
			if(pos)   *pos='\0';
			printf("real url:%s\n",url);
			
		}
		
		
		
		//sprintf(path,"./html_docs/%s",url);
		
		sprintf(path, "./html_docs/%s", url);
		if(debug) printf("path:%s\n",path);
	   
	   //存在響應html不存在響應Not FOUND
	   if(stat(path,&st)==-1){//失敗傳回-1
	   
	   fprintf(stderr,"stat :%s failed.reason:%s\n",path,strerror(errno));
	   not_found(client_sock);
		   
	   }else{//檔案存在
		if(S_ISDIR(st.st_mode)){
			strcat(path,"/index.html");
		}
		   do_http_response(client_sock,path);
		   // do_http_response1(client_sock);
		   
	   }
	   
	   
	   
        
	}
		
	else{//bad request
		//非get請求,讀取所有http頭部,并且響應用戶端501
			fprintf(stderr,"warrning!other request[%s]\n",method);
		do{
		len = get_line(client_sock,buf,sizeof(buf));
		if(debug) printf("read:%s\n",buf);
		
		}while(len>0);
		unimplemented(client_sock);//響應時實作
		
		
		}
	
	//繼續讀取http的頭部
	
	}else{//請求格式有問題,出錯處理
			// 響應時實作
		bad_request(client_sock);
		
	}
}

void bad_request(int client_sock){
	const char*reply= "HTTP/1.0 400 bad_request\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML zh-CN\">\r\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
	<HEAD>\r\n\
	<TITLE>Bad Requst</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
	<P>Bad Request!\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
	</BODY>\r\n\
	</HTML>";

	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);
	
	if(len<=0) fprintf(stderr,"inner reply failed.reason:%s\n",strerror(errno));
	
}

void inner_error(int client_sock){
	const char*reply= "HTTP/1.0 404 inner_error\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML zh-CN\">\r\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
	<HEAD>\r\n\
	<TITLE>NOT FOUND</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
	<P>伺服器内部出錯!\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
	</BODY>\r\n\
	</HTML>";

	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);
	
	if(len<=0) fprintf(stderr,"inner reply failed.reason:%s\n",strerror(errno));
	
	
}
void unimplemented(int client_sock){
	const char*reply= "HTTP/1.0 501 unimplemented\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML zh-CN\">\r\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
	<HEAD>\r\n\
	<TITLE>Mehod Not Implemented</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
	<P>unimplemented\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
	</BODY>\r\n\
	</HTML>";

	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);
	
	if(len<=0) fprintf(stderr,"send reply failed.reason:%s\n",strerror(errno));
	
}

void not_found(int client_sock){
	const char*reply= "HTTP/1.0 505 NOT FOUND\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML zh-CN\">\r\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
	<HEAD>\r\n\
	<TITLE>NOT FOUND</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
	<P>檔案不存在!\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
	</BODY>\r\n\
	</HTML>";

	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);
	
	if(len<=0) fprintf(stderr,"send reply failed.reason:%s\n",strerror(errno));
}

void do_http_response(int client_sock,const char*path){
	
	int ret =0;
	
	FILE *resource = NULL;//c語言檔案操作
	resource = fopen(path,"r");
	
	if(resource==NULL){
		not_found(client_sock);
		return ;
	}
	//1.發送http頭部
	ret = headers(client_sock,resource);
	
	//2.發送http body
	if(ret==0){
	cat(client_sock,resource);
	}
	
	fclose(resource);
	
}
//成功傳回0 失敗傳回-1
int headers(int client_sock,FILE*resource){
	
	struct stat st;
	int fileid = 0;
	char tmp[64];
	
	char buf[1024]={0};
	strcpy(buf,"HTTP/1.0 200 OK\r\n");
	strcat(buf,"Server: Martin Server\r\n");
	strcat(buf,"Content-Type: text/html\r\n");
	strcat(buf,"Connection: Close\r\n");
	
	fileid = fileno(resource);
	
	if(fstat(fileid,&st)==-1){//檔案已經打開但是通路檔案出錯
		
		inner_error(client_sock);//内部出錯
		return -1;
	}
		
	snprintf(tmp,64,"Content-Length:%ld\r\n\r\n",st.st_size);
	strcat(buf,tmp);
	
	if(debug) fprintf(stdout,"header:%s\n",buf);
	
	if(send(client_sock,buf,strlen(buf),0)<0){
		fprintf(stderr,"failed to send.reason:%s.buf:%s\n",strerror(errno),buf);
		return -1;
	}
	return 0;

}
/*******************
說明:實作将html檔案的内容按照行讀取并發送用戶端

**********************/


void cat(int client_sock, FILE *resource){
	char buf[1024];
	
    fgets(buf, sizeof(buf), resource);
	
	while(!feof(resource)){
		int len = write(client_sock, buf, strlen(buf));
		
		if(len<0){//發送body 的過程中出現問題,怎麼辦?1.重試? 2.
			fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
			break;
		}
		
		if(debug) fprintf(stdout, "%s", buf);
		fgets(buf, sizeof(buf), resource);
		
	}
}
void do_http_response1(int client_sock){
	//響應http
	const char *main_header = "HTTP/1.0 200 OK\r\nServer: Martin Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";
	const char *welcome_content = "\
	<html zh-CN\">\n\
	<head>\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
	<title>This is a test</title>\n\
	</head>\n\
	<body>\n\
	<div align=center height=\"500px\" >\n\
	<br/><br/><br/>\n\
	<h2>大家好!</h2><br/><br/>\n\
	<form action=\"commit\" method=\"post\">\n\
	尊姓大名: <input type=\"text\" name=\"name\" />\n\
	<br/>芳齡幾何: <input type=\"password\" name=\"age\" />\n\
	<br/><br/><br/><input type=\"submit\" value=\"送出\" />\n\
	<input type=\"reset\" value=\"重置\" />\n\
	</form>\n\
	</div>\n\
	</body>\n\
	</html>";
	
	//1. 送main_header
	char send_buf[64];
	write(client_sock,main_header,sizeof(main_header));
	
	int len = snprintf(send_buf,64,"%d\r\n\r\n",strlen(welcome_content));
	
	write(client_sock,send_buf,len);
	write(client_sock,welcome_content,strlen(welcome_content));

	//2. 生成Contantlenth
	
	//3. 發送html内容
}

int get_line(int sock,char *buf,int size){
	//讀一行,一個位元組一個位元組的讀每次讀一個位元組
	int count = 0;
	int len =0;
	char ch = '\0';
	
	
	while(count<(size-1)&&ch!='\n'){
		len = read(sock,&ch,1);
		if(len==1){
			if(ch=='\r'){
				continue;
				
			}else if(ch=='\n'){
				break;
				
			}
			
		buf[count] = ch;
		++count;
		}else if(len=-1){
		perror("read failed");
		count = -1;
		break;
			
		}else{//sock網絡關閉時也就是len=0讀0個位元組
		fprintf(stderr,"client close.\n");
		count = -1;
		break;
			
		}
		
		
	}
	if(count>=0) buf[count] = '\0';
	return count;
	

	
	
}

           

相關 html 資源檔案代碼

本檔案是上述浏覽器通路伺服器成功後看到的那個頁面效果,直接複制下面内容

注意編碼 我這裡使用的 帶 bom utf-8 沒有出現中文亂碼,有可能大家使用得是不帶 bom 的 <meta content=“text/html; charset=utf-8 bom” http-equiv=“Content-Type”>

這個檔案的位置,在目前檔案夾建立檔案 目錄 mkdir html_docs 進入這個目錄 建立一個名為 qiniu.html 的檔案将下面的 html 文本放入

<html zh-CN\">
<head>
<meta content=\"text/html; charset=utf-8 bom\" http-equiv=\"Content-Type\">
<title>This is a test</title>
</head>
<body>
<div align=center height=\"500px\" >
<br/><br/><br/>
<h2>Good guys! Welcome to China Service!</h2><br/><br/>
<form action="commit" method="post">
尊姓大名: <input type="text" name="name" />
<br/>芳齡幾何: <input type="password" name="age" />
<br/><br/><br/><input type="submit" value="送出" />
<input type="reset" value="重置" />
</form>
</div>
</body>
</html>

           

總結

本 demo 對上一個 demo 的回聲伺服器進行了改進,使用了線程函數 實作了一個 Mini 型的伺服器 功能上實作了能夠對并發通路進行處理,能夠處理一定并發的通路,但是上述 Mini 型的伺服器要在性能上無法滿足高并發的要求,需要繼續改進.