select限制之文件描述符限制

一、一個進可以打開的最大文件描述符限制。能夠經過兩種方式修改
ulimit -n :獲取最大文件描述符個數
ulimit -n 2048:修改成2048個服務器

 

該限制的測試代碼:socket

 

客戶端程序測試

/*
一、select受最大文件描述符限制。測試程序以下
二、select的fd_set集合容量的限制(FD_SIZE),在頭文件中限制。
*/
#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/time.h>

#include <sys/resource.h>
#define ERR_EXIT(m)\
    do\
    {\
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0);
//爲什麼要使用sleep來推遲ERR_EXIT().  客戶端除去 0             、一、2描述符,建立了1021個描述符。在建立1022個套接字時,失敗了,失敗以後進程退出,發送許多FIN給服務器端。服務器端已完成鏈接隊列中維護了1021個條目。正好有FIN以後,能夠空出套接字處理第1021個條目。若是延遲客戶端的退出服務器端就只有1020個了。
int main(void)
{
    int count=0;
    while(1)
    {    int sock;
        if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
        {
            sleep(5);//推遲客戶端的close(便於準確查看服務器端到底能接受多少鏈接).按理說服務器端只能接受1020個,可是中間有客戶端口關閉使得服務端口可以重複利用,使得服務器端也能使用1021個描述符(0,1,2,3(監聽套接口)),若是不推遲,建立第1022個套接口時,客戶端退出進程,前面的1021個套接口要發送FIN,close和accept交替
            ERR_EXIT("socket");
        }
        struct sockaddr_in servaddr;
        memset(&servaddr,0,sizeof(servaddr));
        servaddr.sin_family=AF_INET;
        servaddr.sin_port=htons(5188);
        servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
        
        if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
            ERR_EXIT("connect");//建立第1022個套接字失敗。 0,1,2已經打開。 1024個
        struct sockaddr_in localaddr;
        socklen_t addrlen=sizeof(localaddr);
        if(getsockname(sock,(struct sockaddr *)&localaddr,&addrlen)<0)
            ERR_EXIT("getsockname");
        printf("ip=%s port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
        printf("count=%d\n",++count);
    }
    return 0;

}

 

 

服務器端程序:spa

#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]=='\n')
            {
                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)
        ;
        
}
void handle_sigpipe(int sig)
{
    printf("recevie a sig=%d\n",sig);//打印,不退出服務器進程
}
int main(void)
{    
    int count=0;//測試描述符限制
    signal(SIGCHLD,handle_sigchld);
    signal(SIGPIPE,handle_sigpipe);
    int listenfd;
    if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
        ERR_EXIT("socket error");
    //本地協議地址賦給一個套接字
    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=sizeof(peeraddr);
    
    int client[FD_SETSIZE];//select最大文件描述符,用來保存已鏈接文件描述符。
    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;
        nready=select(maxfd+1,&rset,NULL,NULL,NULL);//若是是監聽套接口(服務器),已完成鏈接隊列不爲空時,accept再也不阻塞;
        if(nready==-1)
        {
            if(errno==EINTR)
                continue;
            ERR_EXIT("select error");
        }
        if(nready==0)
            continue;
        if(FD_ISSET(listenfd,&rset))//監聽口有事件,已完成隊列不爲空
        {
            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\n");
                exit(EXIT_FAILURE);
            }
            printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
            
            printf("%d\n",++count);//服務器端口應該是1020. 0,1,2和監聽的套接口用掉四個
            
            //select的fd_set集合容量的限制
            FD_SET(conn,&allset);//將已鏈接套接字描述符放入allset,用於監測已鏈接套接口是否有客戶端數據到來
            if(conn>maxfd)
                maxfd=conn;//更新maxfd
            if(--nready<=0)
                continue;//若是事件已經處理完,就繼續循環監聽,再也不執行如下代碼
            
        }
        for(i=0;i<=maxi;i++)//小於等於
        {
            conn=client[i];
            if(conn==-1)
                continue;
        //已經鏈接套接字是否有事件,不用while(1)循環處理客戶端發送,有select監聽。
            if(FD_ISSET(conn,&rset))
            {
                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\n");
                    FD_CLR(conn,&allset);//客戶端清理,select就不用去監聽
                    client[i]=-1;
                    close(conn);//前面程序BUG,對方關閉以後,咱們服務器也要關閉套接口。讓客戶端接收到通知
                }
                fputs(recvbuf,stdout);
                writen(conn,recvbuf,strlen(recvbuf));//write :aaa  bbb ,RST,寫aaa接收bbb,再bbb,有了SIGPIPE
                if(--nready==0)
                    continue;
            }
        }
    }
    return 0;
}
相關文章
相關標籤/搜索