TinyWebServer

2023-11-17

遇到的问题

1. Reactor和Proactor

当下开源软件能做到网络高性能的原因就是 I/O 多路复用吗?

是的,基本是基于 I/O 多路复用,用过 I/O 多路复用接口写网络程序的同学,肯定知道是面向过程的方式写代码的,这样的开发的效率不高。

于是,大佬们基于面向对象的思想,对 I/O 多路复用作了一层封装,让使用者不用考虑底层网络 API 的细节,只需要关注应用代码的编写。大佬们还为这种模式取了个让人第一时间难以理解的名字:Reactor 模式

这里的反应指的是「对事件反应」,也就是来了一个事件,Reactor 就有相对应的反应/响应

Reactor 模式主要由 Reactor 和处理资源池这两个核心部分组成,它俩负责的事情如下

  • Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件;
  • 处理资源池负责处理事件,如 read -> 业务逻辑 -> send;

I/O 复用结合线程池,这就是 Reactor 模式基本设计思想请添加图片描述
即 I/O 多了复用统一监听事件,收到事件后分发(Dispatch 给某进程),是编写高性能网络服务器的必备技术之一。

根据 Reactor 的数量和处理资源池线程的数量不同,有 3 种典型的实现:

  1. 单 Reactor 单线程;
  2. 单 Reactor 多线程;
  3. 主从 Reactor 多线程。

具体的三种典型实现

1.1 单 Reactor 单线程

请添加图片描述
其中,Select 是前面 I/O 复用模型介绍的标准网络编程 API,可以实现应用程序通过一个阻塞对象监听多路连接请求,其他方案示意图类似。

方案说明:

  1. Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发;
  2. 如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理;
  3. 如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应;
  4. Handler 会完成 Read→业务处理→Send 的完整业务流程。

优点:模型简单,没有多线程、进程通信、竞争的问题,全部都在一个线程中完成。

缺点:性能问题,只有一个线程,无法完全发挥多核 CPU 的性能。Handler 在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈。

可靠性问题,线程意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。

使用场景:客户端的数量有限,业务处理非常快速,比如 Redis,业务处理的时间复杂度 O(1)。

1.2 单 Reactor 多线程

请添加图片描述
方案说明:

  1. Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发;
  2. 如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后续的各种事件;
  3. 如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应;
  4. Handler 只负责响应事件,不做具体业务处理,通过 Read 读取数据后,会分发给后面的 Worker 线程池进行业务处理;
  5. Worker 线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给 Handler 进行处理;
  6. Handler 收到响应结果后通过 Send 将响应结果返回给 Client。

优点:可以充分利用多核 CPU 的处理能力。

缺点:多线程数据共享和访问比较复杂;Reactor 承担所有事件的监听和响应,在单线程中运行,高并发场景下容易成为性能瓶颈。

1.3 主从 Reactor 多线程

请添加图片描述

针对单 Reactor 多线程模型中,Reactor 在单线程中运行,高并发场景下容易成为性能瓶颈,可以让 Reactor 在多线程中运行。

方案说明:

  1. Reactor 主线程 MainReactor 对象通过 Select 监控建立连接事件,收到事件后通过 Acceptor 接收,处理建立连接事件;
  2. Acceptor 处理建立连接事件后,MainReactor 将连接分配 Reactor 子线程给 SubReactor 进行处理;
    1. SubReactor 将连接加入连接队列进行监听,并创建一个 Handler 用于处理各种连接事件;
  3. 当有新的事件发生时,SubReactor 会调用连接对应的 Handler 进行响应;
  4. Handler 通过 Read 读取数据后,会分发给后面的 Worker 线程池进行业务处理;
  5. Worker 线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给 Handler 进行处理;
  6. Handler 收到响应结果后通过 Send 将响应结果返回给 Client。

优点:父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线程完成后续的业务处理。

父线程与子线程的数据交互简单,Reactor 主线程只需要把新连接传给子线程,子线程无需返回数据。

