精华内容
下载资源
问答
  • C语言搭建的简单web服务器

    热门讨论 2010-09-15 13:49:18
    用纯C语言编写的简单WEB服务器,实现网页的访问,和简单CGI功能。
  • 由于工作与学习的需要,笔者用C语言实现了一个简版的基于FTP协议的客户端,实现了诸如ls命令,cd命令,pwd命令,以及新建于删除目录,和上传本体文件/下载文件到本地的功能,功能较为简单,但对理解FTP的机制有不错...

    由于工作与学习的需要,笔者用C语言实现了一个简版的基于FTP协议的客户端,实现了诸如ls命令,cd命令,pwd命令,以及新建于删除目录,和上传本体文件/下载文件到本地的功能,功能较为简单,但对理解FTP的机制有不错的帮助。代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    //重定义socket地址类型
    typedef struct sockaddr* SP;
    
    //向服务器端发送指定序列
    int _send(int cli_sock,char* buf,size_t buf_size,\
    			char* ORDER,size_t ORDER_size)
    {
    	sprintf(buf,"%s",ORDER);		//将要发送给服务器的指令写入buf缓冲区
    	send(cli_sock,buf,strlen(buf),0);	//将buf内的指令发送给服务器
    }
    
    //接受服务器端反馈信息
    int _recive(int cli_sock, char* buf, size_t buf_size,size_t ret,size_t _ret)
    {
    	recv(cli_sock,buf,buf_size,0);		//从服务器端接受,接受内存容存入buf
    	printf("recv:%s\n",buf);
    	sscanf(buf,"%d",&ret);				//buf内的内容写入变量ret
    	if(ret != _ret)
    	{
    		printf("操作失败!请检查命令并重试!\n");
    		return EXIT_FAILURE;
    	}
    }
    
    //显示当前目录
    int _PWD(int cli_sock,char* buf,size_t buf_size,size_t ret)
    {
    	_send(cli_sock,buf,buf_size,"PWD\n",4);			//调用发送函数
    
    	_recive(cli_sock,buf,buf_size,ret,257);			//调用接受函数
    }
    
    //进入指定目录
    char* _CWD(int cli_sock,char* buf,size_t buf_size,size_t ret)
    {
    	static char path[20] = {};
    	scanf("%s",path);								//定义一个输入缓冲区并接收
    	sprintf(buf,"CWD %s\n",path);					//将输入缓冲区的内容写入buf
    	send(cli_sock,buf,strlen(buf),0);
    
    	_recive(cli_sock,buf,buf_size,ret,250);
    	return path;
    }
    
    //创建新目录
    int _MKD(int cli_sock,char* buf,size_t buf_size,size_t ret)
    {
    	char path[20] = {};								//原理同上
    	scanf("%s",path);
    	sprintf(buf,"MKD %s\n",path);
    	send(cli_sock,buf,strlen(buf),0);
    
    	_recive(cli_sock,buf,buf_size,ret,257);
    }
    
    //建立数据传输通道
    int _PASV(int cli_sock,char* buf,size_t buf_size,size_t ret,\
    			struct sockaddr_in cli_addr,size_t addrlen)
    {
    	_send(cli_sock,buf,buf_size,"PASV\n",5);		//向服务器发送PASV指令并接收返回值
    
    	_recive(cli_sock,buf,buf_size,ret,227);
    	
    	unsigned char ip1,ip2,ip3,ip4,port1,port2;
    	sscanf(buf,"227 Entering Passive Mode (%hhu,%hhu,%hhu,%hhu,%hhu,%hhu",&ip1,&ip2,&ip3,&ip4,&port1,&port2);    //抓取ip信息
    	char ip[16] = {};
    	sprintf(ip,"%hhu.%hhu.%hhu.%hhu",ip1,ip2,ip3,ip4);
    	short port = port1*256+port2;                                    //计算出端口信息
    	printf("ip=%s port=%hd\n",ip,port);
    
    	int cli_pasv = socket(AF_INET,SOCK_STREAM,0);                    //建立数据传输专用socket
    	if(0 > cli_pasv)
    	{
    		perror("socket");
    		return EXIT_FAILURE;
    	}
    	cli_addr.sin_port = htons(port);
    	cli_addr.sin_addr.s_addr = inet_addr(ip);
    
    	if(connect(cli_pasv,(SP)&cli_addr,addrlen))
    	{
    		perror("connect");
    		return EXIT_FAILURE;
    	}
    	return cli_pasv;
    }
    
    //查看当前目录详情列表
    int _LS(int cli_sock,char* buf,size_t buf_size,size_t ret,\
    			struct sockaddr_in cli_addr,size_t addrlen)
    {
    	int cli_pasv = _PASV(cli_sock,buf,buf_size,ret,cli_addr,addrlen);    //建立数据传输通道
    
    	_send(cli_sock,buf,buf_size,"LIST -al\n",9);                    //向服务器发送指令
    	char buf1[1000000] = {};
    	recv(cli_pasv,buf1,sizeof(buf1),0);
    	printf("%s\n",buf1);
    	close(cli_pasv);
    
    	bzero(buf,buf_size);
    	_recive(cli_sock,buf,buf_size,ret,150);
    	bzero(buf,buf_size);
    	_recive(cli_sock,buf,buf_size,ret,226);
    }
    
    //删除目录
    int _RMD(int cli_sock,char* buf,size_t buf_size,size_t ret,\
    				struct sockaddr_in cli_addr,size_t addrlen)
    {
    	char* arr = _CWD(cli_sock,buf,buf_size,ret);
    	_PWD(cli_sock,buf,buf_size,ret);
    
    	sprintf(buf,"RMD %s\n",arr);
    	send(cli_sock,buf,strlen(buf),0);
    	
    	_recive(cli_sock,buf,buf_size,ret,250);
    
    	_LS(cli_sock,buf,buf_size,ret,cli_addr,addrlen);
    }
    
    //上传
    int _STOR(int cli_sock,char* buf,size_t buf_size,size_t ret,\
    			struct sockaddr_in cli_addr,size_t addrlen)
    {
    	int cli_pasv = _PASV(cli_sock,buf,buf_size,ret,cli_addr,addrlen);
    
    	char path[20] = {};
    	scanf("%s",path);
    	sprintf(buf,"STOR %s\n",path);
    	send(cli_sock,buf,strlen(buf),0);
    	bzero(buf,buf_size);
    	_recive(cli_sock,buf,buf_size,ret,150);
    
    	int fd = open(path,O_RDONLY);                            //打开本地文件
    	if(0 > fd)
    	{
    		perror("open");
    		return EXIT_FAILURE;
    	}
    
    	while(ret = read(fd,buf,buf_size))                       //边读取边上传至服务器
    	{
    		send(cli_pasv,buf,ret,0);
    	}
    	
    	close(fd);
    	close(cli_pasv);
    
    	bzero(buf,buf_size);
    	_recive(cli_sock,buf,buf_size,ret,226);
    }
    
    //下载文件
    int _RETR(int cli_sock,char* buf,size_t buf_size,size_t ret,\
    			struct sockaddr_in cli_addr,size_t addrlen)
    {
    	char path[20] = {};
    	scanf("%s",path);
    	sprintf(buf,"SIZE %s\n",path);
    	send(cli_sock,buf,strlen(buf),0);
    
    	_recive(cli_sock,buf,buf_size,ret,213);
    
    	sprintf(buf,"MDTM %s\n",path);
    	send(cli_sock,buf,strlen(buf),0);
    
    	_recive(cli_sock,buf,buf_size,ret,213);
    
    	int cli_pasv = _PASV(cli_sock,buf,buf_size,ret,cli_addr,addrlen);
    
    	sprintf(buf,"RETR %s\n",path);
    	send(cli_sock,buf,strlen(buf),0);
    	
    	int fd = open(path,O_RDWR|O_CREAT|O_TRUNC, 0666);            //以写和创建的方式打开一个本地文件
    	if(0 > fd)
    	{
    		perror("open");
    		return EXIT_FAILURE;
    	}
    
    	size_t ret_size = 0;
    	
    	while(ret_size = recv(cli_pasv,buf,buf_size,0))            //边写入边下载
    	{
    		write(fd,buf,ret_size);
    	}
    
    	close(cli_pasv);
    
    	bzero(buf,buf_size);
    	_recive(cli_sock,buf,buf_size,ret,150);
    	bzero(buf,buf_size);
    	_recive(cli_sock,buf,buf_size,ret,226);
    }
    
    
    //结束ftp进程
    int _QUIT(int cli_sock,char* buf,size_t buf_size,size_t ret)
    {
    	_send(cli_sock,buf,buf_size,"QUIT\n",5);
    
    	_recive(cli_sock,buf,buf_size,ret,221);
    }
    
    //主函数
    int main(int argc,const char* argv[])
    {
    	int cli_sock = socket(AF_INET,SOCK_STREAM,0);
    	if(0 > cli_sock)
    	{
    		perror("socket");
    		return EXIT_FAILURE;
    	}
    
    	struct sockaddr_in cli_addr = {};
    	cli_addr.sin_family = AF_INET;
    	cli_addr.sin_port = htons(21);
    	cli_addr.sin_addr.s_addr = inet_addr("ip");            //输入你的目标ip
    	socklen_t addrlen = sizeof(cli_addr);
    
    	if(connect(cli_sock,(SP)&cli_addr,addrlen))
    	{
    		perror("connect");
    		return EXIT_FAILURE;
    	}
    
    	char buf[4096] = {};
    	size_t buf_size = sizeof(buf),ret = 0;
    
    	_recive(cli_sock,buf,buf_size,ret,220);
    	printf("连接服务器成功\n");
    
    	char name[20] = {};
    	printf("请输入用户名:");                            //向服务器端发送用户名
    	scanf("%s",name);
    	sprintf(buf,"USER %s\n",name);
    	send(cli_sock,buf,strlen(buf),0);
    
    	_recive(cli_sock,buf,buf_size,ret,331);             //验证用户名是否正确
    
    	char pass[20] = {};
    	printf("请输入密码:");                              //向服务器发送密码
    	scanf("%s",pass);
    	sprintf(buf,"PASS %s\n",pass);
    	send(cli_sock,buf,strlen(buf),0);
    	
    	_recive(cli_sock,buf,buf_size,ret,230);            //验证
    	printf("登录成功!\n");
    
    	_PWD(cli_sock,buf,buf_size,ret);
    
    	char arr[20] = {};
    	printf("Remote system type is UNIX.\nUsing binary mode to transfer files.\n");
    	for(;;)
    	{
    		printf("ftp>>>");
    		scanf("%s",arr);
    		if(0 == strcmp(arr,"pwd"))
    			_PWD(cli_sock,buf,buf_size,ret);                //pwd命令
    		if(0 == strcmp(arr,"cd"))
    			_CWD(cli_sock,buf,buf_size,ret);                //cd命令
    		if(0 == strcmp(arr,"mk"))
    			_MKD(cli_sock,buf,buf_size,ret);                //新建目录命令
    		if(0 == strcmp(arr,"rm"))
    			_RMD(cli_sock,buf,buf_size,ret,cli_addr,addrlen);    //删除目录命令
    		if(0 == strcmp(arr,"ls"))
    			_LS(cli_sock,buf,buf_size,ret,cli_addr,addrlen);     //ls命令
    		if(0 == strcmp(arr,"stor"))
    			_STOR(cli_sock,buf,buf_size,ret,cli_addr,addrlen);    //上传命令
    		if(0 == strcmp(arr,"retr"))
    			_RETR(cli_sock,buf,buf_size,ret,cli_addr,addrlen);    //下载命令
    		if(0 == strcmp(arr,"bye"))
    		{
    			_QUIT(cli_sock,buf,buf_size,ret);                    //bye命令
    			return 0;
    		}
    	}
    }

     

    展开全文
  • 如何搭建HTTP服务、网站?...使用C语言服务器 1.1 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <sys/socket.h> #i...

    如何搭建HTTP服务、网站?——最全的搭建HTTP服务、网站的教程:第一篇
    MacOS如何搭建网站:
    使用C语言服务器
    1.1
    注:请勿盲目拷贝(Copy)代码!
    部分请修改!!!
    大家可以使用便捷路径提供服务哦:
    当前目录:.
    当前目录下的某个文件夹:./(文件夹名称)/
    上级目录:…/
    上级目录的上级目录:…/…/

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <signal.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #define PORT /*侦听端口,请修改!若无法在80端口提供服务,请先终止apache服务器!*/		//监听的端口号
    #define MAX_CONNECT 128 	//最大连接数
    #define BUFFER_SIZE 1024*32 	//32k缓冲区满足绝大多数网页请求
    #define NAME_BUFFER_SIZE 1024	//路径缓冲 
    #define HTTP_END "\r\n\r\n"		//http协议头以两个回车换行结尾
    #define BASE_DIR "/*网站路径(请修改!!!)*/"	//服务器根目录设为当前程序运行路径
    #define DEFAULT_PAGE "/index.html"	//默认页面
    #define HTTP_OK "HTTP/1.1 200 OK\r\n\r\n"	//http正常回应
    
    
    static void *doAccept(void * parm);           
    
    //做协议解析 
    static void *doProc(void *data);
    
    int writeFile(int fd,char *filePath);
    
    int main()
    {
    	pthread_t thread;
    	printf("start!\n");
    	printf("按回车键退出!\n");
    	bzero(&thread,sizeof(thread));
    	pthread_create(&thread,NULL,doAccept,NULL);
    	getchar();
    	printf("end!\n");
    	return 0;
    }
    
    static void *doAccept(void * parm)
    {
    	int socketfd,connectfd;
    	pthread_t thread;
    	struct sockaddr_in serverAddr;
    	pthread_t pthread;
    	signal(SIGCHLD,SIG_IGN);
    	signal(SIGPIPE,SIG_IGN);
    	socketfd = socket(AF_INET,SOCK_STREAM,0);
    	if(socketfd == -1)
    	{
    		printf("创建套接字失败!\n");
    	}
    	else
    	{
    		printf("创建套接字成功!\n");
    		bzero(&serverAddr,sizeof(serverAddr));
    		serverAddr.sin_family=AF_INET;
    		serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    		serverAddr.sin_port = htons(PORT);
    		if( -1==bind(socketfd,(struct sockaddr*)&serverAddr,sizeof(struct sockaddr)) )
    		{
    			//FIXME 绑定端口失败时发生段错误! 
    			printf("绑定端口失败!\n");
    		}
    		else
    		{
    			printf("绑定端口成功!\n");
    			if(-1==listen(socketfd,MAX_CONNECT))
    			{
    				printf("创建监听失败!\n");
    			}
    			else
    			{
    				printf("创建监听成功!\n");
    				printf("在浏览器中输入http://127.0.0.1:%d来访问本服务器\n",PORT);
    				while(1)
    				{
    					//sleep(1);
    					socklen_t socketLen=0;
    					connectfd = accept(socketfd,(struct sockaddr*)&serverAddr,&socketLen);
    					if(connectfd<=0)
    					{
    						printf("接受连接失败!\n");
    					}
    					else
    					{
    						printf("接受连接成功!\n");
    						bzero(&thread,sizeof(thread));
    						pthread_create(&thread,NULL,doProc,(void*)connectfd);
    					}
    				}
    			}
    		}
    		
    	}
    	return NULL;
    }
    
    //做协议解析 
    static void *doProc(void *data)
    {
    	int connectfd = (int)data;
    	char *buffer = (char *)malloc(BUFFER_SIZE+1);
    	if(buffer==NULL)
    		printf("malloc return NULL!");
    	int readLen=read(connectfd,buffer,BUFFER_SIZE);
    	if(readLen<=0)
    		goto END;
    	buffer[readLen]=0;
    	//printf("%s",buffer);
    	int endLen = strlen(HTTP_END);
    	if( (readLen>=endLen) && (strcmp(buffer+readLen-endLen,HTTP_END)==0) )
    	{//是http协议
    		printf("%s",buffer);
    		char *startPos=strstr(buffer,"GET ");
    		
    		if(startPos!=NULL)
    		{
    			startPos+=4;
    			char *endPos=strstr(startPos," ");
    			if(endPos!=NULL&&(endPos-startPos)<NAME_BUFFER_SIZE)
    			{
    				char *nameBuffer=malloc(NAME_BUFFER_SIZE+1);
    				if(nameBuffer==NULL)
    					printf("malloc return NULL!");
    				nameBuffer[0]=0;
    				strcat(nameBuffer,BASE_DIR);
    				strncat(nameBuffer,startPos,endPos-startPos);
    				if(nameBuffer[strlen(nameBuffer)-1]=='/')
    				{
    					printf("is_dir,use default page.\n");
    					strcat(nameBuffer,DEFAULT_PAGE);
    				}
    				/*
    				//判断路径是否为文件夹,貌似不安全 
    				struct stat st;
    				stat(nameBuffer,&st);
    				if (S_ISDIR(st.st_mode))
    				{
    					printf("is_dir,use default page.\n");
    					strcat(nameBuffer,DEFAULT_PAGE);
    				}*/
    				printf("GET:%s\n",nameBuffer);
    				writeFile(connectfd,nameBuffer);
    				free(nameBuffer);
    			}
    		}
    	}
    END:
    	//printf("end doProc\n");
    	free(buffer);
    	close(connectfd);
    	return NULL;
    }
    
    int writeFile(int fd,char *filePath)
    {
    	//printf("write file:%s\n",filePath);
    	int fileHandler=open(filePath,0,O_RDONLY);
    	if(fileHandler<=0)
    		return -1;
    	char *buffer=(char *)malloc(BUFFER_SIZE+1);
    	if(buffer==NULL)
    		printf("malloc return NULL!");
    	write(fd,HTTP_OK,strlen(HTTP_OK));
    	int readLen=0;
    	while( 0<(readLen=read(fileHandler,buffer,BUFFER_SIZE)) )
    	{
    		write(fd,buffer,readLen);
    	}
    	
    	free(buffer);
    	close(fileHandler);
    	return 0;
    }
    

    使用apache服务器核心
    1.1

    打开apache服务器
    如何打开:

    	sudo apachectl start
    

    如何关闭:

    	sudo apachectl stop
    

    如何重启:

    sudo apachectl restart
    

    1.2
    测试访问:
    1.localhost

    2.127.0.0.1

    3.0.0.0.0
    如果访问提示It’s Works!
    则第一步成功了。
    2.1
    放入一个个网页文件:
    apache服务器的默认目录是/Library/WebServer/Documents
    大家按下command、Shift和+
    输入/Library/WebServer/Documents
    再回车即可打开/Library/WebServer/Documents
    注意:若您需要向/Library/WebServer/Documents放入网页,
    请打开终端,输入sudo chmod 777 /Library/WebServer/Documents
    或者在放入网页时输入密码。
    好了,本文结束,希望能帮到您。
    下期预告:配置httpd.conf修改错误代码,如404、403等,并且修改网站路径。

    展开全文
  • 一.void HttpServerStart(const char* ip,short port )来启动服务器 分为如下几步 1.创建socket 2.绑定端口号 3.监听 4.进入事件循环,当每次有新的连接的时候创建一个新线程,使accept被快速调用到,调用...

    基于TCP协议

    HTTP协议解析请求,构造响应

     

    用命令行参数来传入参数

    一.void   HttpServerStart(const char* ip,short port )来启动服务器

    分为如下几步

      1.创建socket

      2.绑定端口号

      3.监听

      4.进入事件循环,当每次有新的连接的时候创建一个新线程,使accept被快速调用到,调用越频繁的话性能越高

      在线程入口函数ThreadEntry()中,对HTTP协议解析请求,构造相应,完成主体的业务逻辑

    分为如下步骤

         1.解析请求

      (1)按行读socket读出HTTP请求的首行,分别解析方法,和URL,注意的从socket中读取一行数据,一个一个字符的读,如果当前字符是航分割符

    。就认为这一行读完了,而不同的浏览器的航分割符不尽相同,有\n  \r  \r\n   做法是把他们都归结为\n处理,上述函数实现的行为是读出的>一行末尾带\n,int  ReadLine(int new_sock,char  line[])

        (2)解析首行,把空格换成\0.用一组指针记录切分出的每个部分。应切出三个部分。分别为方法,URL,版本号   ParseFirstLine(char first_line[],char ** p_method,char ** p_url)

     (3)解析URL,以问好为分割符左边就是url_path右边就是query_string ,int ParseUrl(char  url[],char **  p_url_path,char **  p_query_string )

       (4) 解析Header,在我的程序里只处理了Centend_length,注意当遇到了\n 说明处理完成

     

         2.构造响应

        (1) 处理页面找不到void  Handler404(int new_sock) 并发出

     (2)处理静态页面 (本质是在url_path中存在的页面,和服务其上的这个路经是匹配的,注意这个路径看起来是一个绝对路径,但其实是一个相

    对路径,是相对于HTTP服务器的根路径,把允许对外访问的文件集中放入某个目录下,作为服务器的跟目录,我的服务器使用./wwwroot 作为根目

     录,所以可以看到url_path=>/index.html其实访问的就是./wwwroot/index.html) 并发出int HandlerStaticFile(const   HttpRequest*  req>,int   new_sock)

         ((1))拼接目录(根据url_path构造出当前文件在文件系统上的真实路径,,如果用户传的是一个目录,那么会有这个目录下的默认文件供人访

    >问,这时需要用系统提供的API,也就造成了不可跨平台的问题。)GetFilePath (const char*url_path,char   file_path[])

         ((2))打开文件,读取文件内容,根据文件内容构造HTTP响应,将相应内容写入sock中。其中文件的内容就叫做HTTP相应的body部分,这时需要

    先把数据从内核到用户(读),又从用户到内核(写),系统开销较大,于是我们用ssize_t    sendfile (int  out_fd,int in_fd,off_t  *offset,size_t  count)来优化,这个函数的操作全都在内核中,这里out_fd必须是socket,当我们发现乱码时大部分都是编解码不一致造成的,只是>可以打开浏览器随意找一个包,把这个包中header部分的编解码信息加入我们要发送的信息中。另外说明的一点是当客户端发起一次静态页面的请

    求时,浏览器不一定只发一次请求,如果发现还有链接信息时,或其他路径的信息时,会继续发起请求,比如我的服务器中请求静态页面时就会发

    起两次请求,一次显示Hello world,一次显示猫的图片,WriteStaticFile(const  char * file_path,int new_sock) ,写成功返回200,写失

    败返回404

    (3)处理动态页面  并发出(使用CGI技术.这时一个协议)

        协议的内容是

       ((1))HTTP服务器需要创建子进程

       ((2))子进程进行程序替换到磁盘上的某个可执行程序,这里需要把HTTP的请求方法,query_string(GET) ,body(POST),因为要进行

    程序替换,程序替换虽然不改变PCB,但是要替换了代码段数据段,堆栈上内存从新分配,所以前两个要用环境变量出入子进程,而body用匿名管>道传输。最后要把构造好的动态页面通过匿名管道传回子进程。因为管道是单向通信所以要用两个管道,又因为创建管道其实也就是要创建两个变>量,程序替换之后,也就找不到了,但是管道的文件描述符还是在的,为了可以不通过这两个已经访问不到的变量访问文件,我们在进行程序替换

    之前,把输入输出定向导管道上。

        也就是说在这一步应做的是如下几点

        ((1))先创建一对匿名管道

        ((2))创建子进程

        ((3))父进程执行父进程的相关逻辑(在最开始关掉不用的文件描述符)

             int HanderCGIFather(const HttpRequest*  req,int new_sock,int father_read,int father_write)

            (((1)))父进程把HTTP请求的body部分写到管道中

            (((2)))父进程尝试读取子进程(进程等待)

            (((3)))父进程构造HTTP响应,写会客户端

               (  (  (4)   )   )父进程回收子进程

         ((4))子进程执行子进程的相关逻辑(在最开始关掉不用的文件描述符)

            (((1)))设定环境变量,只可在子进程中设置(子进程的环境变量相互独立)。

            (((2)))进行重定向,标准输入输出重定向到管道上。

            (((3)))根据url_path构造出CGI程序的路径。

            (((4)))进行程序替换了(一旦进行程序替换,代码就执行到了CGI程序内部,也就是进入了CGI程序的内部)

    3.把相应返回到客户端上

     

    一般用sprintf ()函数构造字符串

     

    这个项目中我将会做一个计算器来测试我的动态页面,其中提交数据是静态页面,返回结果为动态页面

    后面是这个自制服务器的缺点;

    CGI经典协议的问题在于性能问题。核心在于CGI协议中要求创建子进程。

    改进方法,基于线程池的思想出现了一个改进版本FastCGI当前主流的HTTP服务器和应用程序交互的协议之一。

     

     

    下面是我的代码,为了让大家看清楚服务器的根路径与虚拟机的根路径,我会把每个源代码文件的路径都加上

    /home/a/Desktop/Mycode/http_server/http_server.c

    
      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <pthread.h>
      4 #include <stdlib.h>
      5 #include <unistd.h>
      6 #include <sys/socket.h>
      7 #include <netinet/in.h>
      8 #include <arpa/inet.h>
      9 #include <sys/stat.h>
     10 #include <fcntl.h>
     11 #include <sys/sendfile.h>
     12 #include <sys/wait.h>
     13 #include "http_server.h"
     14 
     15 
     16 typedef struct   sockaddr sockaddr;
     17 typedef struct   sockaddr_in sockaddr_in ;
     18 
     19 
     20 
     21 //一次从socket中读取以行数据
     22 //把数据放在buf缓冲区中
     23 //如果读取失败,返回值就是-1;
     24 //遇到\n或\r\n或\r证明读取完成
     25 int ReadLine(int  sock,char  buf[],ssize_t  size){
     26         //1.从socket中一次读取一个字符
     27         char  c='\0';
     28         ssize_t   i=0;//当前读了多少个字符
     29         //结束条件;
     30         //a)读到的长度太长,达到了缓冲区的上限
     31         //b)读到了结束标志,并把所有结束标字都转换成\n
     32 
     33         while(i<size-1&&c!='\n'){
     34                 ssize_t  read_size=recv(sock,&c,1,0);
     35                 //读取失败有两种
     36                 if(read_size<0){
     37                         return -1;
     38                 }
     39                 if(read_size==0){
     40                         //因为预期是要读到\n这样的换行符,
     41                         //结果还没有读得到就先读了EOF,这种情况我们也暂时认为是失败的
     42                         return -1;
     43                 }
     44                 if(c=='\r')
     45                 {
     46                         //当前遇到了\r,但还需要确定下一个字符是不是\n
     47                         //MSG_PEEK选项从内核的缓冲区中读取数据,
     48                         //但是读到的数据不会从缓冲区中删除掉。
     49                         recv(sock,&c,1,MSG_PEEK);
     50                         if (c=='\n'){
     51                                 //此时的分隔符就是\r\n
     52                                 recv(sock,&c,1,0);
     53                         }else
     54                         {
     55                                 //当前分隔符确定是\r,此时把分隔符转换成\n
     56                                 c='\n';
     57                         }
     58                 }
     59                 //只要上面的c读到的是\r,那么if结束后,c都变成了\n
     60                 //这种方式就是把前面的\r和\r\n两种情况都统一成了\n
     61                 buf[i++]=c;
     62 
     63         }
     64         buf [i]='\0';
     65         //  printf ("ReadLine\n");
     66         return i;//真正想缓冲区中放置的字符的个数
     67 }
     68 
     69 
     70 int Split(char  input[], const char *split_char,char* output[],int output_size)
     71 {
     72         //使用strtok
     73         int i=0;
     74         char *tmp=NULL;//保存上次的切分结果
     75         char * pch;
     76         //使用线程安全的strtok_r代替strtok
     77         //这一点是以后非常容易出错的一点
     78         pch = strtok_r (input,split_char,&tmp);
     79         while (pch != NULL)
     80         {
     81                 if (i>=output_size)
     82                 {
     83                         return i;
     84                 }
     85                 output[i++]=pch;
     86                 pch = strtok_r (NULL, split_char,&tmp);
     87         }
     88 
     89         //printf ("Split\n");
     90         return i;
     91 
     92 }
     93 
     94 int ParseFirstLine(char  first_line[],char**p_url,char  **p_method)
     95 {
     96         //把首行按空格进行字符串切分
     97         //切分得到的每一个部分,就放在tok数组里
     98         //返回值,就是tok数组包含几个元素。
     99 
    100         char  *tok[10];
    101         //最后一个参数10表示tok数组中最多可以放几个元素
    102         int tok_size=Split(first_line," ",tok,10);
    103         if (tok_size!=3)
    104         {
    105                 printf ("Split  failed!   tok_size =%d\n",tok_size);
    106                 return -1;
    107         }
    108         *p_method=tok[0];
    109         *p_url=tok[1];
    110 
    111         // printf ("ParseFirstLine\n");
    112         return 0;
    113 }
    114 
    115 
    116 
    117 int  ParseQueryString (char  *url,char  **p_url_path,char **p_query_string )
    118 {
    119         *p_url_path=url;
    120         char *p=url;
    121         for (;*p!='\0';++p)
    122         {
    123                 if (*p=='?')
    124                 {
    125                         *p='\0';
    126                         *p_query_string =p+1;
    127                         return 0;
    128                 }
    129         }
    130         //循环结束都没有找到?,说明这个请求不带query_string
    131         *p_query_string=NULL;
    132         // printf ("ParseQueryString\n");
    133         return 0;
    134 }
    135 
    136 
    137 
    138 int    ParseHeader (int sock ,int *content_length)
    139 {
    140         //1.循环从socket中读取一行。
    141         //2.判断当前行是不是Content—Length
    142         //3.如果是Content-Length就直接把value读出来
    143         //4.如果不是就直接丢弃
    144         //5.读到空行,循环结束。
    145         char buf [SIZE]={0};
    146         while (1)
    147         {
    148                 //1.循环从socket中读取一行。
    149                 ssize_t  read_size=ReadLine (sock,buf,sizeof (buf));
    150                 //处理读失败的情况
    151                 if (read_size<=0)
    152                 {
    153                         return -1;
    154                 }
    155                 //处理读完的情况
    156                 if (strcmp (buf ,"\n")==0)
    157                 {
    158                         return 0;
    159                 }
    160                 //2.判定当前行是不是Content-Length
    161                 //如果是Content-Length就直接把value读出来
    162                 //如果不是就直接丢弃
    163                 const  char *content_length_str="Content-Length: ";
    164                 if (content_length!=NULL&&strncmp(buf,content_length_str,strlen(content_length_str))==0)
    165                 {
    166                         *content_length=atoi(buf+strlen(content_length_str));
    167 
    168                 }
    169 
    170 
    171         }
    172         return 0;
    173         //读到空行循环结束
    174 }
    175 
    176 
    177 
    178 void Handler404(int sock)
    179 {
    180         //构造一个完整的HTTP响应
    181         //状态码是404
    182         //body部分也应该是一个404相关的错误页面。
    183         const char*  first_line="HTTP/1.1 404 Not Found\n";
    184         const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    185         const char *  blank_line="\n";
    186         const char * html="<head><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"></head>"
    187                 "<h1>您的页面被喵星人吃掉了!!!</h1>";//提示浏览器按照utf8方式解码
    188         send (sock,first_line ,strlen(first_line),0);
    189         send (sock,type_line ,strlen(type_line),0);
    190         send (sock,blank_line ,strlen(blank_line),0);
    191         send (sock,html ,strlen(html),0);
    192         return;
    193 }
    194 
    195 
    196 
    197 void PrintRequest (Request*req)
    198 {
    199         printf ("method: %s\n",req->method);
    200         printf ("url_path: %s\n",req->url_path);
    201         printf ("query_string: %s\n",req->query_string);
    202         printf ("content_length: %d\n",req->content_length);
    203         return;
    204 }
    205 
    206 
    207 
    208 int IsDir(const char *file_path)
    209 {
    210         struct stat st;
    211         int ret=stat(file_path,&st);
    212         if (ret<0)
    213         {
    214                 return 0;
    215         }
    216         if (S_ISDIR(st.st_mode))
    217         {
    218                 return 1;
    219         }
    220         return 0;
    221 }
    222 
    223 
    224 void HandlerFilePath(const  char*url_path,char  file_path[])
    225 {
    226         //(a,给url_path 加上前缀名(HTTP服务器的根目录)
    227         //url_path-->/index.html
    228         //file_path--->./wwwroot/index.html
    229         sprintf (file_path,"./wwwroot%s",url_path);
    230         //b)例如url_path是/,此时url_path 其实就是一个目录。
    231         //如果是目录的话,就演给这个目录之中追加一个index.html 
    232         //url_path /或者 /image/
    233         if (file_path[strlen(file_path)-1]=='/')
    234         {
    235                 strcat(file_path,"index.html");
    236         }
    237         //url_path=>/image
    238         if (IsDir(file_path))
    239         {
    240                 strcat (file_path,"/index.html");
    241         }
    242         return ;
    243 
    244 }
    245 
    246 
    247 
    248 ssize_t  GetFileSize(const char*  file_path)
    249 {
    250         struct stat  st;
    251         int ret=stat(file_path,&st);
    252         if (ret<0)
    253         {
    254                 //打开文件失败,很可能是文件不存在
    255                 //此时直接返回文件长度为0
    256                 return  0;
    257         }
    258         return st.st_size;
    259 }
    260 
    261 
    262 
    263 
    264 int WriteStaticFile (int sock,const char*  file_path)
    265 {
    266         //1.打开文件
    267         int fd=open (file_path,O_RDONLY);
    268         if (fd<0)
    269         {
    270                 //文件描述符不够用
    271                 //文件不存在(404)不好意思找不到
    272                 perror ("open");
    273                 return 404;
    274         }
    275         //2.把构造出来的HTTP响应写到socket之中
    276         //1).写入首行
    277         //2).写入head
    278         //3).写入空行
    279         //4).写入body(文件内容)
    280         const char *  first_line="HTTP/1.1 200 OK\n";
    281         send (sock,first_line,strlen(first_line ),0);
    282         //const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    283         //const char*  type_line="Content-Type: image/png;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    284         //send (sock,type_line ,strlen(type_line),0);
    285         //两个都不添,让浏览器自己决定
    286         const char *  blank_line="\n";
    287         send (sock,blank_line ,strlen(blank_line),0);
    288         /*ssize_t  file_size=GetFileSize(file_path);
    289           ssize_t  i=0;
    290           for(;i<file_size;i++)
    291           {
    292           char c;
    293           read(fd,&c,1);
    294           send (sock,&c,1,0);
    295           }
    296           */
    297         sendfile(sock,fd,NULL,GetFileSize(file_path));
    298         //3.关闭文件
    299         close (fd);
    300         return 200;
    301 }
    302 
    303 
    304 
    305 int HandlerStaticFile (int sock ,Request *req)
    306 {
    307         //1根据url_path获取到文件在服务器上的真实路径。
    308         char  file_path[SIZE]={0};
    309         HandlerFilePath(req->url_path,file_path);
    310         //2。读取文件,把文件的内容直接写道socket之中
    311         int err_code=WriteStaticFile (sock,file_path);
    312         return err_code;
    313 }
    314 
    315 int    HandlerCGIFather(int new_sock,int   father_read,int father_write,int  child_pid,Request  *req){
    316         //1如果是POST请求,就把body写到管道中
    317         if (strcasecmp(req->method,"POST")==0){
    318                 int i=0;
    319                 char  c='\0';
    320                 for (;i<req->content_length;++i){
    321                         read(new_sock,&c,1);
    322                         write(father_write,&c,1);
    323                 }
    324         }
    325         //2构造HTTP响应
    326         const char *  first_line="HTTP/1.1 200 OK\n";
    327         send (new_sock,first_line,strlen(first_line ),0);
    328         const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    329         //const char*  type_line="Content-Type: image/png;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
    330         send (new_sock,type_line ,strlen(type_line),0);
    331         //两个都可以不添(长度和类型),让浏览器自己决定
    332         const char *  blank_line="\n";
    333         send (new_sock,blank_line ,strlen(blank_line),0);
    334         //循环的从管道中读取数据并写入数据到socket
    335         char  c='\0';
    336         while (read(father_read,&c,1)>0){
    337         //从管道中读数据,如果(父进程,子进程)所有写端关闭,read将会读到EOF,从而返回0
    338                 send(new_sock,&c,1,0);
    339         }
    340         //4回收子进程的资源
    341         waitpid(child_pid,NULL,0);
    342         return 200;
    343 }
    344 
    345 
    346 int   HandlerCGIChild(int child_read,int child_write,Request*  req){
    347         //1.设置必要的环境变量
    348         char   method_env[SIZE]={0};
    349         sprintf (method_env,"REQUEST_METHOD=%s",req->method);
    350         putenv(method_env);
    351         //还需要设置QUERY_STRING或者是CONTENT_LENGTH
    352         if(strcasecmp(req->method,"GET")==0){
    353                 char  query_string_env[SIZE]={0};
    354                 sprintf (query_string_env,"QUERY_STRING=%s",req->query_string);
    355                 putenv(query_string_env);
    356 
    357         }else {
    358                 char content_length_env[SIZE]={0};
    359                 sprintf (content_length_env,"CONTENT_LENGTH=%d",req->content_length);
    360                 putenv(content_length_env);
    361 
    362         }
    363         //2把标准输入输出重定向到管道里
    364         dup2(child_read,0);
    365         dup2(child_write,1);
    366         //3对子进程进行程序替换
    367         //  url_path: /cgi-bin/test
    368         //  file_path: ./wwwroot/cgi-bin/test
    369         char   file_path[SIZE]={0};
    370         HandlerFilePath(req->url_path,file_path);
    371 
    372         execl(file_path,file_path,NULL);
    373         exit(1);//当程序替换失败时。一定让子进程终止,如果子进程不终止,因为父子进程是同一块代码,父进程一直在Listen状态一直等待,子进程也会等待,所以要直接结束进程,避免一直等待端口数据的返回;
    374 //有  exec   l    lp  le   v   vp   ve,首先看知不知道可执行文件的完整路径 ,如有就可以不带P,因为P是在PATH中找,再看要不要环境变量,如果不用,就不需要带e,这里因为通过putenv ()的方式直接设到了ENV里面所以不用,说一直接用execl()
    375         return 200;
    376 }
    377 
    378 
    379 
    380 int HandlerCGI(int  new_sock,Request  *req)
    381 {
    382         int err_code=200;
    383         //1.创建一对匿名管道
    384         int fd1[2],fd2[2];
    385         int ret =pipe(fd1);
    386         if (ret<0)
    387         {
    388                 return 404;
    389         }
    390         ret=pipe (fd2);
    391         if (ret<0)
    392         {
    393                 close(fd1[0]);
    394                 close(fd1[1]);
    395                 return 404;
    396         }
    397         //fd1,fd2这种变量名的描述性太差,后面直接用的话
    398         //是非常容易弄混的,所以直接在此处定义几个
    399         //更加明确的变量名来描述该文件描述符的用途
    400         int   father_read=fd1[0];
    401         int   child_write=fd1[1];
    402         int   father_write=fd2[1];
    403         int   child_read=fd2[0];
    404         //2.创建子进程
    405         ret=fork();
    406         //3.父子进程各执行不同的逻辑
    407         if (ret>0){
    408 
    409                 //father
    410                 //此处父进程优先关闭这两个管道的文件描述符
    411                 //是为了后续父进程从子进程这里读数据时,能够读到EOF,对于管道来说,所有写端关闭,继续读,才有EOF,而此时所有写端,一方面是父进程需要关闭,另一方面子进程也需要关闭。所以此处父进程先关闭不必要的写端之后,后续子进程用完了
    412                 //直接关闭,父进程也就读到了EOF
    413                 close (child_read);
    414                 close(child_write);
    415                 err_code=HandlerCGIFather(new_sock,father_read,father_write,ret,req);
    416                 close(father_read);
    417                 close(father_write);
    418         }else if (ret==0){
    419 
    420                 //child
    421                 close (father_read);
    422                 close (father_write);
    423                 err_code=HandlerCGIChild(child_read,child_write,req);
    424                 //此处代码写上也执行不到,所以不用写close文件描述符
    425 
    426         }else{
    427                 perror("fork");
    428                 close (fd1[0]);
    429                 close (fd1[1]);
    430                 close (fd2[0]);
    431                 close (fd2[1]);
    432                 return  404;
    433         }
    434         //4.收尾工作和错误处理
    435         return 200;
    436 }
    437 
    438 
    439 
    440 void  HandlerRequest(int new_sock)
    441 {
    442         int err_code=200;//错误码初始200,默认没错
    443         //1.读取并解析请求(反序列化)
    444         Request req;
    445         memset (&req,0,sizeof (req));
    446         //a)从socket中读取出首行
    447         if (ReadLine(new_sock,req.first_line,sizeof (req.first_line))<0)
    448         {
    449                 //失败处理
    450                 err_code =404;
    451                 goto END;
    452         }
    453         //b)解析首行,从首行中解析出url和method
    454         if (ParseFirstLine(req.first_line,&req.url,&req.method))
    455         {
    456                 //失败处理
    457                 err_code =404;
    458                 goto END;
    459 
    460         }
    461         //c)解析url,从url中解析出url_path和query_string
    462         if (ParseQueryString(req.url,&req.url_path,&req.query_string))
    463         {
    464                 //失败处理
    465                 err_code =404;
    466                 goto END;
    467 
    468         }
    469         //d)解析Header,丢弃了大部分header,只读取Content—Length
    470         if (ParseHeader(new_sock,&req.content_length))
    471         {
    472                 //失败处理
    473                 err_code =404;
    474                 goto END;
    475 
    476         }
    477 
    478 
    479         PrintRequest (&req);
    480         //2.静态/动态方式生成页面
    481         //3.把生成结果写回客户端上
    482         if (strcasecmp(req.method,"GET")==0&&req.query_string==NULL)
    483         {
    484                 //a)如果是GET方式请求,并且没有query_string,
    485                 //那么返回静态页面
    486                 err_code=HandlerStaticFile(new_sock,&req);
    487         }else  if (strcasecmp(req.method,"GET")==0&&req.query_string!=NULL)
    488         {
    489                 //b)如果是GET方式请求,并且有query_string,
    490                 //那么返回动态页面
    491                 err_code=HandlerCGI(new_sock,&req);
    492         }else  if (strcasecmp(req.method,"POST")==0)
    493         {
    494                 //c)如果请求是POST类型的(一定是带参的,参数是通过body来传给服务器的),那么也返回动态页面
    495                 err_code=HandlerCGI(new_sock,&req);
    496         }else
    497         {
    498                 //失败处理
    499                 err_code =404;
    500                 goto END;
    501         }
    502 
    503         //错误处理;直接返回一个404的HTTP响应
    504 END:
    505         if (err_code !=200)
    506         {
    507                 Handler404(new_sock);
    508         }
    509         close(new_sock);
    510         return;
    511 
    512 }
    513 
    514 
    515 
    516 
    517 void *ThreadEntry(void*arg)//   因为在64位机中指针占8个字节,如果强转回int会有丢失数据的危险
    518 {
    519         int64_t new_sock=(int64_t )arg;
    520         //使用HandlerRequest完成具体的处理请求过程
    521         //相当于线程入口函数只有一个包装,真正干活的是这个函数,这样最大的好处还是解耦和
    522         //一旦需要把服务器改成多进程或者IO多路复用的形式
    523         //代码的改动都是比较小的
    524         HandlerRequest(new_sock);
    525         return NULL;
    526 }
    527 
    528 
    529 
    530 
    531 //服务器启动
    532 void HttpServerStart(const  char*  ip,short   port)
    533 {
    534         int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    535         if (listen_sock<0)
    536         {
    537                 perror("socket");
    538                 return ;
    539         }
    540         int opt=1;
    541         setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof (opt));//加上这个函数是为了使端口处于TIME-WAIT时复用地址
    542         sockaddr_in addr;
    543         addr.sin_family=AF_INET;
    544         addr.sin_addr.s_addr=inet_addr(ip);
    545         addr.sin_port=htons(port);
    546 
    547         int ret=bind(listen_sock,(sockaddr*)&addr,sizeof (addr));
    548         if (ret<0)
    549         {
    550                 perror("bind");
    551                 return ;
    552         }
    553         ret=listen(listen_sock,5);
    554         if (ret<0)
    555         {
    556                 perror("listen");
    557                 return ;
    558         }
    559         printf ("ServerInit  OK\n");
    560         while (1)
    561         {
    562 
    563                 sockaddr_in peer;
    564                 socklen_t  len=sizeof (peer);
    565                 printf ("3\n");
    566                 int64_t   new_sock=accept(listen_sock,(sockaddr*)&peer,&len);
    567                 printf ("2\n");
    568                 if (new_sock<0)
    569                 {
    570                         perror("accept");
    571                         continue;
    572                 }
    573                 //使用多线程的方式来实现TCP服务器
    574                 pthread_t  tid;
    575                 printf ("ThreadEntry\n");
    576                 pthread_create(&tid,NULL,ThreadEntry,(void *)new_sock);
    577                 pthread_detach(tid);
    578         }
    579 
    580 }
    581 
    582 
    583 
    584 
    585 //主函数的参数   ./http_server  [ip]   [port]
    586 int main (int argc,char*   argv[])
    587 {
    588         if (argc!=3)
    589         {
    590                 printf ("Usage   ./http_server  [ip]   [port]\n");
    591                 return 1;
    592         }
    593         HttpServerStart( argv[1],atoi(argv[2]));
    594         return 0;
    595 }
    
    
    
                                                                                                                                                                                                                                                                             
    
    
    
    
    
    
    
    
    
                                                                                                                                                                                                                                                                          
    
                                                                                                                                                                                                                                                                             
    
    
                                                                                                                                                                                            
                                                                                                                                                                                   
    
    

    /home/a/Desktop/Mycode/http_server/http_server.h

      1 #pragma  once
      2 
      3 #define SIZE   10240
      4 //我们需要一个结构体来解析http协议请求
      5 typedef  struct   Request
      6 {
      7 //解析首行
      8 char first_line[SIZE];
      9 char *method;// [SIZE];//方法
     10 char *url;//[SIZE]; //URL
     11 //char  *version;//[SIZE];//版本号,在这里我们暂时用不上
     12 char *url_path;//带层次的文件路径,重点关注的对象内容1
     13 char *query_string;//查询字符串,重点关注的内容2
     14 //接下来解析header部分。
     15 //此处需要使用二叉搜索树或hash表
     16 //这里我们偷懒一下,其他的header都不要了,只保留一个Content——Length
     17 int content_length;
     18 }Request;
     19 /*typedef  struct   Request
     20 {
     21 char first_line[SIZE];//首行
     22 char method [SIZE];//方法
     23 char url[SIZE]; //URL
     24 char version[SIZE];//版本号
     25 char url_path[SIZE];//带层次的文件路径
     26 char query_string[SIZE];//查询字符串
     27 }Request;
     28 */

    /home/a/Desktop/Mycode/http_server/Makefile

      1 http_server:http_server.c
      2         gcc  $^  -o  $@  -lpthread
      3 
      4 
      5 .PHONY:clean
      6 clean:
      7         rm  http_server

    /home/a/Desktop/Mycode/http_server/wwwroot/index.html

    <h1>Hello   world</h1>
    <img  src="/image/1.png">

    /home/a/Desktop/Mycode/http_server/wwwroot/Makefile  

    select:select.c
            gcc  $^  -o  $@  -L  /usr/lib64/mysql  -lmysqlclient
    .PHONY:clean
    clean:
            rm select

    /home/a/Desktop/Mycode/http_server2/wwwroot/calc/calc.c

    
    
      1 //实现一个用来简单计算的CGI程序
      2 
      3 
      4 
      5 
      6 
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <unistd.h>
     10 #include <string.h>
     11 #define  SIZE  (1024*10)
     12 
     13 
     14 void  GetQueryString(char  output[]){
     15         //按照CGI的协议来实现此处的逻辑
     16         //1.先获取到方法
     17         char* method=getenv("REQUEST_METHOD");
     18         if (method==NULL){
     19                 //没有获取到环境变量
     20                 fprintf(stderr,"REQUEST_METHOD  filed\n");
     21                 return ;
     22         }
     23         if (strcmp(method,"GET")==0){
     24                 //获取QUERY_STRING
     25                 char *query_string =getenv("QUERY_STRING");
     26                 if (query_string==NULL){
     27                         fprintf(stderr,"QUERY_STRING  failed\n");
     28                         return ;
     29                 }
     30                 strcpy(output,query_string);
     31         }else{
     32 //post
     33 //获取CONTENT_LENGTH
     34 char*  content_length_env=getenv("CONTENT_LENGTH");
     35 if (content_length_env==NULL){
     36 fprintf(stderr,"CENTENT_LENGTH   failed\n");
     37 return ;
     38 }
     39 int content_length=atoi(content_length_env);
     40 int i=0;
     41 for (;i<content_length;++i){
     42 char  c='\0';
     43 read(0,&c,1);
     44 output[i]=c;
     45 }
     46 return ;
     47 }
     48 }
     49 
     50 int main (){
     51         //1.基于CGI协议获取到需要的参数
     52         char  query_string[SIZE]={0};
     53         GetQueryString(query_string);
     54         //2.根据业务逻辑进行计算
     55         //此时时获取到的QUERY_STRING的形式如下
     56         //a=10&b=20
     57         int a=0;
     58         int b=0;
     59         sscanf(query_string ,"a=%d&b=%d\n",&a,&b);
     60         int sum =a+b;
     61         //3.把结果构造成HTML写回到标准输出
     62         printf ("<html><h1>sum=%d</h1></html>",sum);
     63         return 0;
     64 }
    
    

    /home/a/Desktop/Mycode/http_server2/wwwroot/calc/index.html

    
      4 <html>
      5 <form action="/calc/calc"method="POST">
      6 a:<br>
      7 <input type="text" name="a" >
      8 <br>
      9 b:<br>
     10 <input type="text" name="b" >
     11 <br><br>
     12 <input type="submit" value="Submit">
     13 </form>
     14 </html>
    

    /home/a/Desktop/Mycode/http_server2/wwwroot/calc/Makefile

      1 calc:calc.c
      2         gcc  $^  -o  $@
      3 
      4 .PHONY:clean
      5 clean:
      6         rm  calc

    /home/a/Desktop/Mycode/http_server2/wwwroot/image/1.png 这是一张图片

    /home/a/Desktop/Mycode/http_server2/wwwroot/mysql/select.c

      1 //本程序基于MySQL  API完成对数据库的查询操作
      2 //不管用户输入什么,都尝试把TestTable这个表里的所有数据都查询出来,展示到以页面上
      3 //注意这个CGI存在的问题是,如果访问时不加参数会当成要下载的静态页面,所以访问时应该在后面加上一个参数虽然我们不用,原因是我们前面判断是动态页面还是静态页面的///根据以下步骤1,先看方法,Get方法的QUERY_STRING是否为空,为空时静态页面>    ,不为空时,是动态页面,post请求直接是动态页面
      4 //改进通过stat函数判断是否是可执行程序。
      5 
      6 
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <mysql/mysql.h>
     10 
     11 
     12 int main (){
     13         //1.获取到CGI程序需要的相关参数(此处可以不获取)
     14         //2.调用MySQL  API 来访问和操作数据库
     15         //(a)创建一个MySQL的连接句柄
     16         MYSQL * connect_fd=mysql_init(NULL);
     17         //(b)和数据库服务器建立连接
     18         if (mysql_real_connect(connect_fd,"192.168.0.104","root"," ","TestDB",3306,NULL,0)==NULL){
     19                 fprintf(stderr,"mysql_real_connect  failed\n");
     20                 return 1;
     21         }
     22         fprintf(stderr,"mysql_real_connect  ok\n");
     23         //(c)构造SQL语句,此处不需要加分号,但在SQL语句时是要加的
     24         const char* sql="select *from TestTable";
     25                 //(d)把构造的SQL语句发上给Mysql 服务器,并执行
     26                 int ret=mysql_query(connect_fd,sql);
     27         if (ret<0){
     28                 fprintf(stderr,"mysql_query  failed\n");
     29                 return 1;
     30         }
     31         //(e)解析和遍历返回结果()结果是一个表格
     32         MYSQL_RES*  result=mysql_store_result(connect_fd);
     33         //先获取行数和列数
     34         int rows=mysql_num_rows(result);
     35         int fields=mysql_num_fields(result);
     36         //获取表头(每列的意思)
     37         printf(<html>\n);
     38         printf("rows=%d,fields=%d<br>\n",rows,fields);
     39         MYSQL_FIELD*  field=mysql_fetch_field(result);//一次只取一列的表头
     40         while (field!=NULL){
     41                 printf("%s\t",field->name );
     42                 field=mysql_fetch_field(result);//取下一列的表头
     43         }
     44         printf("<br>");
     45         //按行获取表的具体内容
     46         int i=0;
     47         for (;i<rows;i++){
     48                 MYSQL_ROW  row=mysql_fetch_row(result);//每调用一次就取一行
     49                 int j=0;
     50                 for (;j<fields;++j){
     51                         printf("%s\t",row[j]);
     52                 }
     53                 printf ("<br>");
     54         }
     55         printf("</html>\n");
     56         //(f)断开连接
     57         mysql_close(connect_fd);
     58         //3。把获取的数据组织成html 返回给客户端
     59         return ;
     60 }
    

    基于HTTP服务器实现的业务:

    (1)用户通过浏览器网页(静态页面)提供给服务器一个日期和一个天数(N)。

    (2)服务器会对日期和天数进行计算(动态页面),计算出这个日期N天后的日期。

    (3)通过浏览器返回,以便用户更合理的安排时间。

    日期类我写在了另一篇博客https://blog.csdn.net/a15929748502/article/details/81369350欢迎查阅

    下面是我的测试;

    及结果:

     

    展开全文
  • 【说明】转自:kikilizhm  ... 网页编程对我来说特别亲切,因为我就是从html、ASP...自己的编程爱好也是从那里一点一点被满足。不过离开大学之后很久没有碰过WEB了,最近看到嵌入式中的涉及到的web服务器,了解到了CG

    【说明】转自:kikilizhm  http://blog.csdn.net/kikilizhm/article/details/7840719#comments


    网页编程对我来说特别亲切,因为我就是从html、ASP、PHP一步步接触编程的。自己的编程爱好也是从那里一点一点被满足。不过离开大学之后很久没有碰过WEB了,最近看到嵌入式中的涉及到的web服务器,了解到了CGI编程,就想赶紧试一试。对于cgi,不算接触,但是还是听说过之前,对于纯粹写普通网站的人来说CGI却是很古老的东西了,之前在大学的时候学校的ftp搜索引擎,是一位特别牛b的学长写的,用的就是C实现CGI。后来我也想做一个,但是由于水平有限,后来也就不了了之。前几天在南图想找本关于CGI方面的书竟然都下架了,在网上搜索关于这方面的文章页很少,且很多是针对perl语言的,所以这里针对C语言的CGI写点东西。

            嵌入式中的WEB服务器,由于嵌入式中主要是用C语言来实现,所以这里只谈C语言的CGI,而在嵌入式中的WEB服务器,大家一般选用BOA等,这里为什么选用IIS,主要是因为我们大部分人都是在windows下开发,所以在iis下调试自然是更方便一些。在网上看到很多人在问iis下配置关于C语言的CGI的方法,而网上的全部都是关于Perl的。我经过了两天的时间在网上一遍一遍的百度和摸索,终于试出来基于windows xp的IIS5.1和windows 7的IIS6.1上的CGI配置方法。


    综述: 关于脚本和可执行程序

            在iis中对于动态语言会设计到脚本和可执行程序。对于脚本是需要另外的程序对其进行解释的,例如perl脚本编写的CGI就是脚本,需要安装专门的程序区解释;而对于可执行程序,例如C语言编写的.cgi(是c语言编译出来的.exe程序,将扩展名exe改成cgi),其本身就是可执行程序,不需要另外的程序去解释,所以网上关于使用TC还是VC的编译器cl.exe作为可执行程序去配置c语言编写出来的cgi的说法都是错误的。其实CGI的实现就是将本身脚本或者可执行程序的标准输出,不再是通过屏幕打印,而是经过浏览器输出给客户端显示,所以你用一个vc或者tc的编译器去执行一个.exe的可执行文件是得不到想要的输出的。正确的作法是不需要和perl语言一样配置解释程序,而是配置为让文件执行。

    具体操作:

    由于网上针对IIS 和C语言的CGI的内容较少,所以下面的内容一步步用图片说明。方便向我一样的初次使用者。

    1.编写CGI程序。

    使用任何一种C语言开发工具,TC,VC,C-free均可,C/C++均可。内容我们还是使用经典的hello world。

    1. /****************************  hello.cpp  
    1. 使用C++实现的CGI输出hello world!   
    1. by kikilizhm  
    2. *****************************/  
    3. #include <iostream.h>  
    4. void main()  
    5. {  
    6.     cout<<"Content-type:text/html\n\n"<<endl;  /* 注意这里一定要按照这种格式,表示http头以供浏览器识别,后面的\n\n是http中要求的头和后面的内容必须空一格,                                                  而且必须使用反斜杠,不能使用" /n/n " ,这样会报错的,无法识别。 */  
    7.     cout<<"Hello World!"<<endl;  
    8. }  


    1. /********************************hello.c*************** 
    2. 使用C语言实现的CGI输出加粗的hello world!  
    3. by kikilizhm 
    4. ******************************************************/  
    5. #include <stdio.h>  
    6. int main()  
    7. {  
    8.     printf("Content-type:text/html");  
    9.     printf("\n\n");  
    10.     printf("<b>Hello World!</b>");  
    11. }  

    上面分别是使用C++和C语言编写的hello world 例程,根据个人习惯,然后编译生成 hello.exe 可执行文件,将扩展名exe改为cgi,变为hello.cgi。

    现在我们的cgi程序就写好了,下面我们开始配置iis,看看效果。

    2.针对windows xp  的IIS5.1的配置。其实配置很简单,只要大家看明白其中的根本。

        说明:这里默认IIS已经安装好可用(具体安装IIS的方法可以百度一下,这里不细说),针对iis5.1我们需要的只是配置一下脚本和可执行文件的执行权限即可。

    按照上面说的,exe文件不需要解释程序,直接执行即可。假设我们网站根目录www下cgi目录为脚本存放目录,我们把hello.cgi文件放到cgi目录下,这里为演示方便,正常情况下考虑安全等因素,常将存放脚本的目录使用虚拟目录挂接,不直接放在网站根目录下。




    然后我们在IIS中右击cgi目录,选择属性,打开cgi属性对话框,目录权限设置中将 读取、写入、目录访问前的勾去掉。其中读取是防止在访问cgi程序时,浏览器将cgi文件作为下载文件弹出下载对话框,而不是实际执行显示在浏览器上。写入和目录访问时安全需要。这里特别注意去掉读取权限。

    然后将下方的 ”执行权限“更改为 ”脚本和可执行文件“ ,这里也是重要的一步,不可以用纯脚本,一定要有可执行文件权限。点击确认再确认,重启IIS即可。


    走到这里我们打开浏览器,输入 http://localhost/cgi/hello.cgi ,即可看到hello world的画面,如果还看不到或者出现其他提示错误,一般为网站的访问权限认证设置有问题,可以尝试修改文件夹访问权限。




    **********************************分割线    ====针对win 7 的IIS6.1的配置*******************************************

    3.win7中的IIS界面和windows xp 中的IIS5.1界面发生了很大的变化,IIS 6.1中已经内置有CGI处理模块,这里我们直接使用即可。

    同样这里我们假设我们IIS6.1已经安装好,而且可以使用,新建网站cgi-test,根目录为www,下面有子目录cgi用于存放cgi脚本文件。


    在左侧的cgi-test网站中,我们单击cgi-test网站的cgi目录,右侧会显示cgi主页,我们在右侧 IIS 部分双击模块,进入模块功能,可以看到CgiModule模块,我们接下来就是要使用的这个模块来实现我们的cgi功能。

                     

    看到这里,我们返回cgi目录的主页,双击CGI主页中的”处理程序映射“来配置 cgi文件的映射,在处理程序映射界面,每一条配置的路径相当于配置的文件扩展名,

    状态为已启用或者禁用,处理程序即相应的模块或者脚本解释程序。 在最右侧的操作界面,我们可以看到我们可以选择的操作,其中包括 添加托管处理程序、添加脚本映射、添加通配符脚本映射,添加模块映射,和下面的编辑功能权限等。这里我们使用的是添加模块映射和编辑功能权限。

    单击添加模块映射,在出现的编译模块映射对话框中,按照如图所示配置,请求路径输入 *.cgi ,表示cgi格式文件,模块选择CgiModule,可执行文件为空,名称可以随便写,这里为cgi-exe。确定后,增加了cgi-exe配置,然后右击这条配置选择编译功能权限,确保有执行权限,然后重启iis服务。在浏览器中输入地址,即可查看到hello world的打印。



    看看我们的成果吧,这里只是打印了最简单的字符,服务器已经配置好了,大家可以发挥自己的聪明才智,实现自己的功能吧~ 



    总结:

    经过大概两天的时间,终于摸索出了在iis上配置C语言编写的CGI的方法,其实真正配置下来,花的时间并不多也不复杂,但是由于网上很少人使用iis做C的cgi的服务器,而且网上关于这类问题的回答都是答非所问,甚至是错误的说法,所以摸索起来很困难,但是这两天的时间里,在经历了从最初的没有头绪,到最后的思考网上很多人在说的perl语言的cgi和c语言的cgi的区别,去甄别那些错误的信息,并且从那些错误的信息里找出来对我又帮助的地方,比如刚开始的时候使用vc的编译器cl.exe去执行,并没有得到想要的结果,于是去尝试vc的bin目录下的其他程序,在有的程序返回了一部分字符的时候,在浏览器的错误信息里显示了错误的http头,比之前的空的头还是有点帮助,毕竟有了显示,虽然是个错误信息。然后尝试着用cmd.exe去执行,最后的最后确信不适用程序去执行,后面会出现访问cgi的时候出现下载的问题,用文本方式打开下载的文件,虽然也是和服务器端一样的名称,但是下载的文件内容已经是个文本,里面就是执行打印出来的信息,只是它被下载下来了,而不是在浏览器中显示,后来在网上看到了一个朋友发的msdn社区上的说明,需要去掉读取权限,这才恍然大悟。然后就看到了亲切的打印信息了。

    即使一个一开始不明白或者没有涉及到的问题,不断的去想解决它,即使没有进展,但是对于你对问题的理解还是有很大帮助的,我想这和那种做梦发现苯环之类的是一样的道理。同时也是一种学习的方式,对于一种技术一下子接受不了的时候,可以先看一部分,了解一下,或者强迫自己先学,然后放置一段时间,再回头在学,就会发现很轻松了。

    该睡觉了,希望这里能对大家起到帮助作用,另外,在使用的过程中,推荐大家使用合适的环境,比如在apache中配置cgi更加的简单和安全。针对IIS上的C语言的CGI主要是针对大家在调试过程中使用。


    [add by sail0323]:

    按照kikilizhm的文章,建立了cgi-test网站,但还是无法访问到或者出现如下错误:

    进行了以下设置后,就能运行了:










    结果:




    展开全文
  • 作者历经5年整理最全面 linux学习资料(精心整理)服务器搭建 shell脚本 c语言
  • 搭建简易的c语言CGI和Apache服务器的开发环境 今天中午在研究c语言gui时看到了cgi。之前花了些时间找c语言的gui框架,也找到了几个暂时比较满意的,但是看到了cgi后觉得也可以尝试一下。在web开发方面有点...
  • 要想做好一个程序员,基本的服务器构架的能力还是需要有的。除了在本地开发,我们还要学会如何去部署我们开发好的系统。由于Linux的开源免费且功能强大,性能卓越。很多企业都会选择将服务部署在Linux系统上,所以...
  • 目录   内容 步骤 (一)、FTP原理 (二)、FTP工作方式 ...(四)、iis搭建ftp服务器(filezilla、win7) ...(五)、QT实现C语言捕获FTP包 ...(附录)FTP的C语言代码实现 ...4、iis搭建ftp服务器(fil...
  • 搭建简易的c语言CGI和Apache服务器的开发环境 http://www.cnblogs.com/tt-0411/archive/2011/11/21/2257203.html python配置apache的web服务器方法(python的CGI配置) ...
  • 本工程使用C99标准的纯C语言(不使用STL)编写了CNN,实现在MNIST数据集上的手写数字识别。本文目的是为将来在FPGA等更底层的边缘设备上实现CNN做铺垫,当然CNN的训练过程在服务器上进行,推断过程在边缘端进行,...
  • 最近一直在学习Linux学习编写CGI, 刚刚完成了一个留言本, 把自己的心得写出来与大家分享, 不正确的地方请高手指正.(一)Linux的安装. 我安装的是RedHat9(版本老了一点, Linux2.4内核), 使用KDE(感觉比GNOME漂亮), ...
  • 要使用MPMoviePlayerController控件,必须采用Http请求的形式去请求数据,而TS流的来源是通过HTTP请求拿到的,需要再把这个拿到的TS流发送给IOS播放器,所以试图自己搭建一个HTTP服务器,为此学习了Mongoose的源码,...
  • 几个月没写教程了,今天登录看一下,账号还没被封,高兴之余写个http服务器demo。...它是应用层的协议,底层是基于TCP通信的。HTTP协议的工作过程:客户通过...我们基于http1.1版本编写,因为从零搭建的,所以我决...
  • Google了一下,现在讨论cgi,尤其是c语言cgi的话题已经很少了,花了些精力,总算搭建好了一个可用的开发环境,也运行出了程序。下面简单分享一下,我的实验过程。其实是很简单的事情。  首先,需要用到的这些工具...
  • 上一篇文章《FastDFS 分布式文件系统详解》中带大家详细了解它的核心概念,架构体系及 FastDFS 环境的搭建与使用。但是此时还无法通过 HTTP 对文件进行访问,这篇文章将带大家解决这个问题。如果我们想通过 HTTP ...
  • 首先编写我们服务器上需要的c文件WebServer.c 涉及到的函数API:  int copy(FILE *read_f, FILE * write_f) ----- 文件内容复制的方法  int DoHTML(FILE *f, char * name) ------ socket通讯文件发送的重要方法...
  • Linux聊天室 搭建服务器

    千次阅读 2017-02-10 19:10:25
    最近自己在写c语言中的聊天室项目,简单点说即利用网络编程让不同终端的用户进行聊天等,要想做出好的聊天室项目出来,基础最重要也就是说要搭建服务器和客户端的架构,服务器端:  (1)创建监听套接字: ...
  • 到今天为止,我发了不少关于C语言程序开发的文章了,期间有不少读者问我使用什么 IDE。其实我并没有使用什么 IDE,我更多的是在 Linux 系统下,使用 vim+gcc 编写和编译C语言程序的。使用 vim+gcc 编写和编译C语言...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 687
精华内容 274
关键字:

c语言搭建服务器

c语言 订阅