一、线程同步概念
1、协同步调,按预定先后次序之行与时间有关系的错误
2、数据混乱(资源共享,调度随机,缺乏必要的同步机制)
3、多个控制流访问同一共享资源,必须同步
二、互斥量(互斥锁)
1、mutex
建议锁;
锁,不会限制资源访问;
线程不按规则访问数据依然成功,会出现数据混乱)
2、函数
pthread_mutex_t类型 本质:结构体
pthread_mutex_init 初始化一个互斥锁 1
pthread_mutex_destroy 销毁一个互斥锁
pthread_mutex_lock 加锁 -1
pthread_mutex_unlock 解锁 +1
pthread_mutex_trylock 非阻塞加锁
#include
#include
#include
#include
#include
pthread_mutex_t mutex; //定义锁
void *tfn(void *arg)
{
while (1) {
pthread_mutex_lock(&mutex); //加锁
printf("hello ");
sleep(1); /*模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误*/
printf("world\n");
pthread_mutex_unlock(&mutex); //解锁
sleep(1);//睡眠,释放cpu
}
return NULL;
}
int main(void)
{
pthread_t tid;
pthread_mutex_init(&mutex, NULL); //初始化锁 mutex==1
pthread_create(&tid, NULL, tfn, NULL);
while (1)
{
pthread_mutex_lock(&mutex); //加锁
printf("HELLO ");
sleep(1);
printf("WORLD\n");
pthread_mutex_unlock(&mutex); //解锁
sleep(1);
}
pthread_mutex_destroy(&mutex); //销毁锁
return 0;
}
三、死锁
产生原因:
1、对同一个互斥量,重复加锁
2、持有锁A的线程1请求锁B,持有锁B的线程2请求锁A
避免方法:
1、保证资源的获取顺序,要求每个线程获取资源的顺序一致
2、当得不到所有所需资源时,放弃已经获得的资源,等待。
#include
#include
#include
#include
using namespace std;
int var = 1;
int num = 5;
pthread_mutex_t m_var;
pthread_mutex_t m_num;
void *tfn(void* arg)
{
int i = (int)arg;
if(i == 1)
{
pthread_mutex_lock(&m_var);
var = 11;
sleep(1);
pthread_mutex_lock(&m_num);
num = 111;
pthread_mutex_unlock(&m_var);
pthread_mutex_unlock(&m_num);
cout << "-----thread " << i << "finish" << endl;
pthread_exit(NULL);
}
else if(i == 2)
{
pthread_mutex_lock(&m_num);
var = 22;
sleep(1);
pthread_mutex_lock(&m_var);
num = 222;
pthread_mutex_unlock(&m_var);
pthread_mutex_unlock(&m_num);
cout << "-----thread " << i << "finish" << endl;
pthread_exit(NULL);
}
return NULL;
}
int a = 100;
int main()
{
#if 0
pthread_t tid1, tid2;
int ret1, ret2;
pthread_mutex_init(&m_var, NULL);
pthread_mutex_init(&m_num, NULL);
pthread_create(&tid1, NULL, tfn, (void*)1);
pthread_create(&tid2, NULL, tfn, (void*)2);
sleep(3);
printf("var = %d, num = %d\n", var, num);
ret1 = pthread_mutex_destroy(&m_var);
ret2 = pthread_mutex_destroy(&m_num);
if(ret1 == 0 && ret2 == 0)
{
printf("---------- join thread finish\n");
}
return 0;
#else
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
a = 777;
//pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
printf("--------a = %d\n", a);
pthread_mutex_destroy(&mutex);
return 0;
#endif
}
四、读写锁
1、使用要领:读共享,写独占;写锁优先级高
2、状态:
读模式下加锁状态(读锁)
写模式下加锁状态(写锁)
不加锁状态
3、特性
写模式加锁:解锁前,所有对该锁加锁的线程都会被阻塞
读模式加锁:
如果线程以读模式对其加锁会成功
如果线程以写模式加锁会阻塞;
既有试图以写模式加锁的线程,也有试图以读模式加锁的线程
4、使用场景:适合与对数据结构读的次数远大于写
5、函数
pthread_rwlock_t 类型
pthread_rwlock_init
pthread_rwlock_destory
pthread_rwlock_rdlock
pthread_rwlock_wrlock
pthread_rwlock_tryrdlock
pthread_rwlock_trywrlock
pthread_rwlock_unlock
五、条件变量
1、特性:
不是锁
可以造成线程阻塞
与mutex配合使用
2、函数
pthread_cond_t 类型
pthread_cond_init
pthread_cond_destory
pthread_cond_wait
1)阻塞等待一个条件变量
2)释放已经掌握的互斥锁mutex
3)被唤醒时重新申请获取互斥锁
1)2)步为原子操作
pthread_cond_timewait 绝对时间
pthread_cond_signal 唤醒一个阻塞在条件变量上的线程
pthread_cond_broadcast 唤醒全部阻塞在条件变量上的线程
3、生产者消费者模型
4、优点:减少不必要的竞争
/*借助条件变量模拟 生产者-消费者 问题*/
#include
#include
#include
#include
/*链表作为共享数据,需被互斥量保护*/
struct msg
{
struct msg *next;
int num;
};
struct msg *head;
/*静态初始化 一个条件变量 和 一个互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *consumer(void *p)
{
struct msg *mp;
while(1)
{
pthread_mutex_lock(&lock);
while(head == NULL)//头指针为空,说明没有节点
{
pthread_cond_wait(&has_product, &lock);
}
mp = head;
head = mp->next;//模拟消费掉一个产品
pthread_mutex_unlock(&lock);
printf("-Consume %lu -- %d\n", pthread_self(), mp->num);
free(mp);
sleep(rand() % 5);
}
}
void *producer(void *p)
{
struct msg *mp;
while(1)
{
mp = (struct msg*)malloc(sizeof(struct msg));
mp->num = rand() % 1000 + 1;//模拟生产一个产品
printf("-Produce --------------%d\n", mp->num);
pthread_mutex_lock(&lock);
mp->next = head;
head = mp;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);//将等待在该条件变量上的一个线程唤醒
sleep(rand() % 5);
}
}
int main(int argc, char *argv[])
{
pthread_t pid, cid;
srand(time(NULL));
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
一、信号量
1、sem
进化版互斥锁(1 – N)
保证同步的同时,提高并发
2、函数
sem_t 类型 N代表线程数量
sem_init 是否进程间共享(非0进程间 PTHREAD_PROCESS_SHARED 0线程间 PTHREAD_PROCESS_PRIVATE)
sem_destroy
sem_wait 给信号量加锁 sem-- lock
sem_trywait
sem_timedwait
sem_post
1)给信号量解锁 sem++ unlock
2)唤醒阻塞在信号量上的线程
3、生产者消费者模型
/*信号量实现 生产者 消费者问题*/
#include
#include
#include
#include
#include
#define NUM 5
int queue[NUM]; //全局数组实现环形队列
sem_t blank_number;
sem_t product_number;
void* producer(void* arg)
{
int i = 0;
while(1)
{
sem_wait(&blank_number);//生产者将格子数减一,为0则阻塞等待
queue[i] = rand() % 1000 + 1;//生成一个产品
printf("---produce---%d\n", queue[i]);
sem_post(&product_number);//将产品数加一
i = (i + 1) % NUM;//借助下标实现环形
sleep(rand() % 1);
}
}
void* consumer(void* arg)
{
int i = 0;
while(1)
{
sem_wait(&product_number);//消费者将产品数减一,为0则阻塞等待
printf("---consume---%d\n", queue[i]);
queue[i] = 0;//消费一个产品
sem_post(&blank_number);//消费掉以后,将空格数加一
i = (i + 1) % NUM;
sleep(rand() % 3);
}
}
int main(int argc, char* argv[])
{
pthread_t pid, cid;
sem_init(&blank_number, 0, NUM);
sem_init(&product_number, 0, 0);
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
什么是协议:一组规则。
TCP 协议注重数据的传输,http协议着重于数据的解释。
传输层 TCP,UDP
应用层 HTTP:80,FTP:20,21,TFTP:69,SSH:22,Telnet:23
网络层 IP,ICMP,IGMP
网络接口层 ARP(通过IP找MAC),RARP(通过MAC找IP)
数据没有封装之前,是不能在网络中传递的。
需要我们做的只有应用层,其他部分(传输层、网络层、链路层)内核已经帮我们做了
以太网首部 + IP首部 + TCP 首部 + 应用数据 + 以太网尾部
14 20 20 4
|—————46 ~ 1500——|
以太网的帧格式如下:
目的地址 + 源地址 + 类型 + 数据 + CRC
6 6 2 46~1500 4
类型
0800 数据
0806 ARP
8035 RARP
ARP协议,根据IP 地址获取mac 地址
以太网协议:根据mac地址,完成数据包传输
ff:ff:ff:ff:ff:ff:ff + 00:50:56:3c:9e:ed + 0806 + 8 + 00:50:56:3c:9e:ed + 192.168.0.110 + ff:ff:ff:ff:ff:ff:ff + 123.46.76.22
IP段格式:
版本:IPV4、IPV6 --4位
TTL: time to live(设置下一跳次数,每经过一个路由节点 -1,直到为0丢弃)
源IP:32 位
目的IP:32 位
IP地址,可以在网络环境中,唯一标识一台主机。
端口号,可以网络的一台主机上,唯一标识一个进程。
IP地址+端口号:可以在网络环境中,唯一标识一个进程。
UDP协议:
16位:源地址口号 2^16 = 65536
16位:目的端口号
TCP协议:
16位:源地址口号 2^16 = 65536
16位:目的端口号
32位序号
32位确认序号
6个标志位
16位窗口大小
b/s 模型:
browser-server
c/s 模型:
client-server
网络套接字: socket
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现。)
在通信过程中,套接字成对出现的
小端(pc本地存储):高位存高地址,地位存低地址
大端(网络):高位存低地址,地位存高地址
#include
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
#include
//将点分十进制的ip地址转化为用于网络传输的数值格式
//返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
int inet_pton(int af, const char* src, void* dst);
//将数值格式转化为点分十进制的ip地址格式
//返回值:若成功则为指向结构的指针,若出错则为NULL
const char* inet_ntop(int af, const void *src, char* dst, socklen_t size);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9527);
int dst;
inet_pton(AF_INET, "192.168.9.11", (void*)&dst)
addr.sin_addr.s_addr = dst;//htonl(INADDR_ANY) 取出系统中有效的任意IP地址
bind(fd, (struct sockaddr*)&addr, size);
man 7 ip =》sockaddr_in
#include
int socket(int domain, int type, int protocol);//创建套接字
domain:AF_INET、AF_INET6
type:SOCK_STREAM(tcp)、SOCK_DGRAM(udp)
protocol:0
返回值:成功:文件描述符
失败: -1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//绑定IP + port
int listen(int sockfd, int backlog);//同时设置监听上线,最大值128
//阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP + port)
addrlen: 传入传出。入:addr 的大小,出:客户端addr实际大小
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
addr:传入参数。服务器的地址结构(IP + port)
addrlen: 服务器的地址结构的大小
如果不使用bind绑定客户端地址结构,采用“隐式绑定”
#include
#include
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char * str)
{
perror(str);
exit(1);
}
int main(int argc, char* argv[])
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
sys_err("socket error");
}
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9517);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
listen(fd, 128);
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int cfd = accept(fd, (struct sockaddr *)&client_addr, &client_len);
if(cfd == -1)
{
sys_err("socket error");
}
char buf[1024] = {0};
while(1)
{
memset(buf, '\0', sizeof(buf));
int ret = read(cfd, buf, sizeof(buf));
printf("----%s----%d----\n", buf, ret);
for(int i = 0; i < ret; ++i)
{
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
}
close(fd);
close(cfd);
return 0;
}
nc(内测 net connet) 命令
nc 127.0.0.1 9517
char client_ip[1024];
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, sizeof(client_ip));
printf("client ip = %s, port = %d", client_ip, client_addr.sin_port);
#include
#include
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char * str)
{
perror(str);
exit(1);
}
int main(int argc, char* argv[])
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
sys_err("socket error");
}
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9517);
inet_pton(AF_INET, "192.168.0.109", &serv_addr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(ret != 0)
{
sys_err("connect err");
}
int nCount = 5;
char buf[BUFSIZ] = {0};
while(nCount--)
{
write(fd, "hello\n", 6);
int n = read(fd, buf, sizeof(buf));
printf("----%s----%d----\n", buf, n);
}
close(fd);
return 0;
}
客户端发起请求:SYN TCP标志位,1000 包号,(0)包所携带数据的大小
服务器应答:SYN TCP标志位,8000 包号,应答ACK 标志位,1001 包号
客户端确认:ACK 标志位 8001包号
半关闭:一端发,一端收数据(没有发送功能,只有接收功能)
win 4096 , 滑动窗口大小
如果发送端发送的速度较快,接收端接收到数据后处理的速度较慢,而接收缓冲区的大小是固定的,就会丢数据。
TCP协议通过“滑动窗口” 机制解决这一问题
在外面再封装一层,例如:
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
int n;
again:
if((n = accept(fd, sa, salenptr)) < 0)
{
if((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
wrap.c
自定义函数
wrap.h
自定义函数原型
readn N个字节
readline 读一行
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void catch_child(int signum)
{
while((waitpid(0, NULL, WNOHANG)) > 0);
return;
}
int main()
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in srv_addr;
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(9517);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
listen(lfd, 128);
int cfd;
struct sockaddr_in clt_addr;
socklen_t clt_addr_len = sizeof(clt_addr);
pid_t pid;
int ret;
char buf[BUFSIZ] = {0};
while(1)
{
cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
pid = fork();
if(pid < 0)
{
perror("fork error");
}
else if(pid == 0)
{
close(lfd);
while(1)
{
memset(buf, 0, sizeof(buf));
ret = read(cfd, buf, sizeof(buf));
if(ret == 0)
{
close(cfd);
exit(1);
}
for(int i = 0; i < ret;i++)
{
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
//printf("---%s---\n", buf);
}
}
else
{
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD, &act, NULL);
if(ret != 0)
{
perror("signal error");
}
close(cfd);
}
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
struct s_info
{
struct sockaddr_in clt_addr;
int connfd;
};
#define MAXLINE 8192
void*do_work(void* arg)
{
int n;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE] = {0};
char str[8000] = {0};
while(1)
{
n = read(ts->connfd, buf, MAXLINE);
if(n == 0)
{
printf("the client %d close...\n", ts->connfd);
break;
}
printf("received from %s at port %d\n",
inet_ntop(AF_INET, &(*ts).clt_addr.sin_addr, str, sizeof(str)), ntohs((*ts).clt_addr.sin_port));
for(int i = 0; i < n; i++)
{
buf[i] = toupper(buf[i]);
}
write(ts->connfd, buf, n);
}
close(ts->connfd);
return (void*)0;
}
int main(int argc, char* argv[])
{
struct sockaddr_in servaddr;
int listenfd;
struct s_info ts[256];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9517);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
listen(listenfd, 128);
printf("accepting client connect...\n");
struct sockaddr_in cliaddr;
socklen_t cliaddr_len;
pthread_t tid;
int connfd;
int i = 0;
while(1)
{
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
ts[i].clt_addr = cliaddr;
ts[i].connfd = connfd;
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
pthread_detach(tid);
i++;
}
return 0;
}
一定出现在【主动关闭连接请求端】
保证最后一个ACK能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求)
int opt = 1;//设置端口复用
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));
通信双方中,只有一端关闭通信。
close(cfd);
shutdown(int fd, int how);
how: SHUT_RD 关闭读端
SHUT_WR 关闭写端
SHUT_RDWR 关闭读写
shutdown 不考虑描述符的引用计数,直接关闭描述符
1、如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
2、在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其他的进程将无法进行通信。但,如果一个进程close(sfd)将不会影响到其他进程。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds : 监控的文件描述符集里最大文件描述符加1
readfds:监控有读数据到达文件描述符集合,传入传出参数
writefds:监控写数据到达文件描述符集合,传入传出参数
exceptfds:监控异常发送达到文件描述符集合,传入传出参数
timeout:定时阻塞监控时间,3种情况
1、NULL,永远等下去
2、设置timeval,等待固定时间
3、设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval
{
long tv_sec;
long tv_usec;
};
原理:借助内核,select 来监听,客户端连接、数据通信事件。
void FD_CLR(int fd, fd_set *set);//将指定的文件描述符从集合中清空
int FD_ISSET(int fd, fd_set *set);//判断这个文件描述符是不是在集合里面
void FD_SET(int fd, fd_set *set);//把某个文件描述符添加到集合里面
void FD_ZERO(fd_set *set);//将集合内所有元素置零
//伪代码
lfd = socket();
bind();
listen();
fd_set rset, allset;
FD_ZERO(&allset);
FD_SET(lfd, &allset);
while(1)
{
rset = allset;
ret = select(lfd + 1, &rset, NULL, NULL, NULL);//监听文件描述符集合对应事件。
if(ret > 0)
{
if(FD_ISSET(lfd, &rset))//true 存在
{
cfd = accept();
FD_SET(cfd, &allset);
}
for(int i = lfd + 1; i < 1024; i++)
{
FD_ISSET(i, &rset)
read();
小--大
write();
}
}
}
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[])
{
int listenfd;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
struct sockaddr_in serv_addr;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(9517);
bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
listen(listenfd, 128);
fd_set rset, allset;
int maxfd = 0;
maxfd = listenfd;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
int n, nready;
int connfd;
char buf[BUFSIZ] = {0};
struct sockaddr_in clie_addr;
socklen_t clie_addr_len;
while(1)
{
rset = allset;
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if(nready < 0)
{
printf("select error\n");
}
if(FD_ISSET(listenfd, &rset))
{
clie_addr_len = sizeof(clie_addr);
connfd = accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
FD_SET(connfd, &allset);
if(maxfd < connfd)
{
maxfd = connfd;
}
if(0 == --nready)
{
continue;
}
}
printf("--listenfd:%d, maxfd:%d--\n",listenfd, maxfd);
for(int i = listenfd + 1; i <= maxfd; ++i)
{
if(FD_ISSET(i, &rset))
{
if((n = read(i, buf, sizeof(buf))) == 0)
{
close(i);
FD_CLR(i, &allset);
}
else if(n > 0)
{
for(int j = 0; j < n; ++j)
{
buf[j] = toupper(buf[j]);
}
write(i, buf, n);
printf("--%s--\n", buf);
}
}
}
}
close(listenfd);
return 0;
}
缺点:监听上限受文件描述符限制,最大1024
检测满足条件的fd,自己添加业务逻辑提高小。提高了编码难度
优点:跨平台。win、linux、macOS
int poll(struct pollfd fds, nfds_t nfds, int timeout);
fds:监听的文件描述符【数组】
nfds:监听数组的,实际有效监听个数
timeout:超时时长。毫秒
struct pollfd {
int fd; / file descriptor待监听的文件描述符 /
short events; / requested events待监听的文件描述符对应的监听事件 POLLIN、POLLOUT、POLLERR*/
short revents; /* returned events 传入时,给0。如果满足对应事件的话,返回非0–》POLLIN、POLLOUT、POLLERR*/
};
优点:
自带数组结构。可以将 监听事件集合 和 返回事件集合分离
拓展 监听上限。超出1024限制。
缺点:
不能跨平台。Linux
无法直接定位满足监听事件的文件描述符,编码难度较大。
cat /proc/sys/fs/file-max -->当前计算机所能打开的最大文件个数
sudo vi /etc/security/limits.conf
软件限制:soft nofile 10000
硬件限制:hard nofile 100000
int epoll_create(int size);
epoll_create :创建epoll模型,efd指向红黑树根节点
size:创建的红黑树的监听节点数量
返回值:指向新创建的红黑树的根节点的fd。
失败:-1 error
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl:将lfd及对应的结构体设置到树上,efd可找到该树
epfd:epoll_create 函数的返回值
op:对该监听红黑树所做的操作
EPOLL_CTL_ADD 添加fd到 监听红黑树
EPOLL_CTL_MOD 修改fd在 监听红黑树上的监听事件
EPOLL_CTL_DEL 将一个fd从监听红黑树上摘下(取消监听)
fd:
待监听的fd
event:
本质 struct epoll_event{
uint32_t events;//EPOLLIN/ EPOLLOUT/ EPOLLERR
epoll_data_t data;
};
typedef union epoll_data{
void *ptr;
int fd; //对应监听事件的fd
void *ptr;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
返回值:成功 0; 失败:-1 errno
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);//阻塞监听
epfd:epoll_create 函数的返回值
events: 【数组】传出参数
maxevents:数组元素的总个数 1024
timeout:
-1 阻塞
0 不阻塞
>0 超时事件(毫秒)
返回值:
>0 满足监听的总个数
Edget Triggered(ET) 边缘触发只有数据到来才触发,不管缓冲区中是否还有数据。
Level Triggered(LT) 水平触发只要有数据都会触发。
event.events = EPOLLIN | EPOLLET;//ET 边沿触发
//event.events = EPOLLIN;//LT水平触发(默认)
P75 网络中ET和LT模式
P76 epoll的ET非阻塞模式
P77 epoll优缺点总结
P78 补充对比ET和LT
P79 epoll反应堆模型总述
P80 epoll反应堆main逻辑
P81 epoll反应堆-给lfd和cfd指定回调函数
P82 epoll反应堆-init listen socket
P83 epoll反应堆-wait被触发后read和write回调及监听
P84 epoll反应堆-超时时间
P85 总结
P86 复习
P87 补充说明epoll的man手册
P88 epoll反应堆再说明
P89 ctags使用