TinyWebServer项目学习笔记

TinyWebServer项目学习

什么是socket?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口;它屏蔽了网络模型中传输层下面的所有层,让用户觉得网络从主机A的应用层到传输层直接传到了主机B的传输层再到B的应用层。

参考:

  1. https://blog.nowcoder.net/n/b19e6193439b4c4c85cab8ff2a38bdbb?from=nowcoder_improve
  2. https://zhuanlan.zhihu.com/p/119085959

端口复用:

1
setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&buf,1));

在端口复用技术中最重要的一个函数是setsockopt(),这个函数就决定了端口的重绑定问题。为了通知套接口实现不要因为一个地址已被一个套接口使用就不让它与另一个套接口捆绑,应用程序可在bind()调用前先设置 SO_REUSEADDR选项。

我们这里要使用的是socket中的 SO_REUSEADDR,下面是它的解释。

SO_REUSEADDR 提供如下四个功能:

SO_REUSEADDR:允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。SO_REUSEADDR:允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。SO_REUSEADDR:允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。SO_REUSEADDR:允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。

accept函数做了什么?

项目中为什么用非阻塞socket?

什么是epoll?

epoll是I/O复用的一种方式,是目前用的最多也是最好的一种方式。I/O多路复用就是用多个I/O连接复用一个线程/进程,来实现多个I/O的管理。当有I/O事件产生时,它可以知道是哪个I/O(哪个fd文件描述符)。

参考:

  1. https://www.cnblogs.com/lojunren/p/3856290.html
  2. https://www.xiaolincoding.com/os/8_network_system/selete_poll_epoll.html
  3. https://www.jianshu.com/p/67c988f750df
  4. https://www.cnblogs.com/skyfsm/p/7102367.html
  5. https://www.cnblogs.com/fnlingnzb-learner/p/5835573.html

ET和LT两种模式的区别

当fd有一个事件的时候,比如读或者写事件,LT模式在这个事件没有处理完的情况下,会继续把这个事件加入到就绪队列中,直到处理完成;ET模式只会通知一次,不管你有没有处理完成,这个事件也不会加入到就绪队列中,除非有新的数据进来产生新的事件。

参考:

https://zhuanlan.zhihu.com/p/441677252

惊群现象

参考:

https://wenfh2020.com/2021/09/25/thundering-herd/

什么是Reactor模式和Proactor模式?

这两个模式是高性能网络模式。

Reactor是非阻塞同步模型,I/O多路复用监控事件,收到事件后,根据事件类型分配给某个进程/线程。

Proacotr是异步网络模式。

参考:

  1. https://xiaolincoding.com/os/8_network_system/reactor.html
  2. https://zhuanlan.zhihu.com/p/95662364

什么是线程池?

参考:

https://zhuanlan.zhihu.com/p/444375447

https://zhuanlan.zhihu.com/p/367309864

https://zhuanlan.zhihu.com/p/367309864 c++11写的线程池

c的线程库和c++的线程库

参考:

https://blog.csdn.net/sinat_35261315/article/details/79275839

信号量,互斥锁,条件变量

条件变量是为了更高效的使用cpu等资源,让进程/线程在满足一定条件后才能去竞争共享资源。

条件变量参考:

  1. https://blog.csdn.net/m0_37621078/article/details/89766449
  2. https://www.cnblogs.com/yunfan1024/p/14140687.html

参考:

  1. https://www.cnblogs.com/549294286/p/3687678.html
  2. https://www.cnblogs.com/cangqinglang/p/13328217.html

什么是单例模式?

参考:

https://zhuanlan.zhihu.com/p/37469260

局部静态变量为什么是线程安全的?

私有构造函数是什么以及作用

在 C++ 中,构造函数是用于创建和初始化对象的特殊成员函数。默认情况下,构造函数是公有的(public),可以在类的外部和内部访问和调用。然而,将构造函数放在私有区域(private)内是一种常见的技术,被称为私有构造函数(Private Constructor)。

私有构造函数的目的是限制类的实例化,即防止类的用户直接创建对象。这在某些设计模式和类的实现中非常有用。通过将构造函数声明为私有,只有类的内部成员函数可以调用构造函数。这意味着只能在类的内部创建对象,而无法在类的外部进行实例化。

示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass {
private:
MyClass() {
// 私有构造函数
}

public:
static MyClass* createInstance() {
return new MyClass();
}

void someFunction() {
// 类的成员函数
}
};

