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

参考

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];	 //请求urlchar 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) {//404do_http_response(clnt_sock, "./error.html");//无法获取文件信息}else if (t==3) {//500do_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++;//获取urlif (buf[i] == '/'&&buf[i+1]==' ') {//无urlint 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;         // 文件所有者的用户 IDgid_t     st_gid;         // 文件所有者的组 IDdev_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;//提取pathwhile (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;//声明一个文件指针并将其初始化为nullerrno_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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/751058.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言—判断字符串是否回文

回文的概念&#xff1a;顺读和倒读是一样的 比如&#xff1a;12321 madam 上海自来水来自海上 1.法一 #include<stdio.h> #include<string.h> int main() {char s[100] ;gets(s);int start 0, end strlen(s) - 1;int flag 1;while (start < end &&…

java过滤器Filter相关知识点汇总

1.Filter概述 Servlet Filter又称Servlet过滤器&#xff0c;它是在Servlet2.3规范中定义的&#xff0c;能够对Servlet容器传给Web资源的request对象和response对象执行检查和修改。 Filter不是Servlet&#xff0c;不能直接访问&#xff0c;其本身也不能生成request对象和resp…

很好的一本书,推荐给你们《Hello 算法》

算法犹如美妙的交响乐&#xff0c;每一行代码都像韵律般流淌。 愿这本书在你的脑海中轻轻响起&#xff0c;留下独特而深刻的旋律。 本项目旨在打造一本开源免费、新手友好的数据结构与算法入门教程。 全书采用动画图解&#xff0c;内容清晰易懂、学习曲线平滑&#xff0c;引导…

子组件自定义事件$emit实现新页面弹窗关闭之后父界面刷新

文章目录 需求弹窗关闭之后父界面刷新展示最新数据 实现方案AVUE 大文本默认展开slotVUE 自定义事件实现 父界面刷新那么如何用呢? 思路核心代码1. 事件定义2. 帕斯卡命名组件且在父组件中引入以及注册3. 子组件被引用与父事件监听4.父组件回调函数 5.按钮弹窗事件 需求 弹窗…

HCIP—OSPF虚链路实验

OSPF虚链路—Vlink 作用&#xff1a;专门解决OSPF不规则区域所诞生的技术&#xff0c;是一种虚拟的&#xff0c;逻辑的链路。实现非骨干区域和骨干区域在逻辑上直接连接。注意虚链路条件&#xff1a;只能穿越一个区域&#xff0c;通常对虚链路进行认证功能的配置。虚链路认证也…

算法-归并排序-788. 逆序对的数量

题目 给定一个长度为 n 的整数数列&#xff0c;请你计算数列中的逆序对的数量。 逆序对的定义如下&#xff1a;对于数列的第 i 个和第 j个元素&#xff0c;如果满足 i<j且 a[i]>a[j]&#xff0c;则其为一个逆序对&#xff1b;否则不是。 输入格式 第一行包含整数 n&a…

【渗透测试】redis漏洞利用

redis安装及配置 wget http://download.redis.io/releases/redis-3.2.0.tar.gz tar xzf redis-3.2.0.tar.gz cd redis-3.2.0 make cp /root/redis-6.2.6/redis.conf /usr/local/redis/bin/ cd /usr/local/redis/bin/ vi redis.conf #修改内容如下&#xff1a; #protected-mode …

C语言基础知识笔记

总体上必须清楚的: 1)程序结构是三种: 顺序结构 , 循环结构(三个循环结构), 选择结构(if 和 switch) 2)读程序都要从main()入口, 然后从最上面顺序往下读(碰到循环做循环,碰到选择做选择)。 3)计算机的数据在电脑中保存是以 二进制的形式. 数据存放的位置就是 他的地址. 4)bit是…

【图像分割】使用Otsu 算法及迭代计算最佳全局阈值估计并实现图像分割(代码实现与分析)

