Linux之HTTP服务器的构建

在这里插入图片描述

欢迎诸位来阅读在下的博文~
在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交流,共同努力!
江山如画,客心如若,欢迎到访,一展风采

文章目录

  • 参考环境
  • 参考书籍
  • 一、HTTP的工作原理
      • 1. 建立连接
      • 2. 发送请求
      • 3. 服务器处理请求
      • 4. 发送响应
      • 5. 断开连接
      • 6. 客户端处理响应
  • 二、客户端请求的消息结构
  • 三、服务器响应的消息结构
  • 四、HTTP状态码
  • 五、从零开始搭建HTTP服务器
    • 编译运行

参考环境

  1. VMware Workstation Pro
  2. Ubuntu20.04(运行服务器)

参考书籍

《Linux C/C++ 服务器开发实践》——朱文伟 李建英

一、HTTP的工作原理

HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最广泛的协议之一,它定义了客户端(通常是浏览器)和服务器之间交换数据的规则。浏览器作为HTTP客户端通过URL向HTTP服务器端,即Web服务器发送所有请求。常见的Web服务器有:Apache服务器、IIS服务器等。
其模型如下:
在这里插入图片描述

以下是 HTTP 的工作原理的简化概述:

1. 建立连接

  • 客户端发起请求:用户通过浏览器或其他客户端软件输入 URL 或点击链接,客户端软件解析 URL 并向服务器发起连接请求。
  • DNS解析:客户端首先通过 DNS(Domain Name System)将域名解析为服务器的 IP 地址。
  • TCP连接:客户端通过 TCP(Transmission Control Protocol)与服务器建立连接。HTTP 通常使用 TCP 的 80 端口,但是也可以改为8080或者其他端口。

2. 发送请求

  • 请求行:客户端发送一个 HTTP 请求,通常包括请求方法(如 GET、POST)、请求的 URL 和 HTTP 版本(如 HTTP/1.1 或 HTTP/2)。
  • 请求头:请求还包括一系列的请求头,如 Host(指定服务器的主机名)、User-Agent(标识客户端类型)、Accept(指定客户端可接受的响应类型)等。
  • 请求体(可选):如果请求方法(如 POST)需要发送数据,请求体将包含这些数据。

3. 服务器处理请求

  • 服务器接收请求:服务器接收到请求后,根据请求方法、URL 和其他信息处理请求。
  • 资源定位:服务器确定请求的资源位置,这可能是静态文件或需要动态生成的数据。
  • 处理请求:如果是动态请求,服务器可能需要执行数据库查询、业务逻辑处理等。

4. 发送响应

  • 状态行:服务器发送一个 HTTP 响应,包括 HTTP 版本、状态码(如 200 表示成功)和状态短语(如 “OK”)。
  • 响应头:响应还包括一系列的响应头,如 Content-Type(指定响应内容的类型)、Content-Length(指定响应内容的长度)等。
  • 响应体:这是服务器返回给客户端的实际数据,可以是 HTML 页面、图片、视频等。

5. 断开连接

  • 关闭连接:一旦响应被客户端接收,TCP 连接通常会被关闭。对于 HTTP/1.1,如果请求头中包含 Connection: keep-alive,则连接可能会被保持打开状态,以便后续请求重用。

6. 客户端处理响应

  • 解析响应:客户端软件解析响应内容,并根据内容类型进行相应的处理(如渲染 HTML 页面、显示图片等)。
    HTTP 是无状态的,这意味着每个请求都是独立的,服务器不会保存关于客户端的任何信息(除非使用 cookie 或其他机制)。这使得 HTTP 适用于简单的请求-响应模型,但也有一些局限性,比如在需要维护会话状态的应用中。

二、客户端请求的消息结构

客户端发送一个HTTP请求到服务器,改请求消息由请求行(状态行)、请求头部(首部行)、空行和请求数据(实体)四个部分组成,如图所示
在这里插入图片描述

注:sp 指空格,cr指回车符,lf指换行符

