网络io与io 多路复用

本文介绍了网络IO操作中如何通过线程池和io多路复用技术(如select/poll)提高性能,包括阻塞和非阻塞IO的区别,以及在Linux环境下如何使用poll和accept函数实现并发连接处理。
摘要由CSDN通过智能技术生成

网络io与io 多路复用

本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接,详细查看详细的服务:链接


    阻塞io 
        等待某个条件的满足
        accep就是一个阻塞函数,当有一个链接成功 满足需求,自然就退出了
        
        
    非阻塞io 
        自动运行,不需要 我们满足某些条件
            //非阻塞
            通过这个方式可以吧阻塞io 变成非阻塞
        int flags = fcntl(sockfd,F_GETFL,0);
        flags |= O_NONBLOCK;
        fcntl(sockfd,F_SETFD,flags);

    
    
    ret也是个阻塞函数    
    先有得连接,才有得accept,链接成功与否和accept没关系
    
    

fd是字节套 依次增加的,谁先创建是就是 靠前

网络io

网络io可以通过循环进行,监听请求,但是这样 只能一次处理一个,不能实现多线程, 也可以使用线程池方式,但是线程池方式 缺点占用空间较大

int main()
{

    //open
    int sockfd = socket(AF_INET, SOCK_STREAM,0);//  获取字节套件,AF_INET表示使用ipv4 地址,sock——stream表示使用tcp
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(struct sockaddr_in)); // 清零
    servaddr.sin_family = AF_INET; //设置为ipv4
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置 ip 为任意ip可以绑定
    servaddr.sin_port = htons(9999); //监听端口9999

    if (-1==bind(sockfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr_in))){ //如果绑定失败提示错误信息,这里-1 代表错误
        //将一个特定的地址和端口,绑定监听,以便客户端可以连接

        printf("bind failed : %s",strerror(errno));
        return -1;
    }

    listen(sockfd,10); //开始监听连接请求,第二个参数是等待连接的最大数量


