1、一个进能够打开的最大文件描述符限制。可以通过两种方式修改
ulimit -n :获取最大文件描述符个数
ulimit -n 2048:修改为2048个
该限制的测试代码:
客户端程序:
/* 1、select受最大文件描述符限制。测试程序如下 2、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 、1、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; }
服务器端程序:
#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; }