C语言实现基于Linux,epoll和多线程的WebServer服务器
代码结构:

Server.h
头文件,对函数进行了声明
#pragma once
#include<stdio.h>
// 新建一个用于TCP监听的socket文件描述符,并返回
int initListenFd(unsigned short port);// 启动epoll
int epollRun(int lfd);// accept建立连接
void* acceptClient(void* arg);// 接收http请求, cfd表示连接socket, epfd表示epoll树
void* recvHttpRequest(void* arg);// 解析HTTP的请求行
int parseRequestLine(const char *line, int cfd);// 发送文件(即HTTP相应报文的数据部分,不包括状态行和首部行)
int sendFile(const char* fileName, int cfd);// 发送响应头(状态行+首部行)
int sendHeadMsg(int cfd, int status, const char* descr, const char* type, int length);// 根据文件的后缀/文件名,得到文件的type,作为HTTP响应报文的首部字段content_type的值
const char* getFileType(const char* name);//发送目录
int sendDir(const char* dirName, int cfd);// 将字符转换为整形
int hexToDec(char c);//解码
// to 存储解码之后的数据, 传出参数, from被解码的数据, 传入参数
void decodeMsg(char* to, char* from);
Server.c
函数实现
#include "Server.h"
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <pthread.h>
#include <stdio.h>
#include <fcntl.h> // 设置socket非阻塞
#include <errno.h> // 判断errno
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <assert.h>
#include <sys/sendfile.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <ctype.h> // 封装线程工作函数的参数
typedef struct FdInfo{int fd; // 文件描述符(用于监听/通信)int epfd; // epoll树实例 pthread_t tid; // 线程id
}FdInfo;// 新建一个用于TCP监听的socket文件描述符,并返回
int initListenFd(unsigned short port){// 1. 创建监听的fdint lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd==-1){perror("socket");return -1;}// 2. 设置端口复用int opt = -1;int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);if(ret == -1){perror("setsockopt");return -1;}// 3. 绑定Ip和端口号struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;ret = bind(lfd, (struct sockaddr*)&addr, sizeof addr);if(ret == -1){perror("bind");return -1;} // 4. 设置监听ret = listen(lfd, 128);if(ret == -1){perror("listen");return -1;}// 5. 返回fdreturn lfd;
}// 启动epoll
int epollRun(int lfd){// 1. 创建epoll实例int epfd = epoll_create(1); // 该参数已经费用,1没有实际意义if(epfd == -1){perror("epoll_create");return -1;}// 2. 将lfd添加到epoll的红黑树上struct epoll_event ev; // 创建返回时的数据evev.data.fd=lfd;ev.events = EPOLLIN;int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);if(ret == -1){perror("epoll_ctl(EPOLL_CTL_ADD)");return -1;}// 3. 开始检测epoll树上是否有就绪的节点,并进行处理struct epoll_event evs[1024]; //epoll_wait的传出参数,就绪的节点被放在里面int size = sizeof(evs) / sizeof(struct epoll_event); while(1){int num = epoll_wait(epfd, evs, size, -1); //-1表示一直阻塞,直到检测到已就绪的文件描述符for(int i=0;i<num;++i){ //遍历前num个就绪的节点FdInfo *info = (FdInfo *)malloc(sizeof(FdInfo)); int fd = evs[i].data.fd;info->epfd = epfd;info->fd = fd;if(fd == lfd){ // 如果是监听socket,则建立新的连接 accept// acceptClient(fd, epfd); pthread_create(&info->tid, NULL, acceptClient, info);}else{ // 如果是连接socket,则进行处理// recvHttpRequest(fd, epfd); pthread_create(&info->tid, NULL, recvHttpRequest, info);}}}
}// accept建立连接,并将得到的用于连接的socket文件描述符添加到epoll树实例上去
void* acceptClient(void* arg){ // lfd表示用于监听的socket,epfd表示epoll实例FdInfo *info = (FdInfo*)arg;// 1. 建立连接int cfd = accept(info->fd, NULL, NULL);if(cfd == -1){perror("accept");return NULL;}// printf("连接socket:%d\n",cfd);//2. 设置连接socket非阻塞int flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);//3. 将用于连接的socket添加到epoll树上struct epoll_event ev;ev.data.fd=cfd ;ev.events = EPOLLIN | EPOLLET ; // 用于连接的socket即需要读,又需要写int ret = epoll_ctl(info->epfd, EPOLL_CTL_ADD, cfd, &ev);if(ret == -1){perror("epoll_ctl(EPOLL_CTL_ADD)");return NULL;}free(info);return NULL;
}// 连接socket的工作函数
void* recvHttpRequest(void* arg){FdInfo *info = (FdInfo*)arg;int len = 0, total=0; // len是每一次while读取的数据长度,total表示总共的长度char tmp[1024] = { 0 }; // 每一次循环读取的数据char buf[4096] = { 0 }; // 存储整个的客户端数据块,即将每一次循环读取的数据连在一起// 由于使用了非阻塞cfd,所以需要循环的接受数据while((len=recv(info->fd, tmp, sizeof tmp, 0)) > 0){if(total+len<sizeof buf){memcpy(buf+total,tmp, len); //将mid_buf拼接到buf后面}total+=len; // 更新total}// 判断recv是读完了数据,还是读数据失败了,因为二者都会返回-1(根据error number进行判断)if(len==-1 && errno == EAGAIN){ //如果是读数据读完了,则可以得到一个HTTP请求报文// 截取出HTTP的请求行char *pt = strstr(buf, "\r\n"); //pt指针指向\r这个字符int reqLen = pt-buf;buf[reqLen] = '\0'; //直接截断parseRequestLine(buf, info->fd);}else if (len == 0 ){ // 客户端断开了连接epoll_ctl(info->epfd, EPOLL_CTL_DEL, info->fd, NULL); //从epoll模型上删除当前用于通信的节点close(info->fd); //关闭文件描述符}else{ // 如果是读数据失败了perror("recv");}free(info);return NULL;
}// HTTP报文格式:
// 方法 URL 版本[CRLF] 如:get /xxx/1.jpg HTTP/1.1
// 首部字段: 值[CRLF]
// ...[CRLF]
// 首部字段: 值[CRLF]
// [CRLF]
// 数据实体// 解析HTTP的请求行
int parseRequestLine(const char *line, int cfd){// 解析请求行char method[12]; // 存储客户端请求方法,如GET/POSTchar path[1024];sscanf(line, "%[^ ] %[^ ]", method, path);printf("请求内容: \nmethod:%s, path:%s\n\n",method, path);// 不区分大小写的比较,如果不等于0,则返回-1(目前只接收get方法)if(strcasecmp(method, "get") != 0){ return -1;}decodeMsg(path, path); //转换为utf8编码,这样可以支持中文等特殊字符// 处理客户端请求的静态资源(目录或文件),因为当前获得的/xxx/1.jpg是相对于工作路径的,所以需要转换为./xxx/1.jpg或者xxx/1.jpgchar *file = NULL;if(strcmp(path, "/") == 0){ //如果get当前的工作目录file = "./";}else{file = path+1; // get工作目录中的一个资源}// 判断文件的属性(目录还是文件)struct stat st; //stat函数的传出采纳数int ret = stat(file, &st);if(ret == -1){ //-1表示文件不存在// 如果文件不存在,返回404界面sendHeadMsg(cfd, 404, "Not Found", getFileType(".html"), -1); //发送HTTP相应报文的状态行和首部行,-1表示让浏览器自己读数据的长度sendFile("404.html", cfd);return 0;}// 判断文件的类型,如果是目录if(S_ISDIR(st.st_mode)) //S_ISDIR是Linux提供的宏,判断是否是目录,是则返回1{// 将本地目录的内容发送给客户端sendHeadMsg(cfd, 200, "OK", getFileType(".html"), -1);sendDir(file, cfd);}// 如果是文件else{// 将文件的内容发送给客户端sendHeadMsg(cfd, 200, "OK", getFileType(file), st.st_size); //发送HTTP相应报文的状态行和首部行sendFile(file, cfd); //发送文件}return 0;
}// 发送响应头(状态行+首部行)
int sendHeadMsg(int cfd, int status, const char* descr, const char* type, int length){char buf[4096] = {0};// 状态行sprintf(buf, "http/1.1 %d %s\r\n", status, descr);// 首部行和空行(\r\n)sprintf(buf+strlen(buf), "content-type: %s\r\n", type);sprintf(buf+strlen(buf), "content-length: %d\r\n\r\n", length);//发送状态行和首部行send(cfd, buf, strlen(buf), 0);return 0;
}// 发送数文件(HTTP相应报文的数据部分),由于使用了TCP面向连接的流式传输协议,所以可以读一部分发一部分数据
int sendFile(const char* fileName, int cfd){ // cfd表示建立连接的socket文件描述符// 1.打开文件,并获得文件描述符int fd = open(fileName, O_RDONLY); //O_RDONLY表示只读文件assert(fd>0);
#if 0 // 直接手写发送数据,但是可以有更简单的方式while(1){char buf[1024];int len = read(fd, buf, sizeof(buf)); //读数据if(len>0){send(cfd, buf, len, 0); // 将读到的数据发送给客户端usleep(10); // 给接收端一些时间去接收数据,防止客户端接收数据出错}else if(len == 0){break;}else{perror("read");}}
#else //直接使用系统函数sendfile发送文件,相比于手写更简单off_t offset = 0;int size = lseek(fd, 0, SEEK_END); //得到文件的大小,seek_end将文件指针移动到文件的末尾lseek(fd, 0, SEEK_SET); // seek_set将文件的指针重新移动到文件的头部while(offset<size) //循环发送文件,如果是大文件,只发送一次sendfile会导致写缓存写满,剩余的数据就发送不出去了{int ret = sendfile(cfd, fd, &offset, size-offset); //sendfile会自动给offset赋值,表示当前读的偏移量// printf("ret value: %d\n", ret);if(ret == -1 && errno!=EAGAIN){perror("sendfile");}}#endifclose(fd);return 0;
}/* HTML文件发送目录时的格式
<html><head><title>test</title></head><body><table><tr> // 每一都tr都是一个行,td是一个列<td></td> // 文件名<td></td> // 文件大小</tr><tr><td></td><td></td></tr></table></body>
</html>
*/
//发送目录,使用scandir进行单层目录的遍历
int sendDir(const char* dirName, int cfd){ //dirName表示目录名char buf[4096] = { 0 };sprintf(buf, "<html><head><title>%s</title></head><body><table>", dirName);struct dirent** namelist; //namelist指向的是一个指针数组struct dirent* tmp[]int num = scandir(dirName, &namelist, NULL, alphasort);for(int i=0;i<num;++i){char* name = namelist[i]->d_name; //拿到了文件名字struct stat st; //将目录名与文件名进行拼接char subPath[1024] = { 0 };sprintf(subPath, "%s/%s", dirName, name);stat(subPath, &st); //用于判断name所表示的文件的类型if(S_ISDIR(st.st_mode)){ //如果是目录sprintf(buf+strlen(buf), "<tr><td><a href=\"%s/\">%s</a></td><td>%ld</td></tr>",name, name, st.st_size); //使用a标签设置超链接标签}else{ // 如果是文件sprintf(buf+strlen(buf), "<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",name, name, st.st_size); //使用a标签设置超链接标签}send(cfd, buf, strlen(buf),0);memset(buf,0,sizeof buf);free(namelist[i]);}sprintf(buf, "</table></body></html> ");send(cfd, buf, strlen(buf), 0);free(namelist);return 0;
}// 根据文件的后缀/文件名,得到文件的type,作为HTTP响应报文的首部字段content_type的值
const char* getFileType(const char* name)
{// a.jpg a.mp4 a.html// 自右向左查找‘.’字符, 如不存在返回NULLconst char* dot = strrchr(name, '.');if (dot == NULL)return "text/plain; charset=utf-8"; // 纯文本if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)return "text/html; charset=utf-8";if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)return "image/jpeg";if (strcmp(dot, ".gif") == 0)return "image/gif";if (strcmp(dot, ".png") == 0)return "image/png";if (strcmp(dot, ".css") == 0)return "text/css";if (strcmp(dot, ".au") == 0)return "audio/basic";if (strcmp(dot, ".wav") == 0)return "audio/wav";if (strcmp(dot, ".avi") == 0)return "video/x-msvideo";if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)return "video/quicktime";if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)return "video/mpeg";if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)return "model/vrml";if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)return "audio/midi";if (strcmp(dot, ".mp3") == 0)return "audio/mpeg";if (strcmp(dot, ".ogg") == 0)return "application/ogg";if (strcmp(dot, ".pac") == 0)return "application/x-ns-proxy-autoconfig";return "text/plain; charset=utf-8";
}// 将字符转换为整形数
int hexToDec(char c)
{if (c >= '0' && c <= '9')return c - '0';if (c >= 'a' && c <= 'f')return c - 'a' + 10;if (c >= 'A' && c <= 'F')return c - 'A' + 10;return 0;
}// 解码
// to 存储解码之后的数据, 传出参数, from被解码的数据, 传入参数
void decodeMsg(char* to, char* from)
{for (; *from != '\0'; ++to, ++from){// isxdigit -> 判断字符是不是16进制格式, 取值在 0-f// Linux%E5%86%85%E6%A0%B8.jpgif (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])){// 将16进制的数 -> 十进制 将这个数值赋值给了字符 int -> char// B2 == 178// 将3个字符, 变成了一个字符, 这个字符就是原始数据*to = hexToDec(from[1]) * 16 + hexToDec(from[2]);// 跳过 from[1] 和 from[2] 因此在当前循环中已经处理过了from += 2;}else{// 字符拷贝, 赋值*to = *from;}}*to = '\0';
}
main函数
#include "Server.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char* argv[]){// 用户传入的参数为 [默认参数,port, path],即启动当前服务器,设置端口号port和工作路目录pathif(argc<3){printf("./a.out port path\n");return -1;}// 拿到端口号unsigned short port = atoi(argv[1]); // argv to int// 修改当前目录到工作目录chdir(argv[2]);printf("端口号: %d\n工作目录: %s\n", port, argv[2]);// 初始化用于监听的套接字int lfd = initListenFd(port); //设置端口号,并返回一个监听文件描述符printf("用于监听的文件描述符: %d\n",lfd);//启动服务器程序epollRun(lfd);return 0;
}
启动指令:
# 1. 在文件目录下,编译所有.c文件,生成可执行文件Server
gcc *.c -l pthread -o Server# 2. 运行Server, 运行指令的格式为 ./Server port filePath, 如:
./Server 10000 /
# 表示端口号为10000, 工作目录为根目录/(只是实例,一般需要自己的一个sources资源目录)
控制台输打印示例
参考:大丙课堂
相关文章:
C语言实现基于Linux,epoll和多线程的WebServer服务器
代码结构: Server.h 头文件,对函数进行了声明 #pragma once #include<stdio.h> // 新建一个用于TCP监听的socket文件描述符,并返回 int initListenFd(unsigned short port);// 启动epoll int epollRun(int lfd);// accept建立连接 vo…...
微信小程序数字键盘(仿微信转账键盘)
微信小程序input自带数字输入键盘,不过是直接调用的系统键盘,无法个性化。 代码中使用使用了Vant WeappVant UI小程序版,这里就不介绍相关安装说明了,大家自行安装Vant Weapp。 json 用到的组件 {"usingComponents": …...
mac电脑强大的解压缩软件BetterZip 5.3.4 for Mac中文版及betterzip怎么压缩
BetterZip 5.3.4 for Mac 是Mac系统平台上一款功能强大的文件解压缩软件,不必解压就能快速地检查压缩文档。它能执行文件之间的合并并提供密码。使用它,用户可以更快捷的向压缩文件中添加和删除文件。它支持包括zip、gz、bz、bz2、tar、tgz、tbz、rar、7…...
Llama 2 来袭 - 在 Hugging Face 上玩转它
🤗 宝子们可以戳 阅读原文 查看文中所有的外部链接哟! 引言 今天,Meta 发布了 Llama 2,其包含了一系列最先进的开放大语言模型,我们很高兴能够将其全面集成入 Hugging Face,并全力支持其发布。Llama 2 的社…...
linux操作历史history定制
history记录 Linux中历史操作记录history是一个很有用的功能,有时忘记了,翻翻以前的命令,十分方便。 # 展示所有历史记录 history # 筛选历史记录 history | grep nginx # 清除全部记录 -c history -c # 指定删除某一行,15是行号 history -…...
微信小程序 wx.showModal
微信小程序--wx.showModal_海轰Pro的博客-CSDN博客...
Java开发中的分层开发和整洁架构
分层开发(横向拆分) 分层开发的概念: maven多模块开发项目管理.可以利用这种管理功能,实现一个项目的多层次模块开发–分层开发. 比如,当前项目HelloController依赖HelloService 这样做目的: 复杂开发过程.解耦(不调整依赖关系,无法解耦).分层开发(横向拆分)和纵向拆分的区别…...
Spring 多数据源方法级别注解实现
Spring框架提供了多种数据源管理方式,其中多数据源管理是其中之一。多数据源管理允许应用程序使用多个数据源,而不是只使用一个数据源,从而提高了应用程序的灵活性和可靠性。 多数据源管理的主要目的是让应用程序能够在不同的数据库之间切换&…...
Redis在云服务器上的安装与客户端连接配置
文章目录 Redis1.Redis的安装2.设置远程连接3.客户端连接3.1 客户端下载 Redis 1.Redis的安装 yum 安装 redis,使用以下命令,直接将 redis 安装到 linux 服务器: yum -y install redis 启动 redis使用以下命令,以后台运行方式启…...
语言模型输出端共享Embedding的重新探索
©PaperWeekly 原创 作者 | 苏剑林 单位 | 科学空间 研究方向 | NLP、神经网络 预训练刚兴起时,在语言模型的输出端重用 Embedding 权重是很常见的操作,比如 BERT、第一版的 T5、早期的 GPT,都使用了这个操作,这是因为当模型…...
Spring中事务失效的8中场景
1. 数据库引擎不支持事务 这里以 MySQL为例,MyISAM引擎是不支持事务操作的,一般要支持事务都会使用InnoDB引擎,根据MySQL 的官方文档说明,从MySQL 5.5.5 开始的默认存储引擎是 InnoDB,之前默认的都是 MyISAMÿ…...
安卓——转场动画
先创建一个名为anim的包 往里面写入两个xml页 为淡入淡出的效果 淡入效果 <alpha xmlns:android="http://schemas.android.com/apk/res/android"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:fromAlpha...
多位数码管动态扫描显示变化数据(数码管右移1)
/*----------------------------------------------- 内容:多位数码管分别显示不同数字,这种扫描显示方式成为动态扫描,并不停变化赋值 ------------------------------------------------*/ #include<reg52.h> //包含头文件࿰…...
充分了解java阻塞队列机制
多线程基础 1.阻塞队列1.1 什么是 阻塞队列1.2 阻塞队列的特点 1.3 阻塞队列常用方法1.3.1 抛出异常:add、remove、element1.3.2 返回结果但是不抛出异常offer、poll、peek1.3.3 阻塞put和take1.3.4 小结 1.4 常见的阻塞队列1.4.1 ArrayListBlockingQueue1.4.2 LinkedBlockingQ…...
安装使用LangChain时的报错解决
刚刚装了LangChain但是引入各种包都报错,原因貌似为 Python3.7 不支持 LangChain,需要开启一个新的Python3.10环境,再重新安装即可正常运行。 创建新的python环境 conda create -n new_env python3.10 重新安装 pip install langchain 这是当…...
【MySQL】库的操作
🌠 作者:阿亮joy. 🎆专栏:《零基础入门MySQL》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录 👉库…...
Java设计模式之工厂模式
什么是工厂模式 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 工厂模式提供了一种将对象的实例化过程封装在工厂类中的方式。通过使用工厂模式ÿ…...
正则表达式-速成教程
正则表达式-速成教程 今天遇到一枚程序媛在群里吐槽,并附了截图;然后无意中看到她的一个正则与她的注释描述不一致,就提醒了一下。顺带着给了个速成教程,在这里把这个速成教程贴出来,一是为了自己备份;二是…...
C语言中的数组(详解)
C语言中的数组(详解) 一、一维数组1.一维数组的创建2.数组的初始化3.一维数组的使用4.一维数组在内存中的存储二、二维数组1.二维数组的创建2.二维数组的初始化3.二维数组的使用4.二维数组在内存中的存储三、数组越界四、数组作为函数参数1.冒泡排序2.数…...
【App管理04-Bug修正 Objective-C语言】
一、咱们刚才已经把这个给大家做完了吧 1.这个Label怎么显示到上面去了, 我们现在是把它加到我们的控制器的View里面吧 我们看一下这个坐标是怎么算的,来,我们找一个坐标, 咱们的坐标,是不是用这个View的frame,减的吧 来,咱们在这里,输出一下这个Frame,看一下吧 在…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...
WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
Mac flutter环境搭建
一、下载flutter sdk 制作 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 1、查看mac电脑处理器选择sdk 2、解压 unzip ~/Downloads/flutter_macos_arm64_3.32.2-stable.zip \ -d ~/development/ 3、添加环境变量 命令行打开配置环境变量文件 ope…...
