C++ WebServer的细节理解

简介: 这篇文本介绍了三个Linux编程概念:1) 使用`fcntl`将文件描述符设置为非阻塞模式,以避免I/O操作阻塞进程并提高并发性;2) `mmap`和`munmap`用于内存映射文件,提升大文件访问效率和实现进程间通信;3) `struct iovec`在`readv`和`writev`中用于从/向多个缓冲区进行I/O操作,提升效率。这些技术常用于高效服务器编程。
  1. 文件描述符-非阻塞模式
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK);
    1
    代码解释:
    上面这句代码,先查询文件描述符 fd 当前的标志,然后将 O_NONBLOCK 标志加入,并通过 F_SETFL 更新文件描述符,最终实现将该文件描述符切换为非阻塞模式。

O_NONBLOCK 设置在文件描述符上时,后续对该文件描述符的 I/O 操作(读写等)会变为非阻塞模式。

在非阻塞模式下,如果 I/O 操作不能立即完成(例如,因为没有数据可读 或 写缓冲区满),系统不会让调用进程阻塞等待,而是立即返回一个错误(通常为 EAGAIN 或 EWOULDBLOCK)。
这样,进程可以避免因等待 I/O 完成而被长时间阻塞,实现更高的响应性和并发性。

非阻塞模式—应用场景举例:

int accept(int sockfd, struct sockaddr addr, socklen_t len)
1
accept()函数,用于接受客户端的连接请求。默认情况下,accept() 是阻塞的。这意味着,如果没有待处理的连接请求(即没有客户端尝试连接到服务器),accept() 会一直阻塞,直到有新的连接请求到达或发生其他特定条件(如超时)为止。

当 accept() 处于阻塞状态时,它所在的线程是不能去做其他事情的。
若要改变这种行为,可以采用以下方法:

非阻塞模式:如前所述,可以使用 fcntl() 函数将套接字设置为非阻塞模式(使用 O_NONBLOCK 标志)。在这种模式下,accept() 调用将不会阻塞,而是立即返回。如果此时没有待处理的连接请求,accept() 将返回一个错误(通常为 EAGAIN 或 EWOULDBLOCK)。这样,线程可以在没有可用连接时执行其他任务,然后在适当的时候再次尝试 accept()。

异步 I/O:如 Linux 中的 io_uring 或 Windows 中的 overlapped I/O,可以异步地执行 accept(),使得线程在发起 accept() 请求后可以继续执行其他任务,然后通过回调、事件通知等方式获知 accept() 的结果。

  1. mmap() 和 munmap()
    mmap() 和 munmap() 是用于内存映射操作的系统调用函数。
    mmap() 允许将文件或其他对象直接映射到进程的虚拟地址空间中,从而实现高效的数据访问。

void mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset);
1
void* addr:

指定映射开始的期望虚拟地址。如果设为 NULL,内核将自动选择一个合适的地址。
size_t length:

指定映射区域的长度。必须是页面大小的整数倍。
int prot:

控制映射区域的访问权限:
PROT_READ:允许读取。
PROT_WRITE:允许写入。
PROT_NONE:禁止访问。
int flags:

控制映射行为和映射内容的处理方式:
MAP_SHARED:创建一个可共享的映射,对映射区域的修改会影响到所有映射此区域的进程,并且可能会同步回文件。
MAP_PRIVATE:创建一个私有映射,对映射区域的修改仅影响当前进程,不会改变底层文件。
int fd:

用于指定要映射的文件。如果使用匿名映射,可以传入 -1 或者一个未打开的文件描述符。
off_t offset:

文件中的偏移量,映射从该位置开始。通常应是文件系统块大小的整数倍。
返回值:

成功时返回映射区域的起始地址。
出现错误时返回 MAP_FAILED(通常是一个负值,如 (void) -1)。
int munmap(void
addr, size_t length);
1
void* addr:
指定要解除映射的内存区域的起始地址,该地址应是之前 mmap() 调用返回的地址。
size_t length:
指定解除映射的区域长度,应与对应的 mmap() 调用中的 length 参数一致。
返回值: 成功时返回 0。出现错误时返回 -1。

mmap() 常用于以下场景:

高效文件访问:将文件直接映射到内存,避免了常规的文件I/O操作,对于大数据文件或频繁访问的文件尤其高效。
进程间通信(IPC):通过映射同一份共享内存,不同进程可以直接通过内存地址来交换数据,无需通过管道、套接字等传统IPC机制。
例如,映射一个已打开文件的部分内容到内存中:

include

include

include

include

int main() {
const char* filename = "/path/to/file";
int fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}

