linux事件管理

linux事件管理

2023年7月15日发(作者:)

linux事件管理1,socket⽹络上的两个程序通过⼀个双向的通信连接实现数据的交换,这个连接的⼀端称为⼀个socket,⽤于描述IP地址和端⼝。socket架构逻辑tcp原理int socket(int domain, int type, int protocol);domain:协议域。常⽤的有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采⽤对应的地址,如AF_INET决定了要⽤ipv4地址(32位的)与端⼝号(16位的)的组合、AF_UNIX决定了要⽤⼀个绝对路径名作为地址。type:指定Socket类型。常⽤的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是⼀种⾯向连接的Socket,针对于⾯向连接的TCP服务应⽤。数据报式Socket(SOCK_DGRAM)是⼀种⽆连接的Socket,对应于⽆连接的UDP服务应⽤。protocol:指定协议。常⽤协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。注意:type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当第三个参数为0时,会⾃动选择第⼆个参数类型对应的默认协议int bind(SOCKET socket, const struct sockaddr* address, socklen_t address_len);socket:是⼀个套接字描述符。address:是⼀个sockaddr结构指针,该结构中包含了要结合的地址和端⼝号。address_len:确定address长度。int accept( int fd, struct socketaddr* addr, socklen_t* len);fd:套接字描述符。addr:返回连接着的地址len:接收返回地址的缓冲区长度注意:accept的第⼀个参数为服务器的socket描述字,是服务器开始调⽤socket()函数⽣成的,称为监听socket描述字;⽽accept函数返回的是已连接的socket描述字。⼀个服务器通常通常仅仅只创建⼀个监听socket描述字,它在该服务器的⽣命周期内⼀直存在。内核为每个由服务器进程接受的客户连接创建了⼀个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭int recv(SOCKET socket, char FAR* buf, int len, int flags);socket:⼀个标识已连接套接⼝的描述字。buf:⽤于接收数据的缓冲区。len:缓冲区长度。flags:指定调⽤⽅式。取值:MSG_PEEK 查看当前数据,数据将被复制到缓冲区中,但并不从输⼊队列中删除;MSG_OOB 处理带外数据。ssize_t recvfrom(int sockfd, void buf, int len, unsigned int flags, struct socketaddr* from, socket_t* fromlen);sockfd:标识⼀个已连接套接⼝的描述字。buf:⽤于接收数据的缓冲区。len:缓冲区长度。flags:调⽤操作⽅式。是以下⼀个或者多个标志的组合体,可通过or操作连在⼀起:(1)MSG_DONTWAIT:操作不会被阻塞;(2)MSG_ERRQUEUE: 指⽰应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的⽅式传递进来,使⽤者应该提供⾜够⼤的缓冲区。导致错误的原封包通过msg_iovec作为⼀般的数据来传递。导致错误的数据报原⽬标地址作为msg_name被提供。错误以sock_extended_err结构形态被使⽤。(3)MSG_PEEK:指⽰数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据。(4)MSG_TRUNC:返回封包的实际长度,即使它⽐所提供的缓冲区更长, 只对packet套接字有效。(5)MSG_WAITALL:要求阻塞操作,直到请求得到完整的满⾜。然⽽,如果捕捉到信号,错误或者连接断开发⽣,或者下次被接收的数据类型不同,仍会返回少于请求量的数据。(6)MSG_EOR:指⽰记录的结束,返回的数据完成⼀个记录。(7)MSG_TRUNC:指明数据报尾部数据已被丢弃,因为它⽐所提供的缓冲区需要更多的空间(8)MSG_CTRUNC:指明由于缓冲区空间不⾜,⼀些控制数据已被丢弃。(9)MSG_OOB:指⽰接收到out-of-band数据(即需要优先处理的数据)。(10)MSG_ERRQUEUE:指⽰除了来⾃套接字错误队列的错误外,没有接收到其它数据。int sendto( SOCKET s, const char FAR* buf, int size, int flags, const struct sockaddr FAR* to, int tolen);2,socketpairsocketpair创建了⼀对⽆名的套接字描述符(只能在AF_UNIX域中使⽤),存储于⼀个⼆元数组,例如sv[2] .每⼀个描述符既可以读也可以写。在同⼀个进程中也可以进⾏通信,向sv[0]中写⼊,就可以从sv[1]中读取(只能从sv[1]中读取),也可以在sv[1]中写⼊,然后从sv[0]中读取;但是,若没有在0端写⼊,⽽从1端读取,则1端的读取操作会阻塞,即使在1端写⼊,也不能从1读取,仍然阻塞;int socketpair(int d, int type, int protocol, int sv[2]);第1个参数d,表⽰协议族,只能为AF_LOCAL或者AF_UNIX第2个参数type,表⽰类型,只能为0第3个参数protocol,表⽰协议,可以是SOCK_STREAM或者SOCK_DGRAM。⽤SOCK_STREAM建⽴的套接字对是管道流,与⼀般的管道相区别的是,套接字对建⽴的通道是双向的,即每⼀端都可以进⾏读写。参数sv,⽤于保存建⽴的套接字对3,IO多路复⽤I/O复⽤模型会⽤到select、poll、epoll函数,这⼏个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。⽽且可以同时对多个读操作,多个写操作的I/O函数进⾏检测,直到有数据可读或可写时,才真正调⽤I/O操作函数。io过程结构异步IO:异步IO不是顺序执⾏。⽤户进程进⾏aio_read系统调⽤之后,⽆论内核数据是否准备好,都会直接返回给⽤户进程,然后⽤户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是⾮阻塞的。⽤户进程发起aio_read操作之后,⽴刻就可以开始去做其它的事。⽽另⼀⽅⾯,从kernel的⾓度,当它收到⼀个asynchronous read之后,⾸先它会⽴刻返回,所以不会对⽤户进程产⽣任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到⽤户内存,当这⼀切都完成之后,kernel会给⽤户进程发送⼀个signal或执⾏⼀个基于线程的回调函数来完成这次 IO 处理过程,告诉它read操作完成了Linux提供了AIO库函数实现异步,但是⽤的很少。⽬前有很多开源的异步IO库,例如libevent、libev、libuv。异步IO⽰例Select:select 函数监视的⽂件描述符分3类,分别是writefds、readfds、和exceptfds。调⽤后select函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有except),或者超时(timeout指定等待时间,如果⽴即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。缺点:1、 单个进程可监视的fd数量被限制,即能监听端⼝的⼤⼩有限。 ⼀般来说这个数⽬和系统内存关系很⼤,具体数⽬可以cat/proc/sys/fs/file-max察看。32位机默认是1024个2、 对socket进⾏扫描时是线性扫描,即采⽤轮询的⽅法,效率较低当套接字⽐较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历⼀遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,⾃动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的3、需要维护⼀个⽤来存放⼤量fd的数据结构,这样会使得⽤户空间和内核空间在传递该结构时复制开销⼤poll:poll本质上和select没有区别,将⽤户传⼊的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加⼊⼀项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它⼜要再次遍历fd。这个过程经历了多次⽆谓的遍历。它没有最⼤连接数的限制,原因是它是基于链表来存储的,但是同样有⼀个缺点:1、⼤量的fd的数组被整体复制于⽤户态和内核地址空间之间,⽽不管这样的复制是不是有意义。2、poll还有⼀个特点是“⽔平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。epoll:epoll⽀持⽔平触发和边缘触发,最⼤的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知⼀次。还有⼀个特点是,epoll使⽤“事件”的就绪通知⽅式,通过epoll_ctl注册fd,⼀旦该fd就绪,内核就会采⽤类似callback的回调机制来激活该fd,epoll_wait便可以收到通知epoll的优点:1、没有最⼤并发连接的限制,能打开的FD的上限远⼤于1024(1G的内存上能监听约10万个端⼝);2、效率提升,不是轮询的⽅式,不会随着FD数⽬的增加效率下降。只有活跃可⽤的FD才会调⽤callback函数;即Epoll最⼤的优点就在于它只管你“活跃”的连接,⽽跟连接总数⽆关,因此在实际的⽹络环境中,Epoll的效率就会远远⾼于select和poll。3、 内存拷贝,利⽤mmap()⽂件映射内存加速与内核空间的消息传递;即epoll使⽤mmap减少复制开销。4,libeventlibevent就是⼀个基于事件通知机制的库,⽀持/dev/poll、kqueue、event ports、select、poll和epoll事件机制,也因此它是⼀个跨操作系统的库为了实际处理每个请求,libevent 库提供⼀种事件机制,它作为底层⽹络后端的包装器。事件系统让为连接添加处理函数变得⾮常简便,同时降低了底层 I/O 复杂性。这是 libevent 系统的核⼼。默认情况下是单线程的,每个线程有且只有⼀个event_base,对应⼀个struct event_base结构体(以及附于其上的事件管理器),⽤来schedule托管给它的⼀系列event,可以和操作系统的进程管理类⽐,当然,要更简单⼀点。当⼀个事件发⽣后,event_base会在合适的时间(不⼀定是⽴即)去调⽤绑定在这个事件上的函数(传⼊⼀些预定义的参数,以及在绑定时指定的⼀个参数),直到这个函数执⾏完,再返回schedule其他事件。struct event_base *base = event_base_new();event_base内部有⼀个循环,循环阻塞在epoll/kqueue等系统调⽤上,直到有⼀个/⼀些事件发⽣,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上。每个事件对应⼀个struct event,可以是监听⼀个fd或者POSIX信号量之类。struct event使⽤event_new来创建和绑定,使⽤event_add来启⽤:struct event

listen_event;listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void)base);event_add(listen_event, NULL);然后需要启动event_base的循环,这样才能开始处理发⽣的事件。循环的启动使⽤event_base_dispatch,循环将⼀直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数event_base_dispatch(base);typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)对于⼀个服务器⽽⾔,上⾯的流程⼤概是这样组合的:1. listener = socket(),bind(),listen(),设置nonblocking2. 创建⼀个event_base3. 创建⼀个event,将该socket托管给event_base,指定要监听的事件类型,并绑定上相应的回调函数。对于listener socket来说,只需要监听EV_READ|EV_PERSIST4. 启⽤该事件5. 进⼊事件循环6. (异步) 当有client发起请求的时候,调⽤该回调函数,进⾏处理。

发布者:admin,转转请注明出处:http://www.yc00.com/news/1689408555a243301.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信