HTTP协议定义了8种请求方法,来表明对Request-URI指定资源的不同操作方式,具体如下:

  1. GET:请求获取由Request-URI标识的资源。它通常用于请求服务器发送一个文件或数据。
  2. POST:用于向服务器提交数据,通常用于提交表单数据或上传文件。POST请求可能会导致服务器上的状态改变或副作用。
  3. PUT:用于将请求中的数据存储在服务器上。它通常用于更新资源,但如果资源不存在,可能会创建一个新的资源。
  4. DELETE:请求服务器删除由Request-URI标识的资源。
  5. HEAD:与GET方法类似,但服务器在响应中只返回头部信息,不返回实体主体(即不返回实际内容)。
  6. OPTIONS:用于获取服务器支持的HTTP请求方法和其他选项信息。
  7. TRACE:用于沿着到目标资源的路径执行消息回环测试,主要用于诊断和调试。
  8. CONNECT:用于建立一个隧道,将连接转换为透明的TCP/IP通道,通常用于SSL加密的连接。

以下是这些方法的归纳:

  • GET:检索数据。
  • POST:提交数据。
  • PUT:更新或创建数据。
  • DELETE:删除数据。
  • HEAD:检查数据的存在性或状态。
  • OPTIONS:查询服务器能力。
  • TRACE:跟踪请求链。
  • CONNECT:建立隧道。

虽然HTTP的请求方式有8种,但是在实践中常用到的是GET和POST,其他方法可以通过这两种基本方式间接实现。

三、服务器响应的消息结构

HTTP响应由四个部分组成,分别是:状态行、消息报头(响应头)、空行和响应正文,如图:
在这里插入图片描述

在这里插入图片描述

四、HTTP状态码

当浏览者访问一个网页时,浏览器会向网页所在服务器发出请求。在浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头用以响应浏览器的请求。
HTTP状态码的英文时HTTP Status Code。下面时常见的HTTP状态码:
200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误

类型描述
1**信息,服务器收到请求,需要请求者继续执行操作
2**成功,操作被成功接收并处理
3**重定向,需要进一步的操作以完成请求
4**客户端错误,请求包含语法错误或无法完成请求
5**服务器错误,服务器在处理请求的过程中发生了错误

五、从零开始搭建HTTP服务器

现在有很多现成开源的HTTP服务器,比如非常流行的Apache,诸位若想详细了解,可以浏览下面博客一二:
在linux上架设Web服务器Apache(Ubuntu)
Linux系统的Apache2如何启动cgi模块(Ubuntu)

但是,我今天打算自己手动搭建一个,诸位若有兴趣,也可以一起来吧~

main.c

#include "httpserver.h"int main(void)
{int server_sock = -1;u_short port = 8888;int client_sock = -1;struct sockaddr_in client_name;socklen_t client_name_len = sizeof(client_name);pthread_t newthread;server_sock = startup(&port);printf("httpd running on port %d\n", port);while (1){client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);if (client_sock == -1)error_die("accept");/* accept_request(client_sock); */if (pthread_create(&newthread , NULL, (void*(*)(void*))accept_request, (void*)(intptr_t)client_sock) != 0)perror("pthread_create");}close(server_sock);return(0);
}

httpserver.h

#ifndef _HTTPSERVER_H_
#define _HTTPSERVER_H_
#include "myhead.h"#define ISspace(x) isspace((int) (x))#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"//处理从套接字上监听到的一个HTTP请求,此函数很大部分体现服务器处理请求流程
void accept_request(void*);//返回给客户端这是一个错误请求
void bad_request(int);//读取服务器上某个文件,写道套接字中
void cat(int, FILE *);//处理发生在执行cgi程序事出现的错误
void cannot_execute(int);//打印错误信息,并退出
void error_die(const char *);//运行cgi程序的处理,是主要的函数
void execute_cgi(int, const char *, const char *, const char *);//读取套接字的一行,把回车符换行符等情况都统一为换行符结束
int get_line(int, char *, int);//把HTTP响应头部写到套接字中
void headers(int, const char *);//处理找不到请求文件的情况,即发送404
void not_found(int);//读取文件并返回给客户端,里面调用cat
void serve_file(int, const char *);//初始化http服务,包括建立套接字、绑定端口、进行监听等。
int startup(u_short *);//返回浏览器,表明method不支持
void unimplemented(int);#endif

httpserver.c