struct stat sb;
if (fstat(fd, &sb) == -1) {
    perror("fstat");
    close(fd);
    return 1;
}

// 映射整个文件
void* mapped_region = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped_region == MAP_FAILED) {
    perror("mmap");
    close(fd);
    return 1;
}

// 使用映射区域...
// ...

// 解除映射
if (munmap(mapped_region, sb.st_size) == -1) {
    perror("munmap");
    close(fd);
    return 1;
}

close(fd);
return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
在WebServer中,使用方法也类似;

int stat(const char path, struct stat buf);

stat((srcDir + path).data(), &mmFileStat_);
1
2
3
stat() 是一个标准C库函数,通常在 头文件中声明,用于获取文件或目录的状态信息。

const char path: 一个指向包含文件路径的指针。可以是绝对路径,也可以是相对于当前工作目录的相对路径。
struct stat
buf: 一个指向 struct stat 结构体的指针,用于接收由 stat() 函数填充的关于指定路径对象的信息,例如:
– 文件大小(st_size)
– 最近访问、修改和状态更改时间(st_atime, st_mtime, st_ctime)
– 权限位(st_mode)
函数返回值:

如果成功,返回0;否则,返回一个非零值(通常是错误代码),表示发生了某种错误(如权限不足、路径不存在等)。

  1. struct iovec
    struct iovec 是用于系统调用 readv 和 writev 的一个数据结构,这两个系统调用允许你从多个缓冲区读取数据或者向多个缓冲区写入数据。
    使用 iovec 结构,可以指定多个缓冲区,每个缓冲区都有自己的地址和长度。这对于高效地处理I/O操作非常有用。

struct iovec 结构在 头文件中定义:

struct iovec {
void iov_base; / 指向缓冲区的指针 /
size_t iov_len; /
缓冲区的大小 */
};
1
2
3
4
在服务器中,声明 struct iovec iov_[2]; 时,实际上创建了一个包含两个 iovec 结构的数组。
这使得可以同时指定两个不同的缓冲区进行读写操作。例如,如果你想从文件中读取一些数据到两个不同的缓冲区,你可以这样做:

include

include

include

include

int main() {
const char* filename = "example.txt";
int fd = open(filename, O_RDONLY);
if (fd == -1) {
std::cerr << "Failed to open file\n";
return 1;
}

char buffer1[100], buffer2[100];
struct iovec iov[2] = {
    { .iov_base = buffer1, .iov_len = sizeof(buffer1) },
    { .iov_base = buffer2, .iov_len = sizeof(buffer2) }
};

ssize_t bytesRead = readv(fd, iov, 2);
if (bytesRead == -1) {
    std::cerr << "Error reading from file\n";
    close(fd);
    return 1;
}
close(fd);
return 0;

}

相关文章
|
12月前
|
tengine 应用服务中间件 网络安全
nginx学习(核心配置详解)
nginx学习(核心配置详解)
130 0
|
设计模式 安全 Java
【Tomcat技术专题】循序渐进,分析Servlet容器鼻祖的Server和Service组件原理
【Tomcat技术专题】循序渐进,分析Servlet容器鼻祖的Server和Service组件原理
133 0
【Tomcat技术专题】循序渐进,分析Servlet容器鼻祖的Server和Service组件原理
|
应用服务中间件 nginx
【Nginx】第七节 模块介绍
【Nginx】第七节 模块介绍
84 0
【Nginx】第七节 模块介绍
|
应用服务中间件 nginx
【Nginx】一个项目一个配置文件,每个项目按端口区分开来,反向代理到本地
【Nginx】一个项目一个配置文件,每个项目按端口区分开来,反向代理到本地
92 0
【Nginx】一个项目一个配置文件,每个项目按端口区分开来,反向代理到本地
|
存储 JavaScript 前端开发
《深入理解Nginx:模块开发与架构解析》一3.6 处理用户请求
本节书摘来自华章出版社《深入理解Nginx:模块开发与架构解析》一书中的第3章,第3.6节,作者 陶辉,更多章节内容可以访问云栖社区“华章计算机”公众号查看
1800 0
|
tengine 应用服务中间件 nginx
重新认识 nginx 配置文件解析规则
重新认识 nginx 配置文件解析规则
2818 0
|
应用服务中间件 nginx
|
应用服务中间件 nginx 网络协议
|
JavaScript Unix 应用服务中间件
《深入理解Nginx:模块开发与架构解析》一1.5 configure详解
本节书摘来自华章出版社《深入理解Nginx:模块开发与架构解析》一书中的第1章,第1.5节,作者 陶辉,更多章节内容可以访问云栖社区“华章计算机”公众号查看
2191 0