TInyhttpd是一個簡單的http服務(wù)器的實現(xiàn),代碼總共有500多行,但是讀下來對http的具體實現(xiàn)過程和linux網(wǎng)絡(luò)編程的學(xué)習(xí)都很有好處。我重寫了代碼,然后進行了詳細(xì)的注釋。注釋見詳細(xì)代碼。?
具體的可以用圖表示:?
?
代碼:
/*#!/usr/bin/env
* ******************************************************
* Last modified: 2018-05-06 16:27
* Filename : tinyhttpd.c
* Description :
* ********************************************************/
#include<stdio.h>
#include<sys/wait.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/select.h>
#include<ctype.h>
#include<pthread.h>
#include<stdint.h>#define ISspace(x) isspace((int)(x))#define SERVER_STRING "Server:jdbhttpd/0.1.0\r\n"#define STDIN 0
#define STDOUT 1
#define STDERR 2void execute_cgi(int ,const char *,const char *,const char *);
void bad_requests(int );
void accept_request(void *);
int startup(u_short *);
void error_die(const char *);
void unimplemented(int );
void not_found(int );
void serve_file(int,const char*);
void headers(int,const char*);
void cat(int,FILE*);
void cannot_execute(int);
int get_line(int,char*,int );void error_die(const char *sc)
{perror(sc);exit(1);
}void accept_request(void *arg)
{int client = (intptr_t)arg;char buf[1024];size_t numchars;//保存請求方式char method[255];//保存請求urlchar url[255];//保存請求文件路徑char path[512];size_t i,j;struct stat st;//如果cgi=1 則執(zhí)行cgi程序int cgi = 0;char *query_string =NULL;numchars = get_line(client,buf,sizeof(buf));i = 0,j = 0;while(!ISspace(buf[i])&&(i<sizeof(method)-1)){//請求報文第一行就會說明請求方式,是否為GET或者POSTmethod[i] = buf[i];i++;}j = i;method[i] = '\0';//tinyhttpd只實現(xiàn)了GET和POSTif(strcasecmp(method,"GET")&&strcasecmp(method,"POST")){//tinyhttpd只實現(xiàn)了GET和POSTunimplemented(client);return;}if(strcasecmp(method,"POST")==0) cgi = 1;i = 0;//跨過空格while(ISspace(buf[j])&&(j<numchars)) j++;//得到請求的urlwhile(!ISspace(buf[j])&&(i<sizeof(url)-1)&&(j<numchars)){url[i] = buf[j];i++;j++;}url[i] = '\0';//對于GET請求,如果有攜帶參數(shù),則query_string指針指向url中?后面的GET參數(shù)if(strcasecmp(method,"GET")==0){query_string = url;while((*query_string!='?')&&(*query_string!='\0'))query_string++;if(*query_string=='?'){cgi = 1;*query_string = '\0';query_string++;}}sprintf(path,"htdocs%s",url);//如果path是一個目錄,默認(rèn)設(shè)置為首頁index.htmlif(path[strlen(path)-1]=='/') strcat(path,"index.html");if(stat(path,&st)==-1){while((numchars>0)&&strcmp("\n",buf)) numchars = get_line(client,buf,sizeof(buf));not_found(client);}else{//如果為目錄if((st.st_mode&S_IFMT)==S_IFDIR) strcat(path,"/index.html");if((st.st_mode&S_IXUSR)||(st.st_mode&S_IXGRP)||(st.st_mode&S_IXOTH)) cgi = 1;//返回靜態(tài)文件if(!cgi) serve_file(client,path);elseexecute_cgi(client,path,method,query_string);}close(client);
}void execute_cgi(int client,const char *path,const char *method,const char *query_string)
{char buf[1024];int cgi_input[2];int cgi_output[2];pid_t pid;int status;int i;char c;int numchars = 1;int content_length = -1;buf[0] = 'A';buf[1] = '\0';//讀取并且舍棄頭部信息if(strcasecmp(method,"GET")==0){while((numchars>0)&&strcmp("\n",buf))numchars = get_line(client,buf,sizeof(buf));}else{numchars = get_line(client,buf,sizeof(buf));//15是因為Content-Length長度為15while((numchars>0)&&strcmp("\n",buf)){buf[15] = '\0';//求得Content-content_length的長度if(strcasecmp(buf,"Content-Length:")==0)content_length = atoi(&(buf[16]));numchars = get_line(client,buf,sizeof(buf));}if(content_length==-1){bad_requests(client);return;}}if(pipe(cgi_output)<0){cannot_execute(client);return;}if(pipe(cgi_input)<0){cannot_execute(client);return;}//fork一個子進程if((pid = fork())<0){cannot_execute(client);return;}sprintf(buf,"HTTP/1.0 200 OK\r\n");send(client,buf,strlen(buf),0);//子進程執(zhí)行cgi,并將輸出傳到cgi_output[1]if(pid==0){char meth_env[255];char query_env[255];char length_env[255];//對標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出進行重定向dup2(cgi_output[1],STDOUT);dup2(cgi_input[0],STDIN);close(cgi_output[0]);close(cgi_input[1]);sprintf(meth_env,"REQUEST_METHOD=%s",method);putenv(meth_env);if(strcasecmp(method,"GET")==0){sprintf(query_env,"QUERY_STRING=%s",query_string);putenv(query_env);}else{sprintf(length_env,"CONTENT_LENGTH=%d",content_length);putenv(length_env);}execl(path,path,NULL);exit(0);}//父進程傳輸數(shù)據(jù)和把數(shù)據(jù)發(fā)送到瀏覽器else{close(cgi_input[0]);close(cgi_output[1]);if(strcasecmp(method,"POST")==0){for(i = 0;i<content_length;i++){recv(client,&c,1,0);write(cgi_input[1],&c,1);}}while(read(cgi_output[0],&c,1)>0) send(client,&c,1,0);close(cgi_output[0]);close(cgi_input[1]);waitpid(pid,&status,0);}
}void cannot_execute(int client)
{char buf[1024];sprintf(buf,"HTTP/1.0 500 Internal Server Error\r\n");send(client,buf,sizeof(buf),0);sprintf(buf,"Content-type: text/html\r\n");send(client,buf,sizeof(buf),0);sprintf(buf,"\r\n");send(client,buf,sizeof(buf),0);sprintf(buf,"<P>Error prohibited CGI execution. \r\n ");send(client,buf,sizeof(buf),0);
}void bad_requests(int client)
{char buf[1024];sprintf(buf,"HTTP/1.0 400 BAD REQUEST\r\n");send(client,buf,sizeof(buf),0);sprintf(buf,"Content-type: text/html\r\n");send(client,buf,sizeof(buf),0);sprintf(buf,"\r\n");send(client,buf,sizeof(buf),0);sprintf(buf,"<P>your browser senta bad request, ");send(client,buf,sizeof(buf),0);sprintf(buf,"such as a POST without a Content-Length.\r\n");send(client,buf,sizeof(buf),0);
}void headers(int client,const char* filename)
{char buf[1024];(void)filename;strcpy(buf,"HTTP/1.0 200 OK\r\n");send(client,buf,strlen(buf),0);strcpy(buf,SERVER_STRING);send(client,buf,strlen(buf),0);sprintf(buf,"Content-Type: text/html\r\n");send(client,buf,strlen(buf),0);strcpy(buf,"\r\n");send(client,buf,strlen(buf),0);
}void cat(int client,FILE *resource)
{char buf[1024];fgets(buf,sizeof(buf),resource);while(!feof(resource)){send(client,buf,strlen(buf),0);fgets(buf,sizeof(buf),resource);}
}void serve_file(int client,const char *filename)
{FILE *resource = NULL;int numchars = 1;char buf[1024];buf[0] = 'A';buf[1] = '\0';while((numchars>0)&&strcmp("\n",buf)) numchars = get_line(client,buf,sizeof(buf));resource = fopen(filename,"r");if(resource==NULL) not_found(client);else{//添加http頭部headers(client,filename);//并發(fā)送文件內(nèi)容cat(client,resource);}fclose(resource);
}void not_found(int client)
{char buf[1024];sprintf(buf,"HTTP/1.0 404 NOT FOUND\r\n");send(client,buf,strlen(buf),0);sprintf(buf,SERVER_STRING);send(client,buf,strlen(buf),0);sprintf(buf,"Content-Type: text/html\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"<HTML><TITLE>Not Found</TITLE>\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"<BODY><P>The server could not fulfill\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"your request because the resource specified\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"is unavailable or nonexistent.\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"</BODY></HTML>\r\n");send(client,buf,strlen(buf),0);}void unimplemented(int client)
{char buf[1024];sprintf(buf,"HTTP/1.0 501 Method Not implemented\r\n");send(client,buf,strlen(buf),0);sprintf(buf,SERVER_STRING);send(client,buf,strlen(buf),0);sprintf(buf,"Content-Type: text/html\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"<HTML><HEAD><TITLE>Method Not Implemented\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"</TITLE></HEAD>\r\n");send(client,buf,strlen(buf),0);sprintf(buf,"<BODY><P>HTTP request method not supported. \r\n");send(client,buf,strlen(buf),0);sprintf(buf,"</BODY></HTML>\r\n");send(client,buf,strlen(buf),0);
}//解析一行http報文
int get_line(int sock,char *buf,int size)
{int i = 0;char c = '\0';int n;while((i<size-1)&&(c!='\n')){//先取一個字節(jié)n = recv(sock,&c,1,0);if(n>0){//因為請求報文末尾是以\r\n結(jié)束的if(c=='\r'){//偷窺一個字節(jié),判斷是否為\n,是則結(jié)束n = recv(sock,&c,1,MSG_PEEK);if(n>0&&(c=='\n')) recv(sock,&c,1,0);else c = '\n';}buf[i] = c;i++;}else c = '\n';}buf[i] = '\0';return i;
}int startup(u_short *port)
{int httpd = 0;int on = 1;struct sockaddr_in name;httpd = socket(PF_INET,SOCK_STREAM,0);//新建一個服務(wù)器端socketif(httpd == -1) error_die("socket");memset(&name,0,sizeof(name));//對socket的基本屬性進行賦值name.sin_family = AF_INET;name.sin_port = htons(*port); //htons從主機字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序name.sin_addr.s_addr = htonl(INADDR_ANY);//setsockopt原型 int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t optlen)if((setsockopt(httpd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)//{error_die("setsockopt failed");}//對端口進行綁定if(bind(httpd,(struct sockaddr*)&name,sizeof(name))<0) error_die("bind");//動態(tài)分配一個端口if(*port==0){socklen_t namelen = sizeof(name);if(getsockname(httpd,(struct sockaddr*)&name,&namelen)==-1){error_die("getsockname");}*port = ntohs(name.sin_port);}//對socket進行監(jiān)聽//5代表內(nèi)核維護一個隊列跟蹤這些完成的連接但服務(wù)器還沒有接受處理//或者正在進行的連接,代表大小的上限if(listen(httpd,5)<0) error_die("listen");else printf("listen success\n");return httpd;
}int main(void)
{int server_sock = -1;u_short port = 0;int client_sock = -1;struct sockaddr_in client_name;socklen_t client_name_len = sizeof(client_name);pthread_t newthread;//對服務(wù)器端口號進行綁定server_sock = startup(&port);printf("httpd running on the port %d\n",port);while(1){//接受來自客戶端的連接client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);if(client_sock==-1) error_die("accept");if(pthread_create(&newthread,NULL,(void*)accept_request,(void*)(intptr_t)client_sock)!=0) //每次收到請求,創(chuàng)建一個線程來處理接受到的請求,把client_sock轉(zhuǎn)成地址參數(shù)//傳入pthread_createperror("pthread_create");}//關(guān)掉服務(wù)器端socketclose(server_sock);return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
然后寫命令./tinyhttpd執(zhí)行得到?
?
然后輸入紫色?
?
當(dāng)然在這個過程中有幾個問題:?
首先index.html的不能有執(zhí)行權(quán)限,還有后綴為.cgi的程序必須有執(zhí)行權(quán)限,可以使用chmod命令改變執(zhí)行權(quán)限。
然后使用wireshark對本地回環(huán)端口進行監(jiān)聽,然后追蹤tcp流。可以看到http的請求和應(yīng)答報文。
POST /color.cgi HTTP/1.1
Host: 127.0.0.1:36299
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:36299/
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
Connection: keep-alive
Upgrade-Insecure-Requests: 1color=pinkHTTP/1.0 200 OK
HTTP/1.0 200 OK
--------------10
Content-Type: text/html; charset=ISO-8859-1<!DOCTYPE htmlPUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>PINK</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body bgcolor="pink">
<h1>This is pink</h1>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。 https://blog.csdn.net/u014303647/article/details/80231844
總結(jié)
以上是生活随笔為你收集整理的Tinyhttpd的实现和一些基本问题的解决的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。