当前位置:首页 » 《资源分享》 » 正文

通过c/c++在linux系统实现Mini型http服务器的并发访问_ice_elephant的博客

22 人参与  2021年09月29日 09:03  分类 : 《资源分享》  评论

点击全文阅读


文章目录

  • 测试效果
  • 相关函数介绍
    • pthread_create函数
    • stat函数
  • 程序测试源码
  • 相关 html 资源文件代码
  • 总结

测试效果

测试平台:通过 MobaXterm Personal Edition 连接的本地 Ubuntu 4.2.0-27-generic

当服务器端程序启动后,在浏览器网址搜索栏输入 192.168.60.132/qiniu.html登录到我的服务器端. 192.168.60.132 是我电脑的ip地址.
在 linux shell 窗口 输入 ip addr 可获取本机ip
在这里插入图片描述
编译文件
由于实现并发访问使用到线程函数 pthread_create 因此编译时要在 -o 的前面加上 pthread这个函数的库否则会报错
在这里插入图片描述
启动服务器端程序
在这里插入图片描述
客户端测试

在浏览器网址栏 192.168.60.132/qiniu.html 前面是本机 ip 地址 后面访问的是 qiniu.html 这个文件,这个文件是设定的一个 html 格式的文件用来显示
客户端使用并发测试,分别使用电脑的 谷歌浏览器、火狐浏览器、ubuntu下的telnet 和 Windows 下进行 ip 访问测试
火狐浏览器测试 我们看到了打开了一个改服务器网页 服务器网页显示的这个样式与 这个qiniu.html 文件有关
在这里插入图片描述
服务器响应
收到浏览器的请求后 服务器端打印 连接的客户端ip 端口号 等相关信息在这里插入图片描述
客户端测试
使用谷歌浏览器测试在这里插入图片描述
服务器端的响应与上述火狐浏览器相同

客户端测试
Ubuntu 下的 telnet 测试
输入 telnet 192.168.60.132 80 telnet 本机ip http 协议的 80端口
在这里插入图片描述
客户端成功连接后服务器响应,同样打印上述相关信息
在这里插入图片描述
说明,通过浏览器访问服务器,浏览器就相当于是客户端它内部有会对我们输入的 ip 进行解析自动补全 http 协议的相关内容

相关函数介绍

pthread_create函数

创建一个新线程,并行的执行任务。
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
返回值:成功:0; 失败:错误号。
参数:
pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;
参数1:传出参数,保存系统为我们分配好的线程ID
参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
参数4:线程主函数执行期间所使用的参数。
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,以后再详细介绍pthread_join。
pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。
attr参数表示线程属性,本节不深入讨论线程属性,所有代码例子都传NULL给attr参数,表示线程属性取缺省值。

stat函数

stat函数
作用:返回文件的状态信息
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

   int stat(const char *path, struct stat *buf);
   int fstat(int fd, struct stat *buf);
   int lstat(const char *path, struct stat *buf);

path:
文件的路径
buf:
传入的保存文件状态的指针,用于保存文件的状态
返回值:
成功返回0,失败返回-1,设置errno

struct stat {
dev_t st_dev; /* ID of device containing file /
ino_t st_ino; /
inode number /
mode_t st_mode; /
S_ISREG(st_mode) 是一个普通文件 S_ISDIR(st_mode) 是一个目录*/

           nlink_t   st_nlink;   /* number of hard links */
           uid_t     st_uid;     /* user ID of owner */
           gid_t     st_gid;     /* group ID of owner */
           dev_t     st_rdev;    /* device ID (if special file) */
           off_t     st_size;    /* total size, in bytes */
           blksize_t st_blksize; /* blocksize for filesystem I/O */
           blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
           time_t    st_atime;   /* time of last access */
           time_t    st_mtime;   /* time of last modification */
           time_t    st_ctime;   /* time of last status change */
       };

程序测试源码

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include<string.h>
#include<errno.h>
#include<sys/stat.h>

#define SERVER_PORT 80

static int debug = 1;

