一起学习epoll
epoll
是Linux内核中的一种可扩展IO事件处理机制,最早在 Linux 2.5.44内核中引入,可被用于代替POSIX select 和 poll 系统调用,并且在具有大量应用程序请求时能够获得较好的性能( 此时被监视的文件描述符数目非常大,与旧的 select 和 poll 系统调用完成操作所需 O(n) 不同, epoll能在O(1)时间内完成操作,所以性能相当高),epoll 与 FreeBSD的kqueue类似,都向用户空间提供了自己的文件描述符来进行操作。
IO模型实现reactor 模式的工作流程:
(1)主线程向epoll内核事件表内注册socket上的可读就绪事件。
(2)主线程调用epoll_wait()等待socket上有数据可读
(3)当socket上有数据可读,epoll_wait 通知主线程。主线程从socket可读事件放入请求队列。
(4)睡眠在请求队列上的某个可读工作线程被唤醒,从socket上读取数据,处理客户的请求。
然后向 epoll内核事件表里注册写的就绪事件
(5)主线程调用epoll_wait()等待数据可写 。
/*!
* Email: scictor@gmail.com
* Auth: scictor
* Date: 2019-9-9
* File: epoll_reactor_threadpoll.h
* Class: epoll_reactor_threadpoll (if applicable)
* Brief:
* Note:
*/
#ifndef THREAD_POOL_H
#define THREAD_POOL_H #include <pthread.h>
#include <semaphore.h> typedef void *(*func_call)(void*); /*Individual job*/
typedef struct thpool_job_t {
// void (*function)(void* arg); //函数指针
void *(*function)(void* arg); //函数指针
void *arg ; //函数的参数
/*struct tpool_job_t *next ; //指向下一个任务
struct tpool_job_t *prev ; //指向前一个任务*/
struct thpool_job_t *next ; //指向下一个任务
struct thpool_job_t *prev ; //指向前一个任务
}thpool_job_t ; /*job queue as doubly linked list双向链表*/
typedef struct thpool_jobqueue {
thpool_job_t *head ; //队列的头指针(头部添加job任务)
thpool_job_t *tail; //对列的尾指针(尾部做任务,并移除)
int jobsN; //队列中工作的个数
sem_t *queueSem; //原子信号量
}thpool_jobqueue; /*thread pool*/ typedef struct thpool_t {
pthread_t *threads ; //线程的ID
int threadsN ; //线程的数量
thpool_jobqueue *jobqueue; //工作队列的指针 }thpool_t; /*线程池中的线程都需要互斥锁和指向线程池的一个指针*/
typedef struct thread_data{
pthread_mutex_t *mutex_p ;
thpool_t *tp_p ;
}thread_data; /*
* 初始化线程池
* 为线程池, 工作队列, 申请内存空间,信号等申请内存空间
* @param :将被使用的线程ID
* @return :成功返回的线程池结构体,错误返回null
*/ thpool_t *thpool_init (int threadsN); /*
* 每个线程要做的事情
* 这是一个无止境循环,当撤销这线程池的时候,这个循环才会被中断
*@param: 线程池
*@return:不做任何的事情
*/ void thpool_thread_do (thpool_t *tp_p); /*
*向工作队列里面添加任何
*采用来了一个行为和他的参数,添加到线程池的工作对列中去,
* 如果你想添加工作函数,需要更多的参数,通过传递一个指向结构体的指针,就可以实现一个接口
* ATTENTION:为了不引起警告,你不得不将函数和参数都带上
*
* @param: 添加工作的线程线程池
* @param: 这个工作的处理函数
* @param:函数的参数
* @return : int
*/ int thpool_add_work (thpool_t *tp_p ,void* (*function_p) (void *), void* arg_p ); /*
*摧毁线程池
*
*这将撤销这个线程池和释放所申请的内存空间,当你在调用这个函数的时候,存在有的线程还在运行中,那么
*停止他们现在所做的工作,然后他们被撤销掉
* @param:你想要撤销的线程池的指针
*/ void thpool_destory (thpool_t *tp_p); /*-----------------------Queue specific---------------------------------*/ /*
* 初始化队列
* @param: 指向线程池的指针
* @return :成功的时候返回是 0 ,分配内存失败的时候,返回是-1
*/
int thpool_jobqueue_init (thpool_t *tp_p); /*
*添加任务到队列
*一个新的工作任务将被添加到队列,在使用这个函数或者其他向别的类似这样
*函数 thpool_jobqueue_empty ()之前,这个新的任务要被申请内存空间
*
* @param: 指向线程池的指针
* @param:指向一个已经申请内存空间的任务
* @return nothing
*/
void thpool_jobqueue_add (thpool_t * tp_p , thpool_job_t *newjob_p); /*
* 移除对列的最后一个任务
*这个函数将不会被释放申请的内存空间,所以要保证
*
*@param :指向线程池的指针
*@return : 成功返回0 ,如果对列是空的,就返回-1
*/
int thpool_jobqueue_removelast (thpool_t *tp_p); /*
*对列的最后一个任务
*在队列里面得到最后一个任务,即使队列是空的,这个函数依旧可以使用
*
*参数:指向线程池结构体的指针
*返回值:得到队列中最后一个任务的指针,或者在对列是空的情况下,返回是空
*/
thpool_job_t * thpool_jobqueue_peek (thpool_t *tp_p); /*
*移除和撤销这个队列中的所有任务
*这个函数将删除这个队列中的所有任务,将任务对列恢复到初始化状态,因此队列的头和对列的尾都设置为NULL ,此时队列中任务= 0
*
*参数:指向线程池结构体的指针
*
*/
void thpool_jobqueue_empty (thpool_t *tp_p); #endif
/*!
* Email: scictor@gmail.com
* Auth: scictor
* Date: 2019-9-9
* File: epoll_reactor_threadpoll.cpp
* Class: epoll_reactor_threadpoll (if applicable)
* Brief:
* Note:
*/
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h> #include "threadPool.h" static int thpool_keepalive = ; //线程池保持存活
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ; //静态赋值法初始化互斥锁 thpool_t * thpool_init (int threadsN){
thpool_t *tp_p ; if (!threadsN || threadsN < ){
threadsN = ; } tp_p = (thpool_t *)malloc (sizeof (thpool_t)) ;
if (tp_p == NULL){
fprintf (stderr ,"thpool_init (): could not allocate memory for thread pool\n");
return NULL ;
}
tp_p->threads = (pthread_t *)malloc (threadsN * sizeof (pthread_t));
if (tp_p->threads == NULL){
fprintf( stderr , "could not allocation memory for thread id\n");
return NULL;
}
tp_p->threadsN = threadsN ; if (thpool_jobqueue_init (tp_p) == -){
fprintf (stderr ,"could not allocate memory for job queue\n");
return NULL;
} /*初始化信号*/
tp_p->jobqueue->queueSem = (sem_t *)malloc (sizeof (sem_t)); /*定位一个匿名信号量,第二个参数是0表示。这个信号量将在进程内的线程是共享的,为1表示进程间共享,第三个参数是信号量的初始值*/
sem_init (tp_p->jobqueue->queueSem, , ); int t ; for (t = ; t < threadsN ; t++){
printf ("Create thread %d in pool\n", t); //第四个参数是传递给函数指针的一个参数,这个函数指针就是我们所说的线程指针
if (pthread_create (&(tp_p->threads[t]) , NULL , (void *) thpool_thread_do , (void *)tp_p)){
free (tp_p->threads); free (tp_p->jobqueue->queueSem);
free (tp_p->jobqueue);
free (tp_p);
}
}
return tp_p ;
} /*
* 初始化完线程应该处理的事情
* 这里存在两个信号量,
*/ void thpool_thread_do (thpool_t *tp_p){
while (thpool_keepalive)
{
if (sem_wait (tp_p->jobqueue->queueSem)) //如果工作队列中没有工作,那么所有的线程都将在这里阻塞,当他调用成功的时候,信号量-1
{
fprintf(stderr , "Waiting for semaphore\n");
exit ();
} if (thpool_keepalive)
{
void *(*func_buff) (void *arg);
void *arg_buff;
thpool_job_t *job_p; pthread_mutex_lock (&mutex);
job_p = thpool_jobqueue_peek (tp_p);
func_buff = job_p->function ;
arg_buff= job_p->arg ;
thpool_jobqueue_removelast (tp_p);
pthread_mutex_unlock (&mutex); func_buff (arg_buff); free (job_p);
}
else
{
return ;
}
}
return ; } int thpool_add_work (thpool_t *tp_p ,void * (*function_p )(void *), void *arg_p){ thpool_job_t *newjob ; newjob = (thpool_job_t *)malloc (sizeof (thpool_job_t));
if (newjob == NULL)
{
fprintf (stderr,"couldnot allocate memory for new job\n");
exit ();
}
newjob->function = function_p ;
newjob->arg = arg_p ; pthread_mutex_lock (&mutex);
thpool_jobqueue_add (tp_p ,newjob);
pthread_mutex_unlock (&mutex);
return ;
} void thpool_destory (thpool_t *tp_p){
int t ; thpool_keepalive = ; //让所有的线程运行的线程都退出循环 for (t = ; t < (tp_p->threadsN) ; t++ ){ //sem_post 会使在这个线程上阻塞的线程,不再阻塞
if (sem_post (tp_p->jobqueue->queueSem) ){
fprintf (stderr,"thpool_destory () : could not bypass sem_wait ()\n");
} }
if (sem_destroy (tp_p->jobqueue->queueSem)!= ){
fprintf (stderr, "thpool_destory () : could not destroy semaphore\n");
} for (t = ; t< (tp_p->threadsN) ; t++)
{
pthread_join (tp_p->threads[t], NULL);
}
thpool_jobqueue_empty (tp_p);
free (tp_p->threads);
free (tp_p->jobqueue->queueSem);
free (tp_p->jobqueue);
free (tp_p); } int thpool_jobqueue_init (thpool_t *tp_p)
{
tp_p->jobqueue = (thpool_jobqueue *)malloc (sizeof (thpool_jobqueue));
if (tp_p->jobqueue == NULL)
{
fprintf (stderr ,"thpool_jobqueue malloc is error\n");
return - ;
}
tp_p->jobqueue->tail = NULL ;
tp_p->jobqueue->head = NULL ;
tp_p->jobqueue->jobsN = ;
return ; } void thpool_jobqueue_add (thpool_t *tp_p , thpool_job_t *newjob_p){
newjob_p->next = NULL ;
newjob_p->prev = NULL ; thpool_job_t *oldfirstjob ;
oldfirstjob = tp_p->jobqueue->head; switch (tp_p->jobqueue->jobsN)
{
case :
tp_p->jobqueue->tail = newjob_p;
tp_p->jobqueue->head = newjob_p;
break;
default :
oldfirstjob->prev= newjob_p ;
newjob_p->next = oldfirstjob ;
tp_p->jobqueue->head= newjob_p;
break;
} (tp_p->jobqueue->jobsN)++ ;
sem_post (tp_p->jobqueue->queueSem); //原子操作,信号量增加1 ,保证线程安全 int sval ;
sem_getvalue (tp_p->jobqueue->queueSem , &sval); //sval表示当前正在阻塞的线程数量 } int thpool_jobqueue_removelast (thpool_t *tp_p){
thpool_job_t *oldlastjob , *tmp;
oldlastjob = tp_p->jobqueue->tail ; switch (tp_p->jobqueue->jobsN)
{
case :
return - ;
break;
case :
tp_p->jobqueue->head = NULL ;
tp_p->jobqueue->tail = NULL ;
break;
default :
tmp = oldlastjob->prev;
tmp->next = NULL ;
tp_p->jobqueue->tail = oldlastjob->prev; }
(tp_p->jobqueue->jobsN) -- ;
int sval ;
sem_getvalue (tp_p->jobqueue->queueSem, &sval);
printf("sval:%d\n", sval);
return ;
}
thpool_job_t * thpool_jobqueue_peek (thpool_t *tp_p){
return tp_p->jobqueue->tail ;
} void thpool_jobqueue_empty (thpool_t *tp_p)
{
thpool_job_t *curjob;
curjob = tp_p->jobqueue->tail ;
while (tp_p->jobqueue->jobsN){
tp_p->jobqueue->tail = curjob->prev ;
free (curjob);
curjob = tp_p->jobqueue->tail ;
tp_p->jobqueue->jobsN -- ;
}
tp_p->jobqueue->tail = NULL ;
tp_p->jobqueue->head = NULL ;
}
/*!
* Email: scictor@gmail.com
* Auth: scictor
* Date: 2019-9-9
* File: epoll_reactor_main.cpp
* Class: %{Cpp:License:ClassName} (if applicable)
* Brief:
* Note:
*/
#include <arpa/inet.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <pthread.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include "threadPool.h" #define MAX_EVENT_NUMBER 1000
#define SIZE 1024
#define MAX 10 //从主线程向工作线程数据结构
struct fd
{
int epollfd;
int sockfd ;
}; //用户说明
struct user
{
int sockfd ; //文件描述符
char client_buf [SIZE]; //数据的缓冲区
};
struct user user_client[MAX]; //定义一个全局的客户数据表 /*
EPOLL事件有两种模型 Level Triggered (LT) 和 Edge Triggered (ET):
LT(level triggered,水平触发模式)是缺省的工作方式,并且同时支持 block 和 non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
ET(edge-triggered,边缘触发模式)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次触发就绪事件。
*/
//由于epoll设置的EPOLLONESHOT模式,当出现errno =EAGAIN,就需要重新设置文件描述符(可读)
void reset_oneshot (int epollfd , int fd)
{
struct epoll_event event ;
event.data.fd = fd ;
/*
EPOLLONESHOT:
epoll有两种触发的方式即LT(水平触发)和ET(边缘触发)两种,在前者,只要存在着事件就会不断的触发,直到处理完成,而后者只触发一次相同事件或者说只在从非触发到触发两个状态转换的时候儿才触发。
这会出现下面一种情况,如果是多线程在处理,一个SOCKET事件到来,数据开始解析,这时候这个SOCKET又来了同样一个这样的事件,而你的数据解析尚未完成,那么程序会自动调度另外一个线程或者进程来处理新的事件,这造成一个很严重的问题,不同的线程或者进程在处理同一个SOCKET的事件,这会使程序的健壮性大降低而编程的复杂度大大增加!!即使在ET模式下也有可能出现这种情况!!
解决这种现象有两种方法,一种是在单独的线程或进程里解析数据,也就是说,接收数据的线程接收到数据后立刻将数据转移至另外的线程。
第二种方法就是本文要提到的EPOLLONESHOT这种方法,可以在epoll上注册这个事件,注册这个事件后,如果在处理写成当前的SOCKET后不再重新注册相关事件,那么这个事件就不再响应了或者说触发了。要想重新注册事件则需要调用epoll_ctl重置文件描述符上的事件,这样前面的socket就不会出现竞态这样就可以通过手动的方式来保证同一SOCKET只能被一个线程处理,不会跨越多个线程。 events 可以是以下几个宏的集合:
EPOLLIN //表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT //表示对应的文件描述符可以写;
EPOLLPRI //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR //表示对应的文件描述符发生错误;
EPOLLHUP //表示对应的文件描述符被挂断;
EPOLLET //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT//只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。 EPOLLIN事件:
当对方关闭连接(FIN), EPOLLERR,都可以认为是一种EPOLLIN事件,在read的时候分别有0,-1两个返回值。
*/
event.events = EPOLLIN|EPOLLET|EPOLLONESHOT ;
epoll_ctl (epollfd , EPOLL_CTL_MOD, fd , &event); }
//向epoll内核事件表里面添加可写的事件
int addreadfd (int epollfd , int fd , int oneshot)
{
struct epoll_event event;
event.data.fd = fd ;
event.events |= ~ EPOLLIN ;
event.events |= EPOLLOUT ;
event.events |= EPOLLET;
if (oneshot)
{
event.events |= EPOLLONESHOT ; //设置EPOLLONESHOT }
/*
EPOLL_CTL_ADD //注册新的fd到epfd中;
EPOLL_CTL_MOD //修改已经注册的fd的监听事件;
EPOLL_CTL_DEL //从epfd中删除一个fd;
*/
epoll_ctl (epollfd , EPOLL_CTL_MOD ,fd , &event); }
//群聊函数,相当于放入缓存
int groupchat (int epollfd , int sockfd , char *buf)
{
int i = ;
for ( i = ; i < MAX ; i++)
{
if (user_client[i].sockfd == sockfd)
{
continue ;
}
strncpy (user_client[i].client_buf ,buf , strlen (buf));
addreadfd (epollfd , user_client[i].sockfd , );
}
}
//接受数据的函数,也就是线程的回调函数
void *funcation (void *args)
{
int sockfd = ((struct fd*)args)->sockfd;
int epollfd =((struct fd*)args)->epollfd;
char buf[SIZE];
memset (buf , '\0', SIZE); printf ("start new thread to receive data on fd :%d\n", sockfd); //由于我将epoll的工作模式设置为ET模式,所以就要用一个循环来读取数据,防止数据没有读完,而丢失。
while ()
{
int ret = recv (sockfd ,buf , SIZE- , );
if (ret == )
{
close (sockfd);
break;
}
else if (ret < )
{
if (errno == EAGAIN)
{
reset_oneshot (epollfd, sockfd); //重新设置(上面已经解释了)
break;
}
}
else
{
printf (" read data is %s\n", buf);
// sleep (5);
groupchat (epollfd , sockfd, buf );
} }
printf ("end thread receive data on fd : %d\n", sockfd);
return NULL;
}
//这是重新注册,将文件描述符从可写变成可读
int addagainfd (int epollfd , int fd)
{
struct epoll_event event;
event.data.fd = fd;
event.events |= ~EPOLLOUT ;
event.events = EPOLLIN|EPOLLET|EPOLLONESHOT;
epoll_ctl (epollfd , EPOLL_CTL_MOD , fd , &event);
}
//与前面的解释一样
int reset_read_oneshot (int epollfd , int sockfd)
{
struct epoll_event event;
event.data.fd = sockfd ;
event.events = EPOLLOUT |EPOLLET |EPOLLONESHOT;
epoll_ctl (epollfd, EPOLL_CTL_MOD , sockfd , &event);
return ; } //发送读的数据到所有客户端
//int readfun (void *args)
void *readfun (void *args)
{
int sockfd = ((struct fd *)args)->sockfd ;
int epollfd= ((struct fd*)args)->epollfd ; int ret = send (sockfd, user_client[sockfd].client_buf , strlen (user_client[sockfd].client_buf), ); //发送数据
if (ret == )
{
close (sockfd);
printf ("发送数据失败\n");
return (void *)- ;
}
else if (ret == EAGAIN)
{
reset_read_oneshot (epollfd , sockfd);
printf("send later\n");
return (void *)-;
}
memset (&user_client[sockfd].client_buf , '\0', sizeof (user_client[sockfd].client_buf));
addagainfd (epollfd , sockfd);//重新设置文件描述符
return NULL;
}
//套接字设置为非阻塞
int setnoblocking (int fd)
{
int old_option = fcntl (fd, F_GETFL);
int new_option = old_option|O_NONBLOCK;
fcntl (fd , F_SETFL , new_option);
return old_option ;
} int addfd (int epollfd , int fd , int oneshot)
{
struct epoll_event event;
event.data.fd = fd ;
event.events = EPOLLIN|EPOLLET ;
if (oneshot)
{
event.events |= EPOLLONESHOT ; }
epoll_ctl (epollfd , EPOLL_CTL_ADD ,fd , &event);
setnoblocking (fd);
return ;
} int main(int argc, char *argv[])
{
struct sockaddr_in address ;
const char *ip = "127.0.0.1";
int port = ; memset (&address , , sizeof (address));
address.sin_family = AF_INET ;
inet_pton (AF_INET ,ip , &address.sin_addr);
address.sin_port =htons( port) ; int listenfd = socket (AF_INET, SOCK_STREAM, );
assert (listenfd >=);
int reuse = ;
setsockopt (listenfd , SOL_SOCKET , SO_REUSEADDR , &reuse , sizeof (reuse)); //端口重用,因为出现过端口无法绑定的错误
int ret = bind (listenfd, (struct sockaddr*)&address , sizeof (address));
assert (ret >= ); ret = listen (listenfd , );
assert (ret >=); struct epoll_event events[MAX_EVENT_NUMBER]; int epollfd = epoll_create (); //创建内核事件描述符表
assert (epollfd != -);
addfd (epollfd , listenfd, ); thpool_t *thpool ; //线程池
thpool = thpool_init () ; //线程池的一个初始化 while ()
{
int ret = epoll_wait (epollfd, events, MAX_EVENT_NUMBER , -);//等待就绪的文件描述符,这个函数会将就绪的复制到events的结构体数组中。
if (ret < )
{
printf ("poll failure\n");
break;
}
int i =;
for ( i = ; i < ret; i++ )
{
int sockfd = events[i].data.fd ; if (sockfd == listenfd)
{
struct sockaddr_in client_address ;
socklen_t client_length = sizeof (client_address);
int connfd = accept (listenfd , (struct sockaddr*)&client_address,&client_length);
user_client[connfd].sockfd = connfd ;
memset (&user_client[connfd].client_buf , '\0', sizeof (user_client[connfd].client_buf));
addfd (epollfd , connfd , );//将新的套接字加入到内核事件表里面。
}
else if (events[i].events & EPOLLIN)
{
struct fd fds_for_new_worker ;
fds_for_new_worker.epollfd = epollfd ;
fds_for_new_worker.sockfd = sockfd ; thpool_add_work (thpool, funcation ,&fds_for_new_worker);//将任务添加到工作队列中
}else if (events[i].events & EPOLLOUT)
{ struct fd fds_for_new_worker ;
fds_for_new_worker.epollfd = epollfd ;
fds_for_new_worker.sockfd = sockfd ;
thpool_add_work (thpool, readfun , &fds_for_new_worker );//将任务添加到工作队列中
} } } thpool_destory (thpool);
close (listenfd);
return EXIT_SUCCESS;
}
这个例子只是作为学习参考,里面有一些问题,可以试着修改以此提高你对epoll的认识.
可以使用telnet作为测试客户端:$ telnet 127.0.0.1 8086
一起学习epoll的更多相关文章
- 利用epoll写一个"迷你"的网络事件库
epoll是linux下高性能的IO复用技术,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率.另一点原因就是获取 ...
- epoll 系列函数简介、与select、poll 的区别
一.epoll 系列函数简介 #include <sys/epoll.h> int epoll_create(int size); int epoll_create1(int flags) ...
- 从I/O事件到阻塞、非阻塞、poll到epoll的理解过程
I/O事件 I/O事件 非阻塞I/O.在了解非阻塞I/O之前,需要先了解I/O事件 我们知道,内核有缓冲区.假设有两个进程A,B,进程B想读进程A写入的东西(即进程A做写操作,B做读操作).进程A ...
- epoll 原理
本文转载自epoll 原理 导语 以前经常被人问道 select.poll.epoll 的区别,基本都是靠死记硬背的,最近正好复习 linux 相关的内容,就把这一块做个笔记吧,以后也能方便查阅. e ...
- 【深入浅出Linux网络编程】 “基础 -- 事件触发机制”
回顾一下“"开篇 -- 知其然,知其所以然"”中的两段代码,第一段虽然只使用1个线程但却也只能处理一个socket,第二段虽然能处理成百上千个socket但却需要创建同等数量的线程 ...
- IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇) 当你发现自己最受欢迎的一篇blog其实大错特错时,这绝对不是一件让人愉悦的事. <IO - 同步,异步,阻塞,非阻塞 >是我在开始学习e ...
- 深入NIO Socket实现机制(转)
http://www.jianshu.com/p/0d497fe5484a# 前言 Java NIO 由以下几个核心部分组成: Buffer Channel Selector 以前基于net包进行so ...
- IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)【转】
同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别? 详细请看下链接: IO ...
- 【UNIX】select、poll、epoll学习
三者都是UNIX下多路复用的内核接口,select是跨平台的接口,poll是systemV标准,epoll是linux专有的接口,基于poll改造而成. select 函数原型: int select ...
随机推荐
- CentOS7下的AIDE入侵检测配置
一.AIDE的概念 AIDE:Advanced Intrusion Detection Environment,是一款入侵检测工具,主要用途是检查文档的完整性.AIDE在本地构造了一个基准的数据库,一 ...
- Codeforces I. Barcelonian Distance(暴力)
题目描述: In this problem we consider a very simplified model of Barcelona city. Barcelona can be repres ...
- P4139 上帝与集合的正确用法[欧拉定理]
题目描述 求 \[ 2^{2^{2\cdots}} ~mod ~p \] 简单题,指数循环节. 由于当\(b>=\psi(p)\)时,有 \[ a^b=a^{b ~mod~\psi(p)+\ps ...
- QT 子文件的建立(pri)
QT 在做项目的时候有时会有许多不同种类的文件,如果这些文件放在一起会显得特别乱,我们可以将文件用文件夹分类,这样会比较有条理. 1. 在项目文件夹下建立新的文件夹,并在文件夹中添加文本文档将后缀改为 ...
- zabbix4.2.5常见问题指南
一.zabbix配置postgres监控 rpm -ivh https://download.postgresql.org/pub/repos/yum/9.5/redhat/rhel-7-x86_64 ...
- HBase应用
太多column family的影响 每个 MemoryStore分配到的内存较少,进而导致过多的合并,影响性能 几个column family比较合适呢 推荐是:1-3个 划分column fa ...
- 使用docker搭建etcd
下载etcd代码然后拷贝到服务器 来自为知笔记(Wiz)
- Kali Linux 2019.4中文乱码解决
1.先换源deb http://mirrors.aliyun.com/kali kali-rolling main non-free contribdeb-src http://mirrors.ali ...
- WinDbg常用命令系列---断点操作b*
ba (Break on Access) ba命令设置处理器断点(通常称为数据断点,不太准确).此断点在访问指定内存时触发. 用户模式下 [~Thread] ba[ID] Access Size [O ...
- 使用css3变量创建炫酷悬停效果
原文地址:www.zcfy.cc/article/stunning-hover-effects-with-css-variables 效果: 主要使用css中的var做动画效果,代码如下: <! ...