参考:HTTP协议的简介及其工作原理等
地址:https://qingmu.blog.csdn.net/article/details/108046553?spm=1001.2014.3001.5502

目录

  • HTTP协议的简介及其工作原理
    • 1、HTTP简介
      • 1.1、什么是超文本(HyperText)?
      • 1.2、什么是URL?
      • 1.3、什么是超文本传输协议HTTP?
    • 2、HTTP工作原理
      • 2.1、请求/相应交互模式
      • 2.2、HTTP的连接方式(非持久性/持久性)和无状态性
  • HTTP的报文结构和HTTP代理
    • 1、HTTP报文结构
      • 1.1、请求报文
      • 1.2、响应报文
      • 1.3、请求报文中的一些方法
      • 1.4、响应报文中的状态码
      • 1.5、首部字段或消息头
    • 2、HTTP代理
      • 2.1、什么是HTTP用户代理?
      • 2.2、使用HTTP代理的Web访问过程
  • HTTP高效解析的方法之哈希加速
  • HTTP高效解析方法之协议状态机
    • 协议状态机原理
    • 协议状态机的处理机制

HTTP协议的简介及其工作原理

1、HTTP简介

1.1、什么是超文本(HyperText)?

包含有超链接(Link)和各种多媒体元素标记(Markup)的文本。这些超文本文件彼此链接,形成网状(Web),因此又被称为网页(Web Page)。这些链接使用URL表示。最常用的超文本格式是超文本标记语言HTML。

1.2、什么是URL?

URL即统-资源定位符(Uniform Resource Locator),用来唯一地标识万维网中的某一个文档。 URL由协议、主机和端口(默认为80)以及文件名三部分构成。 如:

1.3、什么是超文本传输协议HTTP?

是一种按照URL指示,将超文本文档从一台主机(Web服务器)传输到另一台主机(浏览器)的应用层协议, 以实现超链接的功能。

2、HTTP工作原理

2.1、请求/相应交互模式

在用户点击URL为http://www.sxtyu.com/index.html的链接后,浏览器和Web服务器执行以下动作:

①、浏览器分析超链接中的URL

②、浏览器向DNS请求解析www.sxtyu.com的IP地址

③、DNS将解析出的IP地址202.2.16.21返回浏览器

④、浏览器与服务器建立TCP连接(80端)

⑤、浏览器请求文档: GET /index.htm

⑥、服务器给出响应,将文档index.html发送给浏览器

⑦、释放TCP连接

⑧、浏览器显示index.html中的内容

2.2、HTTP的连接方式(非持久性/持久性)和无状态性

2.2.1、非持久性连接

即浏览器每请求一个Web文档, 就创建一个新的连接, 当文档传输完毕后,连接就立刻被释放。

HTTP1.0、HTTP0.9采用此连接方式。

对于请求的Web页中包含多个其他文档对象(如图像、音、视频等)的链接的情况,于请求每个链接对应的文档都要创建新连接,效率低下。

2.2.2、持久性连接

即在一个连接中, 可以进行多次文档的请求和响应。服务器在发送完响应后,不立即释放连接,浏览器可以使用该连接继续请求其他文档。连接保持的时间可以由双方进行协商。

2.2.3、无状态性

