网络系统编程笔记
捡起来做个毕设
Linux网络编程基础API
- 创建socket通过socket系统调用,并传入(地址族、服务类型(如SOCK_STREAM)、具体协议(默认为0)),并返回一个socket文件描述符
- 使用bind()来命名socket,因为socket创建的时候并未给定socket地址,因此需要将socket绑定到socket地址上(这样客户端才能知道怎么连接它)
- ipv4 socket地址结构(sockaddr_in),包括地址族(6字节)、端口号(2字节)、IPv4地址结构(4字节)
1
2
3
4
5
6
7
8
9
struct sockaddr_in {
sa_family_t sin_family; // 地址族:AF_INET
u_int16_t sin_port; // 端口号,要用网络字节序表示(注意是2字节因此搭配htons使用)
struct in_addr sin_addr; // 在写服务器程序的时候可以赋值为0(或INADDR_ANY)表示本机中所有的ip地址,
};
struct in_addr {
u_int32_t s_addr; // IPv4地址,要用网络字节序表示
} - 对于socket来说,read(默认阻塞)返回0(i.e.EOF)就表示对端主动关闭了连接
- 之所以区分监听描述符和连接描述符是因为(服务器不单是处理一个客户连接)
- 建立连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 都需要通用socket地址结构(sockaddr)作为参数(因为那时没有void*指针),因此sockaddr_in需要强制转换为sockaddr通用socket地址结构
// len用sizeof获取,单位为字节。因为strlen返回为字符数,刚好字符也为1字节,因此也可以
int bind(int listenfd, const struct sockaddr* addr, socklen_t sockaddr_len);
// backlog参数"提示"内核监听队列的最大长度,如果最大长度超过backlog服务器将不受理新的客户连接
// backlog只表示处于完全连接(ESTABLISHED)或半连接(SYN_RCVD)的socket上限
int listen(int listenfd, int backlog);
// 默认情况下accept是阻塞的,addr为传出参数,传出的是peer socket(也就是客户端)的socket地址结构
// 成功时返回一个非负的连接文件描述符
int accept(int listenfd, struct sockaddr* addr, socklen_t* addrlen);
int connect(int sockfd, const struct sockaddr* addr, socklen_t sockaddr_len); - TCP数据读写(send\recv),UDP数据读写(recvfrom、sendto),因为UDP没有连接的概念,因此进行读写的时候需要加上socket地址结构作为参数
1
2
3
4
5
6
7
8
9
// flags参数通常设置为0即可
ssize_t recv(int socket, void* buf, size_t len, int flags);
ssize_t send(int sockfd, const void* buf, size_t len, int flags);
// src_addr为传出参数,如果不需要接收的socket地址结构信息,可以设置为NULL,*len也设置为NULL
ssize_t recvfrom(int socket, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
// dest_addr为对端socket的地址
ssize_t sendto(int socket, const void* buf, size_t len, int flags, struct sockaddr* dest_addr, socklen_t* addrlen); - htons/ntohs,htonl/ntohl完成网络字节序与主机字节序相互之间的转换
在头文件#include <netinet/in.h>
中 - socket地址信息函数
1
2
3
int getsockname(int sockfd, struct sockaddr*, socklen_t*); // 将当前连接socket本地端socket地址保存在参数2,3中
int getpeername(int sockfd, struct sockaddr*, socklen_t*); // 将当前连接socket远端socket地址信息保存在参数2,3中 - IP地址点分十进制字符串与网络字节序整数的相互转换
1
2
3
int inet_pton(int af, const char* src, void* dst); // 将点分十进制字符串转换为网络字节序整数
const char* inet_ntop(int af, const void* src, char* dst, socklen_t); // 最后一个参数指定目标存储空间的大小 - 网络信息API(Ip地址和端口号不便记忆,可以通过主机名来替代IP地址,服务名来代替端口号)
- gethostbyname/gethostbyaddr(通过主机名或IP地址获取主机信息)
1
2
3
4
5
6
7
8
9
10
struct hostent* gethostbyname(const char* name); // 会先再本地的/etc/hosts配置文件中查找,查不到再去本地dns服务器中查找
struct hostent* gethostbyaddr(const void* addr, size_t len, int type) // type为地址族
struct hostent {
char* h_name; // 主机名
char** h_aliases; // 主机别名列表
int h_addrtype; // 地址族
int h_length; // 地址长度
char** h_addr_list; // 按网络字节序列出的主机IP地址列表
}; - getservbyname/getservbyport
1
2
3
4
5
6
7
8
9
struct servent* getservbyname(const char* name, const char* proto);
struct servent* getservbyport(int port, const char* proto); // proto参数通常是"tcp"(获取流服务)、"udp"(获取数据报服务)、NULL(获取所有类型的服务)
struct servent {
char* s_name;
char** s_aliases;
int s_port;
char* s_proto; // 服务类型,通常是tcp或者udp
}; - getaddrinfo,根据主机名获得socket地址
1
2
3
4
5
6
7
8
9
10
11
12
13
int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result); // result参数指向一个链表,该链表用于存储getaddrinfo反馈的结果
void freeaddrinfo(struct addrinfo* res); // 对result这块区域的释放
struct addrinfo {
int ai_flags;
int ai_family; // 地址族
int ai_socktype; // 服务类型SOCK_STREAM
int ai_protocol; // 与socket调用的第三个参数含义相同
socklen_t ai_addrlen; // socket地址ai_addr的长度
char* ai_canonname; // 主机的别名
struct sockaddr* ai_addr; // 指向socket地址
struct addrinfo* ai_next; // 指向下一个addrinfo结构
}; - getnameinfo(与getaddrinfo是反着来的),根据socket地址获得主机名
1
2
3
// 返回的主机名存储在host参数指向的缓存中,服务名存储在serv指向的缓存中
int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags);
- gethostbyname/gethostbyaddr(通过主机名或IP地址获取主机信息)
- 操作进程的命令
- strace:打印一个正在运行的程序和它的子进程调用的每个系统调用的轨迹
- ps:列出当前系统中的进程(包括僵尸进程)
- top:打印出关于当前进程资源的使用信息
- 全局变量environ指向环境变量列表的首地址
- 进程相关api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
char* strstr(const char *str1, const char *str2); // 返回str1中第一次出现str2的位置,若不存在则返回NULL
// 如果overwrite覆写参数为1,如果环境变量列表中出现该变量,则覆写它; 0 otherwise
int setenv(const char *name, const char *value, int overwrite);
// 在当前进程的上下文中加载一个程序并运行
int execve(const char *pathname, char *const argv[],
char *const envp[]);
// 子进程复制父进程的虚拟地址空间,共享文件(继承了打开文件表),相同但是独立的地址空间(相同的用户栈、堆、全局本地变量,代码段)
pid_t fork(void);
// dup2复制描述符表项oldfd到描述符表项newfd(底层中将描述符表项newfd的内容删除,
// 如文件打开表项、v-node表项。再将描述符表项newfd指向文件描述符表项所指的打开文件表项)
int dup2(int oldfd, int newfd); - 读取文件元数据
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
// stat函数以一个文件名进行输入,fstat以一个文件描述符输入
// 两个函数用来获取当前文件的元数据并填写到stat结构体中
int stat(const char* filename, struct stat* buf);
int fstat(int fd, struct stat* buf);
struct stat {
dev_t st_dev; // 设备
ino_t st_ino; // inode
mode_t st_mode; // 文件权限和类型
nlink_t st_nlink; // 硬链接的个数
uid_t st_uid; // 用户id
gid_t st_gid; // 组id
dev_t st_rdev; // 设备类型
off_t st_size; // 整体大小(字节)
unsigned long st_blksize; // 文件I/O的块大小
unsigned long st_blocks; // 分配块的数量
time_t st_atime; // 最后访问的时间
time_t st_mtime; // 最后修改的时间
time_t st_ctime; // 最后改变的时间
};
// 常用宏
S_ISLNK(st_mode) //是否是一个连接.
S_ISREG(st_mode) //是否是一个常规文件.
S_ISDIR(st_mode) //是否是一个目录
S_ISCHR(st_mode) //是否是一个字符设备.
S_ISBLK(st_mode) //是否是一个块设备
S_ISFIFO(st_mode) //是否 是一个FIFO文件.
S_ISSOCK(st_mode) //是否是一个SOCKET文件一些辅助函数
1
2
3
4
5
6
7
// 返回指向s的指针
void* memset(void* s, int c, size_t n);
// 等同于memset(s, '\0', n)的作用
void bzero(void* s, size_t n);
// 获取文件路径名的后缀部分
char* basename(filename);
线程相关
- Posix线程相关API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// tid为线程id,attr为线程属性(默认为NULL),f为线程执行的函数,arg为线程执行函数的参数
// 第三个参数为void*的指针,一定要有个void*参数(第四个)
int pthread_create(pthread_t* tid, pthread_addr_t* attr, func* f, void* arg);
// 返回当前线程的pid
pthread_t pthread_self(void);
// 下面这个api显示终止当前调用的线程
// 注意exit函数终止进程及所有进程相关的线程,释放内存和打开文件的资源,并返回相应的状态给父进程
void pthread_exit(void* thread_return);
// 回收已终止线程的资源。该函数会阻塞直到线程tid终止,然后回收资源
// 返回thread_return参数,作为pthread_exit的参数
int pthread_join(pthread_t tid, void **thread_return); // 等待具体线程终止
// 线程分为可分离的和可结合的,可结合的线程能够被其他线程回收和终止
// 在被回收之前它的内存资源(例如栈)是不释放的,调用它能够使线程变为可分离的
// 使得系统能够自动回收线程资源,避免内存泄漏。
int pthread_detach(pthread_t tid); - 信号量。具有非负整数值的全局计数(用来记录所拥有的资源),用来记录空闲的线程。如果该全局变量为0该线程将会阻塞(没有资源可取),直到计数大于0
- P操作,挂起该线程。
- V操作,唤醒该线程。
- 计数为1的信号量可作为锁。
- 可重入函数(线程安全函数的真子集),当它被多个线程调用时不会引入共享的全局变量
- LWP(lighted weight process)
ps -Lf pid
查看当前进程下的线程-f
标识整个format展示- 为什么要有线程?
- 进程间信息难以共享
- fork创建进程的开销太大,即便有写时复制技术,还需要复制VA,复制页表,打开文件表(在进程结构中)等
- 临界区:访问某一共享资源的代码片段
- pthread_t为无符号整型变量,printf时用
%ld
格式符 - 条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
将会释解锁mutex并阻塞在条件变量上,成功返回后mutex被锁定并由调用线程拥有 - 信号量
int sem_init(sem_t* sem, int pshared, unsigned int value); // 在sem地址处初始化一个信号量
。如果pshared为0表示信号量在当前进程的线程之间共享(信号量的地址会在全局变量中),如果pthread为非0则会在进程之间共享(信号量的地址会在共享内存中)
高级I/O函数
- 宏
- 标准输入 STDIN_FILENO
- 标准输出 STDOUT_FILENO
- 标准错误 STDERR_FILENO
- pipe(用于IPC)
- 内部传输的数据是字节流,linux管道容量的大小默认为65536字节
- 定义
1
2
int pipe(int fd[2]); // 两个文件描述符,fd[0]为读端,fd[1]为写端 - 管道是半双工的,若要实现双向数据传输,应使用两个管道
- 默认情况下是阻塞的(管道空read就会被阻塞,管道满write就会被阻塞)
- 非阻塞管道(将fd[0]和fd[1]设置为非阻塞的)
- 如果fd[0]的引用计数减少为0,即没有任何进程从管道中读数据,则针对fd[1]进行write将失败并引发SIGPIPE信号(默认执行动作时terminate)
- 如果fd[1]的引用计数减少为0,则对fd[0]进行read将会返回0,即EOF(表示对端关闭了连接)
- 双向管道
1
2
3
4
// 直接创建双向管道(两个fd既可读又可写),前三个参数与socket系统调用的参数一样
int socketpair(int domain, int type, int protocol, int fd[2]);
- dup2
1
2
3
int dup(int file_descriptor); // 返回一个新的fd,该fd与参数fd指向同一个打开文件表中的项
int dup2(int oldfd, int newfd); // 返回不小于newfd的整数值 - readv/writev。将多块分散的内存写入/读出fd,通常web服务器中的内容可以放在分散的内存中
1
2
3
4
5
6
7
8
// vector参数类型是iovec结构数组,count表示数组的大小(即分散内存的个数)
ssize_t readv(int fd, const struct iovec* vector, int count);
ssize_t writev(int fd, const struct iovec* vector, int count);
struct iovec {
void* iov_base; // 内存起始地址
size_t iov_len; // 这块内存的长度
};- snprintf将格式输出到缓冲区中,sprintf将格式输出到指针所指向的内存区域,printf将格式输出到标准输出。
- sendfile。函数在两个文件描述符之间直接传递数据(在内核中),从而避免用户缓冲区和内核缓冲区之间的数据拷贝,实现了零拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// in_fd是写入的fd,out_fd是读出的fd,in_fd必须是一个类似mmap函数的文件描述符
// 必须指向真实的文件,不能是socket和管道
// offset指出从读入文件流的哪个位置开始读,如果为空,则使用默认起始位置
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
``到fd`
- mmap。内存映射文件,可以将申请的这段内存作为进程间通信的共享内存(通过shm_open函数返回共享对象的fd供进程使用),也可以将文件映射到其中
``` c
// start为用户虚拟地址,prot用于设置共享内存段的访问权限
// PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE
// flags参数控制内存段内容被修改后程序的行为
// mmap函数成功时返回指向目标内存区域的指针
void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void* start, size_t length); - splice在两个文件描述符间移动数据(零拷贝操作)
- tee在两个管道描述符之间复制数据(零拷贝操作)
- fcntl(file control)。
- 定义
1
2
3
// cmd参数指定执行何种类型的操作,后面是可变参数类型
int fcntl(int fd, int cmd, ...); - 常用操作及参数
- F_GETFD(无第三个参数)。获取fd的标志,成功时返回fd的标志
- F_SETFD(第三个参数类型为long)。设置fd的标志,成功时返回0
- F_GETFL(第三个参数类型为void)。获取fd的状态标志
- F_SETFL(第三个参数类型为long)。设置fd的状态标志,成功时返回0
- F_GETOWN(无第三个参数)。获得SIGIO和SIGURG信号的素组进程的PID或GID
- F_SETOWN(第三个参数为long)。成功时返回0
- F_GETSIG(无第三个参数),获取当前应用程序被通知fd可读可写时是哪个信号通知的,返回信号值(0表示SIGIO)
- F_SETSIG(第三个参数为long),设置当fd可读/写时,系统应该触法哪个信号来通知程序
- fcntl函数还可以为目标文件描述符指定宿主进程,宿主进程将捕获信号(将信号和描述符关联起来)
- 定义
Linux服务器程序规范
- 服务器程序一般以后台进程(i.e. 守护进程)的方式运行;有一套日志系统(至少能输出日志到文件中);服务器程序一般以一个专门的非root身份运行;Linux服务器通常是可配置的(用配置文件来管理);会有一个PID文件记录后台进程的PID;需要考虑到系统资源的限制。
服务器框架
- 模块
- I/O处理单元:处理客户连接,读写网络数据
- 逻辑单元:业务进程或线程
- 网络存储单元:数据库、文件或缓存
- 请求队列(通常被实现为池的一部分):各单元之间的通信方式
- 在处理I/O的时候,阻塞(进程放弃处理机处于暂停的状态)和非阻塞都是同步I/O,只有调用特殊的API才是异步I/O
- 同步I/O模型
- 阻塞I/O,可能被阻塞的系统调用包括accept、send、recv和connect
- 非阻塞I/O,执行的系统调用无论事件是否发生总是立即返回。可以根据errno来区分情况:对accept、read、recv,事件未发生errno通常被设置为EAGIN或EWOULDBLOCK;对connect而言errno会被设置为EINPROGRESS(意为”在处理中”)
- I/O复用,应用程序通过I/O复用函数向内核注册事件,I/O复用函数本身是阻塞的(因此需要通过并发编程手段来提高服务器的性能),可同时监听多个I/O事件
- 信号驱动I/O,比如
- 通过SIGIO/SIGURG信号(必须与文件描述符关联使用)报告I/O事件。可以为一个目标文件描述符指定一个宿主进程,然后当描述符fd可读/写时,系统将触发相应的信号,目标文件描述符的宿主进程将捕获到信号,执行相应的信号处理函数(这一过程是异步的(注意不是异步I/O))
- 完成非阻塞I/O的操作(同步的过程)
- 异步I/O模型
- 异步I/O,由操作系统告诉内核用户读写缓冲区的位置,以及I/O操作完成后内核通知应用程序的方式,包括文件的偏移量。异步I/O的读写操作总是立即返回,真正的I/O操作已经被内核接管(内核来执行I/O操作)。等最后完成写入后内核在通知用户写入完成
- 同步I/O vs. 异步I/O
- 同步I/O向应用程序通知的是I/O就绪事件,而异步I/O向应用程序通知的是I/O完成事件。注意这里和接下来事件处理模式的同步和异步指的是何种I/O事件
事件处理模式
- 服务器通常处理三类事件:I/O事件、信号及定时事件
- Reactor模式(使用同步I/O模型),即I/O多路复用+非阻塞I/O
- 利用I/O复用,分为主线程,请求队列,工作线程三部分。主线程往内核事件表中注册socket读就绪事件,epoll_wait等待socket上有数据可读,若有事件发生,通知主线程将事件放入请求队列中,睡眠在请求队列上的工作线程被唤醒,从socket上读取客户请求的数据,再注册socket可写事件,若socket可写,epoll_wait通知主线程,主线程将事件放入请求队列,工作线程将往socket上写入服务器处理客户请求的结果。
- Proactor模式(使用异步I/O模型),可以勇reactor来模拟,使用主线程完成数据的读写
- 并发模式。是指I/O处理单元和多个逻辑单元之间协调完成任务的方式,这里的”异步”指的是程序的执行需要由系统事件来驱动(比如中断、信号),半同步半异步模式允许一个线程同时处理多个客户连接
I/O复用
- 使用到I/O多路复用的场景
- 客户端要同时处理多个socket
- 客户端要同时处理用户输入和网络连接
- 服务器要同时监听socket和连接socket
- 服务器要同时处理TCP请求和UDP请求
- 服务器要同时监听多个端口
- select系统调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// nfds为被监听描述符的总数(通常为最大可分配的描述符值加1,因为从0开始)
// 后面三个分别指向事件对应的描述符集合,每一次select内核都会修改这些集合,因此每次select之后都要用宏初始化它
// timeout为0,select立即返回;为NULL则select一直阻塞直到某个fd就绪
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
struct timeval {
long tv_sec; // 秒级
long tv_usec; // 微秒级
};
// fd_set结构体仅包含一个整型数组,每一位标记一个fd,能容纳的最大fd为1024
FD_ZERO(fd_set* fdset); // 清除fdset的所有位
FD_SET(int fd, fd_set* fdset); // 设置fdset的位fd
FD_CLR(int fd, fd_set* fdset); // 清除fdset的位fd
int FD_ISSET(int fd, fd_set* fdset); // 测试fdset的位fd是否被设置 - poll系统调用
1
2
3
4
5
6
7
8
9
// nfds为监听事件集合的大小,timeout为0立即返回,-1将阻塞直到有事件发生
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; // 事件fd(事件发生用于I/O的fd)
short events; // 注册的事件
short revents; // 实际发生的事件,由内核填充
};- 事件类型
- POLLIN,数据可读
- POLLOUT,数据可写
- POLLRDHUP,TCP连接被对方关闭或对方关闭了写操作
- POLLHUP,挂起
- POLLNVAL,文件描述符没有打开
- 事件类型
- epoll系统调用
- 内核事件表,epoll将关心的fd放入内核事件表中,epoll需要额外的文件描述符来唯一标识内核事件表,即epoll_create返回的epollfd。通过函数回调的方式来通知服务器有事件发生(即便有了非阻塞I/O,只有在事件发生的情况下,调用非阻塞I/O才能提高程序的效率)。
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
// size参数只是给内核一个提示告诉它内核事件表需要多大,返回的epollfd(非负fd)将用作其他所有epoll系统调用的第一个参数
// 手册中明确写了,当返回的epfd不用时,要调用close关闭它
int epoll_create(int size); // size参数在linux 2.6.8以后就被忽略了,但是必须要大于0
// 操作内核事件表
// op参数对fd上的事件进行操作:EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL
// 并将改event参数表(添加/删除/改变)更新到epfd连接的内核事件表中
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
struct epoll_event {
__unint32_t events; // epoll事件
epoll_data_t data; // 用户数据
};
typedef union epoll_data {
void* ptr;
int fd; // 用的最多
uint32_t u32;
uint64_t u64;
} epoll_data_t;
// 成功时返回就绪fd的个数,只需要遍历+events数组即可处理事件,时间复杂度O(1)
// epoll_wait将就绪的事件(发生变化的fd)从内核事件表复制到第二个参数events中,maxevents限定events结构体的大小
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
- 内核事件表,epoll将关心的fd放入内核事件表中,epoll需要额外的文件描述符来唯一标识内核事件表,即epoll_create返回的epollfd。通过函数回调的方式来通知服务器有事件发生(即便有了非阻塞I/O,只有在事件发生的情况下,调用非阻塞I/O才能提高程序的效率)。
- epoll的ET和LT模式
- LT模式即水平触发,上一次就绪未处理的fd,后续epoll_wait还是会像应用程序通知该事件(就绪fd从内核事件表中复制到第二个参数events数组中),直到事件被处理
- ET模式即边沿触发,有事件发生应当立即处理,如果不处理后续epoll_wait将不会向应用程序通知该事件(很大程序上降低了一个epoll事件被重复触发的次数),因此需要一次性读完或者写完。这也决定了使用ET模式的fd必须是非阻塞的,如果fd是阻塞的,读写操作将因为没有后续事件的发生而一直处于阻塞状态
- EPOLLONESHOT事件
- 解决了在ET模式下,当一个fd数据未处理完,此时切换到另一个线程继续处理,这时出现了两个线程同时操纵一个fd的局面(我们期望的是一个socket连接在任一时刻都之被一个线程处理)。对于注册了EPOLLONESHOT事件的fd,os最多触发其上注册的一个读或写或异常事件,且只触发一次(需要再次注册该事件到fd上),这时候就不会出现一个fd被两个线程同时读的情况了。
- C语言
char data[10] = {0};
实际上就是赋值10个0字符
信号(软件中断)
- sigaction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// signum为具体的信号(除了SIGKILL、SIGSTOP)
// 如果oldact不为NULL,那么之前的action将会被保存到oldact中(传出参数)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
// 信号处理函数
void (*sa_handler)(int);
// 不常用
void (*sa_sigaction)(int, siginfo_t *, void *);
// 临时阻塞的信号集,在调用处理程序handler时临时阻塞某些信号
// 不允许这些信号中断处理程序handler的执行
sigset_t sa_mask;
// sa_flag指定是用第一个sa_handler还是sa_sigaction进行处理,使用0是sa_handler
int sa_flags;
// 被废弃掉了
void (*sa_restorer)(void);
}; - 信号集,指定一组将由进程阻塞的信号集合
1
2
3
4
5
6
7
8
9
10// 标志位为1表示阻塞这个信号
// 清空信号集中的数据(即接收所有信号),所有标志位置为0,参数为传出参数set
int sigemptyset(sigset_t *set);
// 将信号集中的标志位都置为1(即阻塞所有信号),传出参数为set
int sigfillset(sigset_t *set);
// 往信号集中添加具体的信号,即阻塞某个具体的信号
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
netstat
- 使用
netstat -na | grep TIME_WAIT
查看time wait状态下的连接
gcc
- 参数
- -c编译而不进行链接
- -S进行汇编
- -E进行预处理
- -I指定头文件所在的目录的相对位置
- -D参数表示程序编译的时候指定一个宏,比如在文件中有
#ifdef DEBUG
语句,如果编译时不携带宏,那么DEBUG宏的值就默认是0。这个参数方便进行调试,发布release版本时,配合#ifdef屏蔽掉log信息。 - -w参数屏蔽所有的警告,有时候警告会导致程序崩溃
- -Wall生成所有的警告(如声明但未使用的变量就会提示警告)
- -l程序编译时指定使用的库名称
- -L编译时指定搜索库的路径
- -fpic生成位置无关代码
- -shared生成共享目标文件
- -std参数指定C方言,gcc默认方言为GNU C
静态库
- 命名。linux下静态库的命名位
libxxx.a
- 制作。使用ar(archive)工具,将
.o
文件制作成一个静态库,ar用到的参数- -r。将所选文件添加到一个归档(archive)中
- -c。(create)创建归档
- -s。为这个文件插入一个索引
- 在分发静态库时要将库文件和头文件一起分发
Makefile
- Makefile其他规则一般都是为第一条规则服务的,如果规则用不到则不必执行。先检查依赖是否存在,再去执行相应的shell命令
- 变量。注意自动变量只能在当前规则中使用
- 自定义变量。
变量名=变量值
- 常用预定义变量。
AR
:归档维护程序的名称,默认值为ar(制作静态库的工具)CC
:C编译器的名称,默认值为ccCXX
:C++编译器的名称,默认值为g++$@
: 获取目标的完整名称$<
: 获取第一个依赖文件的名称$^
: 获取所有的依赖文件
- 获取变量的值
$(变量名)
%
为通配符
.PHONY:clean
表示clean是个伪目标,不会因为依赖而不执行clean
- 自定义变量。
HTTP协议
- 请求和响应的头以ASCII的形式给出;而消息内容具有一个类MIME的格式
- 请求报文:请求行、请求头部、空行、请求数据
- web服务器解析请求,定位请求资源
- 响应报文:状态行、响应头部、空行、响应数据