binglinuxc(多路转接)

Posted 月屯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了binglinuxc(多路转接)相关的知识,希望对你有一定的参考价值。

上一篇

目录标题

多路转接IO

接口

select


select使用方式

  1. select共有三个事件集合:读事件集合,写事件集合,异常事件集合
  2. 当需要关注某个文件描述符的某个事件,则将某个文件描述符添加到对应的事件集合当中
    例如:关注0号文件描述符的读事件,则将0号文件描述符添加到读事件集合当中readfds
  3. 如果不关注某种事件,则给select传递参数的时候,传递NULL

select的返回值:

  1. 返回值为就绪的文件描述符的个数
  2. 就绪的文件描述符存储在事件集合当中返回给调用者
    注意:select会将未就绪的文件描述符从事件集合当中去除掉,因此,再次监控的时候需要重新添加

结论:
1.事件集合在内核当中是以数组定义的,但是使用方式是位图
2.位图的大小取决于内核宏__FD_SETSIZE
3.目前的位图的大小为1024比特位,所以select只能监控0~1023号文件描述符

select使用事件集合方式

代码实例

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
    int listen_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(listen_sockfd < 0)
        perror("socket");
        return 0;
    

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(29090);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int ret = bind(listen_sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
        perror("bind");
        return 0;
    

    listen(listen_sockfd, 5);

    /*
     * 测试0号文件描述符的可读事件
     *    0的可读事件怎么触发?标准输入当中输入内容, 回车就能触发
     *
     * 1.初始化一个可读事件集合, 将0号描述符添加进去
     * 2.调用select进行监控
     * 3.处理0号文件描述符的可读事件-read读
     *   读完之后打印
     * */

    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(0, &readfds);
    FD_SET(listen_sockfd, &readfds);

    while(1)
        fd_set tmp = readfds;
        int ret = select(listen_sockfd + 1, &tmp, NULL, NULL, NULL);
        printf("ret : %d\\n", ret);

        if(FD_ISSET(0, &tmp))
            char buf[1024] = 0;
            read(0, buf, sizeof(buf) - 1);
            printf("buf: %s", buf);
        else
            printf("0 is not in readfds\\n");
        

        if(FD_ISSET(listen_sockfd, &tmp))
            //TODO accept
            printf("listen_sockfd ready\\n");
            accept(listen_sockfd, NULL, NULL);
        else
            printf("listen_sockfd not in readfds\\n");
        
    
    return 0;

单线程tcp

select封装

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
#include <vector>

class SelectSvr
    public:
        SelectSvr()
            /*
             * 1. 清空事件集合 + 初始化 max_fd_
             * */
            FD_ZERO(&readfds_);
            max_fd_ = -1;
        

        ~SelectSvr()

        

        void AddFd(int fd)
           /*
            *  1.添加 
            *  2.更新最大文件描述符
            * */ 
            FD_SET(fd, &readfds_);

            if(fd > max_fd_)
                max_fd_ = fd;
            
        

        void DeleteFd(int fd)
            /*
             * 1.移除文件描述符
             * 2.更新
             * */
            FD_CLR(fd, &readfds_);

            // 0 2 5 8
            // 2
            // 0 5 8 == >8
            //
            //
            // 0 2 5 8
            // 8 
            // 0 2 5  => 5
            for(int i = max_fd_; i >= 0; i--)
                if(FD_ISSET(i, &readfds_))
                    max_fd_ = i;
                    break;
                
            
        

        int Select(std::vector<int>* vec)
            int ret = -1;
            while(1)
                fd_set tmp = readfds_;
                ret = select(max_fd_ + 1, &tmp, NULL, NULL, NULL);
                if(ret < 0)
                    return ret;
                else if(ret == 0)
                    continue;
                

                /*
                 * 监控成功
                 * */
                for(int i = 0; i <= max_fd_; i++)
                    if(FD_ISSET(i, &tmp))
                        vec->push_back(i);
                    
                
                break;
            
            return ret;
        

    private:
        fd_set readfds_;

        int max_fd_;
;

服务端单线程

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "my_select.hpp"

/*
 * 1.tcp的初始化工作
 * 2.select监控
 * 3.依照监控进行处理
 *    listen_sock
 *    new_sockfd
 * */

int main()
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_sock < 0)
        perror("socket");
        return 0;
    

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port  = htons(39090);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
        perror("bind");
        return 0;
    

    listen(listen_sock, 5);

    SelectSvr ss;
    ss.AddFd(listen_sock);

    while(1)
        std::vector<int> vec;
        int ret = ss.Select(&vec);
        if(ret < 0)
            continue;
        

        for(size_t i = 0; i < vec.size(); i++)
            if(listen_sock == vec[i])
                //侦听套接字
                struct sockaddr_in peer_addr;
                socklen_t peer_addr_len = sizeof(peer_addr);
                int new_sockfd = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addr_len);
                if(new_sockfd < 0)
                    continue;
                

                ss.AddFd(new_sockfd);
                printf("recv new link, ip : %s, port : %d\\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
            else
               // 新连接套接字有数据到来了
               
               char buf[1024] = 0;
               ssize_t recv_size = recv(vec[i], buf, sizeof(buf) - 1,  0);
               if(recv_size < 0)
                   continue;
               else if(recv_size == 0)
                   ss.DeleteFd(vec[i]);
                   close(vec[i]);
               else
                    printf("[%d sockfd] %s\\n", vec[i], buf);
               

            
        

    

    return 0;

优缺点

优点:
1.According to POSIX.1-2001*/,,遵循posix标准,可以跨平台使用,可以在win平台使用,也可以在linux平台使用
2.select的超时时间可以精确到微妙
缺点:
1.select监控文件描述符的时候,采用轮询遍历的方式,随着监控的文件描述符越多,监控(轮询)效率越低
2.select监控文件描述符的个数是由上限的,.上限取决于内核当中的宏_FD_SETSIZE,这个宏的值为1024
3.select在返回就绪文件描述符的时候,会将未就绪的文件描述符从事件集合当中移除掉,导致二次监控的时候,程序员需要再次手动添加
4.在返回就绪文件描述符的时候,是返回了一个事件集合),并不是将就绪的文件描述符数值直接返回给调用者,需要调用者使用FD_ISSET函数进行判断那些文件描述符就绪了

