接下来四周时间,我将会做一个高并发服务器相关的项目。
前置知识:操作系统系统编程、网络编程、基础的数据结构、C语言。
开发环境:VMware虚拟机:Ubuntu 20.04.6 LTS、vscode
今天先回顾一些基础知识。
1.文件与IO
标准IO(缓冲IO)
- fread->FILE*
- fwrite
- fclose
基础IO
- open->fd
- read(recv recvfrom)
- write
- close
一切接文件
缓冲池、线程池、连接池
非阻塞IO(nonblock)
- 如何使用?
- 如何设置一个非阻塞IO
- O_NONBLOCK
- 应用场景?
- 返回-1:EAGAIN
- 轮询地去查看IO是否完成
- 或者使用IO多路复用机制:EPOLL(边缘模式、水平模式)
同步和异步
协议三要素:语法、语义、同步
异步适合于并发。
高级IO
- select:select count(*) from fds where fd become ready;
- poll
- epoll
IO多路复用、IO感知、IO多路转接
select、poll和epoll是Linux系统中用于实现IO多路复用的三种机制。它们允许程序同时监控多个文件描述符(file descriptor),以便知道哪个或哪些文件描述符已经准备好进行读写操作。这三种机制的发展体现了从简单到高效的递进关系。
1. select
- 基本概念:`select`是最早的IO多路复用机制。它允许程序监控多个文件描述符,等待一个或多个文件描述符成为非阻塞状态。
- 限制:
- 它支持的文件描述符数量有限,通常受到FD_SETSIZE的限制,默认值通常是1024。
- 每次调用`select`时,都需要把整个文件描述符集合从用户空间复制到内核空间,这在文件描述符数量较多时会造成较大的性能开销。
- 每次调用返回时,需要遍历整个文件描述符集合来找出已经准备好的描述符,这也增加了额外的开销。
2. poll
- 基本概念:`poll`提供了与`select`类似的功能,但解决了`select`的一些限制。
- 改进:
- `poll`使用链表而非固定大小的数组,因此不再受FD_SETSIZE的限制,可以监控更多的文件描述符。
- 与`select`类似,`poll`也需要在每次调用时将整个文件描述符集合从用户空间复制到内核空间,并在返回时检查哪些文件描述符已准备就绪。
3. epoll
- 基本概念:`epoll`是在Linux 2.6中引入的,它在性能和可扩展性方面对`select`和`poll`进行了显著的改进。
- 改进:
- `epoll`可以处理数以万计的并发连接,而不会显著降低性能。
- 它通过在内核中使用一个事件表来避免每次调用时复制整个文件描述符集合的开销。只有当IO状态真正改变时,应用程序才需要与内核交互。
- 支持两种模式:LT(水平触发)和ET(边缘触发)。ET模式可以进一步减少系统调用的次数,提高效率。
- 只有准备就绪的文件描述符会被返回,减少了不必要的遍历。
从`select`到`poll`,再到`epoll`,这三种机制在设计上越来越高效和灵活。`select`和`poll`适合管理少量连接,而`epoll`适合处理大量并发连接,特别是在高性能服务器环境中。随着网络应用对高并发和高性能的需求不断增加,`epoll`成为了Linux下实现高效IO多路复用的首选方案。
2.进程
什么是进程?
程序的映像(image),实例(instance)、运行的程序,动态的、资源分配的基本单位(虚拟内存)、封闭性、独立性
程序、进程、线程、管程、协程的区别?
- 程序(Program): 是一组指令和数据的集合,是静态的,存储在磁盘上,需要加载到内存中才能执行。
- 进程(Process): 是程序的一次执行过程,是操作系统进行资源分配和调度的基本单位,具有独立的内存空间和系统资源,进程之间相互独立。
- 线程(Thread): 是进程中的实际执行单位,一个进程可以包含多个线程,线程共享进程的内存空间和系统资源,但拥有独立的执行路径。
- 管程(Monitor): 是一种用于并发编程的同步机制,通过提供对共享资源的互斥访问和条件变量的支持来实现线程之间的协作。
- 协程(Coroutine): 是一种轻量级的线程,可以在不同的执行路径之间切换,但不需要操作系统的支持,由程序员自行控制协程的调度。
fork干了什么事情?
pid判断谁是父亲,谁是孩子、统计信息等不同。
进程三大部分:PCB、数据段、程序段完全拷贝父进程。
写拷贝,如果不做修改,就不拷贝。进程采用段页式管理。拷贝只拷贝一页。
linux采用完全公平算法,放弃了时间片轮转算法。但是仍然具有时间片轮转调度算法的特点。所以往往是父进程先运行。不太可能刚好卡在时间片结束的时候创建子进程。
exec族函数
- p - path (路径): 使用环境变量`PATH`来查找可执行文件。
- v - vector (向量): 参数以字符串数组形式传递。
- l - list (列表): 参数逐个列出。
- e - environment (环境): 允许设置环境变量。
#include <stdio.h>
#include <unistd.h>int main() {// 使用execlp执行ls命令execlp("ls", "ls", "-l", NULL);// 使用execvp执行ps命令char *args[] = {"ps", "aux", NULL};execvp("ps", args);// 使用execl执行echo命令execl("/bin/echo", "echo", "Hello, World!", NULL);// 使用execve执行自定义程序char *cmd = "./custom_program";char *args[] = {"arg1", "arg2", NULL};char *env[] = {"PATH=/usr/bin", NULL};execve(cmd, args, env);// 如果exec函数执行成功,下面的代码将不会被执行perror("exec failed");return 1;
}
特殊进程
-
孤儿进程:当一个父进程结束或终止时,它的子进程还在运行,这些还在运行的子进程就会变成孤儿进程。操作系统通常会让init进程(进程号为1的进程)接管这些孤儿进程。接管后,init进程将成为它们的新父进程,负责收集它们的退出状态。
-
僵尸进程:当一个子进程结束运行,但其父进程尚未通过调用wait()或waitpid()函数来收集子进程的退出状态时,该子进程将成为僵尸进程。僵尸进程已经释放了大部分资源,不再执行任何代码,但在进程表中仍保留一个条目,直到父进程收集其状态信息。
-
闲逛进程(空闲进程):在操作系统中,闲逛进程(也称为空闲进程或空转进程)是一个特殊的系统进程,当系统中没有其他可运行的进程时,它就会被执行。闲逛进程的主要目的是占用CPU,保证CPU不会处于空闲状态。在多数系统中,闲逛进程的进程号为0。
-
守护进程:是一种在后台运行的特殊进程,它独立于控制终端,周期性地执行某种任务或等待处理某些发生的事件。守护进程通常在系统引导装入时启动,在系统关闭时终止。与普通的前台进程相比,守护进程的特点是独立于用户和终端,不与任何终端交互。
进程间通信
- 基于文件的进程间通信
- 共享内存(shmget、mmap数据的拷贝)
- pipe(匿名、有名)
- 条件变量:pthread_cond_signal、pthread_cond_wait(惊群效应)
- 消息队列
- socket
- 信号
3.线程
什么是线程?
Pthread(Process thread):共享性(共享的是进程空间、竞争、同步 & 互斥:PV操作、信号量)、调度的基本单位,虚拟处理器(让线程感觉自己独占CPU)。
用户线程 & 内核线程
内核能感知到线程吗?内核能感知到内核线程,但是感知不到用户线程。
线程模型
- 1:1:频繁地对内核线程进行切换。
- N:1:内核就一个线程,用户N个线程。假设N中的一个阻塞,所有的都会阻塞。
- M:N:M代表程序的逻辑分支,N代表着程序被调度的机会。
线程的同步
- PV
- 互斥锁
- 死锁问题
- 怎么处理死锁(死锁的定义:两个以上资源,资源有限,进程&线程推进不当、死锁避免:银行家算法、死锁预防、死锁检测)
线程的安全
- 什么是线程的安全?
- 临界资源:多线程环境下recv能否正确接收
- 临界区:访问临界资源的那段代码,锁的是临界资源
- 某个函数线程安全吗?
线程池
- 池化技术(TCP三次连接的时候很费事,搞个池子一直连着,不需要频繁地创建销毁线程)
- 怎么实现:构建一个循环任务队列(理发店的等候区)、多个线程(理发店的理发师)、CPU(理发店的工作台)
4.网络编程
socket
应用程序和运输层之间的接口:进程寻址(端口+IP地址)、地址复用
socket编程基础
- socket
- bind
- listen:backlog(
backlog
参数的意义在于指定了socket可以排队的最大连接数) - accept:返回一个新的socketfd
- connect
- send & recv(read, write)
- close
UDP & TCP
- 区别
- 三次握手
- 为什么不是两次,四次可以吗
- 每次发的都是什么包?
- 四次挥手
- 为什么要等2个MSL
- 为什么四次?
- 状态转换
- TCP拥塞控制
- UDP编程
- connect
- recvfrom
- sendto
网络编程只是系统编程的点缀,本质还是进程间通信。
5.小作业: