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,一直等待, 直到解锁

img test