这种模型在许多项目中广泛使用,包括 Nginx 主从 Reactor 多进程模型,Memcached 主从多线程,Netty 主从多线程模型的支持。

参考:
https://blog.csdn.net/u013256816/article/details/115388239?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166657834916800186537364%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166657834916800186537364&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-2-115388239-null-null.142v59control,201v3control_1&utm_term=reactor&spm=1018.2226.3001.4187

2. epoll中epoll_ctl监控的事件额外补充

2.1 EPOLLSHOT的使用

EPOLLSHOT的作用主要用于多线程中

epoll在某次循环中唤醒一个事件,并用某个工作进程去处理该fd,此后如果不注册EPOLLSHOT,在该fd时间如果工作线程处理的不及时,主线程仍会唤醒这个事件,并另派线程池中另一个线程也来处理这个fd

为了避免这种情况,需要在注册时间时加上EPOLLSHOT标志,EPOLLSHOT相当于说,某次循环中epoll_wait唤醒该事件fd后,就会从注册中删除该fd,也就是说以后不会epollfd的表格中将不会再有这个fd,也就不会出现多个线程同时处理一个fd的情况。处理完以后如果需要,要重新挂树

2.2 EPOLLRDHUP的使用

在内核2.6.17(不含)以前版本,要想知道对端是否关闭socket,上层应用只能通过调用recv来判断是否为0才能知道,在2.6.17以后,这种场景上层只需要判断EPOLLRDHUP即可,无需在调用recv这个系统调用。

结论:

  • 客户端直接调用close,会触犯EPOLLRDHUP事件
  • 通过EPOLLRDHUP属性,来判断是否对端已经关闭,这样可以减少一次系统调用。在2.6.17的内核版本之前,只能再通过调用一次recv函数来判断

代码示例:

//服务器端
epoll_event event;
event.fd = connfd;
event.events = EPOLLIN | EPOLLET | EPOLLSHOT | EPOLLRDHUP;

epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &events);

3. 为什么要使⽤线程池?

当你需要限制你应⽤程序中同时运⾏的线程数时,线程池⾮常有⽤。因为启动⼀个新线程会带来性能开销,每个线程也会为其堆栈分配⼀些内存 等。为了任务的并发执⾏,我们可以将这些任务任务传递到线程池,⽽不是为每个任务动态开启⼀个新的线程。

4. 怎么知道线程池的线程数量?

线程池中的线程数量最直接的限制因素是中央处理器(CPU)的处理器(processors/cores)的数量 N :如果你的CPU是4-cores的,对于CPU密集型 的任务(如视频剪辑等消耗CPU计算资源的任务)来说,那线程池中的线程数量最好也设置为4(或者+1防⽌其他因素造成的线程阻塞);对于 IO密集型的任务,⼀般要多于CPU的核数,因为线程间竞争的不是CPU的计算资源⽽是IO,IO的处理⼀般较慢,多于cores数的线程将为CPU 争取更多的任务,不⾄在线程处理IO的过程造成CPU空闲导致资源浪费,公式: 最佳线程数 = CPU当前可使⽤的Cores数 * 当前CPU的利⽤率 * (1 + CPU等待时间 / CPU处理时间) (还有回答⾥⾯提到的Amdahl准则可以了解⼀下)

5. http请求报文中get和post的区别

  • 最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数。
  • GET请求参数会被完整保留在浏览器历史记录⾥,⽽POST中的参数不会被保留。
  • GET请求在URL中传送的参数是有⻓度限制。(⼤多数)浏览器通常都会限制url⻓度在2K个字节,⽽(⼤多数)服务器最多处理64K⼤⼩的 url。
  • GET产⽣⼀个TCP数据包;POST产⽣两个TCP数据包。对于GET⽅式的请求,浏览器会把http header和data⼀并发送出去,服务器响应200 (返回数据);⽽对于POST,浏览器先发送header,服务器响应100(指示信息—表示请求已接收,继续处理)continue,浏览器再发送 data,服务器响应200 ok(返回数据)。

