34 | 自己动手写高性能HTTP服务器(三):TCP字节流处理和HTTP协议实现

36 篇文章 9 订阅
订阅专栏

这一讲,我们延续第 33 讲的话题,继续解析高性能网络编程框架的字节流处理部分,并为网络编程框架增加 HTTP 相关的功能,在此基础上完成 HTTP 高性能服务器的编写。

buffer 对象

你肯定在各种语言、各种框架里面看到过不同的 buffer 对象,buffer,顾名思义,就是一个缓冲区对象,缓存了从套接字接收来的数据以及需要发往套接字的数据。

如果是从套接字接收来的数据,事件处理回调函数在不断地往 buffer 对象增加数据,同时,应用程序需要不断把 buffer 对象中的数据处理掉,这样,buffer 对象才可以空出新的位置容纳更多的数据。

如果是发往套接字的数据,应用程序不断地往 buffer 对象增加数据,同时,事件处理回调函数不断调用套接字上的发送函数将数据发送出去,减少 buffer 对象中的写入数据。

可见,buffer 对象是同时可以作为输入缓冲(input buffer)和输出缓冲(output buffer)两个方向使用的,只不过,在两种情形下,写入和读出的对象是有区别的。

这张图描述了 buffer 对象的设计。

下面是 buffer 对象的数据结构。

//数据缓冲区
struct buffer {
    char *data;          //实际缓冲
    int readIndex;       //缓冲读取位置
    int writeIndex;      //缓冲写入位置
    int total_size;      //总大小
};

buffer 对象中的 writeIndex 标识了当前可以写入的位置;readIndex 标识了当前可以读出的数据位置,图中红色部分从 readIndex 到 writeIndex 的区域是需要读出数据的部分,而绿色部分从 writeIndex 到缓存的最尾端则是可以写出的部分。

随着时间的推移,当 readIndex 和 writeIndex 越来越靠近缓冲的尾端时,前面部分的 front_space_size 区域变得会很大,而这个区域的数据已经是旧数据,在这个时候,就需要调整一下整个 buffer 对象的结构,把红色部分往左侧移动,与此同时,绿色部分也会往左侧移动,整个缓冲区的可写部分就会变多了。

make_room 函数就是起这个作用的,如果右边绿色的连续空间不足以容纳新的数据,而最左边灰色部分加上右边绿色部分一起可以容纳下新数据,就会触发这样的移动拷贝,最终红色部分占据了最左边,绿色部分占据了右边,右边绿色的部分成为一个连续的可写入空间,就可以容纳下新的数据。下面的一张图解释了这个过程。

下面是 make_room 的具体实现。

void make_room(struct buffer *buffer, int size) {
    if (buffer_writeable_size(buffer) >= size) {
        return;
    }
    //如果front_spare和writeable的大小加起来可以容纳数据,则把可读数据往前面拷贝
    if (buffer_front_spare_size(buffer) + buffer_writeable_size(buffer) >= size) {
        int readable = buffer_readable_size(buffer);
        int i;
        for (i = 0; i < readable; i++) {
            memcpy(buffer->data + i, buffer->data + buffer->readIndex + i, 1);
        }
        buffer->readIndex = 0;
        buffer->writeIndex = readable;
    } else {
        //扩大缓冲区
        void *tmp = realloc(buffer->data, buffer->total_size + size);
        if (tmp == NULL) {
            return;
        }
        buffer->data = tmp;
        buffer->total_size += size;
    }
}

当然,如果红色部分占据过大,可写部分不够,会触发缓冲区的扩大操作。这里我通过调用 realloc 函数来完成缓冲区的扩容。

下面这张图对此做了解释。

套接字接收数据处理

套接字接收数据是在 tcp_connection.c 中的 handle_read 来完成的。在这个函数里,通过调用 buffer_socket_read 函数接收来自套接字的数据流,并将其缓冲到 buffer 对象中。之后你可以看到,我们将 buffer 对象和 tcp_connection 对象传递给应用程序真正的处理函数 messageCallBack 来进行报文的解析工作。这部分的样例在 HTTP 报文解析中会展开。

int handle_read(void *data) {
    struct tcp_connection *tcpConnection = (struct tcp_connection *) data;
    struct buffer *input_buffer = tcpConnection->input_buffer;
    struct channel *channel = tcpConnection->channel;

    if (buffer_socket_read(input_buffer, channel->fd) > 0) {
        //应用程序真正读取Buffer里的数据
        if (tcpConnection->messageCallBack != NULL) {
            tcpConnection->messageCallBack(input_buffer, tcpConnection);
        }
    } else {
        handle_connection_closed(tcpConnection);
    }
}

