webserver
serverrrrrrrrrrrrrrrrrrrrrr–
IO 多路复用 VS 多进程/多线程
IO 多路复用不用维护线程进程,大大减小了系统的开销。
多路复用的实现主要基于 select、poll、epoll
select
1 | #include <sys/select.h> |
调用过程:
1)用户进程需要监控某些资源 fds,在调用 select 函数后会阻塞,操作系统会将用户线程加入这些资源的等待队列中。
2)直到有描述副就绪(有数据可读、可写或有 except)或超时(timeout 指定等待时间,如果立即返回设为 null 即可),函数返回。
3)select 函数返回后,中断程序唤起用户线程。用户可以遍历 fds,通过 FD_ISSET 判断具体哪个 fd 收到数据,并做出相应处理。
getopt
getopt
是一个用于解析命令行参数的库函数,主要用于在程序中处理命令行输入。它最常见于 C 和 C++ 编程语言,允许开发者轻松地从命令行输入中提取选项和参数,并处理它们。基本功能
当程序通过命令行执行时,用户可以传递选项(如
-a
、-b
)和参数(如文件名、路径等)。getopt
提供了一种标准化的方法来解析这些选项,并支持短选项(如-a
)以及长选项(如--option
)。它可以识别是否某个选项需要附带参数,并进行相应的处理。
getopt
使用步骤
引入头文件:在 C 或 C++ 程序中使用
getopt
需要包含unistd.h
头文件(在 Unix/Linux 环境下)。
1 #include <unistd.h>定义选项:你需要在代码中定义哪些选项可用。例如,
-a
是一个简单选项,而-b
需要一个参数。调用
getopt
解析:通过循环调用getopt
来解析命令行中的每个选项。示例代码
以下是一个简单的例子,展示如何使用
getopt
来解析命令行选项:
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 #include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int opt;
// 循环处理所有命令行选项
while ((opt = getopt(argc, argv, "ab:c")) != -1) {
switch (opt) {
case 'a':
printf("Option -a selected\n");
break;
case 'b':
printf("Option -b selected with argument: %s\n", optarg);
break;
case 'c':
printf("Option -c selected\n");
break;
default:
fprintf(stderr, "Usage: %s [-a] [-b argument] [-c]\n", argv[0]);
return 1;
}
}
return 0;
}参数解释
argc
和argv[]
:argc
是命令行参数的数量,argv[]
是一个指向字符串的数组,包含命令行参数。opt
:每次调用getopt
时,它返回解析的选项字符。optarg
:当选项需要参数时,optarg
会指向该选项的参数值。"ab:c"
:定义了程序接受的选项。a
和c
是不带参数的选项,b:
需要一个参数(注意冒号:
)。输出示例
如果我们运行该程序:
1 ./program -a -b argument -c输出将是:
1
2
3 Option -a selected
Option -b selected with argument: argument
Option -c selected其他功能
getopt_long
:除了处理短选项外,C 还提供了getopt_long
函数,用于处理长选项(如--help
)。它可以让你定义更具描述性的命令行选项。
getopt
是处理命令行参数的标准工具,尤其适合需要解析简单选项的程序。
非阻塞IO & ET LT
阻塞IO:会一直等待,除非将数据读完。
非阻塞IO:当前没有读到足够的数据时,read
会立刻返回,将errno
设置为EAGAIN
或EWOULDBLOCK
,
ET边缘触发:文件描述符的状态发生变化时产生一次事件。
LT水平出发:检测到有事件时,会触发,没有处理该事件则后续会一直触发。
通常选择 ET + 非阻塞IO,使用ET时,由于只会触发一次,哪怕该文件描述符产生了多个变化也只会有一次事件,所有应该要在事件触发时尽快的读完该文件描述符上的所有事件(如果阻塞了,那么阻塞期间的事件不会产生触发事件,导致不能被处理)
\r\n
HTTP协议是基于TCP/IP协议栈的,而TCP/IP协议栈中的许多协议(如SMTP、FTP等)都继承了这种使用
\r\n
作为行结束的传统。HTTP/1.0和HTTP/1.1规范都明确指出,HTTP消息的头部(Headers)应该使用\r\n
作为行结束符。
recv
These calls return the number of bytes received, or -1 if an error occurred. In the event of an error, errno is set to indicate the error.
When a stream socket peer has performed an orderly shutdown, the return value will be 0 (the traditional “end-of-file” return).
返回-1代表错误,并将errno置为对应的值,返回0代表对端关闭
EAGAIN or EWOULDBLOCK
The socket is marked nonblocking and the receive operation would block, or a receive timeout had been
set and the timeout expired before data was received. POSIX.1 allows either error to be returned for
this case, and does not require these constants to have the same value, so a portable application
should check for both possibilities.这个表通常用于检查一个系统调用(如
read
,write
,connect
等)是否因为非阻塞I/O操作而暂时无法完成。
响应报文部分
stat
stat函数用于取得指定文件的文件属性,并将文件属性存储在结构体stat里
1 | 1#include <sys/types.h> |
mmap
用于将一个文件或其他对象映射到内存,提高文件的访问速度。
建立映射后,这段内存由操作系统管理,可以利用虚拟内存管理的所有特性,如分页和交换。
而
mmap
适用于需要高效读写大文件、共享文件内容或需要文件和内存之间直接映射的场景。
1 | void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset); |
start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址
length:映射区的长度
prot:期望的内存保护标志,不能与文件的打开模式冲突
- PROT_READ 表示页内容可以被读取
flags:指定映射对象的类型,映射选项和映射页是否可以共享
- MAP_PRIVATE 建立一个写入时拷贝的私有映射,内存区域的写入不会影响到原文件
fd:有效的文件描述符,一般是由open()函数返回
off_toffset:被映射对象内容的起点
iovec
定义了一个向量元素,通常,这个结构用作一个多元素的数组。
1 | struct iovec { |
- iov_base指向数据的地址
- iov_len表示数据的长度
writev
writev函数用于在一次函数调用中写多个非连续缓冲区到指定的文件,有时也将这该函数称为聚集写。writev
的使用可以减少内存拷贝的次数和系统调用的次数,从而提高 I/O 性能。writev
函数的原型如下:
1 |
|
线程同步与互斥
信号量
信号量是一种特殊的变量,它只能取自然数值并且只支持两种操作:等待(P)和信号(V).假设有信号量SV,对其的P、V操作如下:
- P,如果SV的值大于0,则将其减一;若SV的值为0,则挂起执行
- V,如果有其他进行因为等待SV而挂起,则唤醒;若没有,则将SV值加一
信号量的取值可以是任何自然数,最常用的,最简单的信号量是二进制信号量,只有0和1两个值.
- sem_init函数用于初始化一个未命名的信号量
- sem_destory函数用于销毁信号量
- sem_wait函数将以原子操作方式将信号量减一,信号量为0时,sem_wait阻塞
- sem_post函数以原子操作方式将信号量加一,信号量大于0时,唤醒调用sem_post的线程
以上,成功返回0,失败返回errno
互斥量
互斥锁,也成互斥量,可以保护关键代码段,以确保独占式访问.当进入关键代码段,获得互斥锁将其加锁;离开关键代码段,唤醒等待该互斥锁的线程.
- pthread_mutex_init函数用于初始化互斥锁
- pthread_mutex_destory函数用于销毁互斥锁
- pthread_mutex_lock函数以原子操作方式给互斥锁加锁
- pthread_mutex_unlock函数以原子操作方式给互斥锁解锁
以上,成功返回0,失败返回errno
条件变量
条件变量提供了一种线程间的通知机制,当某个共享数据达到某个值时,唤醒等待这个共享数据的线程.
- pthread_cond_init函数用于初始化条件变量
- pthread_cond_destory函数销毁条件变量
- pthread_cond_broadcast函数以广播的方式唤醒所有等待目标条件变量的线程
- pthread_cond_wait函数用于等待目标条件变量.该函数调用时需要传入 mutex参数(加锁的互斥锁) ,函数执行时,先把调用线程放入条件变量的请求队列,然后将互斥锁mutex解锁,当函数成功返回为0时,互斥锁会再次被锁上. 也就是说函数内部会有一次解锁和加锁操作.
栈、队列、数组的头、尾
一般的做法:
- 栈的栈顶指针指向当前的栈顶(而不是栈顶的后一个元素)
- 约定rear总是指向队尾元素 元素进队,rear增1 约定front指向当前队中队头元素的前一位置 元素出队,front增1 当rear=StackSize-1时不能再进队。
- 循环队列判满条件:(rear+1)%StackSize=front
尽量使用传统做法,教科书,库都是这么完成的。
时间
一种是这么做:
1 | time_t t = time(NULL); |
另一种是这么做:
1 | struct timeval now = {0, 0}; |