6. 智能指针

7. pthread_create陷阱

如果把类的函数当作回调函数传入pthread_create会报错,编译不通过,原因是this指针会作为默认的参数被传进函数中。

所以要把类的成员函数当作回调函数时,需要设置为静态成员函数(静态成员函数没有this指针,但是静态成员函数只能访问静态成员变量)。

代码笔记

1. 使用了epoll为什么还要线程池?

这里,服务器通过epoll这种I/O复用技术(还有select和poll)来实现对监听socket(listenfd)和连接socket(客户请求)的同时监听。注意I/O复用虽然可以同时监听多个文件描述符,但是它本身是阻塞的,并且当有多个文件描述符同时就绪的时候,如果不采取额外措施,程序则只能按顺序处理其中就绪的每一个文件描述符,所以为提高效率,我们将在这部分通过线程池来实现并发(多线程并发),为每个就绪的文件描述符分配一个逻辑单元(线程)来处理。

2. 服务器程序需要处理的三类事件

服务器程序通常需要处理三类事件:I/O事件,信号及定时事件。有两种事件处理模式:

  • Reactor模式:要求主线程(I/O处理单元)只负责监听⽂件描述符上是否有事件发⽣(可读、可写),若有,则⽴即通知⼯作线程(逻辑单 元),将socket可读可写事件放⼊请求队列,交给⼯作线程处理。
  • Proactor模式:将所有的I/O操作都交给主线程和内核来处理(进⾏读、写),⼯作线程仅负责处理逻辑,如主线程读完成 后 users[sockfd].read() ,选择⼀个⼯作线程来处理客户请求 pool->append(users + sockfd) 。

3. 为什么⽤epoll

Linux下有三种IO复⽤⽅式:epoll,select和poll,为什么⽤epoll,它和其他两个有什么区别呢

  • 对于select和poll来说,所有⽂件描述符都是在⽤户态被加⼊其⽂件描述符集合的,每次调⽤都需要将整个集合拷⻉到内核态;epoll则将整个 ⽂件描述符集合维护在内核态,每次添加⽂件描述符的时候都需要执⾏⼀个系统调⽤。
  • select使⽤线性表描述⽂件描述符集合,⽂件描述符有上限;poll使⽤链表来描述;epoll底层通过红⿊树来描述,并且维护⼀个ready list,将 事件表中已经就绪的事件添加到这⾥,在使⽤epoll_wait调⽤时,仅观察这个list中有没有数据即可。
  • select和poll的最⼤开销来⾃内核判断是否有⽂件描述符就绪这⼀过程:每次执⾏select或poll调⽤时,它们会采⽤遍历的⽅式,遍历整个⽂件 描述符集合去判断各个⽂件描述符是否有活动;epoll则不需要去以这种⽅式检查,当有活动产⽣时,会⾃动触发epoll回调函数通知epoll⽂件 描述符,然后内核将这些就绪的⽂件描述符放到之前提到的ready list中等待epoll_wait调⽤后被处理。
  • select和poll都只能⼯作在相对低效的LT模式下,⽽epoll同时⽀持LT和ET模式。

4. EWOULDBLOCK作用

在使⽤ET模式时,必须要保证该⽂件描述符是⾮阻塞的(确保在没有数据可读时,该⽂件描述符不会⼀直阻塞);并且每次调⽤ read 和 write 的 时候都必须等到它们返回 EWOULDBLOCK (确保所有数据都已读完或写完)。

5. 主线程与工作线程的职责

主线程为异步线程,负责监听文件描述符,接收socket新连接,若当前监听的socket发生了读写事件,然后将任务插入到请求队列。工作线程从请求队列中取出任务,完成读写数据的处理。

6. 同步I/O模拟proactor模式