#include "myhead.h"
#include "httpserver.h"int startup(u_short *port)
{int httpd = 0;struct sockaddr_in name;httpd = socket(PF_INET, SOCK_STREAM, 0);if (httpd == -1)error_die("socket");memset(&name, 0, sizeof(name));name.sin_family = AF_INET;name.sin_port = htons(*port);name.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)error_die("bind");if (*port == 0) /* if dynamically allocating a port */{socklen_t namelen = sizeof(name);if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)error_die("getsockname");*port = ntohs(name.sin_port);}if (listen(httpd, 5) < 0)error_die("listen");return (httpd);
}void error_die(const char *sc)
{perror(sc);exit(1);
}void accept_request(void *arg)
{int client = (intptr_t)arg;char buf[1024];int numchars;char method[255];char url[255];char path[512];size_t i, j;struct stat st;int cgi = 0;char *query_string = NULL;numchars = get_line(client, buf, sizeof(buf));i = 0;j = 0;while (!ISspace(buf[j]) && (i < sizeof(method) - 1)){method[i] = buf[j];i++;j++;}method[i] = '\0';//只能识别GET和POSTif (strcasecmp(method, "GET") && strcasecmp(method, "POST")){//返回浏览器,表明格式不符unimplemented(client);return;}//如果是POST,cgi置为1,即POST的时候开启cgiif (strcasecmp(method, "POST") == 0)cgi = 1;i = 0;//跳过空格while (ISspace(buf[j]) && (j < sizeof(buf)))j++;//从缓冲区中把URL读取出来while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))){url[i] = buf[j];i++;j++;}url[i] = '\0';//处理GET请求if (strcasecmp(method, "GET") == 0){query_string = url;//去找GET参数,即?后面部分while ((*query_string != '?') && (*query_string != '\0'))query_string++;//如果找到了的话,说明这个请求也需要调用脚本来处理//此时就把请求字符串单独抽取出来//GET方法特点,?后面为参数if (*query_string == '?'){cgi = 1;*query_string = '\0';query_string++;}}//保存有效的URL地址并加上请求地址的主页索引,默认目录是在htdocs下//格式化URL到path数组中sprintf(path, "/root/htdocs%s", url);//默认地址,解析到的路径如果是/,则自动加上index.html,代表默认访问该路径下的index.html网页if (path[strlen(path) - 1] == '/')strcat(path, "index.html");//访问请求的文件,如果文件不存在,直接返回并发送404,如果存在,则调用cgi程序处理//根据路径找到对应文件if (stat(path, &st) == -1) //获取文件信息{while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));not_found(client);}else{//如果文件存在但是个目录,则直接拼接index.htmlif ((st.st_mode & S_IFMT) == S_IFDIR)strcat(path, "/index.html");//判断文件的执行权限if ((st.st_mode & S_IXUSR) ||(st.st_mode & S_IXGRP) ||(st.st_mode & S_IXOTH))cgi = 1;//如果不是cgi,直接把服务器文件返回,否则执行cgiif (!cgi)serve_file(client, path);elseexecute_cgi(client, path, method, query_string);}close(client);
}int get_line(int sock, char *buf, int size)
{int i = 0;char c = '\0';int n;while ((i < size - 1) && (c != '\n')){n = recv(sock, &c, 1, 0);/* DEBUG printf("%02X\n", c); */if (n > 0){if (c == '\r'){//从缓冲区读取一个字符,但是不在缓冲区去除,相当于预览n = recv(sock, &c, 1, MSG_PEEK);/* DEBUG printf("%02X\n", c); */if ((n > 0) && (c == '\n'))recv(sock, &c, 1, 0);elsec = '\n';}buf[i] = c;i++;}elsec = '\n';}buf[i] = '\0';return (i);
}void unimplemented(int client)
{char buf[1024];sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</TITLE></HEAD>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 0);
}void not_found(int client)
{char buf[1024];sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>The server could not fulfill\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "your request because the resource specified\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "is unavailable or nonexistent.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 0);
}void serve_file(int client, const char *filename)
{FILE *resource = NULL;int numchars = 1;char buf[1024];buf[0] = 'A';buf[1] = '\0';while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));resource = fopen(filename, "r");if (resource == NULL)not_found(client);else{headers(client, filename);cat(client, resource);}fclose(resource);
}void execute_cgi(int client, const char *path,const char *method, const char *query_string)
{char buf[1024];int cgi_output[2];int cgi_input[2];pid_t pid;int status;int i;char c;int numchars = 1;int content_length = -1;buf[0] = 'A';buf[1] = '\0';if (strcasecmp(method, "GET") == 0)while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));else /* POST */{numchars = get_line(client, buf, sizeof(buf));while ((numchars > 0) && strcmp("\n", buf)){buf[15] = '\0';if (strcasecmp(buf, "Content-Length:") == 0)content_length = atoi(&(buf[16]));numchars = get_line(client, buf, sizeof(buf));}if (content_length == -1){bad_request(client);return;}}sprintf(buf, "HTTP/1.0 200 OK\r\n");send(client, buf, strlen(buf), 0);//建立无名管道,以帮助父子进程通信if (pipe(cgi_output) < 0){cannot_execute(client);return;}if (pipe(cgi_input) < 0){cannot_execute(client);return;}//  fork后管道都复制一份,都是一样的//子进程关闭两个无用的端口,避免浪费//   x<---------------------->1   output//   0<---------------------->x   input//父进程关闭2个无用的端口,避免浪费//   0<---------------------->x   output//   x<---------------------->1   input//此时父子进程可以通信了if ((pid = fork()) < 0){cannot_execute(client);return;}if (pid == 0) /* child: CGI script */{char meth_env[255];char query_env[255];char length_env[255];dup2(cgi_output[1], 1);//将cgi_putput的写端文件描述符复制到1(即标准输出文件描述符),导致任何写入//标准输出的操作都会发送到cig_output的写入端dup2(cgi_input[0], 0); //同理close(cgi_output[0]);close(cgi_input[1]);//设置cgi的环境变量sprintf(meth_env, "REQUEST_METHOD=%s", method);putenv(meth_env);if (strcasecmp(method, "GET") == 0){sprintf(query_env, "QUERY_STRING=%s", query_string);putenv(query_env);}else{ /* POST */sprintf(length_env, "CONTENT_LENGTH=%d", content_length);putenv(length_env);}//使用execl执行cgi的脚本execl(path, path, NULL);exit(0);}else{ /* parent */close(cgi_output[1]);close(cgi_input[0]);if (strcasecmp(method, "POST") == 0)for (i = 0; i < content_length; i++){recv(client, &c, 1, 0);write(cgi_input[1], &c, 1);}while (read(cgi_output[0], &c, 1) > 0)send(client, &c, 1, 0);close(cgi_output[0]);close(cgi_input[1]);waitpid(pid, &status, 0);}
}void headers(int client, const char *filename)
{char buf[1024];(void)filename; /* could use filename to determine file type */strcpy(buf, "HTTP/1.0 200 OK\r\n");send(client, buf, strlen(buf), 0);strcpy(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);strcpy(buf, "\r\n");send(client, buf, strlen(buf), 0);
}void cat(int client, FILE *resource)
{char buf[1024];fgets(buf, sizeof(buf), resource);while (!feof(resource)){send(client, buf, strlen(buf), 0);fgets(buf, sizeof(buf), resource);}
}void bad_request(int client)
{char buf[1024];sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "Content-type: text/html\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "<P>Your browser sent a bad request, ");send(client, buf, sizeof(buf), 0);sprintf(buf, "such as a POST without a Content-Length.\r\n");send(client, buf, sizeof(buf), 0);
}void cannot_execute(int client)
{char buf[1024];sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "Content-type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<P>Error prohibited CGI execution.\r\n");send(client, buf, strlen(buf), 0);
}

