当前位置: 首页 > news >正文

计算机网络(4) -- http协议

一、浏览器与服务器通信过程

浏览器与 web 服务器在应用层通信使用的是 HTTP 协议(超文本传输协议),而HTTP协议在传输层使用的是 TCP 协议。那么浏览器需要和 web 服务器三次握手建立连接后,才可以发送 HTTP 请求报文,服务器收到请求报文后,向浏览器回复 HTTP 应答报文。

浏览器向服务器发起连接前,需要得到服务器的 IP 及端口。用户在浏览器中通常只输入网址(网站域名) ,浏览器会通过DNS 服务查询获取到服务器的 IP 地址。 对于端口来讲,使用 HTTP 协议的程序一般默认使用 80 端口。
浏览器服务器建立连接后,如果两次以上的请求复用同一个 TCP 连接,则称之为长连接。如果浏览器发送一次请求报文,服务器回复一次应答就断开连接,下次交互再重新进行三次握手建立连接,那么就被称作短连接使用长连接显然是更好一些,可以减少网络中的同步报文,也使得服务器的响应速度变快。

http属于应用层,它在传输层使用的是tcp协议;传输层协议:tcp 协议和udp协议。

常见的 web 服务器有

◼ Apache: 简单、速度快、性能稳定,并可做代理服务器使用
◼ IIS(Internet Information Server):安全性、强大、灵活
◼ Nginx:小巧而高效,可以做高效的负载均衡反向代理
◼ Tomcat:技术先进、性能稳定、免费

浏览器与服务器通信过程:

(1)浏览器从URL中解析出服务器的主机名;

(2)浏览器通过DNS协议将服务器的主机名转换成服务器的IP地址;

(3)浏览器将端口号(如果有的话)从URL中解析出来.

(4)浏览器建立一条与Web服务器的TCP连接;

(5)浏览器向服务器发送一条HTTP请求报文;

(6)服务器向浏览器回送一条HTTP响应报文;

(7)关闭连接,浏览器显示文档;

DNS协议