是指同一个客户端(浏览器第二 _次访问同一 个Web服务器上的页面时,服务器无法知道这个客户曾经访问过。HTTP的无状态性简化了服务器的设计,使其更容易支持大量并发的HTTP请求。

HTTP的报文结构和HTTP代理

1、HTTP报文结构

1.1、请求报文

即从客户端(浏览器)向Web服务器发送请求报文。报文的所有字段都是ASCII码。


实例:

GET /js/an.js HTTP/1.1               #请求行
Accept: */*                         #可接受的媒体类型
Accept-Encoding: gzip, deflate      #可接受的编码
Accept-L anguage: zh-cn             #接受的语言
Connection: Keep-Alive              #持久连接
Host: cache.soso.com                #Host
Referer: http://help.soso.com/      #引用 页面
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; InfoPath.2) #用户代理

1.2、响应报文

即从Web服务器到客户机(浏览器)的应答。报文的所有字段都是ASCII码。


实例:

HTTP/1.1200 OK                                               #状态行.
Accept-Ranges: bytes                                         #表示服务器端可以接受range请求
Cache-Control: max -age=86400                               #缓存控制时间
Content-Encoding: gzip                                       #内容编码格式
Content-L ength: 1088                                        #内容长度
Content-Type: text/javascript                                #内容类型
Date: Thu, 18 Jun 2009 15:47:14 GMT                          #时间
ETag:“1902284250“                                            #实体标签
Expires: Fri, 19 Jun 2009 15:47:14 GMT                       #过期时间
L ast-Modified: Fri, 10 Oct 2008 04:13:19 GMT                #最后修改日期
Server: WS CDN Server                                        #Server名称
Vary: Accept-Encoding                                        #告知该响应缓存时取决的方式

1.3、请求报文中的一些方法

方法(Method)是所请求对象所进行的操作,也就是一些命令。请求报文中的操作有:

方法(操作) 含义
GET 请求读取一个页面
POST 附加一个命名资源(如Web页面)
HEAD 请求读取一个Web页面的首部
PUT 请求存储一个Web页面
DELETE 删除Web页面
TRACE 用于测试,要求服务器送回收到的请求
CONNECT 用于代理服务器
OPTION 查询特定选项

1.4、响应报文中的状态码

状态码:Status-code是响应报文状态行中包含的以三位数字,指明特定的要求是否被满足,如果没有满足,原因是什么。状态码分为一下的五类:

状态码 含义 例子
1xx 通知信息 100=服务器正在处理客户请求
2xx 成功 200=请求成功
3xx 重定向 301=页面改变了位置
4xx 客户端错误 403=禁止的页面;401=页面未找到
5xx 服务器错误 500=服务器内部错误;503=以后再试

常见的状态码:
➢200 OK
➢206 Partial Content
➢301 Moved Permanently
➢302 Found
➢304 Not Modified
➢400 Bad Request
➢403 Forbidden
➢404 Not Found
➢502 Bad Gateway
➢503 Service Unavailable
➢504 Gateway Timeout

1.5、首部字段或消息头

头(header) 类型 说明
User- Agent 请求 关于浏览器和它平台的信息,如Moilla5.0
Accept 请求 客户能处理的页面的类型,如text/html
Accept-Charset 请求 客户可以接受的字符集,如Unicode-1-1
Accept-Encoding 请求 客户能处理的页面编码方法,如gzip
Accept-Language 请求 客户能处理的自然语言,如en(英语),zh- cn(简体中文)
Host 请求 服务器的DNS名称。从URL中提取出来,必需。
Authorization 请求 客户的信息凭据列表
Cookie 请求 将以前设置的Cookie送回服务器器,可用来作为会话信息
Date 双向 消息被发送时的日期和时间
Server 响应 关于服务器的信息,如Microsoft IS/6.0
Content-Encoding 响应 内容是如何被编码的(如gzip)
Content-Language 响应 页面所使用的自然语言
Content-Length 响应 以字节计算的页面长度
Content-Type 响应 页面的MIIME类型
Last-Modified 响应 页面最后被修改的时间和日期,在页面缓存机制中意义重大
Location 响应 指示客户将请求发送给别处,即重定向到另个URL
Set-Cookie 响应 服务器希望客户保存一个Cookie

2、HTTP代理

2.1、什么是HTTP用户代理?

HTTP代理又称Web缓存或代理服务器(Proxy Server),是一种网络实体, 能代表浏览器发出HTTP请求,并将最近的一些请求和响应暂存在本地磁盘中,当请求的Web页面先前暂存过,则直接将暂存的页面发给客户端(浏览器),无须再次访问Internet。

2.2、使用HTTP代理的Web访问过程

HTTP高效解析的方法之哈希加速

哈希加速

在我们获取到HTTP的外部请求的时候,第一步去识别它所对应的请求类型,然后再去获取它后面所对的URL,最后在对URL做进一步的解析。

在我们获取到了URL之后,我们需要在数据库中或者文件系统中获取对应的文件(也就是字符串匹配),但是解析和比较字符串的速度很慢,达不到我们的需求,那么我们就需要加快URL的解析,那么如何操作呢?我们可以使用哈希加速

哈希加速:把服务器自己所对应的url全部通过哈希算法,把每一个url路径都算成一个数,然后把输入的URL也进行哈希算法,算成一个数,和服务器的进行比较,如果在服务器的哈希范围内并且有匹配的值,就打开对应的路径所对应的文件,如果哈希值匹配不上,就认为此URL是错误的URL。

这样通过哈希来比较,比字符串的比较快的多。

HASH解析URL链接实现

/*管理URL的对象*/
typedef struct {char    url[MAX_URL_LEN];   //URLsize_t  len;               //长度int     all;                //个数UT_hash_handle hh;          //HASH表
} url_t;/*HASH表的一个节点*/
typedef struct UT_hash_handle {struct UT_hash_table *tbl;void *prev;                       /* prev element in app order      */void *next;                       /* next element in app order      */struct UT_hash_handle *hh_prev;   /* previous hh in bucket order    */struct UT_hash_handle *hh_next;   /* next hh in bucket order        */void *key;                        /* ptr to enclosing struct's key  */unsigned keylen;                  /* enclosing struct's key len     */unsigned hashv;                   /* result of hash-fcn(key)        */
} UT_hash_handle;/*HASH表*/
typedef struct UT_hash_table
{UT_hash_bucket *buckets;unsigned num_buckets, log2_num_buckets;unsigned num_items;struct UT_hash_handle *tail; /* tail hh in app order, for fast append    */ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element *//* in an ideal situation (all buckets used equally), no bucket would have* more than ceil(#items/#buckets) items. that's the ideal chain length. */unsigned ideal_chain_maxlen;/* nonideal_items is the number of items in the hash whose chain position* exceeds the ideal chain maxlen. these items pay the penalty for an uneven* hash distribution; reaching them in a chain traversal takes >ideal steps */unsigned nonideal_items;/* ineffective expands occur when a bucket doubling was performed, but* afterward, more than half the items in the hash had nonideal chain* positions. If this happens on two consecutive expansions we inhibit any* further expansion, as it's not helping; this happens when the hash* function isn't a good fit for the key domain. When expansion is inhibited* the hash will still work, albeit no longer in constant time. */unsigned ineff_expands, noexpand;uint32_t signature; /* used only to find hash tables in external analysis */
#ifdef HASH_BLOOMuint32_t bloom_sig; /* used only to test bloom exists in external analysis */uint8_t *bloom_bv;uint8_t bloom_nbits;
#endif
}/*HASH算法*/
HASH_FIND_STR(urls, url, u);#define HASH_FIND_STR(head,findstr,out)                                          \HASH_FIND(hh,head,findstr,(unsigned)uthash_strlen(findstr),out)#define HASH_FIND(hh,head,keyptr,keylen,out)                                     \
do {                                                                             \unsigned _hf_hashv;                                                            \HASH_VALUE(keyptr, keylen, _hf_hashv);                                         \HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out);               \
} while (0#define HASH_VALUE(keyptr,keylen,hashv)                                          \
do {                                                                             \HASH_FCN(keyptr, keylen, hashv);                                               \
} while (0)#ifdef HASH_FUNCTION
#define HASH_FCN HASH_FUNCTION
#else
#define HASH_FCN HASH_JEN
#endif#define HASH_JEN(key,keylen,hashv)                                               \
do {                                                                             \unsigned _hj_i,_hj_j,_hj_k;                                                    \unsigned const char *_hj_key=(unsigned const char*)(key);                      \hashv = 0xfeedbeefu;                                                           \_hj_i = _hj_j = 0x9e3779b9u;                                                   \_hj_k = (unsigned)(keylen);                                                    \while (_hj_k >= 12U) {                                                         \_hj_i +=    (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 )                      \+ ( (unsigned)_hj_key[2] << 16 )                                         \+ ( (unsigned)_hj_key[3] << 24 ) );                                      \_hj_j +=    (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 )                      \+ ( (unsigned)_hj_key[6] << 16 )                                         \+ ( (unsigned)_hj_key[7] << 24 ) );                                      \hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 )                         \+ ( (unsigned)_hj_key[10] << 16 )                                        \+ ( (unsigned)_hj_key[11] << 24 ) );                                     \\HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                          \\_hj_key += 12;                                                              \_hj_k -= 12U;                                                               \}                                                                              \hashv += (unsigned)(keylen);                                                   \switch ( _hj_k ) {                                                             \case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */         \case 10: hashv += ( (unsigned)_hj_key[9] << 16 );  /* FALLTHROUGH */         \case 9:  hashv += ( (unsigned)_hj_key[8] << 8 );   /* FALLTHROUGH */         \case 8:  _hj_j += ( (unsigned)_hj_key[7] << 24 );  /* FALLTHROUGH */         \case 7:  _hj_j += ( (unsigned)_hj_key[6] << 16 );  /* FALLTHROUGH */         \case 6:  _hj_j += ( (unsigned)_hj_key[5] << 8 );   /* FALLTHROUGH */         \case 5:  _hj_j += _hj_key[4];                      /* FALLTHROUGH */         \case 4:  _hj_i += ( (unsigned)_hj_key[3] << 24 );  /* FALLTHROUGH */         \case 3:  _hj_i += ( (unsigned)_hj_key[2] << 16 );  /* FALLTHROUGH */         \case 2:  _hj_i += ( (unsigned)_hj_key[1] << 8 );   /* FALLTHROUGH */         \case 1:  _hj_i += _hj_key[0];                                                \}                                                                              \HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                             \
} while (0)#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out)                 \
do {                                                                             \(out) = NULL;                                                                  \if (head) {                                                                    \unsigned _hf_bkt;                                                            \HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt);                  \if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) {                         \HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \}                                                                            \}                                                                              \
} while (0)HASH_ADD_STR(urls, url, u);