myhead.h

// myhead.h
#ifndef _MYHEAD_H
#define _MYHEAD_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <linux/input.h>  //跟输入子系统模型有关的头文件
#include <dirent.h>
#include <stdbool.h>
#include <strings.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>
#include <poll.h>
#include <sys/epoll.h>
#include <ctype.h>
#endif

编译运行

使用:gcc httpserver.c main.c -o main -ipthread 编译出可执行文件
如果成功,可以使用./main运行该服务器程序。

但是,现在还缺少.html文件和.cgi文件。在下已提供一份样例,如下:

index.html

<!DOCTYPE html>
<html>
<head>
<title>Index</title>
</head>
<body>
<p>Welcome to my HTTP webserver.</p>
<h1>Show CGI Result:</h1>
<form action="test.cgi" method="POST">
<input type="submit" value="Submit">
</form>
</body>
</html>

test.cpp(将该文件编译,然后将得到的可执行文件改名成test.cgi即可)

#include <iostream>  
using namespace std;  int main()  
{  cout << "Content-Type: text/html\n\n";  //注意结尾是两个\ncout << "<html>\n";  cout << "<head>\n";  cout << "<title>Hello World - First CGI Program</title>\n";  cout << "</head>\n";  cout << "<body bgcolor=\"yellow\">\n";  cout << "<h2> <font color=\"#FF0000\">Hello World! This is my first CGI program</font></h2>\n";  cout << "</body>\n";  cout << "</html>\n";  return 0;  
}