由于异步I/O并不成熟,实际中使用较少,这里将使用同步I/O模拟实现proactor模式。

同步I/O模型的工作流程如下(epoll_wait为例):

  • 主线程往epoll内核事件表注册socket上的读就绪事件。
  • 主线程调用epoll_wait等待socket上有数据可读
  • 当socket上有数据可读,epoll_wait通知主线程,主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。
  • 睡眠在请求队列上某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件
  • 主线程调用epoll_wait等待socket可写。
  • 当socket上有数据可写,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果。

7. GET与POST的区别

注意:GET和POST的区别:

  • 用户向搜索引擎搜关键字时,若使用的是POST的请求类型,会将搜索的内容写入实体体中,而不写入URL中
  • 但是如果你使用的请求类型GET,则会把请求的URL中包含搜索的内容,不填入实体体中。
  • GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100(指示信息—表示请求已接收,继续处理)continue,浏览器再发送data,服务器响应200 ok(返回数据)。

7. 状态机

7.1 状态机基本概念

项目中使用主从状态机的模式进行解析:

从状态机(parse_line)负责读取报文的一行,主状态机负责对该行数据进行解析,主状态机内部调用从状态机,从状态机驱动主状态机。每解析一部分都会将整个请求的m_check_state状态改变,状态机也就是根据这个状态来进行不同部分的解析跳转的:

其实就是自顶向下中学的状态机。

为什么在网络编程中需要状态机?因为写出大量的if else使得代码无比丑且高冗余

8. socketpair

socketpair是用来创建管道套接字。

在该项目中,创建管道是给信号事件使用的,然后可以把管道套接字也给epoll监控,使得信号事件与其他文件描述符都可以通过epoll来监测,从而实现统一处理。

 ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd);

8.1 socketpair函数介绍

socketpair()函数用于创建一对无名的、相互连接的套接子。
如果函数成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1,错误码保存于errno中。

函数原型:

  • int socketpair(int d, int type, int protocol, int sv[2]);

函数参数:

  • 参数1(domain):表示协议族,在Linux下只能为AF_LOCAL或者AF_UNIX。(自从Linux 2.6.27后也支持SOCK_NONBLOCK和SOCK_CLOEXEC)
  • 参数2(type):表示协议,可以是SOCK_STREAM或者SOCK_DGRAM。SOCK_STREAM是基于TCP的,而SOCK_DGRAM是基于UDP的
  • 参数3(protocol):表示类型,只能为0
  • 参数4(sv[2]):套节字柄对,该两个句柄作用相同,均能进行读- 写双向操作
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);

如何使用?

  1. 这对套接字可以用于全双工通信,每一个套接字既可以读也可以写。例如,可以往sv[0]中写,从sv[1]中读;或者从sv[1]中写,从sv[0]中读;
  2. 如果往一个套接字(如sv[0])中写入后,再从该套接字读时会阻塞,只能在另一个套接字中(sv[1])上读成功;
  3. 读、写操作可以位于同一个进程,也可以分别位于不同的进程,如父子进程。如果是父子进程时,一般会功能分离,一个进程用来读,一个用来写。因为文件描述副sv[0]和sv[1]是进程共享的,所以读的进程要关闭写描述符, 反之,写的进程关闭读描述符。

9. 连接池

连接池的功能主要有:初始化,获取连接、释放连接,销毁连接池

10. 线程所调用的函数 process() 详细解析

void http_conn::process()
{
    HTTP_CODE read_ret = process_read();
    if (read_ret == NO_REQUEST) 
    {
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        return;
    }

    bool write_ret = process_write(read_ret);
    if (!write_ret) 
    {
        close_conn();
        //这里是不是要关闭定时器
    }
    
    modfd(m_epollfd, m_sockfd, EPOLLOUT);
}
  1. 先调用process_read()解析请求报文。会获取到客户到底是要做什么,接着process_read()内部会根据主状态机的状态调用de_request(),做具体的业务处理与分析(如注册,登陆等等)
  2. 接着根据服务器处理HTTP请求的结果,会调用process_write()进行响应报文(add_status_line、add_headers等函数)的生成。在生成响应报文的过程中主要调用add_reponse()函数更新m_write_idx和m_write_buf。
  3. 最后会回到主循环的最后一个事件 EPOLLOUT触发,将响应报文真正的发出。