在 buffer_socket_read 函数里,调用 readv 往两个缓冲区写入数据,一个是 buffer 对象,另外一个是这里的 additional_buffer,之所以这样做,是担心 buffer 对象没办法容纳下来自套接字的数据流,而且也没有办法触发 buffer 对象的扩容操作。通过使用额外的缓冲,一旦判断出从套接字读取的数据超过了 buffer 对象里的实际最大可写大小,就可以触发 buffer 对象的扩容操作,这里 buffer_append 函数会调用前面介绍的 make_room 函数,完成 buffer 对象的扩容。

int buffer_socket_read(struct buffer *buffer, int fd) {
    char additional_buffer[INIT_BUFFER_SIZE];
    struct iovec vec[2];
    int max_writable = buffer_writeable_size(buffer);
    vec[0].iov_base = buffer->data + buffer->writeIndex;
    vec[0].iov_len = max_writable;
    vec[1].iov_base = additional_buffer;
    vec[1].iov_len = sizeof(additional_buffer);
    int result = readv(fd, vec, 2);
    if (result < 0) {
        return -1;
    } else if (result <= max_writable) {
        buffer->writeIndex += result;
    } else {
        buffer->writeIndex = buffer->total_size;
        buffer_append(buffer, additional_buffer, result - max_writable);
    }
    return result;
}

套接字发送数据处理

当应用程序需要往套接字发送数据时,即完成了 read-decode-compute-encode 过程后,通过往 buffer 对象里写入 encode 以后的数据,调用 tcp_connection_send_buffer,将 buffer 里的数据通过套接字缓冲区发送出去。

int tcp_connection_send_buffer(struct tcp_connection *tcpConnection, struct buffer *buffer) {
    int size = buffer_readable_size(buffer);
    int result = tcp_connection_send_data(tcpConnection, buffer->data + buffer->readIndex, size);
    buffer->readIndex += size;
    return result;
}

如果发现当前 channel 没有注册 WRITE 事件,并且当前 tcp_connection 对应的发送缓冲无数据需要发送,就直接调用 write 函数将数据发送出去。如果这一次发送不完,就将剩余需要发送的数据拷贝到当前 tcp_connection 对应的发送缓冲区中,并向 event_loop 注册 WRITE 事件。这样数据就由框架接管,应用程序释放这部分数据。

//应用层调用入口
int tcp_connection_send_data(struct tcp_connection *tcpConnection, void *data, int size) {
    size_t nwrited = 0;
    size_t nleft = size;
    int fault = 0;

    struct channel *channel = tcpConnection->channel;
    struct buffer *output_buffer = tcpConnection->output_buffer;

    //先往套接字尝试发送数据
    if (!channel_write_event_registered(channel) && buffer_readable_size(output_buffer) == 0) {
        nwrited = write(channel->fd, data, size);
        if (nwrited >= 0) {
            nleft = nleft - nwrited;
        } else {
            nwrited = 0;
            if (errno != EWOULDBLOCK) {
                if (errno == EPIPE || errno == ECONNRESET) {
                    fault = 1;
                }
            }
        }
    }

    if (!fault && nleft > 0) {
        //拷贝到Buffer中,Buffer的数据由框架接管
        buffer_append(output_buffer, data + nwrited, nleft);
        if (!channel_write_event_registered(channel)) {
            channel_write_event_add(channel);
        }
    }

    return nwrited;
}

HTTP 协议实现

下面,我们在 TCP 的基础上,加入 HTTP 的功能。

为此,我们首先定义了一个 http_server 结构,这个 http_server 本质上就是一个 TCPServer,只不过暴露给应用程序的回调函数更为简单,只需要看到 http_request 和 http_response 结构。

typedef int (*request_callback)(struct http_request *httpRequest, struct http_response *httpResponse);

struct http_server {
    struct TCPserver *tcpServer;
    request_callback requestCallback;
};

在 http_server 里面,重点是需要完成报文的解析,将解析的报文转化为 http_request 对象,这件事情是通过 http_onMessage 回调函数来完成的。在 http_onMessage 函数里,调用的是 parse_http_request 完成报文解析。

