这个httpd主要用到网络编程、字符串处理、文件读写和线程同步几个方面,包括了svrthread.c、httpd.c和httpd.h三个文件,我放在网上等网友来指点。
一、先从网络开始:
//这里产生一个fd用于监听。
int listenfd,clientfd,sockopt_on=1;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if (-1 == listenfd)
{
perror("socket error!\n");
_exit(1);
}
if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&sockopt_on,sizeof(sockopt_on)))
{
//这个是辅助用的,推出地址可以立即再bind()
perror("setsockopet error\n");
}
//下面绑定本机网络,
sockaddr_in host_addr;
host_addr.sin_family = AF_INET;
host_addr.sin_port = htons(80); // http默认端口是:80
host_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY指本机所有网络端口
if (-1 == bind(listenfd, (sockaddr *)&host_addr, sizeof(sockaddr_in)))
{
close(listenfd);
perror("bind error!\n");
_exit(1);
}
if (-1 == listen(listenfd,5)) // 监听
{
close(listenfd);
perror("listen error!\n");
_exit(1);
}
然后用accept()函数等待并接受连接,并产生一个线程来处理事物。
sem_init(&semph, 0, 8); // 放个信号量来控制线程数量,我设置的是8个线程
sockaddr_in addr;
pthread_t svr_id;
int size_addr = sizeof(addr),ret;
for ( ; ; ) // 无线循环,如果没有意外程序可以一直运行
{
clientfd = accept(listenfd,(sockaddr *)&addr,(socklen_t *)&size_addr);
if (-1 == clientfd) {
perror("accept error!\n"); //客户端连接出错不用管他,继续accept()
continue;
}
ret = pthread_create(&svr_id, NULL, svrthread, (void*)clientfd);//这里(void*)clientfd在编译时会提示,我是偷懒,不过C语言的转换是允许的
if (0 != ret)
{
perror("create thread error!\n");
close(clientfd); //不能创建新线程,不知什么原因,我处理成推出!
break;
}
}
//主函数退出
_exit(0);
}
线程的过程函数svrthread尽量简单:
void* svrthread(void *p)
{
sem_wait(&semph); //等待获取信号量
int sock = (int)p; //这里就是上面说的偷懒
httpsvr(sock); //httpsvr()是处理的主要函数
close(sock); // 结束了就释放资源
<pre name="code" class="cpp"> pthread_detach(pthread_self()); //必须,不然线程占有的资源不能回收
sem_post(&semph); //释放信号量
return NULL;
}
二、然后就是处理申请的功能实现。
声明个结构体用来传递数据比较方便:
typedef struct fileinfo {
FILE *fp;
int size;
int is_open;
int map_index;
} fileinfo ;
在httpsvr()之前先看看几个函数。
1、这个是把请求的路径处理成本机的路径,就是字符串处理。
const char DEF_WEB_ROOT[] = "web/";
void fitpath(char *path, int size)
{
assert(NULL!=path);
assert(0<size);
char tmp[SIZE1KB];
strcpy(tmp, path);
int i, rootlen;
rootlen = sizeof(DEF_WEB_ROOT)-1;
if ('/'==tmp[0])
rootlen--;
for (i = 0; i < rootlen; i++)
path[i] = DEF_WEB_ROOT[i];
path[i] = '\0';
strcat(path, tmp);
if ('/'==tmp[strlen(tmp)-1])
strcat(path, DEFAULT_PAGE);
}
然后打开文件,并获取文件信息保存在fileinfo结构体中
2、下面是打开文件的
int open_file(fileinfo *f, const char path[])
{
assert(NULL!=f);
assert(NULL!=path);
f->fp = fopen(path, "r");
if (NULL==f->fp)
{
f->size = 0;
f->is_open = 0;
return 0;
}
fseek(f->fp, 0, SEEK_END);
f->size = ftell(f->fp);
rewind(f->fp);
f->is_open = 1;
这里本来结束了,但是我把mime类型处理一起放进去了,之前声明一个“表”,
要声明结构体:
typedef struct {
char name[16];
char type[64];
} file_type_map ;
和它的对象:
file_type_map typemap[TYPE_TOTAL]={
{.name="xsl",.type="application/xml"},
…… //此处省略很多行
{.name="htm",.type="text/html"},
{.name="html",.type="text/html"},
{.name="", .type=""}
};
然后接着:
const char *p = strrchr(path, '.');
if (NULL!=p)
{
p++;
int index;
for (index = 0; index < TYPE_TOTAL; index++)
{
if (0==strcasecmp(p,typemap[index].name))
break;
}
f->map_index = index;
}
return 1;
}
3、发送文件的函数,直接上代码
void send_file(int sock, fileinfo *f)
{
//把三种情况处理了
assert(-1!=sock);
assert(NULL!=f);
assert(1==f->is_open);
//用一个4K的缓冲区一直发送文件
char buff[SIZE4KB] = {0};
int ret =0;
for (;;)
{
if (feof(f->fp)) //到文件结束,退出
break;
ret = fread(buff, 1, sizeof(buff), f->fp);
if (ret <= 0) //读取文件失败,也退出
break;
ret = send(sock,buff,ret,0);
if (ret <= 0) //发送失败,退出处理
break;
}
}
4、响应和处理报头
创建并发送响应报头,用一个模板代替
const char SUCCED200[] = "200 OK";
const char headtemplate[] =
"HTTP/1.1 %s\n\
Date: Tue, 13 Aug 2013 15:15:15 GMT\n\
MyServer/1.0\n\
Content-Type: %s\n\
Content-Language: zh-cn\n\
Connection: Close\n\
Content-Length: %d\n\n";
int create_header( int sock, fileinfo *f )
{
assert(-1!=sock);
assert(NULL!=f);
char head[SIZE1KB] = {0};
char bf_type[64] = " ";
if (f->map_index<TYPE_TOTAL) //前面查询的MIME的信息插入报头中
strcpy(bf_type, typemap[f->map_index].type);
sprintf(head, headtemplate, SUCCED200, bf_type, f->size);
int ret = send(sock, head, strlen(head), 0);
return ret;
}
用一个函数来处理404和501两种情况,当然对应的文件要放进去
const char ERROR404_FILE[] = "web/ERROR_404.html";
const char ERROR501_FILE[] = "web/ERROR_501.html";
const char ERROR404[] = "404 Not Found";
const char ERROR501[] = "501 Not Implemented";
void http_error(int sock, int err_code)
{
assert(-1!=sock);
fileinfo fin;
if (404 == err_code)
{
if (open_file(&fin, ERROR404_FILE))
{
create_header(sock, &fin, 404);
send_file(sock, &fin);
}
}
else if (501 == err_code)
{
if (open_file(&fin, ERROR501_FILE))
{
create_header(sock, &fin, 501);
send_file(sock, &fin);
}
}
}
这里httpsvr(),调用其他函数,所有的函数,有点长了,往里放注释吧
int httpsvr(int sock)
{
assert(-1!=sock);
char buff[SIZE1KB] = {0};
// 接收数据,也就是请求
int ret = recv(sock, buff, sizeof(buff), 0);
if (0 >= ret)
return -1;
h_method req_svr;
int i_bf;
for (i_bf = 0; i_bf < sizeof(buff); i_bf++)
{
if (' '==buff[i_bf])
break;
}
// 查找请求方式,strncmp()可以处理好
if ( 0 == strncmp(buff, "GET", i_bf) )
req_svr = GET;
else if ( 0 == strncmp(buff, "POST", i_bf) )
req_svr = POST;
else
req_svr = OTHER;
// 本文只处理 GET 方式
if ( GET==req_svr )
{
// 根据报头格式来找请求的文件
char path[SIZE1KB];
int i_path;
i_bf++;
for (i_path = 0; i_path < sizeof(path); i_path++, i_bf++)
{
if (' '==buff[i_bf])
break;
path[i_path] = buff[i_bf];
}
path[i_path] = 0;
fitpath(path,SIZE1KB); // 调用fitpath()处理一下目录
fileinfo fin;
if (open_file(&fin, path)) //调用open_file()打开请求的文件
{
create_header(sock, &fin, 200); //先发送响应报头
send_file(sock, &fin); //紧接着发送文件内容
}
else
{
http_error(sock,404); //文件打开失败就发送404信息
}
}
else
{
http_error(sock,501); //不支持的请求
}
return 0;
}
。
一个可以用的程序是要把多种功能实现组织到一个,一个适用的项目最难的地方就算组织和规划,不然别谈实现。
下面粘贴完整的代码:
httpd.h
#ifndef _HTTPD_H_
#define _HTTPD_H_
#define NDEBUG
/***INCLUDE***/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
/***DEFINE***/
#define SIZE1KB 1024
#define SIZE4KB 4096
#define NEW(t,n) (t *)malloc(sizeof(t)*n)
/***TYPEDEF***/
typedef enum http_method{GET, POST, OTHER} h_method;
typedef struct sockaddr_in sockaddr_in;
typedef struct sockaddr sockaddr;
typedef struct fileinfo {
FILE *fp;
int size;
int is_open;
int map_index;
} fileinfo ;
typedef struct {
char name[16];
char type[64];
} file_type_map ;
/***ACTIONS***/
// fit path to web root
void fitpath(char *path, int size);
// open requested files
int open_file(fileinfo *f, const char path[]);
// send content from "read_file"
void send_file(int sock, fileinfo *f);
// create_header for respond
int create_header( int sock, fileinfo *f, int state );
// as the name
void http_error(int sock, int err_code);
// main serve function
int httpsvr(int sock);
/****/
#endif
httpd.c
#include "httpd.h"
#include <string.h>
#include <assert.h>
/********const values**********/
const char DEF_UP_ROOT[] = "upload/";
const char DEF_WEB_ROOT[] = "web/";
const char DEFAULT_PAGE[] = "index.html";
const char ERROR404_FILE[] = "web/ERROR_404.html";
const char ERROR501_FILE[] = "web/ERROR_501.html";
const char SUCCED200[] = "200 OK";
const char ERROR404[] = "404 Not Found";
const char ERROR501[] = "501 Not Implemented";
#ifdef TYPE_TOTAL
#undef TYPE_TOTAL
#endif
#define TYPE_TOTAL 75
const file_type_map typemap[TYPE_TOTAL]={
{.name="xsl",.type="application/xml"},
{.name="xml",.type="application/xml"},
{.name="log",.type="application/octet-stream"},
{.name="doc",.type="application/msword"},
{.name="bin",.type="application/octet-stream"},
{.name="dll",.type="application/octet-stream"},
{.name="exe",.type="application/octet-stream"},
{.name="pdf",.type="application/pdf"},
{.name="p7c",.type="application/pkcs7-mime"},
{.name="ai",.type="application/postscript"},
{.name="eps",.type="application/postscript"},
{.name="ps",.type="application/postscript"},
{.name="rtf",.type="application/rtf"},
{.name="fdf",.type="application/vnd.fdf"},
{.name="arj",.type="application/x-arj"},
{.name="gz",.type="application/x-gzip"},
{.name="class",.type="application/x-java-class"},
{.name="js",.type="application/x-javascript"},
{.name="lzh",.type="application/x-lzh"},
{.name="lnk",.type="application/x-ms-shortcut"},
{.name="tar",.type="application/x-tar"},
{.name="hlp",.type="application/x-winhelp"},
{.name="cer",.type="application/x-x509-ca-cert"},
{.name="zip",.type="application/zip"},
{.name="cab",.type="application/x-compressed"},
{.name="arj",.type="application/x-compressed"},
{.name="aif",.type="audio/aiff"},
{.name="aifc",.type="audio/aiff"},
{.name="aiff",.type="audio/aiff"},
{.name="au",.type="audio/basic"},
{.name="snd",.type="audio/basic"},
{.name="mid",.type="audio/midi"},
{.name="rmi",.type="audio/midi"},
{.name="mp3",.type="audio/mpeg"},
{.name="vox",.type="audio/voxware"},
{.name="wav",.type="audio/wav"},
{.name="ra",.type="audio/x-pn-realaudio"},
{.name="ram",.type="audio/x-pn-realaudio"},
{.name="bmp",.type="image/bmp"},
{.name="gif",.type="image/gif"},
{.name="jpeg",.type="image/jpeg"},
{.name="jpg",.type="image/jpeg"},
{.name="tif",.type="image/tiff"},
{.name="tiff",.type="image/tiff"},
{.name="xbm",.type="image/xbm"},
{.name="wrl",.type="model/vrml"},
{.name="htm",.type="text/html"},
{.name="html",.type="text/html"},
{.name="c",.type="text/plain"},
{.name="cpp",.type="text/plain"},
{.name="def",.type="text/plain"},
{.name="h",.type="text/plain"},
{.name="txt",.type="text/plain"},
{.name="rtx",.type="text/richtext"},
{.name="rtf",.type="text/richtext"},
{.name="java",.type="text/x-java-source"},
{.name="css",.type="text/css"},
{.name="mpeg",.type="video/mpeg"},
{.name="mpg",.type="video/mpeg"},
{.name="mpe",.type="video/mpeg"},
{.name="avi",.type="video/msvideo"},
{.name="mov",.type="video/quicktime"},
{.name="qt",.type="video/quicktime"},
{.name="shtml",.type="wwwserver/html-ssi"},
{.name="asa",.type="wwwserver/isapi"},
{.name="asp",.type="wwwserver/isapi"},
{.name="cfm",.type="wwwserver/isapi"},
{.name="dbm",.type="wwwserver/isapi"},
{.name="url",.type="wwwserver/isapi"},
{.name="php",.type="wwwserver/isapi"},
{.name="isa",.type="wwwserver/isapi"},
{.name="plx",.type="wwwserver/isapi"},
{.name="cgi",.type="wwwserver/isapi"},
{.name="wcgi",.type="wwwserver/isapi"},
{.name="", .type=""}
};
/********action*********/
// fit the path
void fitpath(char *path, int size)
{
assert(NULL!=path);
assert(0<size);
char tmp[SIZE1KB];
strcpy(tmp, path);
int i, rootlen;
rootlen = sizeof(DEF_WEB_ROOT)-1;
if ('/'==tmp[0])
rootlen--;
for (i = 0; i < rootlen; i++)
path[i] = DEF_WEB_ROOT[i];
path[i] = '\0';
strcat(path, tmp);
if ('/'==tmp[strlen(tmp)-1])
strcat(path, DEFAULT_PAGE);
}
// open file for getting
int open_file(fileinfo *f, const char path[])
{
assert(NULL!=f);
assert(NULL!=path);
f->fp = fopen(path, "r");
if (NULL==f->fp)
{
f->size = 0;
f->is_open = 0;
return 0;
}
fseek(f->fp, 0, SEEK_END);
f->size = ftell(f->fp);
rewind(f->fp);
f->is_open = 1;
const char *p = strrchr(path, '.');
if (NULL!=p)
{
p++;
int index;
for (index = 0; index < TYPE_TOTAL; index++)
{
if (0==strcasecmp(p,typemap[index].name))
break;
}
f->map_index = index;
}
return 1;
}
// send file to client
void send_file(int sock, fileinfo *f)
{
assert(-1!=sock);
assert(NULL!=f);
assert(1==f->is_open);
char buff[SIZE4KB] = {0};
int ret =0;
for (;;)
{
if (feof(f->fp))
break;
ret = fread(buff, 1, sizeof(buff), f->fp);
if (ret <= 0)
break;
ret = send(sock,buff,ret,0);
if (ret <= 0)
break;
}
}
// format and send create_header
const char headtemplate[] =
"HTTP/1.1 %s\n\
Date: Tue, 13 Aug 2013 15:15:15 GMT\n\
MyServer/1.0\n\
Content-Type: %s\n\
Content-Language: zh-cn\n\
Connection: Close\n\
Content-Length: %d\n\n";
int create_header( int sock, fileinfo *f, int state )
{
assert(-1!=sock);
assert(NULL!=f);
char head[SIZE1KB] = {0};
char bf_type[64] = " ";
if (f->map_index<TYPE_TOTAL)
strcpy(bf_type, typemap[f->map_index].type);
sprintf(head, headtemplate, SUCCED200, bf_type, f->size);
int ret = send(sock, head, strlen(head), 0);
return ret;
}
// error serve
void http_error(int sock, int err_code)
{
assert(-1!=sock);
fileinfo fin;
if (404 == err_code)
{
if (open_file(&fin, ERROR404_FILE))
{
create_header(sock, &fin, 404);
send_file(sock, &fin);
}
}
else if (501 == err_code)
{
if (open_file(&fin, ERROR501_FILE))
{
create_header(sock, &fin, 501);
send_file(sock, &fin);
}
}
}
// http main server
int httpsvr(int sock)
{
assert(-1!=sock);
char buff[SIZE1KB] = {0};
int ret = recv(sock, buff, sizeof(buff), 0);
if (0 >= ret)
return -1;
h_method req_svr;
int i_bf;
for (i_bf = 0; i_bf < sizeof(buff); i_bf++)
{
if (' '==buff[i_bf])
break;
}
if ( 0 == strncmp(buff, "GET", i_bf) )
req_svr = GET;
else if ( 0 == strncmp(buff, "POST", i_bf) )
req_svr = POST;
else
req_svr = OTHER;
if ( GET==req_svr )
{
char path[SIZE1KB];
int i_path;
i_bf++;
for (i_path = 0; i_path < sizeof(path); i_path++, i_bf++)
{
if (' '==buff[i_bf])
break;
path[i_path] = buff[i_bf];
}
path[i_path] = 0;
fitpath(path,SIZE1KB);
fileinfo fin;
if (open_file(&fin, path))
{
create_header(sock, &fin, 200);
send_file(sock, &fin);
}
else
{
http_error(sock,404);
}
}
else
{
http_error(sock,501);
}
return 0;
}
svrthread.c
#include "httpd.h"
#include <pthread.h>
#include <netinet/in.h>
#include <semaphore.h>
#include <unistd.h>
//typedef struct sockaddr_in sockaddr_in;
//typedef struct sockaddr sockaddr;
#define PORT 80
#define BACKLOG 5
//
sem_t semph;
//
void* svrthread(void *p)
{
sem_wait(&semph);
int sock = (int)p;
httpsvr(sock);
close(sock);
pthread_detach(pthread_self());
sem_post(&semph);
return NULL;
}
//
const int exitkey = 27;
void* checkexit(void *p)
{
int listenfd = *(int*)p;
while (exitkey!=getchar());
close(listenfd);
puts("server ended\n");
pthread_detach(pthread_self());
pthread_exit(0);
}
int main()
{
int listenfd,clientfd,sockopt_on=1;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if (-1 == listenfd)
{
perror("socket error!\n");
_exit(1);
}
if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&sockopt_on,sizeof(sockopt_on)))
{
perror("setsockopet error\n");
}
sockaddr_in host_addr;
host_addr.sin_family = AF_INET;
host_addr.sin_port = htons(PORT);
host_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (-1 == bind(listenfd, (sockaddr *)&host_addr, sizeof(sockaddr_in)))
{
close(listenfd);
perror("bind error!\n"); _exit(1);
}
if (-1 == listen(listenfd,BACKLOG))
{
close(listenfd);
perror("listen error!\n"); _exit(1);
}
sem_init(&semph, 0, 8);
printf("server start on: %d\n",PORT);
pthread_t chk;
pthread_create(&chk, NULL, checkexit, &listenfd);
sockaddr_in addr;
pthread_t svr_id;
int size_addr = sizeof(addr),ret;
for ( ; ; )
{
clientfd = accept(listenfd,(sockaddr *)&addr,(socklen_t *)&size_addr);
if (-1 == clientfd) {
perror("accept error!\n"); continue;
}
ret = pthread_create(&svr_id, NULL, svrthread, (void*)clientfd);
if (0 != ret)
{
perror("create thread error!\n");
close(clientfd);
break;
}
}
pthread_join(chk,NULL);
_exit(0);
}
编译命令:cc httpd.c svrthread.c -o svrth -lpthread
希望路人来不吝指教。