获得上面两个文件后,将他们统一放在/root/htdocs/目录下(如果没有该目录,可自行创建),
当然,勿要忘记了更改目录和文件的权限:
sudo chmod +x /root/htdocs
(sudo chmod +x /root/htdocs/index.html)x
注意:不要修改.html的权限,因为在代码中当.html文件只读时,才处理
sudo chmod +x /root/htdocs/test.cgi

如此,运行main可执行文件,然后在主机的浏览器上输入:http://你的ip地址:8888/
结果如下:
在这里插入图片描述
点击按钮,会有如下返回:
在这里插入图片描述

当然,你也可以直接访问.cgi文件:
在这里插入图片描述

至此,结束~
在这里插入图片描述
望诸位不忘三连支持一下~

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

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

相关文章

Windows 11上RTX 4090深度学习与大模型微调环境安装指南

【本文原作者&#xff1a;擎创科技资深产品专家 布博士】 在安装深度学习及大模型微调环境时&#xff0c;经历了多次反复操作&#xff08;如CUDA、cuDNN、PyTorch的安装与卸载&#xff09;。为了避免走弯路&#xff0c;总结了以下步骤&#xff1a; 步骤 1&#xff1a;显卡驱动…

11-sentinel利用nacos作持久化

本文介绍sentinel配置数据的持久化方法。由于sentinel官方并没有提供持久化功能&#xff0c;大家在测试过程中也能发现sentinel服务重启后&#xff0c;原来配置的数据就丢了&#xff0c;本文就是来处理这一问题的。 做好心理准备&#xff0c;我们要修改sentinel的源代码&#…

Qt消息对话框的实现

Widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this -> setWindowIcon(QIcon(":/picture/hp.jpg"));//设置窗口图标this -> setW…

Leetcode JAVA刷刷站(57)插入区间

一、题目概述 二、思路方向 为了解决这个问题&#xff0c;我们可以遍历给定的区间列表 intervals&#xff0c;并同时构建一个新的列表来存储最终的合并结果。遍历过程中&#xff0c;我们检查当前区间是否与 newInterval 重叠或相邻&#xff0c;并根据需要进行合并。如果不重叠…

2024新生训练营 8.20 做题活动

A 输出题罢了不讲直接看代码 #inlclude<stdio.h> int main() {printf("Eclipse First&#xff0c;the Rest Nowhere.");return 0; }B 首先要知道的是凡是大于1的整数要么是质数要么是合数&#xff0c;而1既不是质数也不是合数&#xff0c;题目问从0到n所有质数…

微信小程序--30(网络数据请求)

1.小程序中网络数据请求的限制 只能请求HTTPS类型的接口必须将接口的域名添加到信任列表中 2.配置request合法域名 需求描述 希望请求某个域名下的接口 步骤 登录小程序管理后台→开发→开发设置→服务器域名→修改request合法域名 练习 注意事项 域…

系统架构:分而治之

系统架构&#xff1a;分而治之 引言“分而治之”在架构中的应用模块化设计分层化架构微服务架构 分而治之的优势降低复杂性提高灵活性和可扩展性增强可维护性促进团队协作 分而治之的劣势复杂性转移性能开销开发和运维的复杂性数据一致性挑战 结论 引言 “分而治之”是一种分析…

wo是如何克服编程学习中的挫折感的?

你是如何克服编程学习中的挫折感的&#xff1f; 编程学习之路上&#xff0c;挫折感就像一道道难以逾越的高墙&#xff0c;让许多人望而却步。然而&#xff0c;真正的编程高手都曾在这条路上跌倒过、迷茫过&#xff0c;却最终找到了突破的方法。你是如何在Bug的迷宫中找到出口的…

QT基础知识4

思维导图 项目文件里面要加texttospeech模块 widget.h: #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime>//时间类 #include <QTextToSpeech>//语音播报类QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass…

C:每日一练:单身狗(2.0版本)

前言&#xff1a; 今天在刷题的时候突然看到一道题&#xff0c;疑似一位故题。仔细一看&#xff0c;欸&#xff01;这不是就是单身狗的升级版吗&#xff1f;我想那必须再安排一篇&#xff0c;不过由于本篇文章与上一篇单身狗文章所涉及的知识点基本相同&#xff0c;所以还请大…