poll

1.历史地位:跨平台不如select,性能不如后面学到的epoll,比较尴尬的地位
⒉.作用:IO多路转接能够同时等待多个文件描述符的就结状态,换句话说,可以帮助我们同时监控多个文件描述符
3.接口:

int poll(struct pollfd *fds,nfds_t nfds, int timeout);

fds:事件结构数组,存放监控的文件描述符,关心的事件,真实产生的事件nfds:描述fds数组当中有多少有效元素
timeout :超时事件单位s >0:表示带有超时时间的等待 ==0:非阻塞,需要搭配循环来使用<0∶阻塞监控

返回值:> 0:表示多少文件描述符就绪;==0:监控超时;<0:监控出错
例如:无效的文件描述符


#include <stdio.h>
#include <unistd.h>
#include <poll.h>

int main()
    /*
     * 1.准备事件结构数组
     * 2.poll监控
     * 3.处理监控结果
     * */
    struct pollfd arr[10];
    arr[0].fd = 0;
    arr[0].events = POLLIN | POLLOUT; 

    int ret = poll(arr, 1, -1);

    if(arr[0].revents == POLLIN)
        char buf[1024] = 0;
        read(arr[0].fd, buf, sizeof(buf) - 1);

        printf("buf is: %s ", buf);
    

    return  0;


优缺点:

优点:
1.提出了事件结构的方式,在给poll函数传递参数的时候,不需要分别添加到“事件集合”当中。
⒉.事件结构数组的大小可以根据程序员自己进行定义,并没有上限的要求。
3.不用在监控到就绪之后,重新添加文件描述符
缺点:
1.不支持跨平台
2.内核也是对事件结构数组监控的时候采用轮询遍历的方式