// buffer是框架构建好的,并且已经收到部分数据的情况下
// 注意这里可能没有收到全部数据,所以要处理数据不够的情形
int http_onMessage(struct buffer *input, struct tcp_connection *tcpConnection) {
    yolanda_msgx("get message from tcp connection %s", tcpConnection->name);

    struct http_request *httpRequest = (struct http_request *) tcpConnection->request;
    struct http_server *httpServer = (struct http_server *) tcpConnection->data;

    if (parse_http_request(input, httpRequest) == 0) {
        char *error_response = "HTTP/1.1 400 Bad Request\r\n\r\n";
        tcp_connection_send_data(tcpConnection, error_response, sizeof(error_response));
        tcp_connection_shutdown(tcpConnection);
    }

    //处理完了所有的request数据,接下来进行编码和发送
    if (http_request_current_state(httpRequest) == REQUEST_DONE) {
        struct http_response *httpResponse = http_response_new();

        //httpServer暴露的requestCallback回调
        if (httpServer->requestCallback != NULL) {
            httpServer->requestCallback(httpRequest, httpResponse);
        }

        //将httpResponse发送到套接字发送缓冲区中
        struct buffer *buffer = buffer_new();
        http_response_encode_buffer(httpResponse, buffer);
        tcp_connection_send_buffer(tcpConnection, buffer);

        if (http_request_close_connection(httpRequest)) {
            tcp_connection_shutdown(tcpConnection);
            http_request_reset(httpRequest);
        }
    }
}

还记得第 16 讲中讲到的 HTTP 协议吗?我们从 16 讲得知,HTTP 通过设置回车符、换行符作为 HTTP 报文协议的边界。

parse_http_request 的思路就是寻找报文的边界,同时记录下当前解析工作所处的状态。根据解析工作的前后顺序,把报文解析的工作分成 REQUEST_STATUS、REQUEST_HEADERS、REQUEST_BODY 和 REQUEST_DONE 四个阶段,每个阶段解析的方法各有不同。

在解析状态行时,先通过定位 CRLF 回车换行符的位置来圈定状态行,进入状态行解析时,再次通过查找空格字符来作为分隔边界。

在解析头部设置时,也是先通过定位 CRLF 回车换行符的位置来圈定一组 key-value 对,再通过查找冒号字符来作为分隔边界。

最后,如果没有找到冒号字符,说明解析头部的工作完成。

parse_http_request 函数完成了 HTTP 报文解析的四个阶段:

int parse_http_request(struct buffer *input, struct http_request *httpRequest) {
    int ok = 1;
    while (httpRequest->current_state != REQUEST_DONE) {
        if (httpRequest->current_state == REQUEST_STATUS) {
            char *crlf = buffer_find_CRLF(input);
            if (crlf) {
                int request_line_size = process_status_line(input->data + input->readIndex, crlf, httpRequest);
                if (request_line_size) {
                    input->readIndex += request_line_size;  // request line size
                    input->readIndex += 2;  //CRLF size
                    httpRequest->current_state = REQUEST_HEADERS;
                }
            }
        } else if (httpRequest->current_state == REQUEST_HEADERS) {
            char *crlf = buffer_find_CRLF(input);
            if (crlf) {
                /**
                 *    <start>-------<colon>:-------<crlf>
                 */
                char *start = input->data + input->readIndex;
                int request_line_size = crlf - start;
                char *colon = memmem(start, request_line_size, ": ", 2);
                if (colon != NULL) {
                    char *key = malloc(colon - start + 1);
                    strncpy(key, start, colon - start);
                    key[colon - start] = '\0';
                    char *value = malloc(crlf - colon - 2 + 1);
                    strncpy(value, colon + 1, crlf - colon - 2);
                    value[crlf - colon - 2] = '\0';

                    http_request_add_header(httpRequest, key, value);

                    input->readIndex += request_line_size;  //request line size
                    input->readIndex += 2;  //CRLF size
                } else {
                    //读到这里说明:没找到,就说明这个是最后一行
                    input->readIndex += 2;  //CRLF size
                    httpRequest->current_state = REQUEST_DONE;
                }
            }
        }
    }
    return ok;
}

处理完了所有的 request 数据,接下来进行编码和发送的工作。为此,创建了一个 http_response 对象,并调用了应用程序提供的编码函数 requestCallback,接下来,创建了一个 buffer 对象,函数 http_response_encode_buffer 用来将 http_response 中的数据,根据 HTTP 协议转换为对应的字节流。

