Tinyhttpd的实现和一些基本问题的解决
TInyhttpd是一个简单的http服务器的实现,代码总共有500多行,但是读下来对http的具体实现过程和linux网络编程的学习都很有好处。我重写了代码,然后进行了详细的注释。注释见详细代码。
具体的可以用图表示:
代码:
/*#!/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 则执行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只实现了GET和POSTif(strcasecmp(method,"GET")&&strcasecmp(method,"POST")){//tinyhttpd只实现了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请求,如果有携带参数,则query_string指针指向url中?后面的GET参数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是一个目录,默认设置为首页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;//返回静态文件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);//子进程执行cgi,并将输出传到cgi_output[1]if(pid==0){char meth_env[255];char query_env[255];char length_env[255];//对标准输入和标准输出进行重定向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);}//父进程传输数据和把数据发送到浏览器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);//并发送文件内容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')){//先取一个字节n = recv(sock,&c,1,0);if(n>0){//因为请求报文末尾是以\r\n结束的if(c=='\r'){//偷窥一个字节,判断是否为\n,是则结束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);//新建一个服务器端socketif(httpd == -1) error_die("socket");memset(&name,0,sizeof(name));//对socket的基本属性进行赋值name.sin_family = AF_INET;name.sin_port = htons(*port); //htons从主机字节序转换为网络字节序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");//动态分配一个端口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进行监听//5代表内核维护一个队列跟踪这些完成的连接但服务器还没有接受处理//或者正在进行的连接,代表大小的上限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;//对服务器端口号进行绑定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) //每次收到请求,创建一个线程来处理接受到的请求,把client_sock转成地址参数//传入pthread_createperror("pthread_create");}//关掉服务器端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执行得到
然后输入紫色
当然在这个过程中有几个问题:
首先index.html的不能有执行权限,还有后缀为.cgi的程序必须有执行权限,可以使用chmod命令改变执行权限。
然后使用wireshark对本地回环端口进行监听,然后追踪tcp流。可以看到http的请求和应答报文。
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
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014303647/article/details/80231844
Tinyhttpd的实现和一些基本问题的解决相关推荐
- php程序检测不到vc9,XAMPP2016中文精简版启动失败 缺少运行库解决办法
[摘要] XAMPP是一款强大的建站集成软件包,但XAMPP2016 中文精简版在启动的时候会出现"缺少运行库""程序检测不到VC9运行库"等问题,其实只需要正 ...
- 英威腾伺服驱动器故障代码_英威腾伺服电机常见问题及解决方法
本周为大家解析几点 英威腾伺服电机常见的基本问题以及解决方法 更多问题请大家留言哦! 会有专业工程师为您答疑解惑! 电机码报警(图文) 这个是客户使用英威腾伺服,常出现的一个报警代码.这是电机码设置不 ...
- MyBatis-Plus - 一篇带你解决自定义 SQL 注入器失效必杀技
问题分析 Invalid bound statement (not found) 如果你看到这一篇,说明你也是遇到这个问题的人(废话),我们在上一篇(MyBatis-Plus - 一篇带你玩转自定义 ...
- windows中Oracle服务连接不上错误排查及解决方法-实用
windows下Oracle连接不上问题排查思路 外部连接工具plsql,Navicat无法访问oracle数据库服务的排查思路 查看所访问oracle服务的所在服务器能否ping通,oracle服务 ...
- PyTorch攻势凶猛,程序员正在抛弃TensorFlow?
来源 | The Gradient 译者 | 夕颜 出品 | AI科技大本营(ID:rgznai100) 自 2012 年深度学习重新获得重视以来,许多机器学习框架便争相成为研究人员和行业从业人员的新 ...
- 预训练后性能反而变差,自训练要取代预训练了吗?
2020-07-18 13:53:03 编译 | JocelynWang 编辑 | 丛 末 早在2018年底,FAIR的研究人员就发布了一篇名为<Rethinking ImageNet Pre- ...
- SDN/NFV:现状,挑战和未来
信运营商和通信服务提供商(CSP)一直期待网络功能虚拟化(NFV)和软件定义网络(SDN)能够带来的优势,以帮助他们进入快速部署新服务,实现高度的网络自动化和动态重新配置的领域,从而降低资本支出/运营 ...
- 如何在linux下检测内存泄漏
1.开发背景 在 windows 下使用 VC 编程时,我们通常需要 DEBUG 模式下运行程序,而后调试器将在退出程序时,打印出程序运行过程中在堆上分配而没有释放的内存信息,其中包括代码文件名.行号 ...
- iOS pod init 报错
今天新建立项目加入pod,执行pod init报错,错误如下 [!] Oh no, an error occurred. Search for existing GitHub issues simil ...
最新文章
- 23张图!万字详解「链表」,从小白到大佬!
- BiLSTM+CRF的损失由发射矩阵和转移矩阵计算而得 BiLSTM+CRF命名实体识别:达观杯败走记(下篇
- C# 操作XML入门
- mysql四种事务隔离级别
- Linux项目自动化构建工具 make/Makefile
- python操作sqlite3 导入csv文件_[转载]SQLite 3导入导出成txt或csv操作
- linux make编译报错 mv,Linux下安装redis
- mybitsplus的idworker应java什么类型_MyBatisPlus学习整理(一)
- c oracle 32位64位,64位与32位编程的数据类型区别(C/C++)
- Atitit v2 ajax 最佳实践规范 标准化流程attilax总结 r34
- ArchLinux 主题美化
- rtklib-RINEX文件读取-rinex.c解析(一)
- 淘宝 阿里 数据库 内核月报—目录索引
- STM32,仿照LL库,编写FLASH的LL库(内有完成代码)(STM32F0)
- 自制模仿谷歌搜索UI的网页
- 弘辽科技:拼多多客单价怎么算?如何提高?
- Linux必备工具————虚拟机
- Codeforces 371 A,B,C
- 平衡小车c语言程序,【全部开源】两轮平衡小车(原理图、PCB、程序源码、BOM等)...
- 使用指针实现strcpy函数的功能
热门文章
- 制作 OpenStack Linux 镜像 - 每天5分钟玩转 OpenStack(151)
- DropDownlist的Item显示多列数据
- 鸿合一体机触屏没反应怎么办_无线鼠标没反应,我来教您无线鼠标没反应怎么办?...
- 满足什么条件的两个量才可以被分类?
- python pdf报告_Python实现html转换为pdf报告(生成pdf报告)功能示例
- mysql集群会备份数据吗_mysql ndb集群备份数据库和还原数据库的方法
- 1.11 超过人的表现-深度学习第三课《结构化机器学习项目》-Stanford吴恩达教授
- 1.8 为什么是人的表现-深度学习第三课《结构化机器学习项目》-Stanford吴恩达教授
- 关于stm32 hal 库 iic 一直是 busy 问题
- 【PC工具】文件夹多文件群体比较工具beyond compare4