在上面的示例中,构造函数 MyClass() 被声明为私有,并通过公有的静态成员函数 createInstance() 来创建类的实例。只有 createInstance() 函数内部可以调用私有构造函数,从而实现了对实例化过程的控制。

通过这种方式,类的用户无法直接实例化对象,而是需要调用公有的静态函数来获取类的实例。这可以让类的设计者在类的创建过程中施加一些额外的逻辑和限制,以确保对象的正确初始化和使用。

需要注意的是,私有构造函数并不是 C++ 中的强制规定,而是一种编程技巧,用于特定的设计和实现需求。在一般情况下,构造函数应该是公有的,以便类的用户可以直接实例化对象。

私有构造函数的使用场景有以下几个常见的情况:

  1. 单例模式(Singleton Pattern):单例模式是一种创建唯一实例的设计模式。通过将构造函数声明为私有,可以防止类的用户直接实例化多个对象。类内部可以提供一个公有的静态成员函数,用于获取类的唯一实例。这样可以确保在整个程序中只存在一个实例。

  2. 工厂模式(Factory Pattern):工厂模式是一种通过工厂类来创建对象的设计模式。通过将构造函数声明为私有,可以限制类的用户直接创建对象。工厂类内部可以使用私有构造函数来创建对象,并提供公有的静态方法或非静态方法来返回实例化的对象。

  3. 静态类(Static Class):静态类是一种只包含静态成员的类,无需实例化即可访问。通过将构造函数声明为私有,可以防止类的用户实例化静态类的对象。静态类的成员函数和成员变量都可以是静态的,可以通过类名直接访问。

  4. 辅助类(Helper Class):有时候,可能会创建一些只包含静态方法的辅助类,用于提供一些实用函数或算法。将构造函数声明为私有,可以防止类的用户直接实例化辅助类的对象,而只能通过调用静态方法来使用辅助类。

总的来说,私有构造函数主要用于控制对象的创建和实例化过程,以便满足特定的设计需求,例如单例模式、工厂模式、静态类和辅助类等。通过将构造函数设为私有,可以限制对象的创建方式,提供更好的封装和控制。

c++ mysql.h

参考:https://dulishu.top/mysql-c-api/

有限状态机

参考:https://zhuanlan.zhihu.com/p/46347732

c++ RAII原理

使用局部对象来管理资源的技术就是RAII(Resource Acquisition Is Initialization)。

利用类本身的生命周期来管理。

参考:

https://zhuanlan.zhihu.com/p/34660259

https://blog.csdn.net/doc_sgl/article/details/43028009

http中get和post的区别

参考:

https://cloud.tencent.com/developer/article/1915518

https://www.zhihu.com/question/28586791

socket中的listen函数会阻塞进程吗?

在调用 listen 函数之后,它会使套接字进入监听状态,并开始接受传入的连接请求。但是,listen 函数本身不会阻塞进程。它只是设置套接字的状态,告诉操作系统该套接字准备好接受连接。

然而,一旦调用 listen 后,接下来的操作通常是调用 accept 函数来接受连接请求。accept 函数会阻塞进程,直到有新的连接请求到达并被接受。在没有新连接请求到达之前,accept 函数会一直阻塞。

所以,虽然 listen 函数本身不会阻塞进程,但在调用 listen 后,通常会紧接着调用 accept 函数,而 accept 函数可能会导致进程阻塞,直到有新的连接请求到达。

需要注意的是,你可以通过设置套接字为非阻塞模式,使 accept 函数变为非阻塞的。这样,即使没有新连接请求到达,accept 函数也会立即返回,并可以通过其他方式来检查是否有新的连接请求到达。

什么是I/O复用?

I/O 多路复用的本质,是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。

epoll是linux提供的I/O多路复用的一种方式。

一般来说,一个socket连接会用一个线程/进程来管理,当服务器socket监听到有新的客户socket来的时候,会创建一个线程来处理这个客户socket所产生的读/写操作。这样子线程利用率不高,因为读写这种操作,在一段时间内发生的时间很短,大部分的时间都是没有这些操作的。所以采用epoll这种方式,就会让一个线程/进程管理很多个客户socket,当某个客户socket有事件发生的时候,会有事件通知,从而用一个线程进行处理该事件。

可重入函数和不可重入函数

其实就是有没有对共享数据进行除了读操作以外的任意操作。

参考:

https://cloud.tencent.com/developer/article/1685935

https://segmentfault.com/a/1190000022571212

定时器设计的流程

采用发送SIGALRM信号来定时——alarm(系统调用函数)可以周期性的触发SIGALRM信号;