本实验要求理解全局阈值分割的概念&#xff0c;并实现文本图像分割。需要大家深入理解Ostu 算法的实现过程及其迭代原理&#xff0c;同时通过学习使用Otsu 算法及其迭代&#xff0c;实践图像分割技术在文本图像处理中的应用。 以下将从实验原理、实验实现、实验结果分析三部分对…

(黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式

&#xff08;黑马出品_高级篇_01&#xff09;SpringCloudRabbitMQDockerRedis搜索分布式 微服务技术——保护 今日目标1.初识Sentinel1.1.雪崩问题及解决方案1.2.服务保护技术对比1.3.Sentinel介绍和安装1.3.1.初识Sentinel1.3.2.安装Sentinel 1.…

Mock.js了解(Mock就是模拟一个后端,Postman模拟前端)

Mock.js 基于 数据模板 生成模拟数据。基于 HTML模板 生成模拟数据。拦截并模拟 ajax 请求。 基本语法 DTD&#xff08;数据模板定义规范&#xff09; 数据模板的每个属性由3部分构成&#xff1a;属性名、生成规则、属性值&#xff08;‘name|rule’: value&#xff09; 属性名…

Elasticsearch:调整搜索速度

在我之前的文章 “Elasticsearch&#xff1a;如何提高查询性能” 及 “Elasticsearch&#xff1a;提升 Elasticsearch 性能” 里&#xff0c;我详细描述了如何提高搜索的性能。在今天的文章里&#xff0c;我从另外一个视角来描述如何调整搜索的速度。希望对大家有所帮助&#x…

基于springboot+vue的早餐店点餐系统(源码+论文)

作者主页&#xff1a;Java程序员老张 主要内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;…

Python基础综合案例-数据可视化

一、数据可视化 - 折线图可视化 1.1、json数据格式 """ 演示JSON数据和Python字典的相互转换 """ import json # 准备列表&#xff0c;列表内每一个元素都是字典&#xff0c;将其转换为JSON data [{"name": "张大山", &quo…

dp入门:从暴力dfs 到 dp

本篇为小金鱼大佬视频的学习笔记&#xff0c;原视频链接&#xff1a;https://www.bilibili.com/video/BV1r84y1379W?vd_source726e10ea5b787a300ceada715f64b4bf 基础概念 暴力dfs很多时候仅能过部分测试点&#xff0c;要想将其优化&#xff0c;一般以 dfs -> 记忆化搜索 …

JavaEE—— HTTP协议(上篇)

文章目录 一、认识什么是 HTTP 协议二、HTTP 抓包工具1.了解使用哪种工具2.了解抓包工具抓包的原理3.简单使用抓包工具 三、解释 HTTP 中的报文格式1.认识 URL2. 认识 HTTP 请求解释首行 "方法"解释 请求头(header)空行body 3、总结 一、认识什么是 HTTP 协议 HTTP …

PS学习 - 抠图-通道-主题颜色和背景颜色不能相近

抠出蝴蝶 1.通道抠图 套索工具 这里需要圈住你要的&#xff0c;注意尽量小点 ctrl j 复制 然后去掉背景 点击通道 找到明暗对比最大的通道&#xff0c;这里我理解为颜色反差最大的那个&#xff0c;突出你要抠的东西 搜了下说是一般为蓝色 复制通道 ctrll调出色阶 通过移…

Rust 枚举与模式匹配:探索类型安全与表达力的完美结合

Rust 是一种系统编程语言&#xff0c;旨在提供内存安全、并发性和性能。在 Rust 中&#xff0c;枚举&#xff08;Enum&#xff09;和模式匹配&#xff08;Pattern Matching&#xff09;是两个核心概念&#xff0c;它们共同构建了 Rust 强大的类型系统和表达力。本文将深入探讨 …

一文总结python的异常数据处理示例

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…

理解java特性:抽象类和接口

抽象类 抽象类的意义何在&#xff1f; 表面上看抽象类就是其中的抽象方法 不写方法体 只写一个方法声明&#xff1a; public abstract void eat(); 这个eat方法 在基类中是一个抽象概念 不知道动物要吃什么 动物是一个总体概念 所以继承它的子类必须实现这个方法 把抽象变为…