手撕最简单的webserver
C/S模型
必备函数socket()函数Linux 中的一切都是文件,每个文件都有一个整数类型的文件描述符;socket 也是一个文件,也有文件描述符。使用 socket() 函数创建套接字以后,返回值就是一个 int 类型的文件描述符。
Windows 会区分 socket 和普通文件,它把 socket 当做一个网络连接来对待,调用 socket() 以后,返回值是 SOCKET 类型,用来表示一个套接字。
在 Linux 下使用 <sys/socket.h> 头文件中 socket() 函数来创建套接字,原型为:
1int socket(int af, int type, int protocol);
af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。大 ...
信号功能部分
信号函数signal函数头文件:#include <signal.h>
定义函数:void (*signal(int signum, void(* handler)(int)))(int);
函数说明:signal()会依参数signum 指定的信号编号来设置该信号的处理函数. 当指定的信号到达时就会跳转到参数handler 指定的函数执行. 如果参数handler 不是函数指针, 则必须是下列两个常数之一:1、SIG_IGN 忽略参数signum 指定的信号.2、SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式.
关于信号的编号和说明, 请参考附录D
返回值:返回先前的信号处理函数指针, 如果有错误则返回SIG_ERR(-1).
附加说明:在信号发生跳转到自定的 handler 处理函数执行后, 系统会自动将此处理函数换回原来系统预设的处理方式, 如果要改变此操作请改用sigaction().
sigaction函数头文件:#include <signal.h>
定义函数:int sigaction(int signum, const ...
文件I/O详谈
缓存区刷新换行回车进一步示意\r:回车符,把打印【输出】信息的为止定位到本行开头\n:换行符,把输出为止移动到下一行一般把光标移动到下一行的开头,\r\na)比如windows下,每行结尾 \r\nb)类Unix,每行结尾就只有\nc)Mac苹果系统,每行结尾只有\r结论:统一用\n就行了
printf()函数不加\n无法及时输出的解释printf末尾不加\n就无法及时的将信息显示到屏幕 ,这是因为 行缓存[windows上一般没有,类Unix上才有]
需要输出的数据不直接显示到终端,而是首先缓存到某个地方,当遇到行刷新表指或者该缓存已满的情况下,才会把缓存的数据显示到终端设备;ANSIC中定义\n认为是行刷新标记,所以,printf函数没有带\n是不会自动刷新输出流,直至行缓存被填满才显示到屏幕上;
所以大家用printf的时候,注意末尾要用\n;或者:fflush(stdout);或者:setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。标准I/O函数,后边还会讲到
write()多个进程同时去写 ...
nginx中创建worker子进程
总体情况1234567891011121314151617181920ngx_master_process_cycle() //创建子进程等一系列动作 ngx_setproctitle() //设置进程标题 ngx_start_worker_processes() //创建worker子进程 for (i = 0; i < threadnums; i++) //master进程在走这个循环,来创建若干个子进程 ngx_spawn_process(i,"worker process"); pid = fork(); //分叉,从原来的一个master进程(一个叉),分成两个叉(原有的master进程,以及一个新fork()出来的worker进程 //只有子进程这个分叉才会执行ngx_worker_process_cycle() ngx_worker_process_cyc ...
valgrind-内存泄漏检查工具
Valgrind:帮助程序员寻找程序里的bug和改进程序性能的工具集。擅长是发现内存的管理问题;里边有若干工具,其中最重要的是Memcheck(内存检查)工具,用于检查内存的泄漏;
memcheck的基本功能,能发现如下的问题;
a)使用未初始化的内存
b)使用已经释放了的内存
c)使用超过malloc()分配的内存
d)对堆栈的非法访问
e)申请的内存是否有释放
f)malloc/free,new/delete申请和释放内存的匹配
g)memcpy()内存拷贝函数中源指针和目标指针重叠;
内存泄漏检查示范所有应该释放的内存,都要释放掉,作为服务器程序开发者,要绝对的严谨和认真
格式:
valgrind –tool=memcheck 一些开关 可执行文件名
–tool=memcheck :使用valgrind工具集中的memcheck工具
–leak-check=full : 指的是完全full检查内存泄漏
–show-reachable=yes :是显示内存泄漏的地点
–trace-children = yes :是否跟入子进程
–log-file=log.txt:讲调 ...
日志功能
前言为了 避免到时候自己来看这个项目的时候看不懂,还是记录一下各个功能板块代码具体实现的思想
日志代码日志的重要性:供日后运行维护人员去查看、定位和解决问题;新文件:ngx_printf.cxx以及ngx_log.cxx。ngx_printf.cxx:放和打印格式相关的函数;ngx_log.cxx:放和日志相关的函数;ngx_log_stderr() :三个特殊文件描述符【三章七节】,谈到了标准错误 STDERR_FILENO,代表屏幕ngx_log_stderr():往屏幕上打印一条错误信息;功能类似于printfprintf(“mystring=%s,myint=%d,%d”,”mytest”,15,20);(1)根据可变的参数,组合出一个字符串:mystring=mytest,myint=15,20(2)往屏幕上显示出这个组合出来的字符串;
ngx_log_stderr():可以支持任意我想支持的格式化字符 %d, %f,对于扩展原有功能非常有帮助(i)void ngx_log_stderr(int err, const char *fmt, …)(i) p = ngx_ ...
Makefile
项目结构规划(make编译)特别注意:不论是目录还是文件,文件名中一律不要带空格,一律不要用中文,最好的方式:字母,数字,下划线,不要给自己找麻烦,远离各种坑。
主目录名nginx
_include目录:专门存放各种头文件; 如果分散:#include “sfaf/sdafas/safd.h”
app目录:放主应用程序.c(main()函数所在的文件)以及一些比较核心的文件;
1)link_obj:临时目录:会存放临时的.o文件,这个目录不手工创建,后续用makefile脚本来创建
2)dep:临时目录,会存放临时的.d开头的依赖文件,依赖文件能够告知系统哪些相关的文件发生变化,需要重新编译,后续用makefile脚本来创建
3)nginx.c:主文件,main()入口函数就放到这里;
4)ngx_conf.c :普通的源码文件,跟主文件关系密切,又不值得单独放在 一个目录;
misc目录:专门存放各种杂合性的不好归类的1到多个.c文件;暂时为空
net目录:专门存放和网络处理相关的1到多个.c文件,暂时为空
proc目录:专门存放和进程处理相关的1到多个 ...
守护进程
普通进程
进程有对应的终端,如果终端退出,那么对应的进程也就消失了;它的父进程是一个bash
终端被占住了,你输入各种命令这个终端都没有反应;
守护进程守护进程:一种长期运行的进程,这种进程在后台运行,并且不跟任何的控制终端关联。
基本特点:
生存期长,一般是操作系统启动的时候他就启动,操作系统关闭的时候他才关闭。(不是必须但是一般应该是这样)
守护进程和终端无关联,也就是说他们没有控制终端,所以控制终端退出不会导致守护进程退出。
守护进程是在后台运行,不会占着终端,终端可以执行其他命令。
linux操作系统本身就是很多守护进程在默默运行着,维持在系统的日常活动,30~50个左右。
可以用ps -efj来观察一下
ppid=0是内核进程,跟随系统启动而启动;生命周期贯穿整个系统,如上图所示仅有2个。
带有方括号[]的被成为内核守护进程
kthtread既是内核进程也是内核守护进程
老祖init也是系统守护进程,负责启动各个运行层次特定的系统服务;因此很多进程的PPID是init,而且这个init负责收养孤儿进程。
cmd列中名字不带[]的普通守护进程(用户级守护进程)
总结
...
fork()
fork()函数模型进程概念:
一个可执行程序,执行起来就是一个进程,再执行一次又是一个进程(多个进程可以共享同一个可执行文件)
文雅说法:进程定义为程序执行的一个实例
在一个进程(程序)中,可以用fork()创建一个子进程,当该子进程创建时,从fork()指令的的下一条(或者说fork()函数的返回处)开始执行与父进程相同的代码
说白了:fork()函数产生了一个和当前进程完全一样的新进程,并和当前进程一样从fork()函数里返回;原来一条执行通路(父进程),现在变成两条(父进程+子进程)
fork() 一分为二
对于以下代码,两个fork();
1234567891011121314151617181920212223#include <stdio.h>#include <stdlib.h> //malloc,exit#include <unistd.h> //fork#include <signal.h>int main(int argc, char *const *argv){ fork(); // ...
信号
信号概念信号本身是一个通知,信号用于通知某个进程发生了某个事情。
信号都是突发事件,一般不知道什么时候发生,信号是异步发生的,也被称为“软件中断”
信号如何产生?
a)某个进程发送给另外一个进程或发送给自己
b)有内核(操作系统)发送给某个进程,比如键盘输入ctrl+c,kill命令等,内存访问异常,除数为0等硬件会检测到并且通知给内核。
信号名字,都是以SIG开头,比如SIGHUP
UNIX以及类(类似)UNIX类操作系统(linux,freebd,solaris)支持的信号数量各不相同,10~60多个之间。
信号既有名字,但本质上是一些数字,正整数常量,通过宏定义,数字从1开始。
#include <signal.h>
这里我输入
1sudo find / -name "signal.h" | xargs grep -in "SIGHUP"
该条命令的意思是在‘/‘目录下寻找所有signal.h命名的文件,在文件中寻找SIGHUP句子,-i表示查找时忽略大小写,-n显示行号,xargs是用于给grep命令传递参数。
接着打开/ ...