那么我们如何维护一个HASH表呢?

添加一个HASH


int add_url(const char *url)
{url_t  *u;if (!url || strlen(url) >= MAX_URL_LEN) return 0;pthread_rwlock_wrlock(&rwlock);HASH_FIND_STR(urls, url, u);if (u) {pthread_rwlock_unlock(&rwlock);return 0;}u = (url_t *)calloc(1, sizeof(url_t));strncpy(u->url, url, MAX_URL_LEN - 1);u->len = strlen(u->url);if (u->url[u->len - 1] == URL_SPLIT) {//u->url[u->len - 1] = 0;u->all = 1;}HASH_ADD_STR(urls, url, u);pthread_rwlock_unlock(&rwlock);return 1;
}

判断URL在不在HASH表中

int has_url(const char *url)
{char    tmp[MAX_URL_LEN];char   *start, *end, *pos;size_t  ul;url_t  *u;if (!url) {return 0;}ul = strlen(url); if (ul >= MAX_URL_LEN) {return 0;}memset(tmp, 0x00, sizeof(tmp));pos   = tmp;start = (char *)url;pthread_rwlock_rdlock(&rwlock);HASH_FIND_STR(urls, url, u);if (u) {pthread_rwlock_unlock(&rwlock);return 1;}end = strchr(start, URL_SPLIT);for (; end; end = strchr(start, URL_SPLIT)) {memcpy(pos, start, end - start + 1);HASH_FIND_STR(urls, tmp, u);if (u && u->all) {pthread_rwlock_unlock(&rwlock);return 1;}pos += (end - start + 1);start = end + 1;}pthread_rwlock_unlock(&rwlock);return 0;
}

删除一个URL

int del_url(const char *url)
{url_t  *u;if (!url || strlen(url) >= MAX_URL_LEN) {return 0;}pthread_rwlock_wrlock(&rwlock);HASH_FIND_STR(urls, url, u);if (u) {HASH_DEL(urls, u);pthread_rwlock_unlock(&rwlock);return 1;}pthread_rwlock_unlock(&rwlock);return 0;
}

HTTP高效解析方法之协议状态机

协议状态机原理

平时我们解析单个的URL的时候,都是一个线程从头解析到尾,一个线程完成了所有的事情,
协议状态机就是把这一件事情分成很多份,分发给更多的线程去解决,这样当并发量很大的时候就能很好快速的处理,完美解决了高并发的问题。

协议状态机的每一步都存在输出和跳转,下面我们以一个正确的解析方式来分析一下协议状态机

判断方法:判断HTTP协议的第一个字母是G P
每个状态机都有一个跳转条件和输出结果
确定方法; 确定当前状态,根据状态的类型,跳转到不同方法的处理状态


每一步处理完之后,我们都会得到一个状态,正确或错误或其他,我们就跳转到对应的步骤。

我们分成多个线程来处理,当数据量特别大的时候,做并发性的处理非常适合,特别是当流水线在满载运行的时候,效率是非常高的。

相当于是面积换速度的一种方法。

协议状态机的处理机制

1.定义一个协议状态集合

状态结构体:包含了所有状态,随着设计慢慢补充

enum state{ s_dead = 1 /* important that this is > 0 */, s_start_req_or_res, s_res_or_resp_H, s_start_res, s_res_H, s_res_HT, s_res_HTT, s_res_HTTP, s_res_http_major, s_res_http_dot, s_res_http_minor, s_res_http_end, s_res_first_status_code, s_res_status_code, s_res_status_start, s_res_status, s_res_line_almost_done, s_start_req, s_req_method, s_req_spaces_before_url, s_req_schema, s_req_schema_slash, s_req_schema_slash_slash, s_req_server_start, s_req_server, s_req_server_with_at, s_req_path, s_req_query_string_start, s_req_query_string, s_req_fragment_start, s_req_fragment, s_req_http_start, s_req_http_H, s_req_http_HT, s_req_http_HTT, s_req_http_HTTP, s_req_http_major, s_req_http_dot, s_req_http_minor, s_req_http_end, s_req_line_almost_done, s_header_field_start, s_header_field, s_header_value_discard_ws, s_header_value_discard_ws_almost_done, s_header_value_discard_lws, s_header_value_start, s_header_value, s_header_value_lws, s_header_almost_done, s_chunk_size_start, s_chunk_size, s_chunk_parameters, s_chunk_size_almost_done, s_headers_almost_done, s_headers_done, s_chunk_data, s_chunk_data_almost_done, s_chunk_data_done, s_body_identity, s_body_identity_eof, s_message_done};

2. 输出结构体

struct http_parser {/** PRIVATE **/unsigned int type : 2;         /* enum http_parser_type */unsigned int flags : 8;        /* F_* values from 'flags' enum; semi-public */unsigned int state : 7;        /* enum state from http_parser.c */unsigned int header_state : 7; /* enum header_state from http_parser.c */unsigned int index : 7;        /* index into current matcher */unsigned int lenient_http_headers : 1;uint32_t nread;          /* # bytes read in various scenarios */uint64_t content_length; /* # bytes in body (0 if no Content-Length header) *//** READ-ONLY **/unsigned short http_major;unsigned short http_minor;unsigned int status_code : 16; /* responses only */unsigned int method : 8;       /* requests only */unsigned int http_errno : 7;/* 1 = Upgrade header was present and the parser has exited because of that.* 0 = No upgrade header present.* Should be checked when http_parser_execute() returns in addition to* error checking.*/unsigned int upgrade : 1;/** PUBLIC **/void *data; /* A pointer to get hook to the "connection" or "socket" object */
};

3.状态图(跳转逻辑)

#define CURRENT_STATE() p_state
#define UPDATE_STATE(V) p_state = (enum state) (V);switch (CURRENT_STATE()) {   //不同的状态干不同的事s_header_almost_done :parser->flags = 0;parser->content_length = ULLONG_MAX;UPDATE_STATE(s_start_req);  //跳转break; s_header_value_lws : }

https网络编程——HTTP协议的简介、HTTP报文结构和代理、HTTP加速解析方法(哈希加速、协议状态机)相关推荐

  1. 网络编程基础 --> 网络通信机理、报文与协议、套接字通信预备

    此文我们来剖析你从网上获取的各种资源,是如何历经千辛万苦,跑到你的眼前的! 先来了解一下我们日常使用的大部分应用软件的最基本架构~ C/S 架构与B/S架构 C/S架构(client <-> ...

  2. java基础:网络编程(一)简介

    网络编程简介 这些知识点在学习计算机网络时都有详细讲,我这就简单介绍下,具体的看计算机网络相关知识. 1.软件结构 常见的软件结构有C/S和B/S C/S (Client/Server) 表示客户端/ ...

  3. [Linux网络编程学习笔记]套接字地址结构

    好久没有看那Linux网络编程这本书了,今天看到了重点部分-TCP套接字.下面先来看看套接字的地址结构 Linux系统的套接字可以支持多种协议,每种不同的协议都是用不同的地址结构.在头文件<li ...

  4. UNIX网络编程读书笔记:套接口地址结构

    前言 大多数套接口函数都需要一个指向套接口地址结构的指针作为参数.每个协议族都定义它自己的套接口地址结构.这些结构的名字均以"sockaddr_"开头,并以对应每个协议族的唯一后缀 ...

  5. 29.Linux网络编程熟练掌握 TCP 状态张换图熟练堂握端口复用的方法了解半关闭的概念和实现方式了解多路10 转接模型熟练掌握 select 函数的使用熟练使用 fdset 相关函数的使用能够编写

    把昨天的 第二天的内容说一下,复习一下,第二天 讲的东西不算多,但是有两个作业题来写一写, 大致浏览一下,三次握手 四次挥手的过程,大家有没有画一下? 能画出来吗?同学们,大家注意 这个写代码的时候其 ...

  6. pppoe协议交互流程以及报文结构

    文章目录 pppoe出现的原因: pppoe的交互流程 Discovery阶段: session阶段: Terminate阶段: pppoe报文结构: pppoe出现的原因: PPP (Point-t ...

  7. UNIX网络编程笔记(1):TCP简介

    1.简介 TCP(Transmission Control Protocol),即传输控制协议,是一种面向连接的.可靠的.基于字节流的传输层通信协议.TCP协议有以下几个特点: TCP提供客户与服务器 ...

  8. 《Unix网络编程》卷一(简介TCP/IP、基础套接字编程)

    通常说函数返回某个错误值,实际上是函数返回值为-1,而全局变量errno被置为指定的常值(即称函数返回这个错误值). exit终止进程,Unix在一个进程终止时总是关闭该进程所有打开的描述符. TCP ...

  9. 【Java 网络编程】UDP API 简介 ( DatagramSocket | DatagramPacket )

    文章目录 I DatagramSocket II DatagramSocket 构造方法 III DatagramSocket 数据操作 IV DatagramPacket V DatagramPac ...

最新文章

  1. Codeforces Round #698 (Div. 2) D. Nezzar and Board(一步步推出来,超级清晰,不猜结论,看不懂来打我 ~ 好题 )
  2. 19.3.21 计算机网络基础知识
  3. Paper之CV:《One Millisecond Face Alignment with an Ensemble of Regression Trees》的翻译与解读
  4. Web MVC Rest 处理流程分析
  5. Angular应用里具有back功能的按钮实现
  6. 怎么圆角变直角_衣柜设计个圆角有什么用?效果好看又实用,会这样装的都是老木工...
  7. iphone查看删除的短信_苹果删除的短信
  8. ANSI X9.8标准 PIN xor PAN获取PIN BlOCK
  9. junit5和junit4_JUnit声明异常– JUnit 5和JUnit 4
  10. 通用模块(4)——EEPROM(AT24C08)
  11. 软件测试自学毛笔字纹身,254439
  12. Matlab如何在一个窗口绘制多张子图
  13. 南京:探索实施“电子围栏”管理新模式 缓解商圈周边道路交通拥堵
  14. Matlab中set-gca函数的使用
  15. 啊哈c语言一起来找茬答案,啊哈少儿编程网-啊哈C【第三章】来了! 第2节-【说几遍就几遍】 - ahalei.com...
  16. Unix时间戳和北京时间的相互转换(C语言实现 )
  17. 如何提高系统稳定性?
  18. Java分布式二手房项目尚好房第五课 图片上传及前端房源展示
  19. C语言超简单入门——基础知识
  20. 5英寸小屏手机:何以统一性价比与体验?

热门文章

  1. 做知识变现,掌握这项技能才能王炸
  2. hog+svm图像检测流程 --python
  3. Python蓝桥杯算法基础求解生日蜡烛
  4. spring框架AOP的理解,程序高类聚的体现
  5. windows中解压Linux中的tar.gz.part分卷压缩包
  6. ORACLE学习-8.约束constraint
  7. 新的人工智能系统现在可以成功预测地震
  8. 快速学习半导体晶闸管
  9. 程序人生(CSAPP大作业)
  10. (头冷巨制)数据结构学习日志2——(初级掉发向)--关于顺序表.单链表的核心操作实现(By Ivan小黄)