流媒体服务器二:2.RTMP协议学习

RTMP协议详解 一.总体介绍 RTMP协议是应⽤层协议&#xff0c;是要靠底层可靠的传输层协议&#xff08;通常是TCP&#xff09;来保证信息传输的可靠性的。在 基于传输层协议的链接建⽴完成后&#xff0c;RTMP协议也要客户端和服务器通过“握⼿”来建⽴基于传输层链接之 上的RT…

滴滴开源新项目Unify:聚焦Flutter与原生通信难题,助力跨端应用落地

引言 在移动开发领域&#xff0c;移动跨端技术因其提效收益&#xff0c;逐渐成为业界趋势之一。Flutter 作为近年来热门的跨端技术&#xff0c;以高性能、自渲染、泛跨端著称&#xff0c;得到广泛应用。在滴滴国际化业务中&#xff0c;我们大量应用 Flutter。目前已在滴滴国际化…

C语言-使用数组法,指针法实现将一个5X5的矩阵中最大的元素放在中心,四个角分别放四个最小的元素(顺序为从左到右,从上到下,从小到大存放),写一函数实现之。

1.题目要求&#xff1a; 将一个5X5的矩阵中最大的元素放在中心&#xff0c;四个角分别放四个最小的元素&#xff08;顺序为从左到右&#xff0c;从上到下&#xff0c;从小到大存放&#xff09;&#xff0c;写一函数实现之。 2.数组法实现 #define _CRT_SECURE_NO_WARNINGS 1…

python从入门到精通:数据容器

数据容器介绍 一种可以容纳多份数据的数据类型&#xff0c;容纳的每一份数据称之为一个元素&#xff0c;可以是任意类型的数据&#xff0c;如字符串、数字、布尔等。 数据容器根据特点的不同&#xff0c;如&#xff1a; 是否支持重复元素 是否可以修改 是否有序&#xff0…

数据库根据日期统计SQL编写记录

统计每天产生多少数据 SELECT to_char("日期字段",YYYY-MM-dd) time ,count(*) FROM "表名" GROUP BY time;统计每月产生多少数据 SELECT to_char("日期字段",YYYY-MM) time ,count(*) FROM "表名" GROUP BY time;统计每年产生多少数…

【GitLab】使用 Docker 安装 GitLab 1:配置 SSH 端口

使用 Docker 安装 GitLab 要求修改ssh端口 GitLab 使用 SSH 通过 SSH 与 Git 交互。默认情况下,GitLab 使用端口22。 要在使用 GitLab Docker 映像时使用其他端口,您可以执行以下操作之一: 更改服务器的 SSH 端口(推荐)。 更改 GitLab Shell SSH 端口。 更改服务器的 SSH …

C++(week17): C++提高:(七)workflow

文章目录 一、Http协议二、Nginx1.概念2.nginx的安装和部署(1)安装nginx(2)使用nginx服务器部署静态资源 3.HTTP服务器架构(1)基于进程、基于线程&#xff1a;APache(2)事件驱动模型&#xff1a;Nginx(3)反向代理(4)负载均衡 三、workflow异步事件引擎 (异步回调模型)1.服务器底…

Qt找不到QSound头文件,头文件QSoundEffect

目录 Qt找不到QSound头文件 CMake qmke 可能版本不同更新&#xff0c; 找不到QSound头文件 Qt找不到QSound头文件 #include <QSound> CMake 可以看一下这篇来自网上的文章 CMake中添加Qt模块的合理方法 - wjbooks - 博客园 (cnblogs.com) qmke 打开.pro文件&am…

[linux#39][线程] 详解线程的概念

线程&#xff1a;是进程内的一个执行分支。线程的执行粒度比进程要细 什么是线程&#xff1f; • 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程 是“一个进程内部的控制序列” • 一切进程至少都有一个执行线程 • …

BF算法,KMP算法

前言&#xff1a;今天我们来学习两种算法&#xff0c;BF算法和KMP算法。相信会让许多小伙伴们打开新世界的大门。 1 BF算法 实践是检验真理的唯一标准。举一个例子说明BF算法。现在我们要在一个主串中找子串的位置。那我们该如何解决这个问题呢&#xff1f;最简单的办法自然是…