Linux 网络编程4 复用IO

复用IO所需的API

为啥要使用复用IO对网络进行改造?

  1. 应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
  2. 若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
  3. 若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
    所以比较好的方法是使用I/O多路复用。其基本思想是:
  4. 先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
  5. 函数返回时告诉进程那个描述符已就绪,可以进行I/O操作

复用io改造需要的API

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set * readfds, fd_set* writefds, fd_set* exceptfds, struct timeval * timeout);

#include<sys/poll.h>
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
select()参数
-nfds 所有监控文件描述符中最大的哪一个加1
-readfds 所有要读的文件描述符的集合
-writefds 所有要写文件描述符的集合
-exceptfds 其他要向我们通知的文件描述符
-timeout 超时设置
--NULL: 一直阻塞,直到有文件描述符就绪或出错
--时间值为0,仅仅检测文件描述符集的状态,然后立即返回
--时间值不为0,在指定时间内,如果没有事件发生,则超时返回

在我们调用select时进程会一直阻塞直到以下的一种情况发生.

  • 有文件可以读.
  • 有文件可以写.
  • 超时所设置的时间到.
-FD_SET 将fd加到fdset
-FD_CLR 将fd从fdset里清除
-FD_ZERO 从fdset清除所有的文件描述符
-FD_ISSET 判断fd是否在fdset集合中
宏的形式:
void FD_ZERO(fd_set *fdset)
void FD_SET(int fd,fd_set *fdset) 
void FD_CLR(int fd,fd_set *fdset) 
int FD_ISSET(int fd,fd_set *fdset) 

复用IO实现流程图

复用IO编程代码

server 端

#include "net.h"
#include <pthread.h>
#include <sys/select.h>

typedef struct client_info{
    int newfp;
    char ipv4_addr[14];
    int port;
}Client_info, *pClient_info ;


void * process(pClient_info arg);
int main()
{
    fd_set client_net, client_check;
    Client_info cin;
    struct timeval timeout;
    int fp;
    struct sockaddr_in myaddr;
    // 1. open socket
    if((fp = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket fail");
        exit(0);
    }
    int b_reuse = 1;
    setsockopt(fp, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));
    // 2. set bind()
    memset(&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(Netport);

    //优化,使服务器能够接受绑定在任意IP上 INADDR_ANY
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    if(bind(fp,(struct sockaddr*) &myaddr,sizeof(myaddr)) < 0)
    {
        perror("bind fail");
        exit(0);
    }

    // 3. set listen()
    if(listen(fp, BACKLOG) < 0)
    {
        perror("listen fail");
        exit(0);
    }  

    // 4. set accept()
    int newfp = -1;
    
    FD_ZERO(&client_net);
    FD_SET(fp,&client_net);
    int Maxfp = fp;

    struct sockaddr_in recv_addr;
    socklen_t recv_addr_len = sizeof(recv_addr);
    int ret;

    while(1)
    {
        client_check = client_net;
        timeout.tv_sec=5;
        timeout.tv_usec=5000;
    
        if((ret=select(Maxfp+1, &client_check, 0, 0, &timeout))==-1)
            break;
        if(ret==0)
        {
        printf("fd_num=0 \n");
        printf("Maxfp = %d\n", Maxfp);
            continue;
        }
        int i;
        for(i=0; i<Maxfp+1;i++){
            if(FD_ISSET(i,&client_check))
            {
                if(fp == i)                 //有新的连接请求
                {
                    newfp = accept(fp, (struct sockaddr *)&recv_addr, (socklen_t *)&recv_addr_len);
                    if(newfp < 0)
                    {
                        perror("accept fail");
                        exit(0);
                    }
                    FD_SET(newfp,&client_net);
                    if(Maxfp<newfp)
                        newfp=Maxfp;
                    Maxfp++;
                    bzero(cin.ipv4_addr, 16);
                    if(NULL == inet_ntop(AF_INET,(void *)&recv_addr.sin_addr, cin.ipv4_addr, sizeof(recv_addr)))
                    {
                        perror("inet_ntop");
                        exit(0);
                    }
                    cin.port = ntohs(recv_addr.sin_port);
                    cin.newfp = newfp;
                    printf("newfp = %d\n", newfp);
                    printf("success the connet !\n");
                    printf("connect ip address = %s, connect port = %d \n",cin.ipv4_addr, cin.port);
                    }
                else                        //原有fp有新数据传入
                {
                    
                    char buf[BUFSIZ];
                    bzero(buf,BUFSIZ);
                    char buf2[] = "sever send: ";

                    ret = read(i, buf, BUFSIZ-1);
                    if(ret == 0){
                        FD_CLR(i, &client_net);
                        Maxfp--;
                        close(i);
                    }
                    else
                    {
                        printf("(%s : %d): %s \n", cin.ipv4_addr, cin.port, buf); 
                        write(i,strcat(buf2,buf),strlen(strcat(buf2,buf)));
                    }
                }
            }
        }
    }  
    // 5. set close
    close(newfp);
    close(fp);

    return 0;

}


client 端

/*
    复用IO实验
*/
#include "net.h"

int main(int argc, char* argcv[])
{
    if(argc != 3)
    {
        printf("enter error!\n");
        printf("please fill:ip_addr, netport \n");
        exit(0);
    }
    // 1. set 
    int fp = -1;
    if((fp = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket fail");
        exit(0);
    }

    // 2.set connect
    struct sockaddr_in myaddr;
    memset(&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    int netport = atoi(argcv[2]);
    if(netport < 50000)
    {
        netport = Netport;
    }
    myaddr.sin_port = htons(netport);
    //myaddr.sin_addr.s_addr = inet_addr(Netip);
    if(inet_pton(AF_INET, argcv[1], (void *)&myaddr.sin_addr.s_addr) != 1)
    {
        perror("inet_pton fail");
        exit(0);
    }

    if(connect(fp,(struct sockaddr *)&myaddr, sizeof(myaddr)) < 0)
    {
        perror("connect fail");
        exit(0);
    }
    // 3. set read
    char buf[BUFSIZ];
    char recv[BUFSIZ];
    while(1)
    {
        bzero(buf,BUFSIZ);
        bzero(recv, BUFSIZ);
        if(fgets(buf, BUFSIZ, stdin) == NULL)
        {
            continue;
        }
        write(fp, buf, strlen(buf));
        if(!strncmp(buf,QUIT_STR, strlen(QUIT_STR)))
        {
            printf("Client is exiting !\n");
            break;
        }
        read(fp,recv,BUFSIZ-1);
        printf("%s",recv);
        
    }

    close(fp);

    return 0;
}

注意事项

在使用select函数时需要注意以下问题:
select( )函数里面的各个文件描述符fd_set集合的参数在select( )前后发生了变化
slect处理前:表示关心的文件描述符集合
select处理后:有数据的集合(在超时还回情况下)
所以设置两个fd_set,一个用于保存所有产生的文件描述符集合,一个用于进行select检测。

Maxfp的值需要格外注意,当产生newfp后需要找到所有文件描述符中最大的值当做Maxfp,在使用if判断新生成的newfp和Maxfp大小时需要注意有可能两者相等,这时可手动+1,保证后续for循环能顺利找到发生数据变换的文件描述符。且当文件描述符被close后,记得将对应监控位置0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值