可以看到,http_response_encode_buffer 设置了如 Content-Length 等 http_response 头部,以及 http_response 的 body 部分数据。

void http_response_encode_buffer(struct http_response *httpResponse, struct buffer *output) {
    char buf[32];
    snprintf(buf, sizeof buf, "HTTP/1.1 %d ", httpResponse->statusCode);
    buffer_append_string(output, buf);
    buffer_append_string(output, httpResponse->statusMessage);
    buffer_append_string(output, "\r\n");

    if (httpResponse->keep_connected) {
        buffer_append_string(output, "Connection: close\r\n");
    } else {
        snprintf(buf, sizeof buf, "Content-Length: %zd\r\n", strlen(httpResponse->body));
        buffer_append_string(output, buf);
        buffer_append_string(output, "Connection: Keep-Alive\r\n");
    }

    if (httpResponse->response_headers != NULL && httpResponse->response_headers_number > 0) {
        for (int i = 0; i < httpResponse->response_headers_number; i++) {
            buffer_append_string(output, httpResponse->response_headers[i].key);
            buffer_append_string(output, ": ");
            buffer_append_string(output, httpResponse->response_headers[i].value);
            buffer_append_string(output, "\r\n");
        }
    }

    buffer_append_string(output, "\r\n");
    buffer_append_string(output, httpResponse->body);
}

完整的 HTTP 服务器例子

现在,编写一个 HTTP 服务器例子就变得非常简单。

在这个例子中,最主要的部分是 onRequest callback 函数,这里,onRequest 方法已经在 parse_http_request 之后,可以根据不同的 http_request 的信息,进行计算和处理。例子程序里的逻辑非常简单,根据 http request 的 URL path,返回了不同的 http_response 类型。比如,当请求为根目录时,返回的是 200 和 HTML 格式。

#include <lib/acceptor.h>
#include <lib/http_server.h>
#include "lib/common.h"
#include "lib/event_loop.h"

//数据读到buffer之后的callback
int onRequest(struct http_request *httpRequest, struct http_response *httpResponse) {
    char *url = httpRequest->url;
    char *question = memmem(url, strlen(url), "?", 1);
    char *path = NULL;
    if (question != NULL) {
        path = malloc(question - url);
        strncpy(path, url, question - url);
    } else {
        path = malloc(strlen(url));
        strncpy(path, url, strlen(url));
    }

    if (strcmp(path, "/") == 0) {
        httpResponse->statusCode = OK;
        httpResponse->statusMessage = "OK";
        httpResponse->contentType = "text/html";
        httpResponse->body = "<html><head><title>This is network programming</title></head><body><h1>Hello, network programming</h1></body></html>";
    } else if (strcmp(path, "/network") == 0) {
        httpResponse->statusCode = OK;
        httpResponse->statusMessage = "OK";
        httpResponse->contentType = "text/plain";
        httpResponse->body = "hello, network programming";
    } else {
        httpResponse->statusCode = NotFound;
        httpResponse->statusMessage = "Not Found";
        httpResponse->keep_connected = 1;
    }

    return 0;
}


int main(int c, char **v) {
    //主线程event_loop
    struct event_loop *eventLoop = event_loop_init();

    //初始tcp_server,可以指定线程数目,如果线程是0,就是在这个线程里acceptor+i/o;如果是1,有一个I/O线程
    //tcp_server自己带一个event_loop
    struct http_server *httpServer = http_server_new(eventLoop, SERV_PORT, onRequest, 2);
    http_server_start(httpServer);

    // main thread for acceptor
    event_loop_run(eventLoop);
}

运行这个程序之后,我们可以通过浏览器和 curl 命令来访问它。你可以同时开启多个浏览器和 curl 命令,这也证明了我们的程序是可以满足高并发需求的。

$curl -v http://127.0.0.1:43211/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 43211 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:43211
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 116
< Connection: Keep-Alive
<
* Connection #0 to host 127.0.0.1 left intact
<html><head><title>This is network programming</title></head><body><h1>Hello, network programming</h1></body></html>%

总结

这一讲我们主要讲述了整个编程框架的字节流处理能力,引入了 buffer 对象,并在此基础上通过增加 HTTP 的特性,包括 http_server、http_request、http_response,完成了 HTTP 高性能服务器的编写。实例程序利用框架提供的能力,编写了一个简单的 HTTP 服务器程序。