epoll

历史定位:迄今为止,linux平台性能最好的IO多路转接模型。没有之一
接口:
创建epoll句柄:

int epoll_create(int size);

size:目前没有含义了,但是需要大于0,兼容旧内核
返回值:返回的epoll操作句柄;

int epoll_ ctl(int epfd,int op, int fd, strult epoll_event *event);

epfd : epoll操作句柄

op :
EPOLL_CTL_ADD
添加一个文件描述符对应的事件结构到epoll当中
EPOLL_CTL_MOD
修改一个文件描述符的事件结构
EPOLL_CTL_DEL
从epoll当中删除–个文件描述符对应的事件结构

fd:待处理(添加,修改,删除)的文件描述

event:文件描述符对应的事件结构

int epoll_wait(int epfd,struct epoll_event *events, int maxevents,int timeout);

epfd : epoll的操作句柄
events :事件结构数组(集合),从epoll当中获取就绪的事件结构
maxevents :最多━次获取多少个事件结构
timeout :> 0:带有超时事件== 0:非阻塞<0:阻塞

返回值:就绪的文件描述符个数

单线程代码
tcp封装

#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <iostream>
#include <string>
#include <cstring>



class TcpSvr
    public:
        TcpSvr(int tcp_port = 28989)
            tcp_port_ = 28989;
        

        ~TcpSvr()

        

        TcpSvr(int sockfd, int tcp_port = 28989)
            sockfd_ = sockfd;
        

        int InitSvr()
            sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
            if(sockfd_ < 0)
                perror("socket");
                return -1;
            

            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(tcp_port_);
            addr.sin_addr.s_addr = inet_addr("0.0.0.0");
            int ret = bind(sockfd_, (struct sockaddr*)&addr,  sizeof(addr));
            if(ret < 0)
                perror("bind");
                return -2;
            

            ret = listen(sockfd_, 5);
            if(ret < 0)
                perror("listen");
                return -3;
            

            return 0;
        

        int Accept()
            struct sockaddr_in p_addr;
            socklen_t p_addr_len = sizeof(p_addr);
            int new_sockfd = accept(sockfd_, (struct sockaddr*)&p_addr, &p_addr_len);
            if(new_sockfd < 0)
                perror("accept");
            
            printf("hava a new link, %s : %d\\n", inet_ntoa(p_addr.sin_addr), ntohs(p_addr.sin_port));

            return new_sockfd;
        

        int Send(std::string& buf)
            return send(sockfd_, buf.c_str(), buf.size(), 0);
        

        int Recv(std::string* buf)
            char b[1024] = 0;
            ssize_t recv_size = recv(sockfd_, b, sizeof(b) - 1, 0);
            std::cout << "sockfd: "<<sockfd_ << std::endl;
            if(recv_size < 0)
                perror("recv");
            else if(recv_size == 0)
                close(sockfd_);
            else
                buf->assign(b, strlen(b));
            
            return recv_size;
        

        int GetSockfd()
            return sockfd_;
        
    private:
        int sockfd_;
        int tcp_port_;
;

epoll

#include <sys/epoll.h>
#include "tcp_svr.hpp"
int main()
    /*
     * 1.初始化tcp的内容
     * 2.监控侦听套接字
     *
     * 3.处理就绪事件
     *   3.1 接收新连接, 将新连接的套接字添加到epoll
     *   3.2 处理新连接套接字当中的数据(接收数据)
     *
     *
     * */

    TcpSvr ts;
    if(ts.InitSvr() < 0)
        return 0;
    

    int e_fd = epoll_create(5);
    if(e_fd < 0)
        perror

以上是关于binglinuxc(多路转接)的主要内容,如果未能解决你的问题,请参考以下文章

binglinuxc(多路转接)

高效IO——多路转接之poll

IO多路转接 ——— selectpollepoll

多路转接(IO复用)接口介绍

关于多路转接select,poll,epoll的重要问题

TCP--IO多路转接模型