1. 进程
$ ps aux //a: 查看所有终端的信息 u: 查看用户相关的信息 x: 显示和终端无关的进程信息
$ kill -l //执行shell命令查看信号
$ kill -SIGKILL 进程ID //无条件杀死进程
1.1 进程控制
# include <sys/types.h>
# include <unistd.h>
# include <stdlib.h>
# include <sys/wait.h>
# include <sys/mman.h>
# include <sys/ipc.h>
# include <sys/shm.h>
# include <signal.h>
# include <sys/time.h>
# include <pthread.h>
# include <semaphore.h>
pid_t getpid ( void )
pid_t getppid ( void )
pid_t fork ( void )
int execl ( const char * path, const char * arg, . . . )
pid_t wait ( int * status)
pid_t waitpid ( pid_t pid, int * status, int options)
1.2 进程通信
1.2.1 匿名管道
ssize_t read ( int fd, void * buf, size_t count)
ssize_t write ( int fd, const void * buf, size_t count)
int pipe ( int pipefd[ 2 ] )
close ( pipefd[ 0 ] ) ;
char * s = "hello" ;
int n = write ( pipefd[ 1 ] , s, strlen ( s) ) ;
close ( pipefd[ 1 ] ) ;
close ( pipefd[ 1 ] ) ;
char buff[ 128 ] = { 0 } ;
int n = read ( pipefd[ 0 ] , buff, 127 ) ;
close ( pipefd[ 0 ] ) ;
1.2.1 有名管道
int mkfifo ( const char * pathname, mode_t mode)
int fd = open ( "./testfifo" , O_WRONLY) ;
write ( fd, "hello" , strlen ( "hello" ) ) ;
close ( fd) ;
int fd = open ( "./testfifo" , O_RDONLY) ;
char buff[ 128 ] = { 0 } ;
int n = read ( pipefd[ 0 ] , buff, 127 ) ;
close ( fd) ;
1.2.2 内存映射区
void * mmap ( void * addr, size_t length, int prot, int flags, int fd, off_t offset)
int munmap ( void * addr, size_t length)
int fd = open ( "./english.txt" , O_RDWR) ;
void * ptr = mmap ( NULL , 4000 , PROT_READ| PROT_WRITE, MAP_SHARED, fd, 0 ) ;
const char * pt = "hello" ;
memcpy ( ptr, pt, strlen ( pt) + 1 ) ;
printf ( "%s" , ( char * ) ptr) ;
munmap ( ptr, 4000 ) ;
1.2.3 共享内存
int shmget ( key_t key, size_t size, int shmflg)
key_t ftok ( const char * pathname, int proj_id)
void * shmat ( int shmid, const void * shmaddr, int shmflg)
int shmdt ( const void * shmaddr)
int shmctl ( int shmid, int cmd, struct shmid_ds * buf)
int shmid = shmget ( 1000 , 4096 , IPC_CREAT| 0664 ) ;
void * ptr = shmat ( shmid, NULL , 0 ) ;
const char * p = "hello" ;
memcpy ( ptr, p, strlen ( p) + 1 ) ;
int shmid = shmget ( 1000 , 0 , 0 ) ;
void * ptr = shmat ( shmid, NULL , 0 ) ;
shmdt ( ptr) ;
shmctl ( shmid, IPC_RMID, NULL ) ;
1.2.3.1 shm 和 mmap区别
1. 实现进程间通信的方式
mmap:位于每个进程的虚拟地址空间中,并且需要关联同一个磁盘文件才能实现进程间数据通信
shm: 多个进程只需要一块共享内存就够了,共享内存不属于进程,需要和进程关联才能使用
2. 生命周期
mmap:进程退出,内存映射区也就没有了
shm:进程退出对共享内存没有影响,调用相关函数 / 命令 / 关机才能删除共享内存
3. 数据的完整性
mmap:可以完整的保存数据,内存映射区数据会同步到磁盘文件
shm:数据存储在物理内存中,断电之后系统关闭,内存数据也就丢失了
1.2.4 信号
$ kill - l
int kill ( pid_t pid, int sig)
int raise ( int sig)
void abort ( void )
unsigned int alarm ( unsigned int seconds)
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
} ;
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
} ;
int setitimer ( int which, const struct itimerval * new_value, struct itimerval * old_value)
sighandler_t signal ( int signum, sighandler_t handler)
void doing ( int arg) {
printf ( "当前捕捉到的信号是: %d\n" , arg) ;
}
signal ( SIGALRM, doing) ;
1.3 守护进程
守护进程是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了
1.3.1 进程组
pid_t getpgrp ( void )
pid_t getpgid ( pid_t pid)
int setpgid ( pid_t pid, pid_t pgid)
1.3.2 会话
pid_t getsid ( pid_t pid)
pid_t setsid ( void )
1.3.3 创建守护进程
1. 创建子进程,让父进程退出
因为父进程有可能是组长进程,不符合条件,也没有什么利用价值,退出即可
子进程没有任何职务,目的是让子进程最终变成一个会话,最终就会得到守护进程
2. 通过子进程创建新的会话,调用函数 setsid (),脱离控制终端,变成守护进程
2 线程
线程是轻量级的进程,在 Linux 环境下线程的本质仍是进程。
进程有自己独立的地址空间,多个线程共用同一个地址空间。
线程是程序的最小执行单位,进程是操作系统中最小的资源分配单位。
CPU 的调度和切换:线程的上下文切换比进程要快的多。
线程更加廉价,启动速度更快,退出也快,对系统资源的冲击小。
gcc pthread_create.c -o main -lpthread
2.1 多线程
pthread_t pthread_self ( void )
int pthread_create ( pthread_t * thread, const pthread_attr_t * attr, void * ( * start_routine) ( void * ) , void * arg)
void pthread_exit ( void * retval)
int pthread_join ( pthread_t thread, void * * retval)
int pthread_detach ( pthread_t thread) ;
int pthread_cancel ( pthread_t thread) ;
struct Person
{
int age;
} ;
struct Persion p;
void * working ( void * arg)
{
p. age = 12 ;
pthread_exit ( & p) ;
return NULL ;
}
pthread_t tid;
pthread_create ( & tid, NULL , working, NULL )
pthread_detach ( tid) ;
void * ptr = NULL ;
pthread_join ( tid, & ptr) ;
pthread_exit ( NULL ) ;
2.2 C++多线程
# include <thread>
thread ( ) noexcept
thread ( thread&& other ) noexcept
template < class Function , class . . . Args >
explicit thread ( Function&& f, Args&& . . . args )
thread ( const thread& ) = delete
std:: thread:: id get_id ( ) const noexcept
this_thread:: sleep_for ( chrono:: seconds ( 1 ) )
this_thread:: sleep_until ( chrono:: system_clock:: now ( ) + sec)
this_thread:: yield ( )
void join ( )
void detach ( )
bool joinable ( ) const noexcept
static unsigned hardware_concurrency ( ) noexcept
2.3 线程同步
常用的线程同步方式有四种:互斥锁、读写锁、条件变量、信号量。
2.3.1 互斥锁
pthread_mutex_t mutex
int pthread_mutex_init ( pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrict attr)
int pthread_mutex_destroy ( pthread_mutex_t * mutex)
int pthread_mutex_lock ( pthread_mutex_t * mutex)
int pthread_mutex_unlock ( pthread_mutex_t * mutex)
int pthread_mutex_trylock ( pthread_mutex_t * mutex)
2.3.2 读写锁
读写锁是互斥锁的升级版,如果所有的线程都是做读操作, 那么读是并行的,但是使用互斥锁,读操作也是串行的
使用读写锁的读锁锁定了临界区,线程对临界区的访问是并行的,读锁是共享的
使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的,写锁是独占的
使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问,写锁运行,读锁阻塞,因为写锁比读锁的优先级高
pthread_rwlock_t rwlock
int pthread_rwlock_init ( pthread_rwlock_t * restrict rwlock, const pthread_rwlockattr_t * restrict attr)
int pthread_rwlock_destroy ( pthread_rwlock_t * rwlock)
int pthread_rwlock_rdlock ( pthread_rwlock_t * rwlock)
int pthread_rwlock_tryrdlock ( pthread_rwlock_t * rwlock)
int pthread_rwlock_wrlock ( pthread_rwlock_t * rwlock)
int pthread_rwlock_trywrlock ( pthread_rwlock_t * rwlock)
int pthread_rwlock_unlock ( pthread_rwlock_t * rwlock)
2.3.3 条件变量
条件变量只有在满足指定条件下才会阻塞线程,如果条件不满足,多个线程可以同时进入临界区,同时读写临界资源
pthread_cond_t cond
int pthread_cond_init ( pthread_cond_t * restrict cond, const pthread_condattr_t * restrict attr)
int pthread_cond_destroy ( pthread_cond_t * cond)
int pthread_cond_wait ( pthread_cond_t * restrict cond, pthread_mutex_t * restrict mutex)
struct timespec {
time_t tv_sec;
long tv_nsec;
} ;
int pthread_cond_timedwait ( pthread_cond_t * restrict cond, pthread_mutex_t * restrict mutex, const struct timespec * restrict abstime)
time_t mytim = time ( NULL ) ;
struct timespec tmsp;
tmsp. tv_nsec = 0 ;
tmsp. tv_sec = time ( NULL ) + 100 ;
int pthread_cond_signal ( pthread_cond_t * cond)
int pthread_cond_broadcast ( pthread_cond_t * cond)
2.3.4 信号量
信号量主要阻塞线程,如果要保证线程安全,需要信号量和互斥锁一起使用
sem_t sem
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_timedwait ( sem_t * sem, const struct timespec * abs_timeout)
int sem_post ( sem_t * sem)
int sem_getvalue ( sem_t * sem, int * sval)
sem_wait ( & csem) ;
pthread_mutex_lock ( & mutex) ;
3 套接字
# include <arpa/inet.h>
# include <sys/socket.h>
# include <sys/select.h>
# include <poll.h>
# include <sys/epoll.h>
uint16_t htons ( uint16_t hostshort) ;
uint16_t ntohs ( uint16_t netshort)
uint32_t htonl ( uint32_t hostlong)
uint32_t ntohl ( uint32_t netlong)
3.1 socket编程
int inet_pton ( int af, const char * src, void * dst) ;
const char * inet_ntop ( int af, const void * src, char * dst, socklen_t size)
in_addr_t inet_addr ( const char * cp)
char * inet_ntoa ( struct in_addr in)
typedef unsigned short uint16_t
typedef unsigned int uint32_t
typedef uint16_t in_port_t
typedef uint32_t in_addr_t
typedef unsigned short int sa_family_t
struct sockaddr {
sa_family_t sa_family;
char sa_data[ 14 ] ;
}
struct in_addr
{
in_addr_t s_addr;
}
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[ 8 ] ;
}
int socket ( int domain, int type, int protocol)
int bind ( int sockfd, const struct sockaddr * addr, socklen_t addrlen)
int listen ( int sockfd, int backlog)
int accept ( int sockfd, struct sockaddr * addr, socklen_t * addrlen)
ssize_t read ( int sockfd, void * buf, size_t size)
ssize_t recv ( int sockfd, void * buf, size_t size, int flags)
ssize_t write ( int fd, const void * buf, size_t len)
ssize_t send ( int fd, const void * buf, size_t len, int flags)
int connect ( int sockfd, const struct sockaddr * addr, socklen_t addrlen)
3.2 TCP通信流程
3.2.1 服务器端
int lfd = socket ( AF_INET, SOCK_STREAM, 0 ) ;
struct sockaddr_in addr;
addr. sin_family = AF_INET;
addr. sin_port = htons ( 10000 ) ;
inet_pton ( AF_INET, "127.0.0.1" , & addr. sin_addr. s_addr) ;
int ret = bind ( lfd, ( struct sockaddr * ) & addr, sizeof ( addr) ) ;
ret = listen ( lfd, 128 ) ;
while ( 1 )
{
struct sockaddr_in cliaddr;
int clilen = sizeof ( cliaddr) ;
int cfd = accept ( lfd, ( struct sockaddr * ) & cliaddr, & clilen) ;
while ( 1 )
{
char buf[ 1024 ] = { 0 } ;
int len = read ( cfd, buf, sizeof ( buf) ) ;
if ( len <= 0 )
{
break ;
}
printf ( "%d : %s\n" , cfd, buf) ;
len = write ( cfd, "OK" , 3 ) ;
}
close ( cfd) ;
}
close ( lfd) ;
3.2.2 客户端
int fd = socket ( AF_INET, SOCK_STREAM, 0 ) ;
struct sockaddr_in addr;
addr. sin_family = AF_INET;
addr. sin_port = htons ( 10000 ) ;
inet_pton ( AF_INET, "127.0.0.1" , & addr. sin_addr. s_addr) ;
int ret = connect ( fd, ( struct sockaddr * ) & addr, sizeof ( addr) ) ;
while ( 1 )
{
char buf[ 1024 ] = { 0 } ;
fgets ( buf, 127 , stdin ) ;
if ( strncmp ( buff, "end" , 3 ) == 0 )
{
break ;
}
write ( fd, buf, strlen ( buf) + 1 ) ;
memset ( buf, 0 , sizeof ( buf) ) ;
len = read ( fd, buf, sizeof ( buf) ) ;
printf ( "recv data:%s\n" , buf) ;
}
close ( fd) ;
3.3 IO复用
通过这种方式可以同时监测多个文件描述符并且这个过程是阻塞的,一旦检测到有文件描述符就绪(可以读数据或者可以写数据)
程序的阻塞就会被解除,之后就可以基于这些就绪的文件描述符进行通信了,常见的 IO 多路转接方式有:select、poll、epoll。
一、多线程/多进程并发
1. 主线程 / 父进程:调用 accept() 监测客户端连接请求
●如果没有新的客户端的连接请求,当前线程 / 进程会阻塞
●如果有新的客户端连接请求解除阻塞,建立连接
2. 子线程 / 子进程:和建立连接的客户端通信
●调用 read()/recv() 接收客户端发送的通信数据,如果没有通信数据,当前线程/进程会阻塞,数据到达之后阻塞自动解除
●调用 write()/send() 给客户端发送数据,如果写缓冲区已满,当前线程/进程会阻塞,否则将待发送数据写入写缓冲区中
二、IO多路转接并发
使用 IO 多路转接函数委托内核检测服务器端所有的文件描述符(通信和监听两类),这个检测过程会导致进程 / 线程的阻塞
如果检测到已就绪的文件描述符阻塞解除,并将这些已就绪的文件描述符传出,根据传出的所有已就绪文件描述符类型,做出不同的处理
1. 监听的文件描述符:和客户端建立连接
●此时调用 accept() 是不会导致程序阻塞的,因为监听的文件描述符是已就绪的(有新请求)
2. 通信的文件描述符:调用通信函数和已建立连接的客户端通信
●调用 read()/recv() 不会阻塞程序,因为通信的文件描述符是就绪的,读缓冲区内已有数据
●调用 write() / send() 不会阻塞程序,因为通信的文件描述符是就绪的,写缓冲区不满,可以往里面写数据
3. 对这些文件描述符继续进行下一轮的检测
结论:与多进程/线程技术相比,I/O 多路复用技术系统开销小,系统不必创建和维护这些进程/线程
3.3.1 select
通过调用select函数检测若干个文件描述符的状态,其实就是检测这些文件描述符对应的读写缓冲区的状态
●读缓冲区:检测里边有没有数据,如果有数据该缓冲区对应的文件描述符就绪
●写缓冲区:检测写缓冲区是否可以写 (有没有容量),如果有,缓冲区对应的文件描述符就绪
●读写异常:检测读写缓冲区是否有异常,如果没有,该缓冲区对应的文件描述符就绪
检测的文件描述符被遍历检测完后,已就绪的文件描述符会通过 select() 的参数分 3 个集合传出
局限性
●待检测集合(第 2、3、4 个参数)需要频繁的在用户区和内核区之间进行数据的拷贝,效率低
●内核对于 select 传递进来的待检测集合的检测方式是线性的(文件描述符多了后会效率低下)
●使用select能够检测的最大文件描述符个数有上限,默认是1024
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
}
int select ( int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval * timeout)
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)
int maxfd = lfd;
fd_set rdset;
fd_set rdtemp;
FD_ZERO ( & rdset) ;
FD_SET ( lfd, & rdset) ;
while ( 1 )
{
rdtemp = rdset;
int num = select ( maxfd+ 1 , & rdtemp, NULL , NULL , NULL ) ;
if ( FD_ISSET ( lfd, & rdtemp) )
{
struct sockaddr_in cliaddr;
int cliLen = sizeof ( cliaddr) ;
int cfd = accept ( lfd, ( struct sockaddr * ) & cliaddr, & cliLen) ;
FD_SET ( cfd, & rdset) ;
maxfd = cfd > maxfd ? cfd : maxfd;
}
for ( int i= 0 ; i< maxfd+ 1 ; ++ i)
{
if ( i != lfd && FD_ISSET ( i, & rdtemp) )
{
char buf[ 10 ] = { 0 } ;
int len = read ( i, buf, sizeof ( buf) ) ;
if ( len == 0 )
{
FD_CLR ( i, & rdset) ;
close ( i) ;
} else if ( len > 0 )
{
write ( i, buf, strlen ( buf) + 1 ) ;
} else
{
perror ( "read" ) ;
}
}
}
}
3.3.2 poll
与select类似,但有两个区别
●poll没有最大文件描述符数量的限制
●select可以跨平台使用,poll只能在Linux平台使用
struct pollfd {
int fd;
short events;
short revents;
}
struct pollfd myfd[ 100 ] ;
int poll ( struct pollfd * fds, nfds_t nfds, int timeout) ;
struct pollfd fds[ 1024 ] ;
for ( int i= 0 ; i< 1024 ; ++ i)
{
fds[ i] . fd = - 1 ;
fds[ i] . events = POLLIN;
}
fds[ 0 ] . fd = lfd;
int maxfd = 0 ;
while ( 1 )
{
int ret = poll ( fds, maxfd+ 1 , - 1 ) ;
if ( fds[ 0 ] . revents & POLLIN)
{
struct sockaddr_in sockcli;
int len = sizeof ( sockcli) ;
int connfd = accept ( lfd, ( struct sockaddr * ) & sockcli, & len) ;
int i;
for ( i= 0 ; i< 1024 ; ++ i)
{
if ( fds[ i] . fd == - 1 )
{
fds[ i] . fd = connfd;
break ;
}
}
maxfd = i > maxfd ? i : maxfd;
}
for ( int i= 1 ; i<= maxfd; ++ i)
{
if ( fds[ i] . revents & POLLIN)
{
char buf[ 128 ] ;
int ret = read ( fds[ i] . fd, buf, sizeof ( buf) ) ;
if ( ret == - 1 )
{
perror ( "read" ) ;
exit ( 0 ) ;
} else if ( ret == 0 )
{
close ( fds[ i] . fd) ;
fds[ i] . fd = - 1 ;
} else
{
printf ( "客户端say: %s\n" , buf) ;
write ( fds[ i] . fd, buf, strlen ( buf) + 1 ) ;
}
}
}
}
close ( lfd) ;
3.3.3 epoll
epoll相对于select和poll,改进了这些
●对于待检测集合select和poll是基于线性方式处理的,epoll是基于红黑树来管理待检测集合的
●epoll使用的是回调机制,效率高,处理效率不会随着检测集合的变大而下降
●select和poll返回的集合进行判断才能知道哪些文件描述符是就绪的,通过epoll可以直接得到已就绪的文件描述符集合,无需再次检测
●使用 epoll 没有最大文件描述符的限制,仅受系统中进程能打开的最大文件数目限制
●适合服用的文件数量庞大,IO流量频繁的情况
int setsockopt ( int socket, int level, int optname, const void * optval, socklen_t optlen)
int epoll_create ( int size)
typedef union epoll_data {
void * ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t ;
struct epoll_event {
uint32_t events;
epoll_data_t data;
} ;
int epoll_ctl ( int epfd, int op, int fd, struct epoll_event * event)
int epoll_wait ( int epfd, struct epoll_event * events, int maxevents, int timeout)
LT(水平)模式
LT是缺省的工作方式,并且同时支持block和no-block socket。
在这种做法中,内核通知使用者哪些文件描述符已经就绪,之后就可以对这些已就绪的文件描述符进行 IO 操作了。
如果我们不作任何操作,内核还是会继续通知使用者。
水平模式的特点:
读事件:如果文件描述符对应的读缓冲区还有数据,读事件就会被触发,epoll_wait () 解除阻塞
●当读事件被触发,epoll_wait () 解除阻塞,之后就可以接收数据了
●如果接收数据的 buf 很小,不能全部将缓冲区数据读出,那么读事件会继续被触发,直到数据被全部读出
●因为读数据是被动的,必须要通过读事件才能知道有数据到达了,因此对于读事件的检测是必须的
写事件:如果文件描述符对应的写缓冲区可写,写事件就会被触发,epoll_wait () 解除阻塞
●当写事件被触发,epoll_wait () 解除阻塞,之后就可以将数据写入到写缓冲区了
●写事件的触发发生在写数据之前而不是之后,被写入到写缓冲区中的数据是由内核自动发送出去的
●如果写缓冲区没有被写满,写事件会一直被触发
●因为写数据是主动的,并且写缓冲区一般情况下都是可写的(缓冲区不满),因此对于写事件的检测不是必须的
int opt = 1
setsockopt ( lfd, SOL_SOCKET, SO_REUSEADDR, & opt, sizeof ( opt) )
int epfd = epoll_create ( 100 ) ;
struct epoll_event ev;
ev. events = EPOLLIN;
ev. data. fd = lfd;
int ret = epoll_ctl ( epfd, EPOLL_CTL_ADD, lfd, & ev) ;
struct epoll_event evs[ 1024 ] ;
int size = sizeof ( evs) / sizeof ( struct epoll_event ) ;
while ( 1 )
{
int num = epoll_wait ( epfd, evs, size, - 1 ) ;
for ( int i= 0 ; i< num; ++ i)
{
int curfd = evs[ i] . data. fd;
if ( curfd == lfd)
{
int cfd = accept ( curfd, NULL , NULL ) ;
ev. events = EPOLLIN;
ev. data. fd = cfd;
ret = epoll_ctl ( epfd, EPOLL_CTL_ADD, cfd, & ev) ;
}
else
{
char buf[ 1024 ] ;
memset ( buf, 0 , sizeof ( buf) ) ;
int len = recv ( curfd, buf, sizeof ( buf) , 0 ) ;
if ( len == 0 )
{
printf ( "客户端已经断开了连接\n" ) ;
epoll_ctl ( epfd, EPOLL_CTL_DEL, curfd, NULL ) ;
close ( curfd) ;
}
else if ( len > 0 )
{
printf ( "客户端say: %s\n" , buf) ;
send ( curfd, buf, len, 0 ) ;
}
}
}
}
ET(边沿)模式
ET是高速工作方式,只支持no-block socket.
当文件描述符就绪时,内核会通过epoll通知一次使用者,不会再为那个文件描述符发送更多的就绪通知
如果对文件描述符做IO操作导致再次变成未就绪,当这个未就绪的文件描述符再次变成就绪状态,内核会再次进行通知,并且还是只通知一次
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高
边沿模式的特点:
读事件:当读缓冲区有新的数据进入,读事件被触发一次,没有新数据不会触发该事件
●如果有新数据进入到读缓冲区,读事件被触发,epoll_wait () 解除阻塞
●读事件被触发,可以通过调用 read ()/recv () 函数将缓冲区数据读出
◆如果数据没有被全部读走,并且没有新数据进入,读事件不会再次触发,只通知一次
◆如果数据被全部读走或者只读走一部分,此时有新数据进入,读事件被触发,并且只通知一次
写事件:当写缓冲区状态可写,写事件只会触发一次
●如果写缓冲区被检测到可写,写事件被触发,epoll_wait () 解除阻塞
●写事件被触发,就可以通过调用 write ()/send () 函数,将数据写入到写缓冲区中
◆写缓冲区从不满到被写满,期间写事件只会被触发一次
◆写缓冲区从满到不满,状态变为可写,写事件只会被触发一次
epoll的边沿模式下epoll_wait ()检测到文件描述符有新事件才会通知,通知的次数比水平模式少,效率比水平模式要高。
int opt = 1
setsockopt ( lfd, SOL_SOCKET, SO_REUSEADDR, & opt, sizeof ( opt) )
int epfd = epoll_create ( 100 ) ;
struct epoll_event ev;
ev. events = EPOLLIN;
ev. data. fd = lfd;
int ret = epoll_ctl ( epfd, EPOLL_CTL_ADD, lfd, & ev) ;
struct epoll_event evs[ 1024 ] ;
int size = sizeof ( evs) / sizeof ( struct epoll_event ) ;
while ( 1 )
{
int num = epoll_wait ( epfd, evs, size, - 1 ) ;
for ( int i= 0 ; i< num; ++ i)
{
int curfd = evs[ i] . data. fd;
if ( curfd == lfd)
{
int cfd = accept ( curfd, NULL , NULL ) ;
int flag = fcntl ( cfd, F_GETFL) ;
flag |= O_NONBLOCK;
fcntl ( cfd, F_SETFL, flag) ;
ev. events = EPOLLIN| EPOOLET;
ev. data. fd = cfd;
ret = epoll_ctl ( epfd, EPOLL_CTL_ADD, cfd, & ev) ;
}
else
{
char buf[ 10 ] ;
memset ( buf, 0 , sizeof ( buf) ) ;
while ( 1 )
{
int len = recv ( curfd, buf, sizeof ( buf) , 0 ) ;
if ( len == 0 )
{
printf ( "客户端已经断开了连接\n" ) ;
epoll_ctl ( epfd, EPOLL_CTL_DEL, curfd, NULL ) ;
close ( curfd) ;
break ;
}
else if ( len > 0 )
{
printf ( "客户端say: %s\n" , buf) ;
send ( curfd, buf, len, 0 ) ;
}
}
}
}
}