11. http响应报文的笔记

1. iovec结构体的定义与使用

struct iovec 结构体定义了一个向量元素,通常这个 iovec 结构体用于一个多元素的数组(结构体数组),对于每一个元素,iovec 结构体的字段 iov_base 指向一个缓冲区,这个缓冲区存放的是网络接收的数据(read),或者网络将要发送的数据(write)。iovec 结构体的字段 iov_len 存放的是接收数据的最大长度(read),或者实际写入的数据长度(write)。

struct iovec {
    /* Starting address (内存起始地址)*/
    void  *iov_base;   

    /* Number of bytes to transfer(这块内存长度) */
    size_t iov_len;  
};

在 linux 中,使用这样的结构体变量作为参数的函数很多,常见的包括:

#include <sys/uio.h> 
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt,off_t offset);
ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);
ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);

示例代码,向终端(屏幕)打印字符串:i am happy:

//iovec结构体的使用
#include<stdio.h>
#include<sys/uio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<string.h>

void sys_err(const char *ptr,int num)
{
    perror(ptr);
    exit(-1);
}

int main(int argc,char **argv)
{
    struct iovec iov[3];
    char *p1 = "i";
    char *p2 = " am";
    char *p3 = " happy.\n";
    iov[0].iov_base = p1;
    iov[0].iov_len = strlen(p1);

    iov[1].iov_base = p2;
    iov[1].iov_len = strlen(p2);

    iov[2].iov_base = p3;
    iov[2].iov_len = strlen(p3);
    ssize_t ret = writev(STDOUT_FILENO,iov,3);
    if(ret < 0)
    {
        sys_err("writev",-1);
    }
    return 0;
}

2. writev/readv

因为使用read()将数据读到不连续的内存、使用write()将不连续的内存发送出去,要经过多次的调用read、write。

如果要从文件中将多块分散的内存数据写到文件描述符中,会有很多麻烦,并且会多次系统调用+拷贝会带来较大的开销。

所以UNIX提供了另外两个函数—readv()和writev(),它们只需一次系统调用就可以实现在文件和进程的多个缓冲区之间传送数据,免除了多次系统调用或复制数据的开销

writev函数用于在一次函数调用中写多个非连续缓冲区,有时也将这该函数称为聚集写。

writev以顺序iov[0],iov[1]至iov[iovcnt-1]从缓冲区中聚集输出数据。

  • 函数原型
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

struct iovec {
    void  *iov_base;    /* Starting address */
    size_t iov_len;     /* Number of bytes to transfer */
};
  • 函数参数
    • iov是关于iovec的结构体数组,看上一小节
    • iovcnt是结构体数组的长度
  • 返回值
    • 成功:返回读取/写入的fd的字节数。
    • 失败:返回-1;

12. 如何编译

编译:

g++ -o main -g main.cpp timer.cpp http_conn.cpp sql_pool.cpp -I./ -I/usr/include/mysql/ -L/usr/lib64/mysql/ -lmysqlclient -pthread

整体流程

主线程负责监听listenfd,接收新的连接。

浏览器端发出http连接请求,主线程创建http对象接收请求并将所有数据读入对应buffer,将该对象插入任务队列,工作线程从任务队列中取出一个任务进行处理。即,将发生读事件的http对象指针放入请求队列。

当connfd发生了读写事件,就把任务放入请求队列(注意: 请求队列只存放connfd的任务,不存放listenfd的任务),然后工作线程从请求队列中取出任务(注意,取出任务行为触及到锁操作,请求队列是一个共享资源),完成任务的处理。