void do_http_request(int client_sock);
int get_line(int sock,char *buf,int size);
void do_http_response1(int client_sock);
void do_http_response(int client_sock,const char*path);
int headers(int client_sock,FILE*resource);
void cat(int client_sock,FILE*resource);
void inner_error(int client_sock);
void not_found(int client_sock);
void unimplemented(int client_sock);

void bad_request(int client_sock);


int main(void){

    int sock;//代表信箱
    struct sockaddr_in server_addr;


    //1.美女创建信箱
    sock = socket(AF_INET, SOCK_STREAM, 0);

    //2.清空标签,写上地址和端口号
    bzero(&server_addr, sizeof(server_addr));

    server_addr.sin_family = AF_INET;//选择协议族IPV4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址
    server_addr.sin_port = htons(SERVER_PORT);//绑定端口号

    //实现标签贴到收信得信箱上
    bind(sock, (struct sockaddr *)&server_addr,  sizeof(server_addr));

    //把信箱挂置到传达室,这样,就可以接收信件了
    listen(sock, 128);

    //万事俱备,只等来信
    printf("等待客户端的连接\n");


    int done =1;

    while(done){
        struct sockaddr_in client;
        int client_sock, len, i;
        char client_ip[64];
        char buf[256];

        socklen_t  client_addr_len;
        client_addr_len = sizeof(client);
	printf("come here!\n");
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);

        //打印客服端IP地址和端口号
        printf("client ip: %s\t port : %d\n",
                 inet_ntop(AF_INET, &client.sin_addr.s_addr,client_ip,sizeof(client_ip)),
                 ntohs(client.sin_port));
        /*处理http请求,读取客户端的数据*/
		do_http_request(client_sock);
		close(client_sock);
        }


       


		close(sock);
		return 0;
}

void do_http_request(int client_sock){
	/*//1.读取请求行
	int len = 0;
	char buf[256];
	do{
	len = get_line(client_sock,buf,sizeof(buf));
	printf("read line:%s\n",buf);
	}while(len>0);*/
	
	
	int len  = 0;
	char buf[256];
	char method[64];
	char url[256];
	
	char path[256];
	
	struct stat st;
	
	len = get_line(client_sock,buf,sizeof(buf));
	int i =0,j=0;
	if(len>0){//读到请求行
	
		while(!isspace(buf[j])&&i<sizeof(method)-1){
			method[i] = buf[j];
			i++;
			j++;
			
		}
		
		method[i]='\0';
		if(debug) printf("request method:%s\n",method);
		
	
	//判断method中的字符是否是 "GET"
	if(strncasecmp(method,"GET",i)==0){//字符串比较函数
		if(debug) printf("request method=GET\n");
		//获取url
		
		//跳过前面get后面的空格
		while(isspace(buf[j++]));
		i = 0;
		
		while(!isspace(buf[j])&&(i<sizeof(url)-1)){
			url[i] = buf[j];
			i++;
			j++;
		}
		url[i]='\0';
		
		if(debug) printf("url:%s\n",url);
	
	//}
		do{
		len = get_line(client_sock,buf,sizeof(buf));
		if(debug) printf("read:%s\n",buf);
		
		}while(len>0);
	

	
	
			//处理url中的?号
		{
			char *pos =strchr(url,'?'); 
			if(pos)   *pos='\0';
			printf("real url:%s\n",url);
			
		}
		
		
		
		//sprintf(path,"./html_docs/%s",url);
		
		sprintf(path, "./html_docs/%s", url);
		if(debug) printf("path:%s\n",path);
	   
	   //存在响应html不存在响应Not FOUND
	   if(stat(path,&st)==-1){//失败返回-1
	   
	   fprintf(stderr,"stat :%s failed.reason:%s\n",path,strerror(errno));
	   not_found(client_sock);
		   
	   }else{//文件存在
		if(S_ISDIR(st.st_mode)){
			strcat(path,"/index.html");
		}
		   do_http_response(client_sock,path);
		   // do_http_response1(client_sock);
		   
	   }
	   
	   
	   
        
	}
		
	else{//bad request
		//非get请求,读取所有http头部,并且响应客户端501
			fprintf(stderr,"warrning!other request[%s]\n",method);
		do{
		len = get_line(client_sock,buf,sizeof(buf));
		if(debug) printf("read:%s\n",buf);
		
		}while(len>0);
		unimplemented(client_sock);//响应时实现
		
		
		}
	
	//继续读取http的头部
	
	}else{//请求格式有问题,出错处理
			// 响应时实现
		bad_request(client_sock);
		
	}
}

