1、select函数介绍
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数:
nfds 监听所有文件描述符中,最大文件描述符+1
readfds:读 文件描述符监听集合(这三个参数都是传入传出参数)
writefds:写 文件描述符监听集合
exceptfds 异常 文件描述符监听集合
timeout:
>0 设置监听超时长
NULL 阻塞监听
0 非阻塞监听,轮询
返回值:
>0 所有监听集合中, 满足对应事件的总数
0 没有满足监听条件的文件描述符
-1 设置errno
void FD_ZERO(fd_set* set); // 清空一个文件描述符集合
fd_set rset;
FD_ZERO(rset);
void FD_SET(int fd,fd_set* set); // 将待监听的文件描述符,添加到监听集合中
FD_SET(3,&rset);
void FD_CLR(int fd,fd_set* set); // 将一个文件描述符从监听中移除
FD_CLR(4,&rset);
int FD_ISSET(int fd,fd_set* set); // 判断一个文件描述符是否在监听集合中
FD_ISSET(4,&rset);
返回值:在1,不在0
之前写的多线程和多进程服务器,都是通过,accept进行阻塞监听,来一个客户端(子进程、线程)就给他创建一个通信文件描述符,进行读写
要是客户端太多,就会创建太多线程或进程
select:先将建立连接的文件描述符添加到集合中,监听select 的读事件,确定是建立连接,就调用accept,此时不会阻塞,将通信的文件描述符添加到数组中,并且也添加到集合中,要是select返回数大于1表示,还有其他读事件,则去一一处理
2、select代码
#include <unistd.h>
#include <sys/wait.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
#include <signal.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include "socket_wrap.h"
int main()
{
int listenfd,connfd,sockfd;
char buf[BUFSIZ];
bzero(buf,sizeof(buf));
struct sockaddr_in clie_addr,serv_addr;
socklen_t clie_addr_len = sizeof(clie_addr);
listenfd = Socket(AF_INET,SOCK_STREAM,0); // 创建套接字
int opt = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); // 端口复用函数
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9999);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listenfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); // 绑定ip port
Listen(listenfd,128); // 设置连接上限
fd_set rset,allset; // all监听的,rset是传出的
FD_ZERO(&allset); // 将allset集合全部置零
FD_SET(listenfd,&allset); // 将用于连接文件描述符添加到集合中
int ret,maxfd=listenfd; // maxfd表示当前集合中最大的文件描述符术
int n,i,j,maxi=-1; // maxi存储最后一个下标
char client[FD_SETSIZE],str[INET_ADDRSTRLEN]; // FD_SETSIZE 1024 INET_ADDRSTRLEN 16
for(i=0;i<FD_SETSIZE;i++) // 将用于存有时间的文件描述符数组初始化
client[i] = -1;
while(1)
{
rset = allset; // rset待会用来做传出参数
ret = select(maxfd+1,&rset,NULL,NULL,NULL);
if(ret<0)
{
perr_exit("select error");
}
if(FD_ISSET(listenfd,&rset)) // 表示返回的集合中有listenfd事件,
{ // 客户端连接请求
// 此时不需等待,直接建立连接
connfd = Accept(listenfd,(struct sockaddr*)&clie_addr,&clie_addr_len);
printf("客户端:%s:%d\n",inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,str,sizeof(str)),
ntohs(clie_addr.sin_port));
for(i=0;i<FD_SETSIZE;i++)
{
if(client[i]<0)
{
client[i] = connfd; // 将用于通信的文件描述符存入数组中
break;
}
}
if(i == FD_SETSIZE) // i超过1024,就会越界
{
fputs("too many cliens\n",stderr);
exit(1);
}
if(i>maxi) // 保证maxi中存储最后一个下标
maxi = i;
FD_SET(connfd,&allset); // 将通信文件描述符添加到集合中
if(maxfd < connfd) // max中存储最大的文件描述符
maxfd = connfd;
if(ret == 1) // 说明select 只返回了一个并且listenfd
continue;
}
for(i=0;i<=maxi;i++)
{
if((sockfd=client[i])<0)
continue;
if(FD_ISSET(sockfd,&rset))
{
if((n=Read(sockfd,buf,sizeof(buf)))==0)
{
Close(sockfd);
FD_CLR(sockfd,&allset);
client[i] = -1;
printf("客户端:%s:%d已经断开\n",inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,str,sizeof(str)),
ntohs(clie_addr.sin_port));
}
else if(n>0)
{
for(j=0;j<n;j++)
buf[j] = toupper(buf[j]);
Write(STDOUT_FILENO,buf,n);
Write(sockfd,buf,n);
}
if(--ret == 0)
break;
}
}
}
Close(listenfd);
return 0;
}
缺点:监听上限文件描述符限制,最大1024
检测满足条件fd,自己添加业务逻辑提高小,提高了编码难度
优点:
跨平台
3、socket常用函数出错处理封装
socket_wrap.h
#ifndef __WRAP_H_
#define __WRAP_H_
void perr_exit(const char* s);
int Accept(int fd,struct sockaddr* sa,socklen_t* salenptr);
int Bind(int fd,const struct sockaddr* sa,socklen_t salen);
int Connect(int fd,const struct sockaddr* sa,socklen_t salen);
int Listen(int fd,int backlog);
int Socket(int family,int type,int protocol);
ssize_t Read(int fd,void* ptr,size_t nbytes);
ssize_t Write(int fd,const void* ptr,size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd,void* vptr,size_t n);
ssize_t Writen(int fd,const void* vptr,size_t n);
static ssize_t my_read(int fd,char* ptr);
ssize_t Readline(int fd,void* vptr,size_t maxlen);
#endif // !__WRAP_H_
socket_wrap.c
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <arpa/inet.h>
#include "socket_wrap.h"
void perr_exit(const char* s)
{
perror(s);
exit(1);
}
int Accept(int fd,struct sockaddr* sa,socklen_t* salenptr)
{
int n;
again:
if((n=accept(fd,sa,salenptr))<0)
{
if(errno == ECONNABORTED || (errno==EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int fd,const struct sockaddr* sa,socklen_t salen)
{
int n;
if((n=bind(fd,sa,salen))<0)
{
perr_exit("bind error");
}
return n;
}
int Connect(int fd,const struct sockaddr* sa,socklen_t salen)
{
int n;
if((n=connect(fd,sa,salen))<0)
perr_exit("connect error");
return n;
}
int Listen(int fd,int backlog)
{
int n;
if((n=listen(fd,n))<0)
{
perr_exit("listen error");
}
return n;
}
int Socket(int family,int type,int protocol)
{
int n;
if((n=socket(family,type,protocol))<0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd,void* ptr,size_t nbytes)
{
ssize_t n;
again:
if((n=read(fd,ptr,nbytes))<0)
{
if(errno==EINTR)
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd,const void* ptr,size_t nbytes)
{
ssize_t n;
if((n=write(fd,ptr,nbytes))<0)
perr_exit("write error");
return n;
}
int Close(int fd)
{
int n;
if((n=close(fd))<0)
perr_exit("close error");
return n;
}
ssize_t Readn(int fd,void* vptr,size_t n)
{
size_t nleft; // unsigned int 剩余未读取字节数
ssize_t nread; // int 实际读到的字节数
char* ptr;
ptr = vptr;
nleft = n;
while (nleft>0)
{
if((nread = read(fd,ptr,nleft))<0)
{
if(errno == EINTR)
nread = 0;
else
return -1;
}
else if(nread == 0)
break;
nleft -= nread;
ptr+=nread;
}
return n-nread;
}
ssize_t Writen(int fd,const void* vptr,size_t n)
{
size_t nleft;
ssize_t nwritten;
const char* ptr;
ptr = vptr;
nleft = n;
while(nleft>0)
{
if((nwritten=write(fd,ptr,nleft))<=0)
{
if(nwritten<0 && errno==EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static ssize_t my_read(int fd,char* ptr)
{
static int read_cnt;
static char* read_ptr;
static char read_buf[100];
if(read_cnt<=0)
{
again:
if((read_cnt=read(fd,read_buf,sizeof(read_buf)))<0)
{
if(errno == EINTR)
goto again;
return -1;
}
else if(read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
// 传出参数 vptr
ssize_t Readline(int fd,void* vptr,size_t maxlen)
{
ssize_t n,rc;
char c,*ptr;
ptr = vptr;
for(n=1;n<maxlen;n++)
{
if((rc=my_read(fd,&c))==1)
{
*ptr++ = c;
if(c=='\n')
break;
}
else if(rc == 0)
{
*ptr = 0;
return n-1;
}
else
return -1;
}
*ptr = 0;
return n;
}