在linux下用多线程来构建web服务器.

来源:互联网 发布:首大耳鼻咽喉医院 知乎 编辑:程序博客网 时间:2024/05/18 01:20

//此为  这个博客主人写的http://blog.csdn.net/manio/article/details/1334558

//我将里边的内容进行整理,然后转换了一下,

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#define HOSTLEN 256
#define PORTNUM 12345
//#define DEBUG
int make_server_socket_q(int,int);
void error(char *err)
{
fprintf(stderr,"error detected %s :",err);
perror("");
exit(1);
}

//与下方函数进行组合处理,针对传递过来的链接请求,进行相对应的处理.
int make_server_socket(int port) //num for listen
{
return make_server_socket_q(port,1);
}

//被转换成为网络链接部分的函数.
int make_server_socket_q(int port,int backlog)
{
struct sockaddr_in saddr_in;
struct hostent *hp; /*惠普*/
char hostname[HOSTLEN];
int sock_id;
/* 获取一个socket*/
sock_id=socket(PF_INET,SOCK_STREAM,0);
if(sock_id==-1)
error("socket failed");

memset((void *)&saddr_in,0,sizeof(saddr_in));
gethostname(hostname,HOSTLEN);
hp=gethostbyname(hostname);
bcopy(hp->h_addr,(void *)&saddr_in.sin_addr,hp->h_length);
saddr_in.sin_port=htons(port);
saddr_in.sin_family=hp->h_addrtype;
/* 绑定 */
if(bind(sock_id,(struct sockaddr *)&saddr_in,sizeof(struct sockaddr_in))==-1)
{
close(sock_id);
error("bind failed");
}
/* 允许接入*/
if(listen(sock_id,3))//创建未处理的请求队列,给定队列最大数为3;
error("listen failed");
return sock_id;
}
/* 测试是否存在 */
int check_exist(char *filename)
{
FILE *fp;
if((fp=fopen(filename,"r"))==NULL)
return 0;
fclose(fp);
return 1;
}
/* 检测文件类型 */
char * file_type(char *filename)
{
char *p=filename;
char *ext=filename+strlen(filename);
while(ext>p&&*ext!='.')
ext--;
if(ext>p)
return ext+1;
return NULL;
}
/* 报文 头部 */
void do_head(int sock_fd,char *type)
{
char buffer[64];
sprintf(buffer,"HTTP/1.0 200 OK/r/n");
if(type)
sprintf(buffer+strlen(buffer),"Content-Type:%s/r/n",type);
write(sock_fd,buffer,strlen(buffer));//将输出进行读入到buffer中,不过此数据在linux有的博客中可以是图片数据.
}
/* 404 文件没找到 */
void do_nofile(char *filename,int sock_fd)
{
FILE *fp=fdopen(sock_fd,"w");
fprintf(fp,"HTTP/1.1 404 Not Found/r/n");
fprintf(fp,"Content-type:text/plain/r/n");
fprintf(fp,"/r/n");
fprintf(fp,"The file you requested: %s is not found/r/n",filename);
fclose(fp);
}
/* 处理其它命令 */
void do_unkown(int sock_fd)
{
FILE *fp=fdopen(sock_fd,"w");
fprintf(fp,"HTTP/1.0 501 Not Implement /r/n");
fprintf(fp,"Content-Type:text/plain/r/n");
fprintf(fp,"/r/n");
fprintf(fp,"That command is not yet implement/r/n");
fclose(fp);
}
/* 核心处理 从文件中读取 然后写入socket */
void do_show(char *arg,int sock_fd)
{
FILE *sock_fp,*file_fp;
char *extension=file_type(arg);//检查当前数据的类型,然后进行相对应的数据处理,/
char *content="text/plain";
char buf[BUFSIZ]={0};
char c;
if(!extension)
return ;
if(strcmp(extension,"html")==0)
content="text/html";
else if(strcmp(extension,"htm")==0)//这两者是网页的一种
content="text/htm";
else if(strcmp(extension,"gif")==0)
content="image/gif";
else if(strcmp(extension,"jpg")==0)
content="image/jpg";
else if(strcmp(extension,"png")==0)//这三者是图片类型数据
content="image/png";
else
content="text/plain"; //默认处理
sock_fp=fdopen(sock_fd,"w");
file_fp=fopen(arg,"rb");
if(file_fp!=NULL&&sock_fp!=NULL)
{
do_head(sock_fd,content);
while((fgets(buf,BUFSIZ,file_fp)))
fputs(buf,sock_fp);
fclose(file_fp);
fclose(sock_fp);
}
}
/* request处理 */
int process_rq(char *request,int sock_fd)
{
char cmd[16],arg[BUFSIZ],buf[BUFSIZ];

strcpy(arg,"./");
if(sscanf(request,"%s%s",cmd,buf)!=2)
return -1;
/* 下面是处理文件名 */
if(buf[0]=='.'&&buf[1]=='/')
strcpy(arg,buf);
else if(buf[0]=='/')
strcpy(arg+1,buf);
else
strcpy(arg+2,buf);
/* 目前只能支持get命令 */
if(!(strcmp(cmd,"GET")==0||strcmp(cmd,"get")==0))
do_unkown(sock_fd);
else if(!check_exist(arg))//传递给上边的函数让其判断其传递的数据类型是否存在,若存在则对其进行相应的操作.
do_nofile(arg,sock_fd);//若数据类型不存在或者找不到,则进行直接输出错误标识.
else
do_show(arg,sock_fd);//进行核心处理,从文件中读取,然后写入socket.
}
// 线程主函数,应数据类型不知道,这会使数据在传送过程中导致数据的丢失与无法访问,所以现将数据的类型转换完成后在对其进行相应的处理.此函数应该输出的是传递数据的
//类型.
void thread_routine(void *fd)
{
int sock_fd=*((int *)fd);
char request[BUFSIZ];
FILE *sock_fp=fdopen(sock_fd,"r");
fgets(request,BUFSIZ,sock_fp);
#ifdef DEBUG
printf("a call:request= %s/n",request);
#else
process_rq(request,sock_fd);
#endif
fclose(sock_fp);
}
int main(int argc,char *argv[])
{
int port;
int sock_id;
int sock_fd;

FILE *sock_fp;

pthread_t thread;
if(!(argc==1||argc==2))
{
printf("usage : %s port",argv[0]);
return -1;
}
//其连接方式应该为,每进行一次数据请求时进行一次数据的链接,在此数据请求中有while中的两个函数进行处理,一是提取请求中的队列,二是判断请求队列中数据类型然后
//进行相应的数据处理,与链接.
port=argc==1?PORTNUM:atoi(argv[1]); //注意这里如果只有一个参数,即是设置默认端口
if((sock_id=make_server_socket(port))==-1)//将链接部分合并成为一个函数进行,相应数据的处理.
return -1;
printf("now listen to the port #%d/n",port);
fflush(stdout);

//此处是在针对链接成功后的的服务器进行数据的发送,应为发送的数据可能为多种,若按照同一种处理方式会到时数据混乱,所有下边给定每个传送的数据进行整理判断
//然后进行相应的操作.
while(1)
{
sock_fd=accept(sock_id,NULL,NULL);//socket()通信中的函数,本函数从s的等待连接队列中抽取第一个连接,创建一个与s同类的新的套接口并返回句柄。如果队列
//中无等待连接,且套接口为阻塞方式,则accept()阻塞调用进程直至新的连接出现。如果套接口为非阻塞方式且队列中无等待连接,则accept()返回一错误代码。已
//接受连接的套接口不能用于接受新的连接,原套接口仍保持开放
sock_fp=fdopen(sock_fd,"r");//fdopen取一个现存的文件描述符,并使一个标准的I / O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数获得的
//描述符。因为这些特殊类型的文件不能用标准I/O fopen函数打开,首先必须先调用设备专用函数以获得一个文件描述符,然后用fdopen使一个标准I/O流与该描述符
//相结合。
pthread_create(&thread,NULL,(void *)thread_routine,(void *)&sock_fd); //开辟线程处理
fclose(sock_fp);
}

return 0;
}

0 0