c语言实现https服务器(纯享版)

avatar
作者
猴君
阅读量:0

参考

1.90行代码实现C语言版https服务器,基于openssl
2.使用OpenSSL生成自签名SSL/TLS证书和私钥
注意:证书和私钥文件(server.crt,server.key)的生成请参考此链接

代码

#define SERVER_PORT 8080 //设置端口号 #include<stdio.h> #include<string.h> #include<iostream> #include <winsock2.h> #include <ws2tcpip.h>  #include<string.h> #include<openssl/ssl.h> #include<openssl/err.h> #ifdef _WIN32 #include <Windows.h> // Windows 文件操作相关代码 #else #include <sys/stat.h> // Unix/Linux 文件操作相关代码 #endif #pragma comment(lib, "Ws2_32.lib")  struct client_mes {//客户端请求信息结构体 	char IP[20];	//客户ip地址 	int PORT;	//客户端口号 	char method[10];//请求方法 	char url[1024];	 //请求url 	char version[10];//协议及版本信息 }c_mes; struct kay_and_value {//每一个键值对结构体 	char key[10]; 	char value[100]; }; struct url_mes { 	char path[100];//请求路径 	//采用结构体数组来存储键值对 	struct kay_and_value k_v[10]; 	int k_v_len;//实际键值对个数 }u_mes; char messages[1024] = {0};//存储返回信息的全局变量 //定义http响应行全局变量 char u200[] = "HTTP/1.0 200 OK\r\n"; char u400[] = "HTTP/1.0 400 BAD REQUEST\r\n"; char u404[] = "HTTP/1.0 404 NOT FOUND\r\n"; char u500[] = "HTTP/1.0 500 INTERNAL SERVER ERROR\r\n"; char u501[] = "HTTP/1.0 501 METHOD NOT IMPLEMENTED\r\n"; int main() { 	SSL_CTX* initSSL(); 	int creat_socket_listen(); 	char* get_path(); 	void do_http_request(char buf[1024]);//对缓冲区接受到的客户请求信息进行解析 	int do_http_resolve(char url[1024], int clnt_sock);//对客户端请求进行响应 	void do_http_url_process(char url[1024]);//对客户端的url进行解析 	void do_http_response(int clnt_sock, const char* path); 	// 初始化键值对结构体数组 	for (int k = 0; k < 10; k++) { 		strcpy_s(u_mes.k_v[k].key, ""); 		strcpy_s(u_mes.k_v[k].value, ""); 	} 	memset(&c_mes, 0, sizeof(c_mes));//将结构体里面的数据清零 	memset(&u_mes, 0, sizeof(url_mes));  	// 初始化 Winsock 库 	WSADATA wsaData; 	int result = WSAStartup(MAKEWORD(2, 2), &wsaData); 	if (result != 0) { 		fprintf(stderr, "WSAStartup failed with error code: %d\n", result); 		return 1; 	} 	//初始化ssl库 	SSL_CTX* ctx; 	ctx = initSSL(); 	//初始化socket库并实现监听 	int serv_sock; 	serv_sock = creat_socket_listen(); 	//接收客户端请求 	SSL* ssl; 	/*定义一个结构体,用于存储客户端的地址信息,包括IP地址和端口号 	*/ 	struct sockaddr_in clnt_addr; 	/*定义变量clnt_addr_size用来存储结构体clnt_addr的大小 	socklen_t 被设计用来表示套接字地址长度的类型 	遇到问题:若无#include <ws2tcpip.h> socklen_t 会报错 	*/ 	socklen_t clnt_addr_size = sizeof(clnt_addr); 	/*accept()函数用于接受客户端的请求,并创建一个新的套接字用于与客户端通信 	第一个参数:服务器套接字的文件描述符 	第二个参数:函数调用成功后,客户端的信息将保存在结构体clnt_addr中 	第三个参数:指定了clnt_addr结构体的大小 	函数调用成功后返回一个新的文件描述符clnt_sock,代表与客户端通信的套接字 	*/ 	int clnt_sock; 	if ((clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size)) < 0) { 		perror("accept failed"); 		exit(EXIT_FAILURE); 	} 	printf("接收客户端请求成功\n"); 	char client_ip[64]; 	char buf[1024] = { 0 };  	ssl = SSL_new(ctx); 	SSL_set_fd(ssl, clnt_sock); 	if (SSL_accept(ssl)<=0) { 		ERR_print_errors_fp(stderr); 		abort(); 	} 	int size = SSL_read(ssl,buf,sizeof(buf)); 	printf("ssl_read:%s\n",buf);  	/*打印客户端ip地址和端口号 	inet_ntop()函数用于将网络字节序的ip地址转换为可读的字符串格式, 	它被用来将客户端的 IP 地址从 clnt_addr.sin_addr.s_addr 转换为一个字符串,并将结果存储在 client_ip 数组中 	ntohs()用来将网络字节序的端口号转换为主机字节序的端口号 	*/ 	/* 	printf("ip地址:%s\t port:%d\n", 		inet_ntop(AF_INET, &clnt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)), 		ntohs(clnt_addr.sin_port) 	); 	*/ 	 	//设置发出请求的客户端的ip地址和端口号 	strcat_s(c_mes.IP,inet_ntop(AF_INET, &clnt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip))); 	c_mes.PORT=ntohs(clnt_addr.sin_port); 	/*读取客户端请求 	用套接字从clnt_sock中接收数据,并将数据存储到缓冲区buf中,最多接受1024字节的数据 	如果调用成功返回读取的字节数,否则返回-1 	*/ 	/* 	int valread; 	if ((valread = recv(clnt_sock, buf, 1024, 0)) < 0) { 		perror("read failed"); 		exit(EXIT_FAILURE); 	} 	*/ 	 	printf("——————————————————————————\n"); 	printf("读取数据成功\n"); 	printf("客户端请求为:\n%s\n", buf);  	//解析客户端请求 	do_http_request(buf); 	//检验解析成果 	printf("——————————————————————————\n"); 	printf("客户端信息解析成功\n"); 	//printf("客户端信息解析如下:\nip地址:%s\n端口号:%d\n请求方法:%s  长度:%d\n请求url:%s  长度:%d\n请求协议和方法:%s  长度:%d\n",c_mes.IP, c_mes.PORT, c_mes.method,strlen(c_mes.method), c_mes.url, strlen(c_mes.url), c_mes.version, strlen(c_mes.version)); 	printf("——————————————————————————\n"); 	//实现http响应 	printf("对客户端请求进行响应\n"); 	int t=do_http_resolve(c_mes.url,clnt_sock); 	char* paths = get_path(); 	if (t == 1) {//正常访问 		do_http_response(clnt_sock, paths); 	} 	else if (t==2) {//404 		do_http_response(clnt_sock, "./error.html");//无法获取文件信息 	} 	else if (t==3) {//500 		do_http_response(clnt_sock, "./unimplemented.html");//服务器不支持的请求方法 	} 	if (messages[0]!='\0') { 		SSL_write(ssl,messages,sizeof(messages)); 	} 	printf("——————————————————————————\n"); 	printf("客户端返回数据成功!\n"); 	/*发送响应给客户端 	向已连接的套接字clnt_sock(即客户端)发送数据 	发送成功返回成功发送的字节数,发送失败返回-1 	*/ 	//关闭套接字 	closesocket(clnt_sock); 	//关闭套接字 	closesocket(serv_sock); 	SSL_shutdown(ssl); 	SSL_free(ssl); 	SSL_CTX_free(ctx); 	// 释放 Winsock 资源 	WSACleanup(); 	return 0; } //对客户端请求进行解析 void do_http_request(char buf[1024]) {//对缓冲区接受到的客户请求信息进行解析 	int i=0; 	int j = 0; 		//获取方法 	j = 0; 	while (buf[i]!=' ') { 		c_mes.method[j++] = buf[i++]; 	} 	i++; 	//获取url 	if (buf[i] == '/'&&buf[i+1]==' ') {//无url 		int j = 0; 		c_mes.url[j++] = '/'; 		i = i + 1; 	} 	else {//有url   		i++;//跳过/ 		j = 0; 		while (buf[i] != ' ') { 			c_mes.url[j++] = buf[i++]; 		} 			 	} 	i++; 	//获取协议及版本信息 	j = 0; 	while (buf[i] != '\r') { 			c_mes.version[j++] = buf[i++];  	} 		 } //对客户端进行url数据解析并反应(重写get方法) int do_http_resolve(char url[1024],int clnt_sock) { 	char* get_path(); 	void do_http_url_process(char url[1024]);//对客户端的url进行解析 	//判断http请求是get方法 	if ((strcmp(c_mes.method,"GET"))==0) { 		printf("客户请求是get方法\n"); 		//对url中的路径和参数进行解析 		//计算ip地址和端口号的长度 		int lens = strlen(c_mes.IP) + 4; 		lens = lens + 1;//冒号长度 		//http://192.168.10.124:8080/index.html?name=123&psd=1234 		//解析客户端路径和键值对参数 		do_http_url_process(url); 		//输出处理后的数据 		for (int i = 0; i < u_mes.k_v_len; i++) { 			printf("客户端path:%s\t键值对数组u_mes[%d].key=%s,u_mes[%d].value=%s\n", u_mes.path, i, u_mes.k_v[i].key, i, u_mes.k_v[i].value); 		} 		char* paths = get_path();	 		/*判断文件信息 		int stat(const char *pathname, struct stat *buf); 		第一个参数为文件路径名,第二个参数是一个关于文件信息的结构体 		struct stat { 			dev_t     st_dev;         // 文件的设备编号 			ino_t     st_ino;         // 文件的 inode 编号 			mode_t    st_mode;        // 文件的类型和权限 			nlink_t   st_nlink;       // 连接数 			uid_t     st_uid;         // 文件所有者的用户 ID 			gid_t     st_gid;         // 文件所有者的组 ID 			dev_t     st_rdev;        // 如果是特殊文件,设备编号 			off_t     st_size;        // 文件大小(以字节为单位) 			blksize_t st_blksize;     // 文件系统 I/O 缓冲区大小 			blkcnt_t  st_blocks;      // 分配的块数 			time_t    st_atime;       // 最后一次访问时间 			time_t    st_mtime;       // 最后一次修改时间 			time_t    st_ctime;       // 最后一次更改时间 		} 		*/ 		struct stat filebuf; 		memset(&filebuf, 0, sizeof(struct stat)); 		if (stat(paths, &filebuf)==-1) {//获取文件信息失败 			printf("stat %s find fail\n",paths); 			return 2; 		} 		else {//获取文件信息成功,发送响应 			//拼接相对文件路径 			printf("获取文件信息成功\n");			 			//正常入口 			//1代表正常入口,2代表404,3代表服务器不支持的请求方法 			return 1; 		} 	} 	else { 		printf("不是get方法,暂时无法响应!\n"); 		return 3; 	} } void do_http_url_process(char url[1024]) { 	int i = 0; 	int j = 0; 	//提取path 	while (url[i] != '?'&& url[i] != '\0') { 		u_mes.path[j++] = url[i++]; 	} 	u_mes.path[i] = '\0';//加上结束标志 	i++; 	j = 0; 	int temp = 0;//用来标识是否出现等号 	int k = 0;//控制键值对数组下标 	while (url[i] != '\0') {//对键值对进行赋值 		if (url[i] != '&') {//具体每队键值对的赋值 			if (url[i] == '=') { 				temp = 1;//切换到value的赋值 				j = 0; 				i++;//跳过= 			} 			else if (url[i] != '=') { 				if (temp == 0) {//对key赋值 					u_mes.k_v[k].key[j++] = url[i++]; 				} 				else if (temp == 1) {//对value赋值 					u_mes.k_v[k].value[j++] = url[i++]; 				} 			} 		} 		else { 			k++; 			i++; 			j = 0; 			temp = 0; 		} 	} 	u_mes.k_v_len = k;//记录实际参数个数 } //对客户端请求进行具体响应 char*path和char path[100]等价 void do_http_response(int clnt_sock,const char *path) {//传入请求网页和具体参数	 	//发送头部 	void get_message(int clnt_sock, FILE * resource, const char* header); 	//发送主体 	printf("进入do_http_response函数\tpath=%s\n",path); 	//确定http响应状态行 	char* header = u200; 	//声明一个文件指针并将其初始化为null 	errno_t err; 	FILE *resource = NULL; 	//尝试打开文件,成功后返回文件指针,后续可通过文件指针来操作这个文件 	err = fopen_s(&resource,path, "r"); 	if (resource == NULL) { 		printf("找不到请求资源%s\n", path); 		header = u404; 		return ; 	} 	if (strcmp(path, "./error.html") == 0) { 		header = u404; 	} 	else if (strcmp(path, "./unimplemented.html") == 0) { 		header = u501; 	} 	get_message(clnt_sock, resource, header); 	//关闭文件描述符 	fclose(resource); 	 }   //获取发送头部及主体内容 void get_message(int clnt_sock, FILE* resource, const char* header) { 	printf("进入get_headers函数\n"); 	char buff[100] = { 0 };//存放主体信息 	char mess[1024] = { 0 }; 	char clent_mes[2048] = { 0 }; 	struct stat st; 	int fileId = _fileno(resource);//获取文件描述符相关的文件标识符 	if (fstat(fileId,&st)==-1) { 		printf("inner error\n"); 		header = u500; 		return ; 	} 	char buf[1024] = { 0 };//存放头部信息 	char temp[64]; 	//写入状态行 	strcat_s(buf,header); 	//消息报头 	strcat_s(buf,"Server:Martin Server\r\n"); 	strcat_s(buf,"Content_Type:text/htmll\r\n"); 	strcat_s(buf,"Connection:Close\r\n"); 	/* 	sprintf()则将数据输出到指定的字符串中 	*/ 	sprintf_s(temp, "Contene-Length:%d\r\n\r\n", st.st_size); 	strcat_s(buf,temp); 	printf("头部信息为:\n%s\n",buf);  	while (fgets(buff, sizeof(buff), resource) != NULL) {//处理文件内容 		size_t buf_len = strlen(buff); 		if (buf_len > 0) { 			size_t remain_space = sizeof(mess) - sizeof(int) * (strlen(mess) / sizeof(int)); 			if (remain_space >= buf_len) { 				memcpy((char*)mess + strlen((char*)mess), buff, buf_len); 			} 			else { 				printf("mess中没有剩余空间!\n"); 				break; 			} 		} 	} 	strcpy_s(clent_mes, buf); 	strcat_s(clent_mes, mess); 	printf("发送给客户的信息是:%s\n", clent_mes); 	strcpy_s(messages,clent_mes);//全局信息变量赋值 } SSL_CTX* initSSL() { 	SSL_CTX* ctx; 	//SSL库初始化 	SSL_library_init(); 	//载入所有SSL算法 	OpenSSL_add_all_algorithms(); 	//载入所有SSL错误消息 	SSL_load_error_strings(); 	ctx = SSL_CTX_new(SSLv23_server_method()); 	if (ctx == NULL) { 		ERR_print_errors_fp(stderr); 		abort(); 	} 	//载入用户的数字证书,此证书用来发给客户端,证书里面包含公钥 	if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) { 		ERR_print_errors_fp(stderr); 		abort(); 	} 	//载入用户私钥 	if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) { 		ERR_print_errors_fp(stderr); 		abort(); 	} 	//检查用户私钥是否正确 	if (!SSL_CTX_check_private_key(ctx)) { 		ERR_print_errors_fp(stderr); 		abort(); 	} 	return ctx; } int creat_socket_listen() { 	/*创建TCP套接字(通过IPv4族进行面向连接的通信) 	 返回值是新创建套接字的文件描述符,调用成功返回一个非负整数,如果调用失败,返回-1 	第一个参数:地址族			AF_INET表示IPv4地址族 	第二个参数:套接字类型		通过TCP连接传输 	第三个参数:传输协议		0默认情况,根据上面两个参数自动选择*/ 	int serv_sock; 	if ((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) == 0) { 		perror("socket failed"); 		exit(EXIT_FAILURE); 	}  	/* 	sockaddr_in这是一个存储IPv4地址信息的结构体 	struct sockaddr_in { 	short            sin_family;   // 地址族 (AF_INET) 	unsigned short   sin_port;     // 端口号 	struct in_addr   sin_addr;     // IPv4 地址 	char             sin_zero[8];  // 填充 0,保持与 sockaddr 结构体大小的兼容性 	};  	*/ 	struct sockaddr_in serv_addr; 	/* 	将结构体里面的数据清零,后续再次赋值 	*/ 	memset(&serv_addr, 0, sizeof(serv_addr)); 	/*指定地址族为IPv4*/ 	serv_addr.sin_family = AF_INET; 	/*设置了服务器的IP地址 	INADDR_ANY 是一个特殊的常量,它表示服务器将接受来自任何网络接口的连接请求 	htonl()函数用于将主机字节序转换成网络字节序,确保在不同架构的计算机上数据的正确的传输 	主机字节序:大端或者小端 	网络字节序:默认为大端*/ 	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 	/*设置了服务器的端口号,SERVER_PORT为代码顶部设置的宏 	*/ 	serv_addr.sin_port = htons(SERVER_PORT); 	/*绑定 	将一个套接字与特定的ip地址和端口号关联起来,使服务器能够在该地址上监听来自客户端的请求 	第一个参数:服务器套接字的文件描述符,通过此文件描述符对服务器套接字进行操作 	第二个参数:&serv_addr为要绑定到套接字的结构体的指针,由于bind()函数要求的参数类型,所以进行类型转换 	第三个参数:指定了serv_addr结构体的大小*/ 	int valbind; 	if ((valbind = bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))) < 0) { 		perror("bind failed"); 		fprintf(stderr, "Bind failed with error code: %d\n", WSAGetLastError()); 		exit(EXIT_FAILURE); 	}  	//进入监听状态,等待用户发起请求 	int vallisten; 	if ((vallisten = listen(serv_sock, 3)) < 0) { 		perror("listen failed"); 		exit(EXIT_FAILURE); 	} 	printf("等待客户端连接...\n"); 	printf("——————————————————————————\n"); 	return serv_sock; } char* get_path() { 	char paths[20] = {}; 	int i = 0; 	paths[0] = '.'; paths[1] = '/'; 	while (u_mes.path[i] != '\0') { 		//printf("u_mes.path[i]=%c,i=%d\n",u_mes.path[i],i); 		paths[i + 2] = u_mes.path[i]; 		i++; 	} 	paths[i + 2] = '\0'; 	printf("拼接后的文件目录地址paths=%s\n", paths); 	return paths; }    

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!