http报文解析

线程池中的工作线程从任务队列中取出一个任务进行处理。

各子线程通过process函数对任务进行处理,调用process_read函数和process_write函数分别完成报文解析与报文响应两个任务

process_read详解:

注意,报文解析用到了状态机来解决的,状态机简单来说就是看状态调用不同的函数。

在这里插入图片描述

写项目时的琐碎小点总结

listenfd和connfd都设置成了ET/非阻塞。

connfd都加上了EPOLLONESHOT,所以每次事件发生后,都需要重新注册该事件(除了listenfd、信号)

epoll一共注册了三种事件:

  • 连接listenfd
  • 数据到来connfd
  • 信号pipefd[0] (统一事件源)
  • EPOLLOUT
  • 异常事件(EPOLLRDHUP、EPOLLHUP、EPOLLERR)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TinyWebServer 的相关文章

  • 如何获取正在访问 ASP.NET 应用程序的当前用户?

    为了获取系统中当前登录的用户 我使用以下代码 string opl System Security Principal WindowsIdentity GetCurrent Name ToString 我正在开发一个 ASP NET 应用程
  • 没有强命名的代码签名是否会让您的应用程序容易被滥用?

    尝试了解authenticode代码签名和强命名 我是否正确地认为 如果我对引用一些 dll 非强命名 的 exe 进行代码签名 恶意用户就可以替换我的 DLL 并以看似由我签名但正在运行的方式分发应用程序他们的代码 假设这是真的 那么您似
  • “构建”构建我的项目,“构建解决方案”则不构建

    我刚刚开始使用VS2010 我有一个较大的解决方案 已从 VS2008 成功迁移 我已将一个名为 Test 的控制台应用程序项目添加到解决方案中 选择构建 gt 构建解决方案不编译新项目 选择构建 gt 构建测试确实构建了项目 在失败的情况
  • GLKit的GLKMatrix“列专业”如何?

    前提A 当谈论线性存储器中的 列主 矩阵时 列被一个接一个地指定 使得存储器中的前 4 个条目对应于矩阵中的第一列 另一方面 行主 矩阵被理解为依次指定行 以便内存中的前 4 个条目指定矩阵的第一行 A GLKMatrix4看起来像这样 u
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 堆栈溢出:堆栈空间中重复的临时分配?

    struct MemBlock char mem 1024 MemBlock operator const MemBlock b const return MemBlock global void foo int step 0 if ste
  • 重载<<的返回值

    include
  • 显示UnityWebRequest的进度

    我正在尝试使用下载 assetbundle统一网络请求 https docs unity3d com ScriptReference Networking UnityWebRequest GetAssetBundle html并显示进度 根
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • while 循环中的 scanf

    在这段代码中 scanf只工作一次 我究竟做错了什么 include
  • 如何在整个 ASP .NET MVC 应用程序中需要授权

    我创建的应用程序中 除了启用登录的操作之外的每个操作都应该超出未登录用户的限制 我应该添加 Authorize 每个班级标题前的注释 像这儿 namespace WebApplication2 Controllers Authorize p
  • 控件的命名约定[重复]

    这个问题在这里已经有答案了 Microsoft 在其网站上提供了命名指南 here http msdn microsoft com en us library xzf533w0 VS 71 aspx 我还有 框架设计指南 一书 我找不到有关
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • WPF/C# 将自定义对象列表数据绑定到列表框?

    我在将自定义对象列表的数据绑定到ListBox in WPF 这是自定义对象 public class FileItem public string Name get set public string Path get set 这是列表
  • 如何将带有 IP 地址的连接字符串放入 web.config 文件中?

    我们当前在 web config 文件中使用以下连接字符串 add name DBConnectionString connectionString Data Source ourServer Initial Catalog ourDB P
  • 将控制台重定向到 .NET 程序中的字符串

    如何重定向写入控制台的任何内容以写入字符串 对于您自己的流程 Console SetOut http msdn microsoft com en us library system console setout aspx并将其重定向到构建在
  • C# 成员变量继承

    我对 C 有点陌生 但我在编程方面有相当广泛的背景 我想做的事情 为游戏定义不同的 MapTiles 我已经像这样定义了 MapTile 基类 public class MapTile public Texture2D texture pu
  • 混合 ExecutionContext.SuppressFlow 和任务时 AsyncLocal.Value 出现意外值

    在应用程序中 由于 AsyncLocal 的错误 意外值 我遇到了奇怪的行为 尽管我抑制了执行上下文的流程 但 AsyncLocal Value 属性有时不会在新生成的任务的执行范围内重置 下面我创建了一个最小的可重现示例来演示该问题 pr
  • C# 模拟VolumeMute按下

    我得到以下代码来模拟音量静音按键 DllImport coredll dll SetLastError true static extern void keybd event byte bVk byte bScan int dwFlags
  • 如何将服务器服务连接到 Dynamics Online

    我正在修改内部管理应用程序以连接到我们的在线托管 Dynamics 2016 实例 根据一些在线教程 我一直在使用OrganizationServiceProxy out of Microsoft Xrm Sdk Client来自 SDK

