简易http服务器

     这个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
希望路人来不吝指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值