再通过系统调用函数sigaction来注册信号,并为每种信号写一个处理函数。

然后为了统一管理事件,把信号发送也作为一种事件,用了一个socketpair函数建立了一个管道,就是一对互相通信的socket,然后把其中一个socket注册到epoll的内核事件表中。

此时,在信号处理函数中,会让这个管道的另一个socket发送对应的信号,然后epoll就会管理这个事件,并处理信号对应的具体的事件。

EINTR和EAGAIN错误

参考:

https://cloud.tencent.com/developer/article/2054104#

什么是io向量机制

http连接处理

http中keep-alive长连接

在 HTTP 协议中,"Keep-Alive" 是一种机制,用于在单个 TCP 连接上持续保持多个 HTTP 请求和响应的交互。它的主要目的是减少每个请求的连接建立和关闭的开销,提高网络性能和效率。

以下是 "Keep-Alive" 的一些用处:

  1. 连接复用:使用 "Keep-Alive",可以在同一个 TCP 连接上发送多个 HTTP 请求和接收多个 HTTP 响应。这样可以避免为每个请求和响应建立新的连接,减少了连接建立和关闭的时间开销。

  2. 减少延迟:由于避免了频繁的连接建立和关闭,"Keep-Alive" 可以降低请求的延迟。在一个 TCP 连接上,可以立即发送下一个请求,而无需等待连接建立或关闭。

  3. 节省资源:每个 TCP 连接都需要占用一定的系统资源,包括内存和处理能力。使用 "Keep-Alive" 可以减少同时打开的连接数量,从而节省了系统资源。

  4. 并发性能:通过复用连接,"Keep-Alive" 可以提高服务器的并发性能。服务器可以同时处理多个请求,而无需为每个请求分别建立连接。

需要注意的是,"Keep-Alive" 并不是默认启用的,需要在 HTTP 请求和响应的头部中明确指定。具体来说,需要在请求头部中添加 "Connection: keep-alive" 字段,以及在响应头部中添加 "Connection: keep-alive" 字段。

虽然 "Keep-Alive" 可以带来一些性能优势,但它也有一些考虑因素。长时间的连接可能会占用服务器资源,因此需要在服务器端进行适当的配置和管理,以避免资源的滥用和过度消耗。

总之,"Keep-Alive" 提供了一种有效的机制,通过复用连接来减少连接建立和关闭的开销,提高 HTTP 请求的效率和性能。

希望这个解释对你有帮助。如果你有任何进一步的问题,请随时提问。

\r,\n,\0分别是什么意思

\r\n\0 都是转义字符,用于表示特定的控制字符。

  1. \r 表示回车符(Carriage Return),ASCII 值为 13。它将光标移动到当前行的开头位置,常用于在同一行的开头重新输出内容。在文本处理中,\r 通常与换行符 \n 搭配使用,表示回车换行操作。例如,Windows 操作系统使用回车换行符 \r\n 表示换行。
  2. \n 表示换行符(Line Feed),ASCII 值为 10。它将光标移动到下一行的开头位置,常用于表示换行操作。在大多数操作系统中,如 Unix、Linux 和现代的 Mac OS 系统,只使用 \n 表示换行。
  3. \0 表示空字符(Null Character),ASCII 值为 0。它是一个特殊的控制字符,通常用于表示 C-风格字符串的结束符。在以 \0 结尾的字符串中,\0 表示字符串的结束,标志着字符串的终止。

在 C++ 的字符串字面值中,可以使用这些转义字符来表示相应的控制字符。例如:

1
2
std::cout << "Hello, world!\n";
char str[] = "Hello\r\n";

上述代码中,第一行输出了 "Hello, world!" 并在末尾添加了一个换行符。第二行创建了一个字符数组 str,其中包含了 "Hello"、回车符和换行符,用于表示一个以回车换行结尾的字符串。

需要注意的是,这些转义字符具有不同的含义和用途,并且在不同的环境和上下文中可能会有所不同。因此,在使用时需要根据具体情况进行适当的使用和解释。

希望这个解释能帮助你理解 \r\n\0 的含义。如果你有任何进一步的问题,请随时提问。

TinyWebServer项目可改进的地方

  1. 线程池。原项目采用的是liunx下的pthread库,可以采用c++11后提供的thread库。

  2. 登陆的状态没有保存,可以加一个cookie和session

  3. 数据库可以采用redis

  4. 定时器优化,可以优化成时间轮,时间堆。

  5. 用智能指针管理项目中的指针