void bad_request(int client_sock){
	const char*reply= "HTTP/1.0 400 bad_request\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML lang=\"zh-CN\">\r\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
	<HEAD>\r\n\
	<TITLE>Bad Requst</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
	<P>Bad Request!\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
	</BODY>\r\n\
	</HTML>";

	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);
	
	if(len<=0) fprintf(stderr,"inner reply failed.reason:%s\n",strerror(errno));
	
}

void inner_error(int client_sock){
	const char*reply= "HTTP/1.0 404 inner_error\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML lang=\"zh-CN\">\r\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
	<HEAD>\r\n\
	<TITLE>NOT FOUND</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
	<P>服务器内部出错!\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
	</BODY>\r\n\
	</HTML>";

	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);
	
	if(len<=0) fprintf(stderr,"inner reply failed.reason:%s\n",strerror(errno));
	
	
}
void unimplemented(int client_sock){
	const char*reply= "HTTP/1.0 501 unimplemented\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML lang=\"zh-CN\">\r\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
	<HEAD>\r\n\
	<TITLE>Mehod Not Implemented</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
	<P>unimplemented\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
	</BODY>\r\n\
	</HTML>";

	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);
	
	if(len<=0) fprintf(stderr,"send reply failed.reason:%s\n",strerror(errno));
	
}

void not_found(int client_sock){
	const char*reply= "HTTP/1.0 505 NOT FOUND\r\n\
	Content-Type: text/html\r\n\
	\r\n\
	<HTML lang=\"zh-CN\">\r\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
	<HEAD>\r\n\
	<TITLE>NOT FOUND</TITLE>\r\n\
	</HEAD>\r\n\
	<BODY>\r\n\
	<P>文件不存在!\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
	</BODY>\r\n\
	</HTML>";

	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);
	
	if(len<=0) fprintf(stderr,"send reply failed.reason:%s\n",strerror(errno));
}

void do_http_response(int client_sock,const char*path){
	
	int ret =0;
	
	FILE *resource = NULL;//c语言文件操作
	resource = fopen(path,"r");
	
	if(resource==NULL){
		not_found(client_sock);
		return ;
	}
	//1.发送http头部
	ret = headers(client_sock,resource);
	
	//2.发送http body
	if(ret==0){
	cat(client_sock,resource);
	}
	
	fclose(resource);
	
}
//成功返回0 失败返回-1
int headers(int client_sock,FILE*resource){
	
	struct stat st;
	int fileid = 0;
	char tmp[64];
	
	char buf[1024]={0};
	strcpy(buf,"HTTP/1.0 200 OK\r\n");
	strcat(buf,"Server: Martin Server\r\n");
	strcat(buf,"Content-Type: text/html\r\n");
	strcat(buf,"Connection: Close\r\n");
	
	fileid = fileno(resource);
	
	if(fstat(fileid,&st)==-1){//文件已经打开但是访问文件出错
		
		inner_error(client_sock);//内部出错
		return -1;
	}
		
	snprintf(tmp,64,"Content-Length:%ld\r\n\r\n",st.st_size);
	strcat(buf,tmp);
	
	if(debug) fprintf(stdout,"header:%s\n",buf);
	
	if(send(client_sock,buf,strlen(buf),0)<0){
		fprintf(stderr,"failed to send.reason:%s.buf:%s\n",strerror(errno),buf);
		return -1;
	}
	return 0;

}
/*******************
说明:实现将html文件的内容按照行读取并发送客户端

**********************/


