HPServerProgram
High performance server | HP-Socket高性能TCP/UDP/HTTP通信框架
Linux多线程pthread
学习目标
1.后台监测工具 2.定制WEB服务器,实现远程控制内网电脑操作 3.libevent库源码 3.nginx负载均衡、正反向代理 4.keepalived高可用集群服务 时间复杂度计算
网络
arp -a 查看IP和MAC地址 cat /etc/hosts 设置主机名 cat /etc/resolv.conf 查看域名服务器 host www.baidu.com 在首选域名服务器上查询主机域名对应的IP tcpdump -i eth0 -nt -s 500 port domain 观察domain的通信过程 ------------------------------------------ IP地址无状态、无连接,IP上层协议TCP需要自己实现数据确认、超时重传等机制,才达到可靠传输 IP上层协议UDP和HTTP也是无状态协议 IPv6不仅解决了IPv4地址不够的问题,还增加了多播和流的功能,为网络上多媒体内容提供精细的控制;引入自动配置功能,使得局域网管理更方便; 增加了专门的网络安全功能 ------------------------------------------ 大端字节序(bit endian): 一个整数的高位字节存放在低地址处 小端字节序(littel endian):一个整数的高位字节存放在高地址处 PC大多是小端字节序, 小端字节序又称主机字节序 两台主机通信统一采用大端字节序 ------------------------------------------ socket(int domain, int type, int protocol) domain: PF_UN|PF_INET|PF_INET6 type: SOCK_STREAM|SOCK_DFTAM|SOCK_NONBLOCK|SOCK_CLOEXEC SOCK_STREAM: TCP协议 SOCK_DGRAM: UDP协议 自linux 2.6.17后增加两种类型 SOCK_NONBLOCK: 非阻塞 SOCK_CLOEXEC: 用fork创建子进程时关闭socket protocol:通常为0 ------------------------------------------ listen(int sockfd, int backlog); backlog为监听队列的最大长度,也就是完全连接状态(ESTABLISHED)的socket的上限,典型值是5 处于半连接状态(SYN_RCVD)的上限由内核参数定义/proc/sys/net/ipv4/tcp_max_syn_backlog,常用值是1024 ------------------------------------------ TCP读写数据 size_t recv(int sockfd, void *buf, size_t len, int flags); size_t send(int sockfd, const void *buf, size_t len, int flags); flags = MSG_OOB; 带外数据发送标志 内核通知外带数据的两种方式:I/O复用和SIGURG信号 外带数据的位置函数: int sockatmark(int sockfd); 判断下一个读到的数据是否为外带数据,如果是,就用带MSG_OOB标记的revc来接收 ------------------------------------------ UDP读写数据 size_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t* addrlen); size_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t* addrlen); 由于UDP通信没有连接概念,每次发送接收要设置src或dest ------------------------------------------ 通用数据读写(支持TCP和UDP ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags); ------------------------------------------ 网络信息 先查询/etc/hosts, 如果没找到,就查询DNS服务器(/etc/resolv.conf) struct hostent* gethostbyname(const char* name); struct hostent* gethostbyaddr(const void* addr, size_t len, int type); 查询服务的完整信息,读取/etc/services struct servent* getservbyname(const char* name, const char* proto); struct servent* getservbyport(int port, const char* proto); 获取主机IP和服务port int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result); int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags); ------------------------------------------ CGI(Common Gateway Interface)服务器的工作原理 服务器输出到标准输出的内容,直接发送到客户连接对应的socket上 int confd = accept(sockfd, (struct sockaddr*)&client, &client_len); close(STDOUT_FILENO); dup(confd); printf("html content\n"); close(confd) fastCGI(进程池处理CGI请求) java的Servlet用多线程技术处理CGI请求 现代HTML技术,前端JS承担更多责任,CGI还再用于直接返回HTML页面,而Restful风格的API为CGI续命 ------------------------------------------ 高级I/O函数(内存零拷贝, 在内核中进行,不另外分配内存) int pipe(int fd[2]); 两个管道间传输数据 int dup(int fd); 创建一个新的文件描述符,和原来的fd(文件、管道或网络连接)连接 int readv(int fd, const struct iover* verctor, int count); 内存分散读 int writev(int fd, const struct iover* vector, int count); 内存集中写 int sendfile(int out_fd, int in_fd, off_t* offset, size_t count); 进程间共享内存通信 int void mmap(void *start, size_t len, int proto, int flags, int fd, off_t offset); int munmap(void *start, size_t len); 两个文件描述符间移动数据 sszie_t splice(int fd_in, loff_t* off_in, inf fd_out, loff_t *off_out, size_t len, unsigned int flags); 两个管道文件描述符间复制数据 ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags); ------------------------------------------ Linux提供守护进程处理系统日志syslogd, 升级后为rsyslogd, 接收用户进程日志,也接收内核输出日志 用户进程日志接收函数: void syslog(int priority, const char *message, ...); 定制日志内容 void openlog(const char *ident, int logopt, int facility); int setlogmask(int maskpri); void closelog(); rsyslogd的主配置文件是/etc/rsyslog.conf 默认情况下 /var/log/debug 调试信息 /var/log/messages 普通信息 /varlog/kern.log 内核消息 ------------------------------------------ 一个进程有两个用户ID: UID(程序启动时的用户ID)和EUID(有效ID, 程序运行时的用户ID) su程序,UID是普通用户ID,EUID是root用户ID mysqld程序,UID是普通用户ID,EUID是mysql用户ID uid_t getuid(); uid_t geteuid(); ------------------------------------------ 网络设计模式: Reactor: 同步I/O模型, 主线程只负责监听socket上的事件发生, 工作线程负责读写数据、接受新连接、处理客户请求等。 Proactor: 异步I/O模型, 所有的I/O操作交给主线程处理,工作线程只负责业务逻辑 并发编程有多进程和多线程两种形式 服务器主要有两种并发模式: 半同步/半异步模式(half-sync/half-async): 领导者模式(Leader/Followers): ------------------------------------------ I/O复用 虽然能同时监听多个文件描述符,但o它本身是阻塞的 1.select API int select(int nfds, fd_set *readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout); FD_ZERTO(fd_set* fdset); FD_SET(int fd, fd_set* fdset); FD_CLR(int fd, fd_set* fdset); int FD_ISSET(int fd, fd_set* fdset); 2.poll系统调用 int poll(struct pollfd* fds, nfds_t nfds, int timeout); struct pollfd { int fd, short events; 注册的事件 short revents; 实际发生的事件,由内核设置 } 3.epoll系统系统调用 int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event); 其中:op对应如下操作 EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL struct epoll_event { __uint32_t events; epoll事件 epoll_data_t data; 用户数据 int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout); epoll对文件描述符操作有两种模式: LT(Level Trigger,电平触发): 电平变化后,保持电平状态时,可以重复触发 ET(Edge Trigger, 边沿触发): 电平变化后,立即执行触发,触发后不再重复 ET触发设置,在往epoll内核事件表中注册一个文件描述符上的事件EPOLLET 每个使用ET模式的文件描述符都应该是非阻塞的 使用ET触发,仍可以多次触发,注册EPOLLONESHOT事件,内核最多触发其上注册的一个可读、可写或异常事件,且只触发一次 }
信号
查看信号列表:kill -l 杀死一个进程, 9对应的信号是SIGKILL kill 9 pid 信号处理有默认五种方式: 1.结束进程(Term) 2.忽略信号(Ign) 3.结束进程生成核心转储文件(Core) 4.暂停进程(Stop) 5.继续进程(Cont) 一个进程给其它进程发送信号: int kill(pid_t pid, int sig); 信号处理函数 typedef void (*__sighandler_t) (int); 为一个信号设置处理函数 _signhandler_t signal(int sig, _sighandler_t _handler); 设置信号处理函数更健壮的接口: int sigaction(int sig, const struct sigaction* act, struct sigaction* oact); struct sigaction { void (*sa_handler)(int); 信号处理程序,不接收数据 void (*sa_sigaction)(int, signinfo_t* info, void *);信号处理程序,接收数据 sigset_t sa_mask; int sa_flags; } signal不能带数据;当act.sa_flags设置为SA_SIGINFO时,可以带数据 sigset_t指向信号集 信号集函数: int sigemptyset(sigset_t* set); int sigfillset(sigset_t* set); int sigaddset(sigset_t* set, int signo); int sigdelset(sigset_t* set, int signo); int sigismember(const sigset_t* set, int signo); 信号发送函数: int sigqueue(pid_t pid, int sig, const union sigval value); 查看信号掩码: int sigprocmask(); 被挂起的信号: int sigpending(sigset_t* set); 定时器: LInux提供三种定时方法: 1.socket选项SO_RCVTIMEO和SO_SNDTIMEO 2.I/O复用系统调用的超时参数 3.SIGALRM信号 int alarm(); int setitimer(); 高性能定时器 1.时间轮: 即一个散列表。定时N个时间槽,每个槽相隔一个tick, 每个槽指向一个定时器链表 2.时间堆: 即最小堆。
进程是资源分配的最小单位,线程是CPU调度的最小单位。 进程有独立的地址空间,线程只有自己的堆栈和局部变量,没有单独的地址空间 一个程序至少有一个进程,一个进程至少有一个线程
进程
------------------------------------- 多进程 创建进程 fork: 复制.父进程和子进程执行顺序随机 vfork: 共享内存空间。子进程先执行,完成exec或exit(0)后,父进程才执行 clone: 定制共享和复制。执行顺序由flags来决定 父进程返回进程ID, 子进程返回零, 出错返回-1 ------------------------------------- 管道 ------------------------------------- 信号量(Semaphore) P操作:进入临界区。如果if(SV>0) SV--; else 挂起本进程 V操作:退出临界区。如果有挂起进程,该挂起进程进入临界区。否则SV++ Linux内核变量: unsigned shor semval; 信号量的值 unsigned shor semzcnt; 等待信号值变0的进程数量 unsigned shor setncnt; 等待信号值增加的进程数据 pid_t sempid; 最后一次执行semop操作的进程id 1.创建信号量 int semget(key_t key, int num_sems, int sem_flags); 2.执行PV操作, 是对Linux内核变量的操作 int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops); struct sembuf { unsigned short int sem_num; 0+ short int sem_op; >1|0|<0 shor int sem_flg; IPC_NOWAIT|SEM_UNDO } 3.控制信号量 int semctl(int sem_id, int sem_num, int command, ...); ------------------------------------- 共享内存 1.创建共享内存 int shmget(key_t key, size_t size, int shmflg); 2.关联共享内存进程地址 void* shmat(int shm_id, const void* shm_addr, int shmflg); 3.解除关联进程的共享内存 int shmdt(const void* shm_addr); 4.控制共享内存 int shmctl(int shm_id, int command, struct shmid_ds* buf); mmmap(); 父子进程的匿名共享内存 打开一个共享内存对象: int shm_open(const char* name, int oflag, mode_t mode); 关闭一个共享内存对象 int shm_unlink(const char* name); ------------------------------------- 消息队列 1.创建消息队列 int msgget(key_t key, int msgflg); 2.增加一条消息到消息队列 int msgsnd(int msgid, const void* msg_ptr, size_t msg_sz, int msgflg); 3.获取消息 int msgrcv(int msgid, void* msg_ptr, size_t msg_sz, long int msgtype, int msgflg); 3.控制消息队列 int msgctl(int msgid, int command, struct msg_id_ds* buf); IPC 命令行显示sem,sig,shm:ipcs
线程
int pthread_create(); void pthread_exit(); int pthread_join(); int pthread_cancel(); ------------------------------- 信号量 int sem_init(sem_t* sem, int pshared, unsigned int value); int sem_destroy(sem_t* sem); int sem_wait(sem_t* sem); int sem_trywait(sem_t* sem); int sem_post(sem_t* sem); ------------------------------- 互斥锁 int pthread_mutex_init(); int pthread_mutex_destroy(); int pthread_mutex_destroy(); int pthread_mutex_lock(); int pthread_mutex_trylock(); int pthread_mutex_unlock(); ------------------------------- 条件变量 int pthread_cond_init(pthread_cond_t *cond, NULL); int pthread_cond_destroy(); int pthread_cond_broadcast(); int pthread_cond_signal(); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 互斥锁解锁,等待另一个线程发出cond_signal后,重新加锁 ------------------------------ 读写锁 int pthread_rwlock_init(); int pthread_rwlock_rdlock(); int pthread_rwlock_wrlock(); int pthread_rwlock_trywrlock(); int pthread_rwlock_unlock(); int pthread_rwlock_destroy(); ------------------------------ 自旋锁 与互斥量不同,互斥量阻塞后休眠让出CPU, 而自旋锁阻塞后不让出CPU,一直等待, 直到解锁