网络编程select的使用.

记录下简单的select的使用。以防忘记。

服务端代码

#include <stdio.h>
#include "unp.h"
int main() {
    int i,maxi,maxfd,listenfd,clifd;
    int readycnt,client[FD_SETSIZE];
    int n;
    char buff[MAXLINE];
    //这里rset是select的监听列表。但是因为每次循环完都要重新加入监听列表。所以我们把要监听的数据放到allset。然后把allset赋值给rset
    fd_set rset,allset;
    //服务端套接口创建,绑定,启动监听
    struct sockaddr_in serAddr;
    bzero(&serAddr,sizeof(serAddr));
    serAddr.sin_family=AF_INET;
    serAddr.sin_port=htons(SERV_PORT);
    serAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    listenfd=Socket(AF_INET,SOCK_STREAM,0);
    socklen_t len=sizeof(serAddr);
    Bind(listenfd,(SA *)&serAddr,len);
    Listen(listenfd,LISTENQ);
    //初始化select要监听的最大文件描述符id,现在还没有接收客户端,所以最大肯定是服务端监听端口
    maxfd=listenfd;
    maxi=-1;
    //客户端的连接数不能超过FD_SETSIZE。所有套接口描述符都将装在client数组里面。默认没有填充的值为-1.这里初始化一下
    for(i=0;i<FD_SETSIZE;i++){
        client[i]=-1;
    }
    FD_ZERO(&allset);             //初始化监听列表
    FD_SET(listenfd,&allset);     //将服务端端口监听的套接口放入select的监听列表
    for(;;){
        rset=allset;              //所有要监听和取消监听都是操作allset。然后开始的时候把allset赋值给rset
        readycnt=Select(maxfd+1,&rset,NULL,NULL,NULL);  //开始监听
        if(FD_ISSET(listenfd,&rset)){                   //如果是服务端端口的套接口收到消息,说明是有客户端连接上来
            clifd=Accept(listenfd,(SA *)NULL,NULL);     //接收客户端连接
            for(i=0;i<FD_SETSIZE;i++){                  //遍历存放客户端连接套接口的数组。找到一个-1的就是可以存放的位置
                if(client[i]==-1)
                    break;
            }
            if(i==FD_SETSIZE)                           //如果找到结束都没有。则说明所有队列满了
                err_quit("too many clients");

            client[i]=clifd;                //记录客户端套接口
            FD_SET(clifd,&allset);          //加入select的监听队列
            if(clifd>maxfd)                 //更新最大文件描述符
                maxfd=clifd;
            if(i>maxi)                      //更新最高的位置信息。可以让后面不用完整遍历
                maxi=i;
            printf("cli connect success maxi:%d \r\n",maxi);
            if(--readycnt<=0)               //这个就绪套接口处理完成,递减一下。如果已经没其他的了。就不用继续往后遍历了
                    break;
            }
        }
    }
}

客户端例子

#include <stdio.h>
#include "unp.h"

void send_str(int sockfd,int stdinfd){
    fd_set rset;              //监听的列表
    FD_ZERO(&rset);           //重置
    int stdinof=0;            //标记标准输入是否关闭了
    int n;
    char buff[MAXLINE];
    for(;;){
        if(stdinof==0)        //标准输入如果关闭了就不要监听了
            FD_SET(stdinfd,&rset);
        FD_SET(sockfd,&rset);       //监听套接口
        //select第一个参数是要用最大文件描述符+1.我们监听的是标准输入和套接口。标准输入输出是0和1。套接口描述符肯定要大。所以直接填sockfd+1
        Select(sockfd+1,&rset,NULL,NULL,NULL); //阻塞,等待通信管道准备就绪
        if(FD_ISSET(stdinfd,&rset)){                    //判断是否是io管道就绪了
            if((n=Read(stdinfd,buff,MAXLINE))==0){      //接收输入结果,如果结果是0,就代表管道关闭了
                stdinof=1;              //设置后上面不再监听
                Shutdown(sockfd,SHUT_WR);               //半关闭写入接口,服务端就会read到0.然后关闭和客户端的连接
                FD_CLR(stdinfd,&rset);                  //从监听列表移除
                continue;
            }
            Writen(sockfd,buff,n);                      //正常情况就把标准输入的发送给服务端
        }
        if(FD_ISSET(sockfd,&rset)){     //如果是服务端的套接口有数据了
            if((n=Read(sockfd,buff,MAXLINE))==0){       //读取服务端返回数据
                printf("sock read=0\r\n");
                if(stdinof==1)                          //如果标准输入已经关掉了,就不用再往下走了
                    return;
                else
                    err_quit("ser close\r\n");          //其他服务端关掉的情况打印消息并退出
            }
            Write(STDOUT_FILENO,buff,n);                //正常情况就写入到标准输出
        }
    }
}

int main(int argc,char** argv) {
    if(argc!=2)
        err_quit("argc err");
    char *addr=argv[1];
    //链接服务端
    struct sockaddr_in serAddr;
    serAddr.sin_family=AF_INET;
    serAddr.sin_port=htons(SERV_PORT);
    Inet_pton(AF_INET,addr,&serAddr.sin_addr);
    int sockfd=Socket(AF_INET,SOCK_STREAM,0);
    Connect(sockfd,(SA*)&serAddr,sizeof(serAddr));
    //链接成功后的业务逻辑
    send_str(sockfd,STDIN_FILENO);
    exit(0);
}

select还有两点最容易出错的地方,

1、是忘记对最大描述字+1。也就是select的第一个参数经常会出错

2、忘记描述字集是值-结果参数。也就是rset里面是fd-结果参数。所以rset总是要重置来再次监听。因为之前设置的1又变回0了。

posted @ 2018-09-29 15:18  李亚金  阅读(1678)  评论(0编辑  收藏  举报