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;

}

相关文章
|
11月前
|
NoSQL 网络协议 Linux
Redis的实现一:c、c++的网络通信编程技术,先实现server和client的通信
本文介绍了使用C/C++进行网络通信编程的基础知识,包括创建socket、设置套接字选项、绑定地址、监听连接以及循环接受和处理客户端请求的基本步骤。
142 6
|
Java 数据管理 关系型数据库
机票预订系统(java+mysql+navicat)
机票预订系统(java+mysql+navicat)
机票预订系统(java+mysql+navicat)
|
2月前
|
C语言 图形学 Windows
Windows下安装和配置GTK4(基于CLion)
本文介绍了作者选择GTK作为C语言图形库的原因,包括代码简洁、控件丰富和界面美观,并分享了在Windows环境下通过MSYS2安装GTK4及在CLion中配置开发环境的详细步骤。
330 0
|
开发框架 前端开发 Linux
开源项目推荐:C++ Web/Http Server/Rest开发框架(请重点关注Oat++和搜狗workflow)
开源项目推荐:C++ Web/Http Server/Rest开发框架(请重点关注Oat++和搜狗workflow)
4858 0
|
7月前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
156 16
|
12月前
|
机器学习/深度学习 算法 TensorFlow
交通标志识别系统Python+卷积神经网络算法+深度学习人工智能+TensorFlow模型训练+计算机课设项目+Django网页界面
交通标志识别系统。本系统使用Python作为主要编程语言,在交通标志图像识别功能实现中,基于TensorFlow搭建卷积神经网络算法模型,通过对收集到的58种常见的交通标志图像作为数据集,进行迭代训练最后得到一个识别精度较高的模型文件,然后保存为本地的h5格式文件。再使用Django开发Web网页端操作界面,实现用户上传一张交通标志图片,识别其名称。
456 7
交通标志识别系统Python+卷积神经网络算法+深度学习人工智能+TensorFlow模型训练+计算机课设项目+Django网页界面
|
网络协议 Java Unix
从0到服务器开发——TinyWebServer(上)
从0到服务器开发——TinyWebServer
743 1
|
12月前
|
BI 定位技术 C++
超级好用的C++实用库之地理相关接口
超级好用的C++实用库之地理相关接口
109 0
|
设计模式 前端开发 API
【Qt 学习笔记】Qt常用控件 | 多元素控件 | 多元素控件介绍
【Qt 学习笔记】Qt常用控件 | 多元素控件 | 多元素控件介绍
158 2
|
自然语言处理 搜索推荐 Java
计算文本相似度的几种方法
计算文本相似度的几种方法