文章目錄
- 測試效果
- 相關函數介紹
-
- 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
編譯檔案
由于實作并發通路使用到線程函數 pthread_create 是以編譯時要在 -o 的前面加上 pthread這個函數的庫否則會報錯
啟動伺服器端程式
用戶端測試
在浏覽器網址欄 192.168.60.132/qiniu.html 前面是本機 ip 位址 後面通路的是 qiniu.html 這個檔案,這個檔案是設定的一個 html 格式的檔案用來顯示
用戶端使用并發測試,分别使用電腦的 谷歌浏覽器、火狐浏覽器、ubuntu下的telnet 和 Windows 下進行 ip 通路測試
火狐浏覽器測試 我們看到了打開了一個改伺服器網頁 伺服器網頁顯示的這個樣式與 這個qiniu.html 檔案有關
伺服器響應
收到浏覽器的請求後 伺服器端列印 連接配接的用戶端ip 端口号 等相關資訊
用戶端測試
使用谷歌浏覽器測試
伺服器端的響應與上述火狐浏覽器相同
用戶端測試
Ubuntu 下的 telnet 測試
輸入 telnet 192.168.60.132 80 telnet 本機ip http 協定的 80端口
用戶端成功連接配接後伺服器響應,同樣列印上述相關資訊
說明,通過浏覽器通路伺服器,浏覽器就相當于是用戶端它内部有會對我們輸入的 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 型的伺服器要在性能上無法滿足高并發的要求,需要繼續改進.