域名系统(英文:DomainNameSystem,缩写:DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用UDP端口53。

浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。如果是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名 服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。而递归查询,请求方只发一次请求,等待最终结果,中间查询全由接收方完成。DNS服务器是基于UDP的,因此会用到UDP协议。

DNS 查询的迭代过程,是从根域名服务器 → 顶级域名服务器 → 二级域名服务器 → 权威域名服务器,也就是从右往左(从顶级域名往前)逐级查询的。

URL

URL:统一资源定位符,URL是资源标识符最常见的形式;URL描述了一台特定服务器上某资源的特定位置.它们可以明确说明如何从一个精确,固定的位置获取资源。

1.客户端发起请求:浏览器解析这个 URL,向www.joes-hardware.com服务器发送 HTTP 请求,要求获取/specials/saw-blade.gif这个图片文件。

2. 服务器定位资源:服务器收到请求后,在自己的存储中找到对应的saw-blade.gif文件。

3.服务器返回响应:服务器把文件数据打包成 HTTP 响应发给客户端,响应头里会带上关键信息:Content-type: image/gif:告诉客户端,这是一个 GIF 格式的图片;Content-length: 8572:告诉客户端,这个文件的大小是 8572 字节

4.客户端解析并展示:浏览器收到数据后,根据Content-type知道这是图片,直接把它渲染到页面上。

HTTP 协议格式

HTTP 的请求报头结构

HTTP 请求报文段示例

/index.html指定资源文件的名称,这里指的是服务器根目录(站点的根目录,而不是服务器的文件系统根目录 "/")中的索引文件。

注意:Keep-Alive首部只是请求将连接保持在活跃状态。发出keep-alive请求之后,客户端和服务器端并不一定会统一进行keep-alive会话。它们可以在任意时刻关闭空闲的keep-alive连接,并可随意限制keep-alive连接所处理事务的数量。

HTTP 的请求方法:

HTTP 的应答报头结构

HTTP 的应答报头结构:

HTTP 的应答状态:

二、搭建WEB服务器

首先先写出短连接的服务器过程,在一步一步修改。我们通过封装CreatSocket函数用于创建sockfd套接字,在通过一个死循环去处理不同客户端的消息,我们先实现出没有多线程和多进程的处理逻辑。

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> //转换IP地址 #include <netinet/in.h>//转换字节序 #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> //close函数 #include <assert.h> #include <stdbool.h> //创建socket,绑定地址,监听端口 int CreateSocket() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); assert(sockfd != -1); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(80); server_addr.sin_addr.s_addr = inet_addr("192.168.199.128"); int ret = bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); assert(ret != -1); ret = listen(sockfd, 5); assert(ret != -1); return sockfd; } //先接收报头并打印出来 void DealHttpRequest(int c) { char requestHead[1024]; ssize_t n = recv(c, requestHead, sizeof(requestHead)-1, 0); if(n <= 0){ perror("recv"); return; } requestHead[n] = '\0'; printf("Request:\n%s\n", requestHead); } //接收客户端连接,处理请求 void AcceptClient(int sockfd) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int c = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); if(c == -1){ perror("accept"); return; } printf("Accept client %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); //针对客户端的处理 DealHttpRequest(c); close(c); } int main() { int sockfd = CreateSocket(); //处理多个客户端连接 while(true){ AcceptClient(sockfd); } close(sockfd); return 0; }

上述代码就是先接受客户端发送的http请求报头,先进行打印,之后在去处理相应逻辑,但是在这里我们需要注意,80端口不能被普通用户使用,一般情况下,1024一下的端口号都不能被普通用户使用。这时候我们需要切换到root用户,或者将80端口修改为HTTP 协议的 “通用替代端口”-8080端口即可正常启动。这里我演示的是切换root用户。

我通过linux系统下的Firefox浏览器去访问该网址的index.html,但是我的路径下是没有这个文件的,所以他是会连续重传好几次的,但是这时候我们已经可以确定是能够接收到请求报头的,接下来就是对报头内容进行解析,并返回相应的应答报头。

多线程版本

多线程版简易 Web 服务器,采用一连接一线程的并发模型。服务器首先创建套接字、绑定本机 IP 与端口、完成监听初始化。程序通过死循环持续调用accept()阻塞等待客户端连接。每当有新客户端连接成功后,不直接阻塞处理业务,而是创建新线程单独处理本次 HTTP 请求,主线程立刻回归监听状态,实现支持多客户端同时访问的并发能力。

子线程负责具体的 HTTP 请求解析与资源响应工作:首先读取浏览器发来的 HTTP 请求报头,通过字符串解析出用户请求的资源路径;如果用户访问根路径/,则默认访问index.html主页。

通过stat()函数判断目标文件是否存在:

  1. 文件存在:读取文件大小,拼接并发送200 OK响应报头,再读取本地文件内容发送给浏览器,完成正常网页访问。
  2. 文件不存在:跳转读取本地404.html页面,发送404 Not Found响应报头与错误页面内容,实现标准 404 错误提示。

所有请求处理完毕后,线程统一释放堆内存、关闭客户端文件描述符,避免内存泄漏与文件资源泄露,子线程任务执行完毕自动退出。

整体基于 TCP 流式套接字,手动实现简易 HTTP 应答协议,利用多线程解决单进程服务器无法并发访问的问题,完成了基础 Web 服务器的资源访问与错误处理功能。

//多线程版本 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> //转换IP地址 #include <netinet/in.h>//转换字节序 #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> //close函数 #include <assert.h> #include <stdbool.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h> //创建socket,绑定地址,监听端口 int CreateSocket() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); assert(sockfd != -1); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(80); server_addr.sin_addr.s_addr = inet_addr("192.168.199.128"); int ret = bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); assert(ret != -1); ret = listen(sockfd, 5); assert(ret != -1); return sockfd; } //发送应答报头 void SendResponseHead(int c, int contentLength, bool flag) { char responseHead[256] = {0}; if(flag){ strcat(responseHead,"HTTP/1.1 200 OK\r\n"); }else{ strcat(responseHead,"HTTP/1.1 404 Not Found\r\n"); } strcat(responseHead,"Server:MYWEB/1.1\r\n"); sprintf(responseHead + strlen(responseHead) ,"Content-Length:%d\r\n",contentLength); strcat(responseHead,"Content-Type:text/html;charset=utf-8\r\n"); strcat(responseHead,"\r\n"); send(c,responseHead,strlen(responseHead),0); } //发送文件内容 void SendResponseBody(int c, const char *filePath) { int fd = open(filePath, O_RDONLY); if(fd == -1){ perror("open"); return; } char buff[1024]; while(true){ ssize_t n = read(fd, buff, sizeof(buff)); if(n <= 0){ break; } send(c, buff, n, 0); } close(fd); } //先接收报头并打印出来 void* DealHttpRequest(void *arg) { int c = *(int *)arg; char requestHead[1024]; ssize_t n = recv(c, requestHead, sizeof(requestHead)-1, 0); if(n <= 0){ perror("recv"); free(arg); close(c); return NULL; } requestHead[n] = '\0'; //printf("Request:\n%s\n", requestHead); char *requestWay = strtok(requestHead, " "); char *filePath = strtok(NULL, " "); if(strlen(filePath) == 1 && filePath[0] == '/'){ filePath = "/index.html"; } char path[256] = "."; strcat(path, filePath); struct stat st; //利用stat结构体成员st_size获取文件大小 //undo,判断文件是否存在,不存在则返回404 bool flag = true; int ret = stat(path, &st); //assert(ret != -1); if(ret == -1){ flag = false; memset(path, 0, sizeof(path)); strcpy(path, "./404.html"); //先修改路径为404.html,看看是否存在 struct stat st404; if(stat(path, &st404) == -1){ // 连404.html都找不到,只能返回最基础的404响应 SendResponseHead(c, 0, flag); free(arg); close(c); return NULL; } SendResponseHead(c, st404.st_size, flag); //发送404头部 SendResponseBody(c, path); //发送404页面内容 free(arg); close(c); return NULL; } SendResponseHead(c, st.st_size, flag); //发送头部 SendResponseBody(c, path); //发送文件内容 free(arg); close(c); return NULL; } //接收客户端连接,处理请求 void AcceptClient(int sockfd) { while(true){ struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int c = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); if(c == -1){ perror("accept"); return; } printf("Accept client %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); int *pc = (int *)malloc(sizeof(int)); *pc = c; //针对客户端的处理 //多线程处理 pthread_t id; pthread_create(&id,NULL,DealHttpRequest,(void *)pc); pthread_detach(id); // 分离线程,避免资源泄漏 } } int main() { int sockfd = CreateSocket(); //处理多个客户端连接 while(true){ AcceptClient(sockfd); } close(sockfd); return 0; }

HTTPS版本

HTTPS版本,我们需要先生成证书,但是这个证书只是适用于测试使用,并不能直接进行商用。

1.通过这个命令生成证书: openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 2.填写相关信息: Country Name (2 letter code) [AU]:CN //中国的国家代码 State or Province Name (full name) [Some-State]: //省份 Locality Name (eg, city) []: //城市 Organization Name (eg, company) [Internet Widgits Pty Ltd]: //组织(随便填) Organizational Unit Name (eg, section) []: //部门(随便填) Common Name (e.g. server FQDN or YOUR name) []: //最重要!填你的服务器 IP,否则浏览器会报证书域名不匹配 Email Address []: //测试证书可以不填 3.填写完成后会得到两个文件 server.crt //证书 server.key //私钥 将两个文件放在web_ser.c下的路径 4.每个客户端连接都做 SSL 握手 5.用 SSL_read / SSL_write 代替 recv /send HTTPS 不改变 HTTP 协议本身,只负责传输加密。
//https版本 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <assert.h> #include <stdbool.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h> // OpenSSL 头文件 #include <openssl/ssl.h> #include <openssl/err.h> #define PORT 443 #define IP "192.168.199.128" SSL_CTX *ctx = NULL; // 全局 SSL 上下文 // 1. 初始化 OpenSSL + 加载证书私钥 bool InitSSL() { // OpenSSL 初始化 SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); // 创建 TLS 服务器上下文 ctx = SSL_CTX_new(TLS_server_method()); if (!ctx) { ERR_print_errors_fp(stderr); return false; } // 加载证书 if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return false; } // 加载私钥 if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return false; } // 检查私钥是否匹配证书 if (!SSL_CTX_check_private_key(ctx)) { fprintf(stderr, "Private key does not match certificate\n"); return false; } return true; } // 2. 创建 socket、绑定、监听(端口 443) int CreateSocket() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); assert(sockfd != -1); int opt = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = inet_addr(IP); int ret = bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); assert(ret != -1); ret = listen(sockfd, 5); assert(ret != -1); printf("HTTPS server listening on %s:%d\n", IP, PORT); return sockfd; } // 3. 发送 HTTP 响应头(不变) void SendResponseHead(SSL *ssl, int contentLength, bool flag) { char responseHead[256] = {0}; if (flag) strcat(responseHead, "HTTP/1.1 200 OK\r\n"); else strcat(responseHead, "HTTP/1.1 404 Not Found\r\n"); strcat(responseHead, "Server:MYWEB/HTTPS\r\n"); sprintf(responseHead + strlen(responseHead), "Content-Length:%d\r\n", contentLength); strcat(responseHead, "Content-Type:text/html;charset=utf-8\r\n"); strcat(responseHead, "\r\n"); SSL_write(ssl, responseHead, strlen(responseHead)); } // 4. 发送文件内容(用 SSL_write) void SendResponseBody(SSL *ssl, const char *filePath) { int fd = open(filePath, O_RDONLY); if (fd == -1) { perror("open"); return; } char buff[1024]; while (true) { ssize_t n = read(fd, buff, sizeof(buff)); if (n <= 0) break; SSL_write(ssl, buff, n); } close(fd); } // 5. 处理请求线程(SSL 版) void* DealHttpRequest(void *arg) { SSL *ssl = (SSL*)arg; char requestHead[1024]; ssize_t n = SSL_read(ssl, requestHead, sizeof(requestHead)-1); if (n <= 0) { ERR_print_errors_fp(stderr); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } requestHead[n] = '\0'; char *requestWay = strtok(requestHead, " "); char *filePath = strtok(NULL, " "); if (strlen(filePath) == 1 && filePath[0] == '/') filePath = "/index.html"; char path[256] = "."; strcat(path, filePath); struct stat st; bool flag = true; int ret = stat(path, &st); if (ret == -1) { flag = false; strcpy(path, "./404.html"); struct stat st404; if (stat(path, &st404) == -1) { SendResponseHead(ssl, 0, flag); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } SendResponseHead(ssl, st404.st_size, flag); SendResponseBody(ssl, path); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } SendResponseHead(ssl, st.st_size, flag); SendResponseBody(ssl, path); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } // 6. 接受连接 + SSL 握手 + 建线程 void AcceptClient(int sockfd) { while (true) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int c = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); if (c == -1) { perror("accept"); continue; } printf("Accept client %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 新建 SSL 对象 SSL *ssl = SSL_new(ctx); SSL_set_fd(ssl, c); // SSL 握手 if (SSL_accept(ssl) <= 0) { ERR_print_errors_fp(stderr); close(c); SSL_free(ssl); continue; } // 多线程处理 pthread_t id; pthread_create(&id, NULL, DealHttpRequest, (void*)ssl); pthread_detach(id); // 分离线程,避免资源泄漏 } } int main() { if (!InitSSL()) { fprintf(stderr, "SSL init failed\n"); return 1; } int sockfd = CreateSocket(); AcceptClient(sockfd); close(sockfd); SSL_CTX_free(ctx); return 0; }

三、问题

1.HTTP 和 HTTPS 协议区别?

http:超文本传输协议
https :安全的超文本传输协议, 在 HTTP 协议基础上加入了 SSL 协议保证安全传输。
HTTP 和 HTTPS 主要应用于 Web 浏览器和网站服务器之间传递数据, HTTP 协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了 Web 浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此 HTTP 协议不适合传输一些敏感信息,比如信用卡号、密码等。而 HTTPS 为了数据传输的安全,在 HTTP 协议的基础上加入了 SSL 协议, SSL 协议依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
区别:

(1)HTTPS 协议需要申请CA 证书,一般免费的证书很少,需要交费.
(2)HTTP 是超文本传输协议,信息是明文传输, HTTPS 是具有安全性的 SSL 加密传输协议。
(3)HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口号也不一样(HTTP: 80 HTTPS: 443)
(4)HTTP 协议的TCP 直连,明文通信; HTTPS 协议是由SSL+HTTP协议构成的可进行加密传输,身份认证的网络协议,比 HTTP 协议安全,TCP 先连,再做 TLS 加密握手,最后加密跑 HTTP。

2.Cookie是什么?

应用到会话技术,Cookie;(小饼干,计算机里面的意思就是:不经使用者的认可就由服务器电脑直接写入使用者硬盘中的小型文字档案).也就是说是服务器产生的一小串的数据,在响应的过程中传给客户端,客户端及时保存下来,等到下次访问服务器的时候,就会把这个cookie给携带上。

Cookie:有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。

所谓“cookie”数据是指某些网站为了辨别用户身份,储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。通俗来讲就是指缓存数据,包括用户名、密码、注册账户、手机号等公民个人信息。

3.SSL握手

SSL是Secure Sockets Layer(安全套接层协议)的缩写,可以在Internet上提供秘密性传输。Netscape公司在推出第一个Web浏览器的同时,提出了SSL协议标准。其目标是保证两个应用间通信的保密性和可靠性,可在服务器端和用户端同时实现支持。已经成为Internet上保密通讯的工业标准。

SSL能使用户/服务器应用之间的通信不被攻击者窃听,并且始终对服务器进行认证,还可选择对用户进行认证。SSL协议要求建立在应用层协议(HTTP)和传输层协议(TCP)之间。SSL协议的优势在于它是与应用层协议独立无关的,高层的应用层协议(例如:HTTP,FTP,TELNET等)能透明地建立于SSL协议之上。SSL协议在应用层协议通信之前就已经完成加密算法、通信密钥的协商及服务器认证工作。在此之后应用层协议所传送的数据都会被加密,从而保证通信的私密性。

http://www.rkmt.cn/news/1495201.html

相关文章:

  • 护网必学日志分析
  • 2026桥梁工程公司实力榜:木桥以“诚信筑基”领跑行业,六家高潜力本土品牌深度解析 - 品牌发掘
  • 8 套毕业论文降重降 AIGC 工具实测对比,平衡双检测不翻车
  • 终极歌词获取指南:如何快速免费下载网易云和QQ音乐LRC歌词
  • 基于AI的微服务故障注入与混沌工程自动化:从手动演练到智能验证
  • 工业级RAG检索
  • 2026年6月 港澳台联考志愿填报实操与靠谱机构参考 - 起跑123
  • 多模态时代下,鲲鹏极致性能库KVCL重构高效视频数据处理
  • 终极指南:5分钟让Mac通过Android手机USB共享上网的完整解决方案
  • 2026财税代理记账十强品牌测评:六家本土财税服务商以智能税务系统与合规性优势领跑行业深度解析 - 品牌发掘
  • 新手到专家:2026 年 Chrome SEO 插件最优组合与避坑攻略开篇
  • 2026年6月广东港澳台联考志愿填报排名实用指南 - 起跑123
  • wxapkg-convertor:解密微信小程序包的技术实现与应用实践
  • 智能可观测性:基于LLM的日志异常模式挖掘与根因推理
  • i.MX RT1060X引脚配置与BGA封装PCB设计实战指南
  • MonkCode:2026年最值得用的免费AI编程工具
  • 别再手动解压了!用Docker一键部署Matlab 2018b到Linux服务器(含离线激活)
  • 从碎片到全景:用Python stitching库解决你的图像拼接难题
  • 【KOA三维路径规划】五种改进策略开普勒算法山地环境下无人机 3D路径规划【含Matlab源码 15605期】
  • 2026玉林市家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!本地防水补漏公司为您排忧解难!您附近的专业防水团队 - 企业资讯
  • 如何快速清理重复视频?Vidupe智能去重工具帮你一键搞定
  • JN5169 ZigBee模块选型、开发与低功耗设计实战指南
  • INP/CLS/LCP 优化神器!谷歌官方 Web Vitals 插件免费装
  • 2026海口市家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!本地防水补漏公司为您排忧解难!您附近的专业防水团队 - 企业资讯
  • 力扣算法面试150题——二分查找——个人笔记
  • 长沙GEO优化公司排行:5家服务商核心能力实测对比 - 起跑123
  • 山东铝板板材打印技术白皮书:从设备演进到应用落地的全面解析
  • 一张图搞清岗位说明、任职资格与胜任力模型
  • 实战避坑:用C# .NET快速上手SECS/GEM驱动开发(以secs4net库为例)
  • 【毕业设计】基于SpringBoot与Android的宠物社区APP设计与实现基于Android的宠物社区app设计与实现(源码+文档+远程调试,全bao定制等)