随机推荐

  • SQL注入——学生选课系统注入

    目录 前言 一 实验环境 二 实验步骤 1 万能密码 2 堆叠注入 3 报错注入 4 时间盲注 前言 本次实验利用教师指定的学生选课管理系统进行SQL注入 包含万能密码登录 堆叠注入 报错注入和时间盲注 一 实验环境 Windows10虚拟
  • QT 15--获取任何种类文件的某些文件属性:大小、创建时间、上次修改时间等等

    1 首先说一些 如果是mainwindow的QT工程 如果打算做自己手写ui 界面的话 该如何将自己写的内容添加到mainwindow界面呢 方法为 新建一个widget类 然后将所有零件都用布局布置好后 只需将总布局添加到widet 然后
  • KMP时间复杂度分析

    比较过程分析 比较次数 比较次数 红色 蓝色 蓝色部分是相比暴力求解 节省下的比较次数 周期 从比较次数可以看出 呈现 1 1 1 1 5 这样的周期 一个周期内的比较次数 8 周期长度 5 周期个数 n 5 比较总次数 周期个数 一个周期
  • 学成在线笔记+踩坑(10)——课程搜索、课程发布时同步索引库。

    导航 黑马Java笔记 踩坑汇总 JavaSE JavaWeb SSM SpringBoot 瑞吉外卖 SpringCloud 黑马旅游 谷粒商城 学成在线 牛客面试题 java黑马笔记 目录 1 检索模块 需求分析 1 1 全文检索介绍
  • H3 GPIO笔记

    NanoPi NEO Core最近买了一块 这个板子使用全志H3 查看H3的数据手册 把GPIO这部分做个笔记 H3有7组GPIO 如下 分别是PA PC PD PE PF PG PL 没有PB这一组 PA有22个端口 PC有19个端口 P
  • 【LeetCode题解】1475、商品折扣后的最终价格

    题目 给你一个数组 prices 其中 prices i 是商店里第 i 件商品的价格 商店里正在进行促销活动 如果你要买第 i 件商品 那么你可以得到与 prices j 相等的折扣 其中 j 是满足 j gt i 且 prices j
  • CSS动画:Transition与Animation

    本文总结CSS3中两个用来做动画的属性 一个是transition 另一个是animation 差异比较 CSS3 差异 transition 在给定的持续时间内平滑地更改属性值 从一个值到另一个值 也就是只需要指定开始与结束的参数 参数改
  • 让汽车的全景环视更智能更安全!

    随着现代汽车安全技术的进步 我们看到诸如全景环视等先进驾驶辅助 ADAS 技术成为现代汽车的新标准 本演示展示了如何通过精确的实时反射和AI来检测障碍 以提升全景环视系统的性能 让汽车驾驶更安全 尤其是 当全景环视系统内嵌Imaginati
  • cesium-添加点并且可以编辑

    完整代码
  • 05_Numpy任意行&列的删除方法(numpy.delete)

    05 Numpy任意行 列的删除方法 numpy delete 函数Numpy delete 可以删除ndarray数组中任意的行或者列 指定要删除的轴 维度 和要删除的位置 行号 列号 也可以通过切片或列表选择多个行或者列的编号 对以下的
  • 【Unity Shaders】抖音变身漫画1

    先来看一下手机拍出来的效果 我们发现有一张人像变成了卡通漫画脸 其它的只是做了一些图像处理 你可以再拍几张看一下 会发现千篇一律的大眼 小嘴有没有 你想的没错 这个是AI换脸技术 抖音特效里有很多了 把这个漫画脸再加上对图像的漫画处理 最后
  • 解读CUDA Compiler Driver NVCC - Ch.5

    前言 前面几篇文章 我们了解了NVCC的作用 nvcc编译的two stage 每个stage做了什么 怎么去选择虚拟架构和真实架构 JIT编译的原理 好处和弊端以及解决方案 本文我们将了解几个实际的nvcc编译命令 Base Notati
  • el-select中多选回显数据后没法重新选择和更改

    我用element select 多选回显的时候 回显正常 不能点击清除 不能选择改变数据 然后去搜了这篇文章文章链接 博主解释要在select标签上加一个强制渲染 如下图
  • Docker的网络模式

    目录 Docker的四种网络模式 1 Bridge 网络模式 类似于VMware的NAT模式 Bridge 网络模式介绍 bridge模式示意图 2 Host 网络模式 Host 网络模式介绍 Host模式示意图 3 Container 网
  • 【Redis】集合Set和底层实现

    文章目录 Redis 集合 Set Set简介 常用命令 应用场景 共同关注实例 整数集合 整数集合介绍 整数集合的升级 哈希表 哈希表的原理和实现 Redis中的哈希表 rehash 渐进式rehash Redis 集合 Set Set简
  • 如何用xp系统做服务器,xp系统如何做远程服务器呢

    xp系统如何做远程服务器呢 内容精选 换一换 网站的访问与云服务器的网络配置 端口通信 防火墙配置 安全组配置等多个环节相关联 任意一个环节出现问题 都会导致网站无法访问 本节操作介绍网站无法访问时的排查思路 网站无法访问怎么办 如果打开网
  • 14-5_Qt 5.9 C++开发指南_基于HTTP 协议的网络应用程序

    文章目录 1 实现高层网络操作的类 2 基于HTTP协议的网络文件下载 3 源码 3 1 可是化UI设计 3 2 mainwindow h 3 3 mainwindow cpp 1 实现高层网络操作的类 Qt 网络模块提供一些类实现 OSI
  • Synchronized的锁升级过程

    Synchronized的锁升级过程 synchronized锁升级过程 在synchronized中引入了偏向锁 轻量级锁 重量级锁之后 当前具体使用的是synchronzed中的那种类型锁 是根据线程竞争激烈程度来决定的 偏向锁 在锁对
  • vue使用luckysheet,引入图表chartmix,实现打印按钮功能

    1 下载Luckysheet源码 下载地址 https github com dream num Luckysheet 按照下载地址提示 npm run build 打包源码 生成dist文件夹 2 引入luckysheet的js文件和cs
  • TinyWebServer

    遇到的问题 1 Reactor和Proactor 当下开源软件能做到网络高性能的原因就是 I O 多路复用吗 是的 基本是基于 I O 多路复用 用过 I O 多路复用接口写网络程序的同学 肯定知道是面向过程的方式写代码的 这样的开发的效率