使用 Go 语言实现高性能网络服务: 包括TCP连接管理、内存池、epoll、缓存设计、序列化等
AI天才研究院
08-07 1599
Go 是一门开源的编程语言,由 Google 开发并于 2009 年正式发布。静态强类型:在编译时已经把变量的数据类型确定下来,并进行严格类型检查;自动垃圾回收:不需要手动分配和释放内存,通过引用计数实现自动释放无用对象;接口:支持接口、多态特性,可以方便地实现依赖注入、适配器模式、代理模式等;goroutine:采用协程(Coroutine)机制,使得编异步并发程序变得简单;cgo:可以调用 C/C++ 的库函数,通过 cgo 可以直接利用现有的第方库;
应用层——HTTP协议(自己实现一个http协议)——客户端(浏览器)的请求做反序列化和请求分析,然后创建http向响应结构
qq2127189274的博客
06-07 998
应用层——HTTP协议(自己实现一个http协议)——客户端(浏览器)的请求做反序列化和请求分析,然后创建http向响应结构
TCP实现http请求
qq_43384388的博客
07-16 477
/正式主机名//主机别名//主机IP地址类型:IPV4-AF_INET//主机IP地址字节长度,对于IPv4是四字节,即32位//主机的IP地址列表#define h_addr h_addr_list[0] //保存的是IP地址//地址族//端口号//32位IP地址//预留未使用//32位IPv4地址。
基于TCP实现HTTP的get、post请求(前后端)
最新发布
weixin_44910729的博客
08-26 573
在浏览器打开http://127.0.0.1:8080/post.html。在浏览器打开http://127.0.0.1:8080/get.html。请求头多个,每个后面带回车换行(CRLF,即\r\n)get(服务器返回的’get’字符串)2.1.3 请求和响应的格式。3.1.3 请求和响应的格式。2.1.3 请求和响应的格式。3.1 使用post。2.2 实现get请求。
自主实现HTTP
qq_54198669的博客
11-14 475
自主实现HTTP
一步一步实现HTTP服务器-开篇
IT民工
05-18 653
缘起 翻开清单,一条条计划一直列在那里,一天又一天,不知道什么时候下了它,也知不道什么时候完成它,它一直在那静静的等待着。 静下心来,反思自己,才发现自己是多么的无知,多么的没有毅力。设定了无数目标,指定了无数计划,但是到头来呢?都是在无尽的叹息中,放弃了定下的目标。坚持 只是每天不断在内心回响的口号,想起了,就猛地觉醒,要进步、要努力、要坚持、要有目标。但是每每都是拿出计划本亦或是打开清单...
自己动手高性能HTTP服务器):TCP字节流处理HTTP协议实现.pdf
11-17
自己动手高性能HTTP服务器):TCP字节流处理HTTP协议实现
TCP-IP详解卷TCP事务协议,HTTP,NNTP和UNIX域协议
03-23
- TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。它通过次握手建立连接,确保数据的可靠传输,并通过确认、重传和流量控制机制来避免数据丢失或重复。 - TCP使用序列号和确认应答来保证数据的正确...
TCP-IP详解卷TCP事务协议,HTTP, NNTP和UNIX域协议.rar
10-28
TCP是传输层的一个面向连接的协议,它提供了可靠的、基于字节流的端到端服务,确保数据在传输过程中的完整性和顺序。IP则是网络层协议,负责数据包在网络中的路由和传输。TCP/IP结合,构成了互联网的基础。 卷...
TCP-IP详解卷3:TCP事务协议,HTTP,NNTP和UNIX域协议
04-29
首先,TCP(传输控制协议)是互联网协议族的核心,提供了面向连接的、可靠的、基于字节流的传输服务。TCP通过次握手建立连接,确保数据的可靠传输,并通过滑动窗口机制和确认应答来实现流量控制和拥塞控制。事务...
c++实现的一个小型的http服务器
03-11
c++实现的一个小型的http web服务器,是开发嵌入式web服务器很好的参考。
HttpServer:一个使用C#编的简易Web服务器
05-31
HttpServer 一个使用C#编的简易Web服务器, 目前支持: 静态页面处理 :grinning_face_with_smiling_eyes: GET/POST请求 :grinning_face_with_smiling_eyes: 支持HTTPS协议 :grinning_face_with_smiling_eyes: 支持返回JSON :worried_face: 支持路由方法 :worried_face: 快速开始 HTTP服务器示例 class Program { static void Main(string[] args) { ExampleServer server = new ExampleServer("0.0.0.0",4050); server.Start(); } } GET/POST请求示例 public override void OnPost(HttpRequest request, HttpResponse response) { //获取客户端传递的参数 string
C#编Http服务端
05-14
C#编Http服务端
基于C#实现一个最简单的HTTP服务器实例
09-04
主要介绍了基于C#实现一个最简单的HTTP服务器的方法,详细分析了http服务器实现原理与相关技巧,以及对应的注意事项,需要的朋友可以参考下
自己一个web服务器(手动解析HTTP协议+mvc功能)
weixin_34413065的博客
07-15 512
自己实现了一个轻量级的嵌入式HTTP服务器,Java语言。 主要功能如下: 使用nio处理socket连接。 提供类似spring mvc的功能, 包括@Controller,@RequestMapping,参数注入等功能。 不用方库,纯手动解析HTTP协议。 项目总计1700行java代码, {理解原理 + 不实现}天左右,{理解原理 + 自己实现}需要一周左右。 出这个web服务器...
使用 TCP 实现 HTTP
weixin_30677617的博客
08-05 944
利用网络调试助手,向大家展示HTTP是如何使用TCP实现的,其实就是通过TCP发送特定格式的数据。 注意:发送 GET / HTTP/1.1 时后面要跟两行回车,不然失败 111.13.100.91 为百度服务器的IP地址 转载于:https://www.cnblogs.com/liyongjun/p/965...
深入浅出OkHttp,【带你手】构建高效、高性能的网络请求框架
m0_71524094的博客
04-13 702
OkHttp是一种流行的网络访问框架,可以用于在Android和Java应用程序中进行HTTPHTTP/2请求。自己手OkHttp框架的目的是为了深入了解这种框架的功能和内部实现,并自己实现一些功能和特点。网络请求的生命周期:在请求开始前和请求结束后需要进行一些操作,例如建立连接、发送请求、接受响应等。这些操作需要在合适的时候调用。连接池的管理:为了减少网络开销和提高性能,可以使用连接池来管理可复用的连接,避免频繁地建立和断开连接。
基于TCP手动封装http协议
dreams_deng的博客
04-06 2118
1. 客户端,浏览器 2. 服务端基于 socket的 协议解析 服务端实现GET请求 启动服务端代码,请求路径:http://localhost:8888/ GET请求直接放入浏览器地址栏即可 package com.denganzhi.socket; import java.io.BufferedReader; import java.io.IOException; imp...
从零实现一个http服务器
月雲之霄的博客
08-07 279
如果GET请求带参数,那么一般是附加在请求的url后面,参数与参数之间使用&分割,例如请求http://www.hootina.org/index_2013.php?param1=value1¶m2=value2¶m3=value3,我们看下这个请求组装的的http协议包格式: GET /index_2013.php?param1=value1&param2=value2&am...
高性能Web服务器实现:网络协议与并发内幕
最后,高性能服务器实现不仅需要扎实的理论基础,还需要实践经验。理论可以帮助我们理解底层机制,而实践则能验证理论的可行性,并推动技术的优化和创新。理想的开发者应是既能理论创新,又能将理论应用于实践的...
写文章

