select模型(二 改进服务端)

一、
int select(int fds,fd_set *readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
select 监管多个I/O,检测遍历[0,fds)的描述符,select实现的服务器称为并发(非并行)服务器,多核cpu才有并行
可同时检测标准出入和网络端口事件,不会因为阻塞在标准输入而无法处理网络数据


二、
可读:可读事件产生的4种情况(前三种)
套接口缓冲区有数据可读;
连接的读一半关闭,即接收到,读操作将返回0,通知select
如果是监听套接口(服务器),已完成连接队列不为空时;
套接口上发生一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取


可写:(第一种)
套接口发送缓冲区有空间容纳数据(不断产生)
连接的写一半关闭。对方关闭,不会发送数据过来,可以发送数据。第一次发送write,收到RST段。再次调用会产生SIGPIPE信号
套接口上发生一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取


异常:
套接口存在带外数据。

利用select改进回射服务器程序:

  如图所示:fd最多FD_SETSIZE个,fd=3(listenfd)放进rset。接下来若监听套接口产生事件,那么建立连接,将conn放入rset中。同时更新maxfd。

服务器端改进:一个进程

#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<sys/wait.h>
#define ERR_EXIT(m)
    do
    {
        perror(m);
        exit(EXIT_FAILURE);
    }while(0)
ssize_t readn(int fd,void *buf,size_t count)
{
    size_t nleft=count;
    ssize_t nread;
    char *bufp=(char*)buf;
    while(nleft>0)
    {
        if((nread=read(fd,bufp,nleft))<0)
        {
            if(errno==EINTR)
                continue;
            else
                return -1;
        }
        else if(nread==0)
            return (count-nleft);
        bufp+=nread;
        nleft-=nread;
    }
    return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t nleft=count;
    ssize_t nwritten;
    char *bufp=(char*)buf;
    while(nleft>0)
    {
        if((nwritten=write(fd,bufp,nleft))<=0)
        {
            if(errno==EINTR)
                continue;
            return -1;
        }else if(nwritten==0)
            continue;
        bufp+=nwritten;
        nleft-=nwritten;
    }
    return count;

}
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
    while(1)
    {
        int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
        if(ret==-1&&errno==EINTR)
            continue;
        return ret;
    }
}
//偷窥方案实现readline避免一次读取一个字符
ssize_t readline(int sockfd,void * buf,size_t maxline)
{
    int ret;
    int nread;
    size_t nleft=maxline;
    char *bufp=(char*)buf;
    while(1)
    {
        ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
        if(ret<0)
            return ret;
        else if(ret==0)
            return ret;
        nread=ret;
        int i;
        for(i=0;i<nread;i++)
        {
            if(bufp[i]=='
')
            {
                ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
                if(ret!=i+1)
                    exit(EXIT_FAILURE);
                return ret;
            }
        }
        if(nread>nleft)
            exit(EXIT_FAILURE);
        nleft-=nread;
        ret=readn(sockfd,bufp,nread);
        if(ret!=nread)
            exit(EXIT_FAILURE);
        bufp+=nread;//移动指针继续窥看
    }
    return -1;
}
void handle_sigchld(int sig)
{
    
    while(waitpid(-1,NULL, WNOHANG)>0)
        ;
        
}
int main(void)
{    
    
    signal(SIGCHLD,handle_sigchld);
    int listenfd;
    if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
        ERR_EXIT("socket error");
    //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0)
    

    //本地协议地址赋给一个套接字
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(5188);
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址

    //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
    int on=1;
    if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
        ERR_EXIT("setsockopt error");
    //绑定本地套接字
    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
        ERR_EXIT("bind error");
    if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字)
        ERR_EXIT("listen error");
    
    struct sockaddr_in peeraddr;//对方套接字地址
    socklen_t peerlen;
    
/*
    pid_t pid;
    while(1){
        if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
            ERR_EXIT("accept error");
        //连接好之后就构成连接,端口是客户端的。peeraddr是对端
        printf("ip=%s port=%d
",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
        pid=fork();
        if(pid==-1)
            ERR_EXIT("fork");
        if(pid==0){    
                close(listenfd);
                echo_srv(conn);
                //某个客户端关闭,结束该子进程,否则子进程也去接受连接
                //虽然结束了exit退出,但是内核还保留了其信息,父进程并未为其收尸。
                exit(EXIT_SUCCESS);
        }else     close(conn);
    }
*/
    int client[FD_SETSIZE];//select最大文件描述符,用来保存已连接文件描述符。单进程时conn只有一个。
    int i=0;    
    for(i=0;i<FD_SETSIZE;i++)
    {
        client[i]=-1;
    }
    int conn;//已连接套接字(主动套接字)
    int nready;
    int maxi=0;//最大不空闲位置
    int maxfd=listenfd;
    fd_set rset,allset;
    FD_ZERO(&rset);
    FD_ZERO(&allset);
    FD_SET(listenfd,&allset);
    while(1)
    {    
        rset=allset;
        //accept建立连接,套接口就有数据可读
        nready=select(maxfd+1,&rset,NULL,NULL,NULL);//如果是监听套接口(服务器),已完成连接队列不为空时,accept不再阻塞;
        if(nready==-1)
        {
            if(errno==EINTR)
                continue;  //若被信号打断可继续执行select
            ERR_EXIT("select error");
        }
        if(nready==0)
            continue;
        if(FD_ISSET(listenfd,&rset))//监听口有事件,已完成队列不为空
        {
            peerlen=sizeof(peeraddr);
            conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
            if(conn==-1)
                ERR_EXIT("accept error");
            for(i=0;i<FD_SETSIZE;i++)
            {
                if(client[i]<0)
                {
                    client[i]=conn;
                    if(i>maxi)
                        maxi=i;//更新最大不空闲位置
                    break;
                }
                
            }
            if(i==FD_SETSIZE)
            {
                fprintf(stderr,"too many clents
");
                exit(EXIT_FAILURE);
            }
            printf("ip=%s port=%d
",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
            
            FD_SET(conn,&allset);//将已连接套接字描述符放入allset,用于监测已连接套接口是否有客户端数据到来
            if(conn>maxfd)
                maxfd=conn;
            if(--nready<=0)
                continue;//如果事件已经处理完,就继续循环监听,不再执行以下代码
            
        }
        for(i=0;i<=maxi;i++)//小于等于
        {
            conn=client[i];
            if(conn==-1)
                continue;
            if(FD_ISSET(conn,&rset))//已经连接套接字是否有事件,不用while(1)循环处理客户端发送,有select监听。
            {
                int ret;
                char recvbuf[1024];
                memset(&recvbuf,0,sizeof(recvbuf));
                ret=readline(conn,recvbuf,1024);                                                               
                if(ret==-1)
                    ERR_EXIT("readline");            
                else if(ret==0)
                {
                    printf("client close
");
                    FD_CLR(conn,&allset);//客户端关闭,select就不用去监听
                    client[i]=-1;//将已连接套接口数组重置为-1
                }
                fputs(recvbuf,stdout);
                writen(conn,recvbuf,strlen(recvbuf));
                if(--nready==0)
                    break;
            }
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/wsw-seu/p/10955470.html