IO多路复用select 函数解析

函数select

#include <sys/time.h> 
#include <unistd.h> 
int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);

作用:用来够监视我们需要监视的文件描述符(读或写的文件集中的文件描述符)的状态变化情况。并能通过返回的值告知我们。

int select(
int maxfdp,               //集合中所有文件描述符的范围,为所有文件描述符的最大值加1
fd_set *readfds,          //要进行监视的读文件集
fd_set *writefds,         //要进行监视的读文件集
fd_set *errorfds,         //用于监视异常数据。
struct timeval *timeout   //select的超时时间,它可以使select处于三种状态
);

第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数, 不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。

函数返回值

>0:被监视的文件描述符有变化,返回对应位仍然为1的fd的总数。
-1:出错
0 :超时

===========================================

文件描述字(FD)的集合

fd_set这个类型, fd_set是一组文件描述字(fd)的集合,它用一位来表示一个fd(对于fd_set类型通过下面四个宏来操作:

FD_ZERO

用法:FD_ZERO(fd_set*);

用来清空fd_set集合,即让fd_set集合不再包含任何文件句柄。在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。

FD_SET

用法:FD_SET(int ,fd_set *);

用来将一个给定的文件描述符加入集合之中。

FD_CLR

用法:FD_CLR(int ,fd_set*);

用来将一个给定的文件描述符从集合中删除。

FD_ISSET

用法:FD_ISSET(int ,fd_set*);

检测fd在fdset集合中的状态是否变化,当检测到fd状态发生变化时返回真,否则,返回假(也可以认为集合中指定的文件描述符是否可以读写)

======================================

代码实现

服务器的代码实现

/*
    tcp双向通信--》服务器的代码
    多路复用使用思路:
        1.思考要监测什么文件描述符 --》新的套接字(接收)
                                   --》键盘(发生)
        2.监测新套接字的读就绪
          监测键盘的读就绪
*/

#include "myhead.h"


int main()
{
    int tcpsock;
    int newsock;
    int ret;
    char rbuf[100];
    char sbuf[100];
    
    //定义ipv4地址体变量存放需要绑定的ip和端口号
    struct sockaddr_in bindaddr;
    bzero(&bindaddr,sizeof(bindaddr));
    bindaddr.sin_family=AF_INET;
    bindaddr.sin_addr.s_addr=htonl(INADDR_ANY); //女朋友(服务器)自己的ip
    bindaddr.sin_port=htons(20000); //女朋友(服务器)的端口号
    
    //定义ipv4地址体变量存放客户端的ip和端口号
    struct sockaddr_in boyaddr;
    int addrsize=sizeof(boyaddr);
    
    //创建tcp套接字
    tcpsock=socket(AF_INET,SOCK_STREAM,0);
    if(tcpsock==-1)
    {
        perror("创建tcp套接字失败!\n");
        return -1;
    }
    
    int on=1;
    setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    
    //绑定女朋友(服务器)自己的ip和端口号
    ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
    if(ret==-1)
    {
        perror("绑定ip端口失败了!\n");
        return -1;
    }
    
    //监听
    ret=listen(tcpsock,5);
    if(ret==-1)
    {
        perror("监听失败!\n");
        return -1;
    }
    printf("旧的套接字(监听套接字)是:%d\n",tcpsock);
    printf("服务器的代码阻塞在accept位置了!\n");
    //接收客户端的连接请求
    newsock=accept(tcpsock,(struct sockaddr *)&boyaddr,&addrsize);  
    if(newsock==-1)
    {
        perror("接收连接请求失败了!\n");
        return -1;
    }
    printf("有一个客户端连接成功了,产生的新套接字(已连接套接字)是:%d\n",newsock);
    
    fd_set myset;
    
    while(1)
    {
        //由于select会自动剔除没有发送状态改变的文件描述符,所有需要重复添加
        FD_ZERO(&myset);
        FD_SET(newsock,&myset);
        FD_SET(0,&myset);
        //调用select监测新的套接字是否有数据可读,键盘是否有输入
        ret=select(newsock+1,&myset,NULL,NULL,NULL);
        if(ret>0) //监测到了状态变化
        {
            //进一步判断究竟是谁(键盘or新的套接字)发生了读就绪
            if(FD_ISSET(newsock,&myset)) //newsock在集合--》newsock发生了读就绪
            {
                bzero(rbuf,100);
                //接收客户端发过来的信息
                read(newsock,rbuf,100);
                printf("客户端发送过来的内容是:%s\n",rbuf);
            }
            if(FD_ISSET(0,&myset)) //键盘在集合--》键盘发生了读就绪
            {
                bzero(sbuf,100);
                scanf("%s",sbuf);
                write(newsock,sbuf,strlen(sbuf));
            }
        }
        else 
        {
            perror("监测失败!\n");
            return -1;
        }
    }
    //关闭套接字
    close(tcpsock);
    close(ret);
    return 0;
}

客户端通信实现代码

/*
    tcp双向通信--》客户端的代码
    多路复用使用思路:
        1.思考要监测什么文件描述符 --》键盘(跟发送有关)
                                   --》套接字(跟接收有关)
        2.监测键盘的读就绪
              套接字的读就绪
*/



#include "myhead.h"


int main()
{
    int tcpsock;
    int ret;
    char sbuf[100];
    char rbuf[100];
    //定义ipv4地址体变量存放需要绑定的ip和端口号
    struct sockaddr_in bindaddr;
    bzero(&bindaddr,sizeof(bindaddr));
    bindaddr.sin_family=AF_INET;
    bindaddr.sin_addr.s_addr=htonl(INADDR_ANY); //客户端自己的ip
    bindaddr.sin_port=htons(10000); //客户端的端口号
    
    //定义ipv4地址体变量存放女朋友(服务器)的ip和端口号
    struct sockaddr_in girladdr;
    bzero(&girladdr,sizeof(girladdr));
    girladdr.sin_family=AF_INET;
    girladdr.sin_addr.s_addr=inet_addr("192.168.22.9"); //服务器的ip
    girladdr.sin_port=htons(20000); //服务器的端口号
    
    //创建tcp套接字
    tcpsock=socket(AF_INET,SOCK_STREAM,0);
    if(tcpsock==-1)
    {
        perror("创建tcp套接字失败!\n");
        return -1;
    }
    
    int on=1;
    setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    
    //绑定客户端自己的ip和端口号
    ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
    if(ret==-1)
    {
        perror("绑定ip端口失败了!\n");
        return -1;
    }
    
    //拨号连接服务器
    ret=connect(tcpsock,(struct sockaddr *)&girladdr,sizeof(girladdr));
    if(ret==-1)
    {
        perror("连接失败了!\n");
        return -1;
    }
    
    fd_set myset;
    
    while(1)
    {
        //由于select会自动剔除没有发送状态改变的文件描述符,所有需要重复添加
        FD_ZERO(&myset);
        FD_SET(0,&myset); //键盘
        FD_SET(tcpsock,&myset); //套接字
        //调用select监测键盘是否有输入,监测套接字是否有数据可读
        ret=select(tcpsock+1,&myset,NULL,NULL,NULL);
        if(ret>0) //监测到了状态变化
        {
            //进一步判断究竟是谁(键盘or套接字)发生了状态变化
            if(FD_ISSET(0,&myset)) //键盘在集合中--》键盘发生了读就绪
            {
                bzero(sbuf,100);
                //读取键盘的输入
                scanf("%s",sbuf);
                //发送给服务器
                write(tcpsock,sbuf,strlen(sbuf));
            }
            if(FD_ISSET(tcpsock,&myset)) //套接字在集合中--》套接字发生了读就绪
            {
                bzero(rbuf,100);
                read(tcpsock,rbuf,100);
                printf("服务器发过来的信息是:%s\n",rbuf);
            }
        }
        else 
        {
            perror("监测失败!\n");
            return -1;
        }
    }
    
    //关闭套接字
    close(tcpsock);
    return 0;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肖爱Kun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值