热门文章

  • 05|音频降噪如何对症下药? 12251
  • 14|音效三剑客:变调、均衡器、混响 9154
  • 15|AI变声:音频AI技术的集大成者 7453
  • 22|再探HuggingFace:一键部署自己的大模型 7134
  • 11|网络差怎么办?音频网络传输与抗弱网策略 6230

分类专栏

  • Android 开发 19篇
  • C++实战笔记 26篇
  • 大规模数据处理 42篇
  • 技术管理2 37篇
  • 元宇宙 13篇
  • 前端 54篇
  • python自动化 36篇
  • Java 核心技术 41篇
  • 技术管理 37篇
  • Kafka 核心技术 45篇
  • 编译原理之美 41篇
  • 数学基础 56篇
  • 编程入门 35篇
  • 性能工程实战 33篇
  • 面试现场 39篇
  • 程序员工作法 55篇
  • 软件设计之美 36篇
  • 性能测试 33篇
  • 全栈工程师修炼 45篇
  • 搭建直播视频平台
  • AI大模型 30篇
  • 分布式协议与算法 28篇
  • 即时消息技术 23篇
  • Redis核心技术 49篇
  • 秒杀系统设计 9篇
  • 云计算 18篇
  • 程序员进阶攻略 65篇
  • 消息队列 39篇
  • 计算机组成原理 56篇
  • 代码精进之路 46篇
  • 动态规划面试 20篇
  • 系统性能调优 41篇
  • 架构实战案例 21篇
  • Go语言 50篇
  • eBPF 核心技术
  • 架构2 80篇
  • C++数据结构与算法
  • 微服务 39篇
  • 推荐系统 36篇
  • OAuth 2.0 15篇
  • 后端存储 27篇
  • python核心技术 32篇
  • 深入剖析 Kubernetes 52篇
  • 分布式技术原理与算法 35篇
  • RPC 实战与核心原理 27篇
  • Linux內核技术 21篇
  • 后端技术面试 44篇
  • 大数据 41篇
  • 游戏开发 37篇
  • HTTP 40篇
  • 机器学习 43篇
  • 架构 51篇
  • 网络编程 36篇
  • 视频技术 6篇
  • 密码学 20篇
  • Linux性能优化 58篇
  • 操作系统实战 5篇
  • c++ 34篇
  • 敏捷 10篇
  • 高并发系统设计 38篇
  • 音视频入门
  • Linux操作系统 66篇
  • C语言 31篇
  • 网络排查案例 5篇
  • 音频技术 16篇
  • 网络协议 46篇