通过线程池方式进行

    
    void *client_thread(void *arg){


    int fd = *(int*)arg;

    while (1){
        char buffer[buffer_legth]={0};
        int ret = recv(fd,buffer,buffer_legth,0); //接收数据
        // 如果 ret 为 0,表示对方已经关闭了连接。
        // 如果 ret 为负数,表示接收时发生错误。
        //如果超过的话需要多次接受 可以 recv(fd,buffer,sizeof(buffer),0);  ret < sizeof(buffer);


        if(ret == 0){
            close(fd);
            break;
        }
        //判断是否接收完毕 ,关闭

        printf("buffer : %s \n",buffer);
        send(fd,buffer,ret,0);
        //fd: 要发送数据的文件描述符,通常是一个已连接的套接字。
        //buffer: 包含要发送数据的缓冲区的起始地址。
        //ret: 要发送的字节数,通常是通过 recv 函数接收到的字节数。
        //0: 额外的标志,这里传入 0 表示默认行为。
        //发送数据

    }
    }

    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);


    while (1){
        int clentfd = accept(sockfd,(struct sockaddr*)&clientaddr,&len);//accept 函数用于等待并接受客户端的连接请求。
    //printf("clentfd %d,sockfd : %d\n",clentfd,sockfd);

多路复用

多个客服端都能链接,每一个都能收发数据

多线程操作

每个链接开辟一个线程

io多路复用

把一堆 客户端的io 链接进行管理

select
poll
epoll
io_uring

kqueue //mac
iocp //windows

select 和 poll的缺点
1.效率 poll大于 select
2.可拓展性,select 对监控文件描述符数量有有限制,poll 没有明显限制
3.select

梳理一下流程:
1.首先先初始化 pollfd的结构体数组数组,
    数组值为当前的线程sockfd ,初始化内部的.fd 描述符 监听事件events 的状态
2.初始化 客户端的信息,存储信息、大小、还有返回值、接收客户端的描述符
3.循环接收
4.poll 多路复用
5.判断 当前线程的 pollfd的状态 ,是否为已经使用过
6.线程接受
7.修改状态 fd,events
8.判断 maxfd和  减少 现成循环
9.循环 当前的线程
10.判断 状态,并且recv 现成
11.发送线程
select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

nfds:指定监视的文件描述符的数量,是需要监视的文件描述符最大值加一(通常是最大文件描述符加一)。
readfds:用于监视可读事件的文件描述符集合。
writefds:用于监视可写事件的文件描述符集合。
exceptfds:用于监视异常事件的文件描述符集合。
timeout:设置等待的时间,以 struct timeval 结构体表示。如果设置为 NULL,select 将会一直阻塞,直到有事件发生。
代码
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    fd_set rfds,rset; //查看是否可读
    //rfds 用于存储需要被监视的文件描述符,rset 用于存储传递给 select 函数的待检查的文件描述符集合
    FD_ZERO(&rfds); //置零
    FD_SET(sockfd,&rfds); //把当前文件描述符 传给rfds
    int maxfd = sockfd; // 设置文件描述符
    int clentfd = 0; // 套子节描述符
    while (1){ //循环
        rset = rfds; //rset 是一个临时的文件描述符集合,用于传递给 select 函数,而 rfds 是原始的文件描述符集合
        int nready = select(maxfd+1,&rfds,NULL,NULL,NULL); //  最大监控值+1 , 文件描述符集合,监控文件描述符的状态,
        printf("select event \n");
        if (FD_ISSET(sockfd,&rset)){ //判断socksfd id 是否置1 ,置1 代表可读
            clentfd = accept(sockfd,(struct sockaddr*)&clientaddr,&len);//accept 函数用于等待并接受客户端的连接请求。
            printf("accept %d\n",clentfd);
            FD_SET(clentfd,&rfds);
            if (clentfd > maxfd) maxfd = clentfd;
            if ( -- nready == 0)continue;

        }
        int i = 0;
        for(i = sockfd+1; i<maxfd;i++){
            if (FD_ISSET(i,&rset)){
                char buffer[buffer_legth]={0};
                int ret = recv(clentfd,buffer,buffer_legth,0);
                if (ret == 0){

                    close(clentfd);
                    break;
                }
                printf("buffer : %s \n",buffer);
                send(clentfd,buffer,ret,0);
            }
        }


    }

poll

1.只需要一个数组
2.只有一个接口 poll

io的数量意味着什么
并发

pollfd 是个数组
struct pollfd {
   int     fd;  //描述符
	short   events; //监听事件的状态
	short   revents;    //已经发生的事件
};

    
//声明
struct pollfd fds[POLL_SIZE] = {0};
//定义数组长度,监听文件的描述符最大数量 {0} 是初始化,

fds[sockfd].fd = sockfd;
//fd:表示需要监视的文件描述符。

fds[sockfd].events = POLLIN;
//这行代码设置了要监听的事件类型,POLLIN 表示关注可读事件,即当这个套接字上有数据可读时,会被 poll() 函数通知。
events:表示关心的事件类型,可以是以下几个宏的组合:
POLLIN:表示数据可读(包括连接请求)。
POLLOUT:表示数据可写。
POLLERR:表示发生错误。
POLLHUP:表示挂起。
POLLNVAL:表示无效请求。

第二部分:

struct sockaddr_in clientaddr; //存储客户端的地址信息
socklen_t len = sizeof(clientaddr); //客户端的地址大小
int maxfd = sockfd;//返回的 具体值
int clentfd = 0;  //接收的客户端描述符

第三部分:

while (1){
        int nready = poll(fds,maxfd+1,-1); // 这里是为了 poll 多路复用,fds 是监听描述符看它们是否已经就绪,maxfd+1 当前监听的最大值+1 等待下一个的到来,-1 是超时值一直等待,知道有文件描述符返回
        if (fds[sockfd].revents & POLLIN){ // 状态对比,判断是否为已经使用过的
            clentfd = accept(sockfd,(struct sockaddr_in*)&clientaddr,&len);//accept 函数用于等待并接受客户端的连接请求。
            printf("accept %d\n",clentfd);

            fds[clentfd].fd = clentfd;//  把可以连接的 套子节 描述符,放到 fds 的fd 中
            fds[clentfd].events = POLLIN; //修改状态为 可读写

            if (clentfd > maxfd) maxfd = clentfd; //确保 maxfd 永远为最大值 必须放在accept 之后
            if ( -- nready == 0)continue; //线程为0 取消循环
        }

第四部分 和上面一样,不过是置零了 接受完的数据

for (i = 0; i <= maxfd; i++) {
            if (fds[i].revents & POLLIN){
                char buffer[buffer_legth]={0};
                int ret = recv(clentfd,buffer,buffer_legth,0);
                if (ret == 0){
                    fds[clentfd].fd = -1;
                    fds[clentfd].events = 0;
                    close(clentfd);
                    break;
                }
                printf("buffer : %s \n",buffer);
                send(clentfd,buffer,ret,0);
            }
代码


    struct pollfd fds[POLL_SIZE] = {0};
    fds[sockfd].fd = sockfd;
    fds[sockfd].events = POLLIN;





    //需要查下 pollfd 的数据结构是什么样的
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    int maxfd = sockfd;
    int clentfd = 0;
    while (1){
        int nready = poll(fds,maxfd+1,-1);
        if (fds[sockfd].revents & POLLIN){
            clentfd = accept(sockfd,(struct sockaddr_in*)&clientaddr,&len);//accept 函数用于等待并接受客户端的连接请求。
            printf("accept %d\n",clentfd);

            fds[clentfd].fd = clentfd;
            fds[clentfd].events = POLLIN;

            if (clentfd > maxfd) maxfd = clentfd;
            if ( -- nready == 0)continue;
        }

        int i = 0;
        for (i = 0; i <= maxfd; i++) {
            if (fds[i].revents & POLLIN){
                char buffer[buffer_legth]={0};
                int ret = recv(clentfd,buffer,buffer_legth,0);
                if (ret == 0){
                    fds[clentfd].fd = -1;
                    fds[clentfd].events = 0;
                    close(clentfd);
                    break;
                }
                printf("buffer : %s \n",buffer);
                send(clentfd,buffer,ret,0);
            }
        }
    }

    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值