【02.服务器编程模型】


本文主要讲述常用的5种IO模型,以及两种事件处理模式

IO模型

IO模型有同步与异步IO之分
同步IO会导致请求进程阻塞,直到IO操作完成
异步IO不会导致请求进程阻塞
简单来讲两者区别就是:内核通知可以启动IO操作,为同步IO;内核通知IO操作已经完成,为异步IO

同步IO

1.阻塞式IO
进程调用系统调用(如recvfrom函数)后,要等待数据到达缓冲区或发生某种错误(如被信号中断)才返回
2.非阻塞式IO
可以将套接字设置成非阻塞,这样即使没有数据到来也会立即返回一个错误(通常为EWOULDBLOCK或EAGAIN)
3.IO复用
使用IO复用,一个进程就可以管理多个套接字
Linux上IO复用方式有三种:select、poll、epoll
使用IO复用就可以阻塞在系统调用中上(如epoll的epoll_wait函数),而不是阻塞在IO系统调用上
4.信号驱动式IO
描述符就绪时(有数据到来),内核发送SIGIO信号通知进程处理此描述符
优点就是等待数据到达期间进程不会阻塞

异步IO

5.可以调用aio_read等异步函数,内核会在整个操作完成时通知进程,在等待IO完成期间,进程不会阻塞
与信号驱动式IO相比,信号是在数据准备好(数据已在应用缓冲区中)才产生

事件处理模式(多线程)

Reactor模式:主线程负责监听socket连接,工作线程负责读写数据以及逻辑处理。一般用于同步IO
Proactor模式:主线程和内核负责监听socket连接以及所有IO操作,工作线程用于逻辑处理。一般用于异步IO

epoll应用相关代码

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>

#include <sys/epoll.h>

#include <string.h>
#include <assert.h>

int main(){
    //创建socket,设置socketfd属性:SOCK_NONBLOCK,非阻塞;SOCK_CLOEXEC,进程被替换时会关闭打开的文件描述符
    int listenfd=socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC,0);
    assert(listenfd>=0);
    //设置监听fd地址
    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family=AF_INET;
    address.sin_addr.s_addr=htonl(INADDR_ANY);
    address.sin_port=htons(1234);
    //绑定地址
    int ret=bind(listenfd,(struct sockaddr*)(&address),sizeof(address));
    assert(ret!=-1);
    //设置监听队列
    assert(listen(listenfd,SOMAXCONN)!=-1);
    //创建epollfd,EPOLL_CLOEXEC:进程被替换时会关闭打开的文件描述符
    int epollfd=epoll_create1(EPOLL_CLOEXEC);
    assert(epollfd!=-1);
    //添加fd以及感兴趣事件(EPOLLIN,读事件;EPOLLOUT,写事件)
    {
        epoll_event event;
        event.data.fd=listenfd;
        event.events=EPOLLIN;
        epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&event);
    }
    epoll_event events[100];
    char read_buf[1024];
    char write_buf[2048];
    while(1)
    {
        //阻塞进程,等待IO事件到来
        int num=epoll_wait(epollfd,events,100,-1);
        assert(num>=0);
        //遍历事件数组
        for(int i=0;i<num;i++)
        {
            int sockfd=events[i].data.fd;
            if(sockfd==listenfd)
            {
                //设置连接fd地址
                struct sockaddr_in client_addr;
                bzero(&client_addr,sizeof(client_addr));
                socklen_t client_len=sizeof(client_addr);
                //接受新连接
                int connfd=accept4(listenfd,(struct sockaddr*)&client_addr,&client_len,SOCK_NONBLOCK|SOCK_CLOEXEC);
                assert(connfd>0);
                printf("new conn fd:%d\n",connfd);
                //添加事件
                epoll_event event;
                event.data.fd=connfd;
                event.events=EPOLLIN;
                epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,&event);
            }
            else if(events[i].events&EPOLLIN)
            {
                memset(read_buf,'\0',sizeof(read_buf));
                int bytes_read=recv(sockfd,read_buf,sizeof(read_buf)-1,0);
                if(bytes_read==0)
                {
                    printf("client %d closed\n",sockfd);
                    close(sockfd);
                }
                else 
                {
                    memset(write_buf,'\0',sizeof(write_buf));
                    strcpy(write_buf,"server receive data:");
                    strcat(write_buf,read_buf);
                    send(sockfd,write_buf,sizeof(write_buf)-1,0);
                }
            }
        }
    }
    close(listenfd);
    return 0;
}

结果:
在这里插入图片描述
EPOLL有两种工作模式:
LT模式(默认):LT会去遍历在epoll事件表中每个文件描述符,只要文件描述符还有感兴趣的事件,每次epoll_wait都会返回。

ET模式(需要设置,EPOLLET):ET在发现有我们感兴趣的事件发生后,立即返回,并且sleep这一事件的epoll_wait,不管该事件有没有结束。例如读事件,有数据要读到空,errno返回EAGAIN或EWOULDBLOCK为止,否则下次epoll_wait返回不会有上一次IO事件,会导致数据不完整
ET需要相应文件描述符非阻塞,否则最后的读写会阻塞进程

通常情况下只需要注册读事件,毕竟写一般都是主动发送就可以了,除非遇到某种情况发送失败或没有发送完才需要注册写事件

#include <sys/epoll.h>

int epoll_create(int size);
创建epoll文件描述符

int epoll_ctl(int epfd,int op,int fd,struct epoll_event event);
op: EPOLL_CTL_ADD 向事件表中注册fd的事件
EPOLL_CTL_MOD 修改fd上的注册事件
EPOLL_CTL_DEL 删除fd上的注册事件
event:指定事件,是epoll_event结构体指针类型
在这里插入图片描述
data可以用于存储用户数据,但注意是联合体类型,因此只能使用其中一个
在这里插入图片描述
int epoll_wait(int epfd,struct epoll_event
events,int maxevents,int timeout);
一段时间内等待一组文件描述符上的事件,返回文件描述符的个数,并将所有就绪事件复制到第二个参数events中

参考
UNIX网络编程 卷1:套接字联网API 第3版
Linux高性能服务器编程——游双

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值