最新评论

  • 10 分析篇 | 内存泄漏时,我们该如何一步步找到根因?

    hlzwty: 太猛了

  • 07 | Raft算法(一):如何选举领导者?

    wfh2015: 有些地方写的有偏差。 1. Follower等待Leader超时时间,是一个在固定区间的随机数,论文写的参考值是150-300ms 2. 很多时候不用Paxos主要原因有三个:a) Paxos算法是非常难以理解的,论文作者也提到,即使是斯坦福博士专门研究这东西,也花了1年左右的时间 b) Paxos算法有一些细节部分并没有公开 c) Raft是一个完整的算法,在Raft作者博士论文提到过的,除了基本部分意外,还增加了成员变更(可以理解为变更副本数提供了理论基础)/客户端交互/日志压缩,这意味着并不是空中楼阁

  • 06 | 定位防火墙(二):网络层的精确打击

    allen-smith: 请问虚拟机1和2的IP地址是多少,谢谢.

  • 05|码流结构:原来你是这样的H264

    YangShine01: 写的太好了!!!

  • 11 | I/O优化(下):如何监控线上I/O操作?

    CSDN-Ada助手: Python入门 技能树或许可以帮到你:https://edu.csdn.net/skill/python?utm_source=AI_act_python

最新文章

  • 16 | 网络优化(中):复杂多变的移动网络该如何优化?
  • 15 | 网络优化(上):移动开发工程师必备的网络优化知识
  • 14 | 存储优化(下):数据库SQLite的使用和优化
2024
08月 12篇
07月 49篇
06月 81篇
05月 158篇
04月 193篇
03月 255篇
02月 286篇
01月 218篇
2023年931篇
2022年46篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家广西玻璃钢雕塑怎么计算大型主题商场美陈批发玻璃钢雕塑浮雕制作过程顺义玻璃钢花盆厂家美陈玻璃钢雕塑批量定制江苏主题公园玻璃钢雕塑艺术摆件苏州小区玻璃钢雕塑价格甘肃卡通玻璃钢雕塑大量销售虹口区镜面玻璃钢雕塑制造厂家贵港玻璃钢雕塑厂家组合式玻璃钢花盆报价运动商场春季美陈图片岳阳玻璃钢雕塑生产嘉峪关仿真人物玻璃钢雕塑制作常州玻璃钢广场雕塑厂家嘉定区拉丝玻璃钢雕塑广美玻璃钢雕塑临沧市玻璃钢雕塑哪里买宜宾玻璃钢雕塑漆怎么样玻璃钢仿真水果雕塑出售浙江商场玻璃钢花盆北京 玻璃钢雕塑合作江西卡通玻璃钢动物狮子雕塑工业玻璃钢雕塑摆件开发商场美陈鲜花布置仿真玻璃钢雕塑一般多少钱阜阳人物玻璃钢雕塑公司安徽酒店玻璃钢雕塑艺术小品玻璃钢雕塑led楼盘景观雕塑玻璃钢定做价格香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化