void cat(int client_sock, FILE *resource){
	char buf[1024];
	
    fgets(buf, sizeof(buf), resource);
	
	while(!feof(resource)){
		int len = write(client_sock, buf, strlen(buf));
		
		if(len<0){//发送body 的过程中出现问题,怎么办?1.重试? 2.
			fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
			break;
		}
		
		if(debug) fprintf(stdout, "%s", buf);
		fgets(buf, sizeof(buf), resource);
		
	}
}
void do_http_response1(int client_sock){
	//响应http
	const char *main_header = "HTTP/1.0 200 OK\r\nServer: Martin Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";
	const char *welcome_content = "\
	<html lang=\"zh-CN\">\n\
	<head>\n\
	<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
	<title>This is a test</title>\n\
	</head>\n\
	<body>\n\
	<div align=center height=\"500px\" >\n\
	<br/><br/><br/>\n\
	<h2>大家好!</h2><br/><br/>\n\
	<form action=\"commit\" method=\"post\">\n\
	尊姓大名: <input type=\"text\" name=\"name\" />\n\
	<br/>芳龄几何: <input type=\"password\" name=\"age\" />\n\
	<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\
	<input type=\"reset\" value=\"重置\" />\n\
	</form>\n\
	</div>\n\
	</body>\n\
	</html>";
	
	//1. 送main_header
	char send_buf[64];
	write(client_sock,main_header,sizeof(main_header));
	
	int len = snprintf(send_buf,64,"%d\r\n\r\n",strlen(welcome_content));
	
	write(client_sock,send_buf,len);
	write(client_sock,welcome_content,strlen(welcome_content));

	//2. 生成Contantlenth
	
	//3. 发送html内容
}

int get_line(int sock,char *buf,int size){
	//读一行,一个字节一个字节的读每次读一个字节
	int count = 0;
	int len =0;
	char ch = '\0';
	
	
	while(count<(size-1)&&ch!='\n'){
		len = read(sock,&ch,1);
		if(len==1){
			if(ch=='\r'){
				continue;
				
			}else if(ch=='\n'){
				break;
				
			}
			
		buf[count] = ch;
		++count;
		}else if(len=-1){
		perror("read failed");
		count = -1;
		break;
			
		}else{//sock网络关闭时也就是len=0读0个字节
		fprintf(stderr,"client close.\n");
		count = -1;
		break;
			
		}
		
		
	}
	if(count>=0) buf[count] = '\0';
	return count;
	

	
	
}

相关 html 资源文件代码

本文件是上述浏览器访问服务器成功后看到的那个页面效果,直接复制下面内容
注意编码 我这里使用的 带 bom utf-8 没有出现中文乱码,有可能大家使用得是不带 bom 的 <meta content=“text/html; charset=utf-8 bom” http-equiv=“Content-Type”>
这个文件的位置,在当前文件夹建立文件 目录 mkdir html_docs 进入这个目录 建立一个名为 qiniu.html 的文件将下面的 html 文本放入

<html lang=\"zh-CN\">
<head>
<meta content=\"text/html; charset=utf-8 bom\" http-equiv=\"Content-Type\">
<title>This is a test</title>
</head>
<body>
<div align=center height=\"500px\" >
<br/><br/><br/>
<h2>Good guys! Welcome to China Service!</h2><br/><br/>
<form action="commit" method="post">
尊姓大名: <input type="text" name="name" />
<br/>芳龄几何: <input type="password" name="age" />
<br/><br/><br/><input type="submit" value="提交" />
<input type="reset" value="重置" />
</form>
</div>
</body>
</html>

总结

本 demo 对上一个 demo 的回声服务器进行了改进,使用了线程函数 实现了一个 Mini 型的服务器 功能上实现了能够对并发访问进行处理,能够处理一定并发的访问,但是上述 Mini 型的服务器要在性能上无法满足高并发的要求,需要继续改进.


点击全文阅读


本文链接:http://zhangshiyu.com/post/28952.html

线程  函数  文件  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1